diff --git a/opendbc_repo/opendbc/car/honda/interface.py b/opendbc_repo/opendbc/car/honda/interface.py index 519827901..479d95f87 100755 --- a/opendbc_repo/opendbc/car/honda/interface.py +++ b/opendbc_repo/opendbc/car/honda/interface.py @@ -49,11 +49,11 @@ class CarInterface(CarInterfaceBase): cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput)) ret.safetyConfigs = cfgs - # Honda Bosch-A radar (RX-only 16-slot object bank, see radar_interface.py). Enabled by - # default for every Bosch-A platform; a sentinel file acts as a kill switch so no - # params_keys.h rebuild is needed. Reading radar never takes CAN authority, so the - # factory AEB/CMBS/FCW stay live while stock longitudinal is in control. - bosch_a_radar_enabled = not docs and not os.path.exists("/data/params/d/HondaBoschARadarOff") + # Honda Bosch-A radar (RX-only 16-slot object bank, see radar_interface.py). Disabled by + # default and enabled through the Honda vehicle settings UI. The sentinel file avoids a + # params_keys.h rebuild. Reading radar never takes CAN authority, so factory + # AEB/CMBS/FCW stay live while stock longitudinal is in control. + bosch_a_radar_enabled = not docs and os.path.exists("/data/params/d/HondaBoschARadarOn") ret.radarUnavailable = not (candidate in HONDA_BOSCH_A and bosch_a_radar_enabled) # Disable the radar and let openpilot control longitudinal # WARNING: THIS DISABLES AEB! diff --git a/opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py b/opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py index a857c54f7..d3633337b 100644 --- a/opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py +++ b/opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py @@ -35,10 +35,9 @@ from opendbc.car.honda.radar_interface import ( from opendbc.car.honda.values import CAR, HONDA_BOSCH_A, HONDA_BOSCH_A_RADAR_VERIFIED from openpilot.common.params import Params -# This fork enables the Bosch-A radar by default for the verified platforms and uses a sentinel -# file as a kill switch instead of a params_keys.h entry (no libparams rebuild needed), so make -# sure that file is absent while the module-level CP below is computed. -_SWITCH = Path("/data/params/d/HondaBoschARadarOff") +# This fork uses an opt-in sentinel instead of a params_keys.h entry (no libparams rebuild needed). +# The module-level CP below explicitly enables the parser so unit tests don't depend on UI state. +_SWITCH = Path("/data/params/d/HondaBoschARadarOn") _SWITCH.unlink(missing_ok=True) @@ -932,10 +931,8 @@ def test_stale_bus_clears_points_and_flags_temporary_unavailable(): direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) assert len(rr.points) == 1 - # Advance the parser clock well past BOSCH_A_STALE_S with no trigger frame at all. - f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[0] - frames = [CanData(f0, make_f0(1, 0x7, 1000, 1024), BUS)] # not a full/coherent set, and no trigger - rr = ri.update([(300_000_000, frames)]) # +300ms, no trigger msg present + # Advance the parser clock well past BOSCH_A_STALE_S with a completely silent bus. + rr = ri.update([(300_000_000, [])]) # +300ms, no trigger msg present assert rr is not None assert len(rr.points) == 0 assert rr.errors.radarUnavailableTemporary is True @@ -949,8 +946,7 @@ def test_stale_bus_clears_pending_birth_history_before_maturity(): # The birth has started, but no RadarPoint exists yet. A silent bus must still clear the pending # lifecycle/history state rather than allowing it to survive indefinitely. - f0, _, _, _ = BOSCH_A_MAIN_IDS[0] - rr = ri.update([(300_000_000, [CanData(f0, make_f0(1, 0x7, 1000, 1024), BUS)])]) + rr = ri.update([(300_000_000, [])]) assert rr is not None assert len(rr.points) == 0 assert rr.errors.radarUnavailableTemporary is True @@ -1099,7 +1095,7 @@ def test_civic_bosch_radar_dbc_wired_for_parser_unit_tests(): def test_crv_5g_bosch_a_radar_dbc_wired_for_parser_unit_tests(): cp = CarInterface.get_non_essential_params(CAR.HONDA_CRV_5G) cp_sp = CarInterface.get_non_essential_params_sp(cp, CAR.HONDA_CRV_5G) - assert cp.radarUnavailable is False + cp.radarUnavailable = False ri = CarInterface.RadarInterface(cp, cp_sp) assert ri.bosch_a_radar is True assert ri.rcp is not None @@ -1110,7 +1106,7 @@ def test_accord_bosch_a_radar_is_enabled_too(): # the gate follows the hardware family now, not the verified subset cp = CarInterface.get_non_essential_params(CAR.HONDA_ACCORD) cp_sp = CarInterface.get_non_essential_params_sp(cp, CAR.HONDA_ACCORD) - assert cp.radarUnavailable is False + cp.radarUnavailable = False ri = CarInterface.RadarInterface(cp, cp_sp) assert ri.bosch_a_radar is True assert ri.rcp is not None @@ -1156,11 +1152,13 @@ def test_bosch_a_hardware_allowlist_is_exact_and_verified_set_is_explicit(): @pytest.mark.parametrize("car", sorted(_EXPECTED_BOSCH_A_CARS - _VERIFIED_BOSCH_A_CARS, key=lambda candidate: candidate.name)) -def test_bosch_a_radar_is_enabled_for_every_bosch_a_platform(car): - # This fork enables the Bosch-A decoder on the whole hardware family rather than a - # verified-subset allowlist, so users can try it on other Bosch-A cars. - cp = CarInterface.get_non_essential_params(car) - assert cp.radarUnavailable is False +def test_bosch_a_radar_can_be_enabled_for_every_bosch_a_platform(car): + _SWITCH.write_text("1") + try: + cp = CarInterface.get_non_essential_params(car) + assert cp.radarUnavailable is False + finally: + _SWITCH.unlink(missing_ok=True) @pytest.mark.parametrize("car", _EXCLUDED_BOSCH_CARS) @@ -1169,23 +1167,18 @@ def test_bosch_a_gate_stays_closed_for_non_bosch_a_platforms(car): assert cp.radarUnavailable is True -def test_bosch_a_verified_platform_gate_can_open(): - for car in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G): - cp = CarInterface.get_non_essential_params(car) - assert cp.radarUnavailable is False +def test_bosch_a_toggle_defaults_off(): + _SWITCH.unlink(missing_ok=True) + assert CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH).radarUnavailable is True + assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD).radarUnavailable is True + assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD_11G).radarUnavailable is True -def test_bosch_a_toggle_can_close_verified_platform(monkeypatch): - monkeypatch.setattr(honda_interface, "HONDA_BOSCH_A_RADAR_VERIFIED", frozenset({CAR.HONDA_CIVIC_BOSCH})) +def test_bosch_a_toggle_opens_for_verified_platforms(): _SWITCH.write_text("1") try: - assert CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH).radarUnavailable is True + for car in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G): + cp = CarInterface.get_non_essential_params(car) + assert cp.radarUnavailable is False finally: _SWITCH.unlink(missing_ok=True) - - -def test_bosch_a_toggle_defaults_on_but_allowlist_still_gates_platforms(): - _SWITCH.unlink(missing_ok=True) - assert CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH).radarUnavailable is False - assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD).radarUnavailable is False - assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD_11G).radarUnavailable is True diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py index fec68795a..a235dd10c 100644 --- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py +++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py @@ -4,12 +4,37 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +from pathlib import Path + from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp + + +_RADAR_ENABLE_PATH = Path("/data/params/d/HondaBoschARadarOn") class HondaSettings(BrandSettings): def __init__(self): super().__init__() + self.use_bosch_a_radar = toggle_item_sp( + lambda: tr("Use Bosch-A Radar Data"), + lambda: tr("Read factory radar targets from CAN. Read-only; factory AEB and longitudinal control are unaffected. Requires a vehicle restart."), + initial_state=_RADAR_ENABLE_PATH.exists(), + callback=self._on_bosch_a_radar_toggled, + enabled=lambda: ui_state.is_offroad(), + ) + + self.items = [self.use_bosch_a_radar] + + def _on_bosch_a_radar_toggled(self, state: bool): + if state: + _RADAR_ENABLE_PATH.parent.mkdir(parents=True, exist_ok=True) + _RADAR_ENABLE_PATH.touch() + else: + _RADAR_ENABLE_PATH.unlink(missing_ok=True) + def update_settings(self): pass diff --git a/openpilot/selfdrive/ui/translations/app.pot b/openpilot/selfdrive/ui/translations/app.pot index 8b415c384..660bf221f 100644 --- a/openpilot/selfdrive/ui/translations/app.pot +++ b/openpilot/selfdrive/ui/translations/app.pot @@ -2804,3 +2804,11 @@ msgstr "" msgid "Reverse" msgstr "" + +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py +msgid "Read factory radar targets from CAN. Read-only; factory AEB and longitudinal control are unaffected. Requires a vehicle restart." +msgstr "" + +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py +msgid "Use Bosch-A Radar Data" +msgstr "" diff --git a/openpilot/selfdrive/ui/translations/app_zh-CHS.po b/openpilot/selfdrive/ui/translations/app_zh-CHS.po index 923ea4c74..80b918734 100644 --- a/openpilot/selfdrive/ui/translations/app_zh-CHS.po +++ b/openpilot/selfdrive/ui/translations/app_zh-CHS.po @@ -2891,3 +2891,11 @@ msgstr "准备中" msgid "unavailable" msgstr "不可用" + +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py +msgid "Read factory radar targets from CAN. Read-only; factory AEB and longitudinal control are unaffected. Requires a vehicle restart." +msgstr "读取原厂 Bosch-A 雷达目标,仅接收不发送,不影响原车 AEB 和纵向控制。切换后需要重新启动车辆。" + +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py +msgid "Use Bosch-A Radar Data" +msgstr "使用雷达信息"