From 9466f4db85a504c186543f2fecf74ebf3fde5c19 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 17 Oct 2026 21:43:48 -0400 Subject: [PATCH] mapd: disable map-based Smart Cruise Control and speed limit sources until mapd has a position source --- .../ui/sunnypilot/layouts/settings/cruise.py | 7 +++- .../cruise_sub_layouts/speed_limit_policy.py | 6 +++ openpilot/sunnypilot/mapd/__init__.py | 4 ++ .../smart_cruise_control/map_controller.py | 5 ++- .../tests/test_map_controller.py | 15 ++++++- .../lib/speed_limit/speed_limit_resolver.py | 5 ++- .../sunnypilot/sunnylink/capabilities.py | 4 ++ openpilot/sunnypilot/sunnylink/docs/README.md | 2 +- .../sunnypilot/sunnylink/settings_ui.json | 41 +++++++++++++++++-- .../settings_ui_src/pages/cruise.yaml | 19 +++++++++ 10 files changed, 97 insertions(+), 11 deletions(-) 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