From 09acf8ec2f327769f00ee53563ad2dd9225e37a7 Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Sat, 5 Sep 2026 12:08:21 -0400 Subject: [PATCH] Ford: honor C2-free toggle without EPS firmware gate --- docs/ford_virtual_angle_experiment.md | 25 +++++++++++++++---- .../controls/lib/ford_virtual_angle.py | 6 ++--- .../controls/tests/test_ford_virtual_angle.py | 24 ++++++++++++++---- .../sunnypilot/sunnylink/settings_ui.json | 2 +- .../settings_ui_src/pages/vehicle.yaml | 2 +- .../sunnylink/tests/test_settings_schema.py | 4 ++- 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/docs/ford_virtual_angle_experiment.md b/docs/ford_virtual_angle_experiment.md index ebb52af414..daa75bc2af 100644 --- a/docs/ford_virtual_angle_experiment.md +++ b/docs/ford_virtual_angle_experiment.md @@ -31,6 +31,13 @@ measured correction can distinguish them. Replaying old motion verifies command construction, not the truck's counterfactual response or a fix for the unrecorded plateau. +Route83 had the sunnylink toggle on, but its current CarParams omitted the +EPS firmware responses. The former firmware eligibility check therefore +selected the default `FordPathController`; replay reproduced its recorded +C0/C1/C2 requests. Those favorable driving results do not validate v5. The +toggle now selects this experiment on the supported Lightning platform +without depending on firmware-query results. + ## Base request and feedback controlsd uses valid `lateralManeuverPlan.desiredCurvature`, otherwise @@ -128,11 +135,19 @@ filtered heading does not command C1. Vehicle → Ford → **C2-Free Path Tracking (Experimental)** retains the existing `FordVirtualAngleController` key and default-off setting. An already-enabled -setting selects v5 after updating and restarting controlsd. Selection remains -limited to Ford CAN FD, `FORD_F_150_LIGHTNING_MK1`, and EPS firmware -`RL38-14D003-AA`. It takes priority over PSCM Coefficient Observer. Turning it -off and cycling offroad/onroad restores the previous controller selection. -No live device setting is changed by this commit. +setting selects v5 after updating and restarting controlsd. The toggle +controls selection on Ford CAN FD `FORD_F_150_LIGHTNING_MK1`: missing or +different EPS firmware-query results no longer cause a fallback. Other +platforms retain their existing controller. V5 takes priority over PSCM +Coefficient Observer while selected. Turning it off and cycling offroad/onroad +restores the previous controller selection; changes are not applied live onroad. + +Controller selection does not bypass lateral engagement, input-validity, +driver-override or fresh-PSCM-status requirements. The feedback eligibility +rules above still apply, and all C0/C1 bounds and C2/C3 behavior are unchanged. +The analyzed firmware remains `RL38-14D003-AA`; removing the selection check +does not establish validation on other firmware. No live device setting is +changed by this commit. ## Diagnostics and verification diff --git a/openpilot/selfdrive/controls/lib/ford_virtual_angle.py b/openpilot/selfdrive/controls/lib/ford_virtual_angle.py index 70727279c6..b397ed2ed7 100644 --- a/openpilot/selfdrive/controls/lib/ford_virtual_angle.py +++ b/openpilot/selfdrive/controls/lib/ford_virtual_angle.py @@ -300,9 +300,9 @@ class FordVirtualAngleController: def select_virtual_angle_controller(CP, enabled, previous_controller): - # The source route analysis covers only this vehicle/firmware. + # The Sunnylink toggle selects this controller on the Lightning even when + # the startup firmware query omits EPS identification. compatible = CP.brand == 'ford' and CP.flags & FordFlags.CANFD and CP.carFingerprint == 'FORD_F_150_LIGHTNING_MK1' - firmware = compatible and any(str(fw.ecu) == 'eps' and bytes(fw.fwVersion).rstrip(b'\0') == b'RL38-14D003-AA' for fw in CP.carFw) - if enabled and firmware: + if enabled and compatible: return FordVirtualAngleController(CP.steerActuatorDelay) return previous_controller diff --git a/openpilot/selfdrive/controls/tests/test_ford_virtual_angle.py b/openpilot/selfdrive/controls/tests/test_ford_virtual_angle.py index bbfbe91efb..ba2634dc63 100644 --- a/openpilot/selfdrive/controls/tests/test_ford_virtual_angle.py +++ b/openpilot/selfdrive/controls/tests/test_ford_virtual_angle.py @@ -18,14 +18,25 @@ def car_params(**kwargs): class TestVirtualAngleSelection(unittest.TestCase): - def test_opt_in_and_exact_vehicle_firmware_scope(self): + def test_opt_in_and_exact_vehicle_scope(self): for previous in (FordPathController(), FordPscmObserverPathController()): self.assertIs(select_virtual_angle_controller(car_params(), False, previous), previous) - for overrides in ({'brand': 'tesla'}, {'flags': 0}, {'carFingerprint': 'FORD_F_150_MK14'}, - {'carFw': []}, {'carFw': [SimpleNamespace(ecu='eps', fwVersion=b'ML3V')]}): + for overrides in ({'brand': 'tesla'}, {'flags': 0}, {'carFingerprint': 'FORD_F_150_MK14'}): self.assertIs(select_virtual_angle_controller(car_params(**overrides), True, previous), previous) self.assertIsInstance(select_virtual_angle_controller(car_params(), True, previous), FordVirtualAngleController) + def test_toggle_controls_selection_independently_of_firmware_query(self): + for firmware in ([], [SimpleNamespace(ecu='engine', fwVersion=b'engine')], + [SimpleNamespace(ecu='eps', fwVersion=b'RL38-14D003-AA')], + [SimpleNamespace(ecu='eps', fwVersion=b'other')]): + for previous in (FordPathController(), FordPscmObserverPathController()): + with self.subTest(firmware=firmware, previous=type(previous).__name__): + cp = car_params(carFw=firmware, steerActuatorDelay=.3) + self.assertIs(select_virtual_angle_controller(cp, False, previous), previous) + chosen = select_virtual_angle_controller(cp, True, previous) + self.assertIsInstance(chosen, FordVirtualAngleController) + self.assertEqual(chosen.delay, .3) + def test_old_setting_cannot_enable_new_controller(self): from openpilot.common.params import Params with tempfile.TemporaryDirectory(prefix='ford-virtual-params-') as directory: @@ -36,11 +47,14 @@ class TestVirtualAngleSelection(unittest.TestCase): self.assertIs(params.get_default_value('FordVirtualAngleController'), False) self.assertFalse(params.get_bool('FordVirtualAngleController')) previous = FordPathController() - self.assertIs(select_virtual_angle_controller(car_params(), params.get_bool('FordVirtualAngleController'), previous), previous) + # Route83 had the toggle on but no EPS firmware records in CarParams. + cp = car_params(carFw=[]) + self.assertIs(select_virtual_angle_controller(cp, params.get_bool('FordVirtualAngleController'), previous), previous) params.put_bool('FordVirtualAngleController', True, block=True) - chosen = select_virtual_angle_controller(car_params(), params.get_bool('FordVirtualAngleController'), previous) + chosen = select_virtual_angle_controller(cp, params.get_bool('FordVirtualAngleController'), previous) params.put_bool('FordVirtualAngleController', False, block=True) self.assertIsInstance(chosen, FordVirtualAngleController) # only selected at startup + self.assertIs(select_virtual_angle_controller(cp, params.get_bool('FordVirtualAngleController'), previous), previous) if __name__ == '__main__': diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index 074732c112..88b86adda7 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -2184,7 +2184,7 @@ "needs_onroad_cycle": true, "title": "C2-Free Path Tracking (Experimental)", "description": "Follow the planned turn with a measured steering correction on the F-150 Lightning with C2 off.", - "details": "Uses planned curvature for centering and heading, with a bounded correction when measured turning differs from the request. The correction requires fresh steering-controller status and clears during driver override. Default off and this version is not road-validated. Available only on the F-150 Lightning with RL38-14D003-AA steering firmware; other vehicles retain their existing controller. Enable only for controlled testing. Takes priority over PSCM Coefficient Observer while enabled. Turning it off restores the previous controller selection. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone.", + "details": "Uses planned curvature for centering and heading, with a bounded correction when measured turning differs from the request. The correction requires fresh steering-controller status and clears during driver override. Default off and this version is not road-validated. When enabled, this controller is always selected on the Ford CAN FD F-150 Lightning regardless of steering-firmware identification; other vehicles retain their existing controller. Enable only for controlled testing. Takes priority over PSCM Coefficient Observer while enabled. Turning it off restores the previous controller selection. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone.", "enablement": [ { "type": "offroad_only" diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml index fbcec674ba..8e4463331f 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml @@ -15,7 +15,7 @@ sections: needs_onroad_cycle: true title: C2-Free Path Tracking (Experimental) description: Follow the planned turn with a measured steering correction on the F-150 Lightning with C2 off. - details: Uses planned curvature for centering and heading, with a bounded correction when measured turning differs from the request. The correction requires fresh steering-controller status and clears during driver override. Default off and this version is not road-validated. Available only on the F-150 Lightning with RL38-14D003-AA steering firmware; other vehicles retain their existing controller. Enable only for controlled testing. Takes priority over PSCM Coefficient Observer while enabled. Turning it off restores the previous controller selection. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone. + details: Uses planned curvature for centering and heading, with a bounded correction when measured turning differs from the request. The correction requires fresh steering-controller status and clears during driver override. Default off and this version is not road-validated. When enabled, this controller is always selected on the Ford CAN FD F-150 Lightning regardless of steering-firmware identification; other vehicles retain their existing controller. Enable only for controlled testing. Takes priority over PSCM Coefficient Observer while enabled. Turning it off restores the previous controller selection. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone. enablement: - $ref: '#/macros/offroad' - key: FordPscmObserver diff --git a/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py b/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py index f9b81f382b..65d5e64174 100644 --- a/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py +++ b/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py @@ -289,7 +289,9 @@ class TestKnownVehicleSettings(OpenpilotTestCase): # No other toggle can prevent disabling this experiment while offroad. assert servo["enablement"] == [{"type": "offroad_only"}] assert "F-150 Lightning" in servo["description"] - assert "RL38-14D003-AA" in servo["details"] + assert "always selected on the Ford CAN FD F-150 Lightning regardless of steering-firmware identification" in servo["details"] + assert "Turning it off restores the previous controller selection" in servo["details"] + assert "RL38-14D003-AA" not in servo["details"] assert "not road-validated" in servo["details"] assert "offroad" in servo["details"] and "onroad" in servo["details"]