diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise.py
index 61254ce624..f698277202 100644
--- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise.py
+++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise.py
@@ -8,6 +8,7 @@ from enum import IntEnum
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise_sub_layouts.speed_limit_settings import SpeedLimitSettingsLayout
from openpilot.selfdrive.ui.ui_state import ui_state
+from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp, simple_button_item_sp
from openpilot.system.ui.widgets import Widget
@@ -29,6 +30,7 @@ ACC_ENABLED_DESCRIPTION = tr_noop("Enable custom Short & Long press increments f
ACC_NOLONG_DESCRIPTION = tr_noop("This feature can only be used with sunnypilot longitudinal control enabled.")
ACC_PCMCRUISE_DISABLED_DESCRIPTION = tr_noop("This feature is not supported on this platform due to vehicle limitations.")
ONROAD_ONLY_DESCRIPTION = tr_noop("Start the vehicle to check vehicle compatibility.")
+MAP_UNAVAILABLE_DESCRIPTION = tr_noop("Map data is temporarily unavailable.")
class CruiseLayout(Widget):
@@ -54,7 +56,8 @@ class CruiseLayout(Widget):
self.scc_m_toggle = toggle_item_sp(
title=tr("Smart Cruise Control - Map"),
- description=tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
+ description=(f"{tr(MAP_UNAVAILABLE_DESCRIPTION)}\n\n" if not MAPD_AVAILABLE else "") +
+ tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
param="SmartCruiseControlMap")
self.custom_acc_toggle = toggle_item_sp(
@@ -139,7 +142,7 @@ class CruiseLayout(Widget):
if has_long or has_icbm:
self.custom_acc_toggle.action_item.set_enabled(((has_long and not ui_state.CP.pcmCruise) or has_icbm) and ui_state.is_offroad())
self.scc_v_toggle.action_item.set_enabled(True)
- self.scc_m_toggle.action_item.set_enabled(True)
+ self.scc_m_toggle.action_item.set_enabled(MAPD_AVAILABLE)
else:
ui_state.params.remove("CustomAccIncrementsEnabled")
ui_state.params.remove("SmartCruiseControlVision")
diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py
index e6846744a2..0af10af709 100644
--- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py
+++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py
@@ -8,6 +8,8 @@ from collections.abc import Callable
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
+from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
+from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp
from openpilot.system.ui.widgets.network import NavButton
@@ -43,6 +45,8 @@ class SpeedLimitPolicyLayout(Widget):
param="SpeedLimitPolicy",
button_width=250,
)
+ if not MAPD_AVAILABLE:
+ self._speed_limit_policy.action_item.set_enabled_buttons({Policy.car_state_only})
items = [
self._speed_limit_policy
@@ -51,6 +55,8 @@ class SpeedLimitPolicyLayout(Widget):
@staticmethod
def _get_policy_description():
+ if not MAPD_AVAILABLE:
+ return f"{tr('Map data is temporarily unavailable. Using Speed Limit data only from Car.')}"
return get_highlighted_description(ui_state.params, "SpeedLimitPolicy", SPEED_LIMIT_POLICY_DESCRIPTIONS)
def _render(self, rect):
diff --git a/openpilot/sunnypilot/mapd/__init__.py b/openpilot/sunnypilot/mapd/__init__.py
index 57525254ce..3ca3bc08f5 100644
--- a/openpilot/sunnypilot/mapd/__init__.py
+++ b/openpilot/sunnypilot/mapd/__init__.py
@@ -3,3 +3,7 @@ from openpilot.common.basedir import BASEDIR
MAPD_BIN_DIR = os.path.join(BASEDIR, 'openpilot', 'third_party/mapd_pfeiferj')
MAPD_PATH = os.path.join(MAPD_BIN_DIR, 'mapd')
+
+# mapd still runs (OSM downloads) but has no position source since ubloxd owns LastGPSPosition;
+# map-based features stay off until mapd is rebuilt
+MAPD_AVAILABLE = False
diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/map_controller.py b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/map_controller.py
index 0d85f060e1..99998e356b 100644
--- a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/map_controller.py
+++ b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/map_controller.py
@@ -9,6 +9,7 @@ from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
+from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
from openpilot.sunnypilot.navd.helpers import Coordinate
from openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control import MIN_V
@@ -75,7 +76,7 @@ class SmartCruiseControlMap:
def __init__(self):
self.params = Params()
self.mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else self.params
- self.enabled = self.params.get_bool("SmartCruiseControlMap")
+ self.enabled = MAPD_AVAILABLE and self.params.get_bool("SmartCruiseControlMap")
self.long_enabled = False
self.long_override = False
self.is_enabled = False
@@ -101,7 +102,7 @@ class SmartCruiseControlMap:
def update_params(self):
if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
- self.enabled = self.params.get_bool("SmartCruiseControlMap")
+ self.enabled = MAPD_AVAILABLE and self.params.get_bool("SmartCruiseControlMap")
def update_calculations(self, sm: messaging.SubMaster) -> None:
gps = sm[self.gps_location_service]
diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py
index 720a3195e2..3744279b40 100644
--- a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py
+++ b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py
@@ -8,7 +8,7 @@ import json
import math
import platform
from types import SimpleNamespace
-from unittest.mock import MagicMock
+from unittest.mock import MagicMock, patch
from openpilot.cereal import custom
from openpilot.common.params import Params
@@ -23,6 +23,8 @@ MapState = VisionState = custom.LongitudinalPlanSP.SmartCruiseControl.MapState
class TestSmartCruiseControlMap(OpenpilotTestCase):
def setup_method(self):
+ self.mapd_patch = patch("openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control.map_controller.MAPD_AVAILABLE", True)
+ self.mapd_patch.start()
self.params = Params()
self.mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else self.params
self.reset_params()
@@ -30,6 +32,9 @@ class TestSmartCruiseControlMap(OpenpilotTestCase):
self.sm = MagicMock()
self.sm.__getitem__.return_value = SimpleNamespace(latitude=0.0, longitude=0.0)
+ def teardown_method(self):
+ self.mapd_patch.stop()
+
def reset_params(self):
self.params.put_bool("SmartCruiseControlMap", True, block=True)
@@ -50,6 +55,14 @@ class TestSmartCruiseControlMap(OpenpilotTestCase):
assert self.scc_m.state == VisionState.disabled
assert not self.scc_m.is_active
+ def test_mapd_unavailable(self):
+ self.mapd_patch.stop()
+ self.scc_m = SmartCruiseControlMap()
+ for _ in range(int(10. / DT_MDL)):
+ self.scc_m.update(self.sm, True, False, 0., 0., 0.)
+ assert self.scc_m.state == VisionState.disabled
+ self.mapd_patch.start()
+
def test_disabled(self):
for _ in range(int(10. / DT_MDL)):
self.scc_m.update(self.sm, False, False, 0., 0., 0.)
diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py b/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py
index a226e0d120..0b53c766e4 100644
--- a/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py
+++ b/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py
@@ -13,6 +13,7 @@ from openpilot.common.gps import get_gps_location_service
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD, get_sanitize_int_param
+from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import LIMIT_MAX_MAP_DATA_AGE, LIMIT_ADAPT_ACC
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy, OffsetType
@@ -48,6 +49,8 @@ class SpeedLimitResolver:
Policy.max().value,
self.params
)
+ if not MAPD_AVAILABLE:
+ self.policy = Policy.car_state_only
self._policy_to_sources_map = {
Policy.car_state_only: [SpeedLimitSource.car],
Policy.map_data_only: [SpeedLimitSource.map],
@@ -91,7 +94,7 @@ class SpeedLimitResolver:
def update_params(self):
if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
- self.policy = self.params.get("SpeedLimitPolicy", return_default=True)
+ self.policy = self.params.get("SpeedLimitPolicy", return_default=True) if MAPD_AVAILABLE else Policy.car_state_only
self.is_metric = self.params.get_bool("IsMetric")
self.offset_type = self.params.get("SpeedLimitOffsetType", return_default=True)
self.offset_value = self.params.get("SpeedLimitValueOffset", return_default=True)
diff --git a/openpilot/sunnypilot/sunnylink/capabilities.py b/openpilot/sunnypilot/sunnylink/capabilities.py
index 8d303f0ae9..b7dc611a4e 100644
--- a/openpilot/sunnypilot/sunnylink/capabilities.py
+++ b/openpilot/sunnypilot/sunnylink/capabilities.py
@@ -15,6 +15,7 @@ from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.common.hardware import HARDWARE
+from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
# Wire-protocol version for the capabilities payload. Bump on breaking changes
@@ -43,6 +44,7 @@ CAPABILITY_FIELDS = (
"device_type",
"subaru_has_sng",
"hyundai_alpha_long_available",
+ "mapd_available",
)
CAPABILITY_LABELS: dict[str, str] = {
@@ -65,6 +67,7 @@ CAPABILITY_LABELS: dict[str, str] = {
"device_type": "Device type",
"subaru_has_sng": "Subaru Stop-and-Go available",
"hyundai_alpha_long_available": "Hyundai Alpha Longitudinal available",
+ "mapd_available": "Map data available",
}
# Explicit defaults for non-boolean capability fields
@@ -132,6 +135,7 @@ def generate_capabilities(params: Params | None = None) -> dict:
caps["is_sp_release"] = params.get_bool("IsReleaseSpBranch")
caps["is_development"] = params.get_bool("IsDevelopmentBranch")
caps["stock_longitudinal"] = params.get_bool("ToyotaEnforceStockLongitudinal")
+ caps["mapd_available"] = MAPD_AVAILABLE
bundle = params.get("CarPlatformBundle")
bundle_brand = _bundle_field(bundle, "brand")
diff --git a/openpilot/sunnypilot/sunnylink/docs/README.md b/openpilot/sunnypilot/sunnylink/docs/README.md
index cbc1eafbf4..4f283521d6 100644
--- a/openpilot/sunnypilot/sunnylink/docs/README.md
+++ b/openpilot/sunnypilot/sunnylink/docs/README.md
@@ -156,7 +156,7 @@ The tables below describe the **compiled** `settings_ui.json` schema — what th
**Enablement rules**: Grayed out (disabled) when rules fail. Frontend shows a contextual badge explaining why.
-**Capability fields** (referenced in rules): `has_longitudinal_control`, `has_icbm`, `icbm_available`, `torque_allowed`, `brand`, `pcm_cruise`, `alpha_long_available`, `steer_control_type`, `has_bsm`, `is_release`, `is_sp_release`, `is_development`, `tesla_has_vehicle_bus`, `has_stop_and_go`, `stock_longitudinal`
+**Capability fields** (referenced in rules): `has_longitudinal_control`, `has_icbm`, `icbm_available`, `torque_allowed`, `brand`, `pcm_cruise`, `alpha_long_available`, `steer_control_type`, `has_bsm`, `is_release`, `is_sp_release`, `is_development`, `tesla_has_vehicle_bus`, `has_stop_and_go`, `stock_longitudinal`, `mapd_available`
---
diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json
index c9d4c826f4..284473409c 100644
--- a/openpilot/sunnypilot/sunnylink/settings_ui.json
+++ b/openpilot/sunnypilot/sunnylink/settings_ui.json
@@ -857,19 +857,47 @@
},
{
"value": 1,
- "label": "Map Data Only"
+ "label": "Map Data Only",
+ "enablement": [
+ {
+ "type": "capability",
+ "field": "mapd_available",
+ "equals": true
+ }
+ ]
},
{
"value": 2,
- "label": "Car State Priority"
+ "label": "Car State Priority",
+ "enablement": [
+ {
+ "type": "capability",
+ "field": "mapd_available",
+ "equals": true
+ }
+ ]
},
{
"value": 3,
- "label": "Map Data Priority"
+ "label": "Map Data Priority",
+ "enablement": [
+ {
+ "type": "capability",
+ "field": "mapd_available",
+ "equals": true
+ }
+ ]
},
{
"value": 4,
- "label": "Combined"
+ "label": "Combined",
+ "enablement": [
+ {
+ "type": "capability",
+ "field": "mapd_available",
+ "equals": true
+ }
+ ]
}
]
},
@@ -1001,6 +1029,11 @@
}
],
"enablement": [
+ {
+ "type": "capability",
+ "field": "mapd_available",
+ "equals": true
+ },
{
"type": "any",
"conditions": [
diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/cruise.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/cruise.yaml
index 39d6fbffd4..09efe4e0c6 100644
--- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/cruise.yaml
+++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/cruise.yaml
@@ -167,12 +167,28 @@ sections:
label: Car State Only
- value: 1
label: Map Data Only
+ enablement:
+ - type: capability
+ field: mapd_available
+ equals: true
- value: 2
label: Car State Priority
+ enablement:
+ - type: capability
+ field: mapd_available
+ equals: true
- value: 3
label: Map Data Priority
+ enablement:
+ - type: capability
+ field: mapd_available
+ equals: true
- value: 4
label: Combined
+ enablement:
+ - type: capability
+ field: mapd_available
+ equals: true
- key: SpeedLimitOffsetType
widget: multiple_button
title: Speed Limit Offset Type
@@ -246,6 +262,9 @@ sections:
field: has_icbm
equals: true
enablement:
+ - type: capability
+ field: mapd_available
+ equals: true
- type: any
conditions:
- type: capability