From 465db1bc61c2193de07a1287ae4424d2fcf4e6c4 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:55:37 -0600 Subject: [PATCH] Preserve Ford lateral intent across separate brake status receipts --- openpilot/starpilot/aol/transport_pause.py | 10 +++++++++- .../starpilot/tests/ford_lifecycle_helpers.py | 14 +++++++++++++- .../tests/test_ford_standstill_brake.py | 16 ++++++++++++++++ prebuilt.json | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/openpilot/starpilot/aol/transport_pause.py b/openpilot/starpilot/aol/transport_pause.py index a2c247cdae..9f5c81799c 100644 --- a/openpilot/starpilot/aol/transport_pause.py +++ b/openpilot/starpilot/aol/transport_pause.py @@ -394,7 +394,15 @@ class TransportPauseFeedback: for event in sm['onroadEvents'])): return None native = independent_lateral_health(sm, cp, cs, self.session, now_ns) - if (native is None or not native.pandaInventory or not native.requestedLongitudinal or native.longitudinalAllowed or + # Separate health may report brake withdrawal before the matching axis status. + # Retain only the existing LAT intent; LONG still follows its physical pedal. + ford_brake_health = bool(ford_withdrawal and cs.brakePressed and not cs.gasPressed and + native is not None and native.sourcePandaStatesMonoTime > 0 and + native.sourcePandaStatesMonoTime < int(sm.logMonoTime['pandaStates']) and + all(not slot.controlsAllowed for slot, config in zip(sm['pandaStates'], cp.safetyConfigs, strict=True) + if str(config.safetyModel) not in ('silent', 'noOutput'))) + if (native is None or not native.pandaInventory or not native.requestedLongitudinal or + native.longitudinalAllowed and not ford_brake_health or native.observedMonoTime < self.lateral_native_ns or (native.pandaSerial, tuple(slot.hardwareSerial for slot in native.pandaInventory)) != self.lateral_inventory): return None diff --git a/openpilot/starpilot/tests/ford_lifecycle_helpers.py b/openpilot/starpilot/tests/ford_lifecycle_helpers.py index 185e109a6b..cd27b6944d 100644 --- a/openpilot/starpilot/tests/ford_lifecycle_helpers.py +++ b/openpilot/starpilot/tests/ford_lifecycle_helpers.py @@ -204,6 +204,7 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag now = [10_000_000_000] queued, published, previous_sd = [], {}, {} delayed_native = {} + pending_native_wire = {} source_gap_messages = {} source_gap_active = [False] steps = start_tick = native.tick @@ -448,7 +449,15 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag # Match pandad's 10Hz native publication, including a pedal-first USB read. # Values are actual native results; retained messages keep their stamps. if getattr(monkeypatch, 'ford_native_10hz', False): - if not delayed_native or steps % 10 == 0 or (brake or cancel) and not permission & 2 and delayed_native.get('permission', 3) & 2: + if getattr(monkeypatch, 'ford_native_health_first', False): + if not delayed_native: + delayed_native.update(wire=wire.as_reader(), panda=panda.as_reader(), permission=permission) + elif steps % 10 == 0: + pending_native_wire.update(wire=wire.as_reader()) + delayed_native.update(panda=panda.as_reader(), permission=permission) + elif steps % 10 == 1 and pending_native_wire: + delayed_native.update(wire=pending_native_wire.pop('wire')) + elif not delayed_native or steps % 10 == 0 or (brake or cancel) and not permission & 2 and delayed_native.get('permission', 3) & 2: delayed_native.update(wire=wire.as_reader(), panda=panda.as_reader(), permission=permission) wire_message, panda_message = delayed_native['wire'], delayed_native['panda'] else: @@ -1078,6 +1087,9 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag if scenario == 'brake-native-cadence': for _ in range(41): state, axis, sd_state, command = step(engaged=True) + if hasattr(monkeypatch, 'ford_brake_phase'): + while steps % 10 != monkeypatch.ford_brake_phase: + state, axis, sd_state, command = step(engaged=True) assert command.latActive and command.longActive and host.aol_card_intent.allowed_latch if getattr(monkeypatch, 'ford_standstill_brake', False): for slowing_tick in range(300): diff --git a/openpilot/starpilot/tests/test_ford_standstill_brake.py b/openpilot/starpilot/tests/test_ford_standstill_brake.py index 36d9132a04..7a4ed1ff0f 100644 --- a/openpilot/starpilot/tests/test_ford_standstill_brake.py +++ b/openpilot/starpilot/tests/test_ford_standstill_brake.py @@ -5,6 +5,11 @@ from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES, for @pytest.fixture(autouse=True) def configuration(monkeypatch, request): monkeypatch.ford_topology = request.node.callspec.params['topology'] + mode = request.node.callspec.params.get('mode') + if mode is not None: + monkeypatch.ford_angle = mode != 'curvature' + monkeypatch.ford_starpilot = mode == 'starpilot' + monkeypatch.ford_pinion = True @pytest.mark.parametrize('identity', [FORD_FACTORIES[11]]) @@ -16,3 +21,14 @@ def test_native_first_brake_while_already_stopped(ford_joined, monkeypatch, topo monkeypatch.ford_native_first_pedal = True monkeypatch.ford_native_10hz = True run_actual_producer(ford_joined, monkeypatch, scenario='brake-native-cadence') + + +@pytest.mark.parametrize('identity', [FORD_FACTORIES[11]]) +@pytest.mark.parametrize('alpha,hands_free,topology', [(True, True, 'automatic')]) +@pytest.mark.parametrize('mode', ['angle', 'curvature', 'starpilot']) +@pytest.mark.parametrize('phase', range(10)) +def test_brake_retains_lateral_with_health_before_native_axis_status(ford_joined, monkeypatch, mode, phase, topology): + monkeypatch.ford_native_10hz = True + monkeypatch.ford_native_health_first = True + monkeypatch.ford_brake_phase = phase + run_actual_producer(ford_joined, monkeypatch, scenario='brake-native-cadence') diff --git a/prebuilt.json b/prebuilt.json index b164ee6947..4d679ef75d 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "4a6d1ae614b9a07311e0e5f7565ea677ca19303cfc64d7557523499b56279f52", "schema_version": 1} +{"protected_inventory_sha256": "803e3f48381fb605404075f5d247a5830c23bd3ccf6abf12ec3aadd420608976", "schema_version": 1}