From a0d80165dfe2815a0ac34e9beb04a8f99d0231f1 Mon Sep 17 00:00:00 2001 From: alan-polk <56047663+alan-polk@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:53:08 -0500 Subject: [PATCH] Ford AOL: a rejected host actuator frame doesn't hand the bus to the camera The previous commit let the camera's actuator through again as soon as one host frame was rejected. Mid-steer that put the camera's inactive frame (its own counter) on the bus in place of the blocked one, and the F-150's PSCM ramped out and reported lateral control faulty for several seconds, from a single blocked frame. Without AOL, static blocking makes a rejected frame a missed one, which the PSCM rides through. Block the camera's copy while the host is sending, accepted or not; only a host that has stopped sending for the AOL host timeout gives the actuator back to the camera. Co-Authored-By: Claude Opus 5.5 --- opendbc_repo/opendbc/safety/modes/ford_aol.h | 10 ++++++---- .../opendbc/safety/tests/test_ford_aol.py | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/opendbc_repo/opendbc/safety/modes/ford_aol.h b/opendbc_repo/opendbc/safety/modes/ford_aol.h index 9b761d0056..5c0d75b5d3 100644 --- a/opendbc_repo/opendbc/safety/modes/ford_aol.h +++ b/opendbc_repo/opendbc/safety/modes/ford_aol.h @@ -30,9 +30,11 @@ static bool ford_aol_lka_ready = false; static uint8_t ford_aol_owned = 0U; static uint32_t ford_aol_accepted_ts = 0U; -// The host sends the steering actuator every frame, active or not. While its last frame was -// accepted, the camera's copy stays off the bus, so the PSCM hears one sender, as it would -// under static blocking. A rejected or stale host frame lets the camera's copy through again. +// The host sends the steering actuator every frame, active or not. While it is sending, the +// camera's copy stays off the bus, so the PSCM hears one sender, as it would under static +// blocking. A rejected host frame is a missed frame, as without AOL: letting the camera's +// inactive frame through in its place, mid-steer, makes the PSCM ramp out and fault. Only a +// host that has stopped sending gives the actuator back to the camera. static bool ford_aol_host_actuator_seen = false; static uint32_t ford_aol_host_actuator_ts = 0U; @@ -337,7 +339,7 @@ static void ford_aol_tx(const CANPacket_t *msg, bool accepted) { const bool display_owned = steering_owned || (accepted && retained_display && ((permission & 1U) != 0U) && !relay_malfunction); ford_aol_owned = (steering_owned ? FORD_AOL_STEERING_OWNED : 0U) | (display_owned ? FORD_AOL_DISPLAY_OWNED : 0U); ford_aol_accepted_ts = microsecond_timer_get(); - ford_aol_host_actuator_seen = accepted; + ford_aol_host_actuator_seen = true; ford_aol_host_actuator_ts = ford_aol_accepted_ts; } if ((msg->bus == 0U) && ((msg->addr == 0x18AU) || (msg->addr == 0x3D8U))) { diff --git a/opendbc_repo/opendbc/safety/tests/test_ford_aol.py b/opendbc_repo/opendbc/safety/tests/test_ford_aol.py index 1ccc10cbe6..7e746db552 100644 --- a/opendbc_repo/opendbc/safety/tests/test_ford_aol.py +++ b/opendbc_repo/opendbc/safety/tests/test_ford_aol.py @@ -230,9 +230,10 @@ class TestFordAolDriverIntent(unittest.TestCase): frame = create_lat_ctl_msg(self.packer, bus, False, offset, 0., 0., 0.) return frame[0], libsafety_py.make_CANPacket(frame[0], frame[2], frame[1]) - def test_one_actuator_sender_while_host_frames_are_accepted(self): + def test_one_actuator_sender_while_the_host_sends(self): # The camera's actuator is forwarded under AOL so the stock system keeps its message when the host - # isn't sending; while the host's inactive frames reach the bus, two senders confuse the PSCM + # isn't sending; while the host sends, two senders confuse the PSCM, and a camera frame standing in + # for a rejected host frame mid-steer makes it ramp out and fault for word in self.PROFILES: with self.subTest(word=word): self.reset(word) @@ -254,8 +255,16 @@ class TestFordAolDriverIntent(unittest.TestCase): self.assertEqual(self.safety.safety_fwd_hook(2, address), 0) self.assertTrue(self.safety.safety_tx_hook(inactive)) self.assertEqual(self.safety.safety_fwd_hook(2, address), -1) - self.assertFalse(self.safety.safety_tx_hook(self.host_actuator(valid=False)[1])) + # a rejected host frame is a missed frame, not the camera's turn + _, rejected = self.host_actuator(valid=False) + for _ in range(10): + self.pump(5) + self.assertFalse(self.safety.safety_tx_hook(rejected)) + self.assertEqual(self.safety.safety_fwd_hook(2, address), -1) + self.pump(31) self.assertEqual(self.safety.safety_fwd_hook(2, address), 0) + self.assertFalse(self.safety.safety_tx_hook(rejected)) + self.assertEqual(self.safety.safety_fwd_hook(2, address), -1) self.safety.set_safety_hooks(structs.CarParams.SafetyModel.ford, word) self.assertEqual(self.safety.safety_fwd_hook(2, address), 0)