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 <noreply@anthropic.com>
This commit is contained in:
alan-polk
2026-10-30 10:53:08 -05:00
parent 67f4250d7e
commit a0d80165df
2 changed files with 18 additions and 7 deletions
+6 -4
View File
@@ -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))) {
@@ -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)