From b58fbc1976a7930249bf2002151b78ba9b283e75 Mon Sep 17 00:00:00 2001 From: MazdaNick Date: Thu, 17 Sep 2026 19:42:33 -0400 Subject: [PATCH] mazda: read invalidLkasSetting from the camera's intervention bits Issue: a settings-menu or camera state can leave the EPS taking our steer request and applying none of it, with no block or fault. Fix: CAM_SETTINGS.LKAS_INERVENTION_ON1 and ILKAS_NTERVENTION_ON2 clear together when the lane assist is off; read that in CarState and raise invalidLkasSetting, replacing the LANE_LINES term. Latch that the camera sent CAM_SETTINGS once, so a car that never sends it cannot read as off, and the state holds through the message's silent cycles. Validation: mazda car suite 546 passed; ruff clean. --- opendbc/car/mazda/carstate.py | 7 ++--- .../car/mazda/tests/test_mazda_carstate.py | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/opendbc/car/mazda/carstate.py b/opendbc/car/mazda/carstate.py index ec515fc8f51..cb03ca5bf33 100644 --- a/opendbc/car/mazda/carstate.py +++ b/opendbc/car/mazda/carstate.py @@ -83,6 +83,7 @@ def __init__(self, CP, CP_SP): self.cam_laneinfo_seen = False self.cam_laneinfo_silent_frames = 0 self.cam_empty_seen = False + self.cam_settings_seen = False self.radar_session_refused = False self.radar_session_response = 0 self.fsc_settled_frames = 0 @@ -305,9 +306,8 @@ def update(self, can_parsers) -> tuple[structs.CarState, structs.CarStateSP]: ret.cruiseState.standstill = cp.vl["PEDALS"]["STANDSTILL"] == 1 and not self.CP.openpilotLongitudinalControl ret.cruiseState.speed = cp.vl["CRZ_EVENTS"]["CRZ_SPEED"] * CV.KPH_TO_MS - # Stock LKAS must be active. - # TODO: is this needed? - ret.invalidLkasSetting = cam_laneinfo_fresh and cp_cam.vl["CAM_LANEINFO"]["LANE_LINES"] == 0 + self.cam_settings_seen |= len(cp_cam.vl_all["CAM_SETTINGS"]["LKAS_INERVENTION_ON1"]) > 0 + ret.invalidLkasSetting = self.cam_settings_seen and not all(cp_cam.vl["CAM_SETTINGS"][s] for s in ("LKAS_INERVENTION_ON1", "ILKAS_NTERVENTION_ON2")) if ret.cruiseState.enabled: if not self.lkas_allowed_speed and self.acc_active_last: @@ -387,6 +387,7 @@ def get_can_parsers(CP, CP_SP): cam_messages = [ # Read these optional camera messages without making them part of canValid. ("CAM_LANEINFO", float("nan")), + ("CAM_SETTINGS", float("nan")), ("CAM_TRAFFIC_SIGNS", float("nan")), ("CAM_EMPTY", float("nan")), ("CAM_PEDESTRIAN", float("nan")), diff --git a/opendbc/car/mazda/tests/test_mazda_carstate.py b/opendbc/car/mazda/tests/test_mazda_carstate.py index 0d13dfe641a..7d0e738f823 100644 --- a/opendbc/car/mazda/tests/test_mazda_carstate.py +++ b/opendbc/car/mazda/tests/test_mazda_carstate.py @@ -734,3 +734,29 @@ def test_rolling_or_leaving_standby_releases(self, release): kw.update(release) rig.step(100, 0, kw.pop('blocked'), **kw) assert not rig.CS.steer_first_engage_hold + + +def test_intervention_bits_become_an_invalid_lkas_setting(): + CI, pk = car_interface(alpha_long=False), packer() + healthy = pk.make_can_msg("CAM_SETTINGS", 2, {"LKAS_INERVENTION_ON1": 1, "ILKAS_NTERVENTION_ON2": 1}) + assert not feed(CI, 0, healthy)[0].invalidLkasSetting + off = pk.make_can_msg("CAM_SETTINGS", 2, {"LKAS_INERVENTION_ON1": 0, "ILKAS_NTERVENTION_ON2": 0}) + assert feed(CI, 1, off)[0].invalidLkasSetting + # the flag holds through the silent cycles between the message's arrivals + for i in range(2, 12): + assert feed(CI, i)[0].invalidLkasSetting + mixed = pk.make_can_msg("CAM_SETTINGS", 2, {"LKAS_INERVENTION_ON1": 1, "ILKAS_NTERVENTION_ON2": 0}) + assert feed(CI, 12, mixed)[0].invalidLkasSetting + + +def test_cam_settings_absence_never_reads_as_off(): + # the parser's default zeros must not read as off on a car that never sends CAM_SETTINGS + CI = car_interface(alpha_long=False) + for i in range(10): + assert not feed(CI, i)[0].invalidLkasSetting + + +def test_lane_lines_zero_alone_is_not_an_invalid_lkas_setting(): + CI, pk = car_interface(alpha_long=False), packer() + lanes = pk.make_can_msg("CAM_LANEINFO", 2, {"LANE_LINES": 0}) + assert not feed(CI, 0, lanes)[0].invalidLkasSetting