diff --git a/opendbc_repo/opendbc/safety/modes/ford_aol.h b/opendbc_repo/opendbc/safety/modes/ford_aol.h index f8f5db9c7e..d25f0c6709 100644 --- a/opendbc_repo/opendbc/safety/modes/ford_aol.h +++ b/opendbc_repo/opendbc/safety/modes/ford_aol.h @@ -30,7 +30,7 @@ static uint8_t ford_aol_owned = 0U; static uint32_t ford_aol_accepted_ts = 0U; // An accepted inactive actuator plus a truthful OFF/standby display can own -// cluster continuity without owning steering. Keep the original 200ms bound. +// cluster continuity without owning steering. Keep the existing command lease. #define FORD_AOL_NEUTRAL_STEERING_SEEN 0x80U static bool ford_aol_neutral_request_seen = false; static uint32_t ford_aol_neutral_steering_ts = 0U; @@ -123,6 +123,12 @@ static void ford_aol_host_request(uint8_t mask) { ford_aol_neutral_request_seen = true; ford_aol_request_seen |= mask != 0U; if (ford_aol_retained_display_profile() && ((mask & 1U) == 0U)) { + // Bridge Off UI before the next inactive actuator without renewing its accepted-command deadline. + if ((mask == 0U) && ford_aol_neutral_sources_current() && ((ford_aol_owned & FORD_AOL_STEERING_OWNED) != 0U) && + (safety_get_ts_elapsed(microsecond_timer_get(), ford_aol_accepted_ts) <= AOL_HOST_REQUEST_TIMEOUT_US)) { + ford_aol_neutral_steering_ts = ford_aol_accepted_ts; + ford_aol_neutral_display_mask |= FORD_AOL_NEUTRAL_STEERING_SEEN; + } ford_aol_owned &= FORD_AOL_STEERING_OWNED; } } diff --git a/opendbc_repo/opendbc/safety/tests/test_ford_neutral_display.py b/opendbc_repo/opendbc/safety/tests/test_ford_neutral_display.py index cfd72b77d7..ee7994695b 100644 --- a/opendbc_repo/opendbc/safety/tests/test_ford_neutral_display.py +++ b/opendbc_repo/opendbc/safety/tests/test_ford_neutral_display.py @@ -150,3 +150,69 @@ def test_neutral_continuity_across_real_microsecond_timer_wrap(owner): assert owner.safety.safety_fwd_hook(2, 0x18A) == -1 assert owner.safety.safety_fwd_hook(2, 0x3D8) == -1 assert owner.safety.aol_get_permission_mask() == 0 + + +def active_then_off(owner, full=False): + owner.arm() + bus = CanBus(fingerprint=gen_empty_fingerprint()) + address, data, src = mache_can.create_lka_msg(owner.packer, bus) + assert owner.safety.safety_tx_hook(libsafety_py.make_CANPacket(address, src, data)) + address, data, src = mache_can.create_lat_ctl2_msg(owner.packer, bus, 1, 0, 1, 0., 0., 0) + assert owner.safety.safety_tx_hook(libsafety_py.make_CANPacket(address, src, data)) + assert owner.safety.aol_get_permission_mask() == 1 + if full: + owner.pump(10, main=5, mask=3, heartbeat=True) + assert owner.safety.aol_get_permission_mask() == 3 + owner.safety.aol_set_host_request(0) + owner.safety.set_aol_test_heartbeat(False) + assert owner.safety.aol_get_permission_mask() == 0 + + +@pytest.mark.parametrize('full', [False, True]) +def test_off_display_precedes_next_inactive_actuator_without_stock_warning_gap(owner, full): + active_then_off(owner, full=full) + assert owner.safety.safety_fwd_hook(2, 0x18A) == 0 + assert ui(owner, 0x18A) + assert owner.safety.safety_fwd_hook(2, 0x18A) == -1 + assert owner.safety.safety_fwd_hook(2, 0x3D8) == 0 + assert ui(owner, 0x3D8) + assert owner.safety.safety_fwd_hook(2, 0x3D8) == -1 + assert owner.safety.safety_fwd_hook(2, 0x3D6) == 0 + assert owner.safety.aol_get_permission_mask() == 0 + owner.pump(4, main=3, mask=0, heartbeat=False) + inactive(owner) + assert owner.safety.safety_fwd_hook(2, 0x18A) == -1 + + +def test_off_handover_ui_and_host_repeats_do_not_renew_the_original_command_lease(owner): + active_then_off(owner) + assert ui(owner, 0x18A) + for _ in range(10): + owner.pump(3, main=3, mask=0, heartbeat=False) + assert ui(owner, 0x18A) + owner.pump(1, main=3, mask=0, heartbeat=False) + assert not ui(owner, 0x18A) + assert owner.safety.safety_fwd_hook(2, 0x18A) == 0 + assert owner.safety.aol_get_permission_mask() == 0 + + +@pytest.mark.parametrize('loss', ['eps', 'gear', 'lka', 'acc-fault', 'rx']) +def test_off_handover_returns_stock_on_real_faults(owner, loss): + active_then_off(owner) + assert ui(owner, 0x18A) + if loss == 'eps': + owner.rx('EPAS_INFO', {'EPAS_Failure': 1}) + elif loss == 'gear': + owner.pump(10, main=3, mask=0, gear=1, heartbeat=False) + elif loss == 'lka': + owner.rx('Lane_Assist_Data3_FD1', {'LatCtlSte_D_Stat': 0}) + elif loss == 'acc-fault': + owner.rx('EngBrakeData', {'CcStat_D_Actl': 1, 'BpedDrvAppl_D_Actl': 1}) + else: + address, data, bus = owner.packer.make_can_msg('BrakeSysFeatures', 0, {'Veh_V_ActlBrk': 36., 'VehVActlBrk_D_Qf': 3}) + bad = bytearray(data) + bad[0] ^= 1 + assert not owner.safety.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, bad)) + assert not ui(owner, 0x18A) + assert owner.safety.safety_fwd_hook(2, 0x18A) == 0 + assert owner.safety.aol_get_permission_mask() == 0 diff --git a/prebuilt.json b/prebuilt.json index 11efe703a8..9596db4d4a 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "845201e8021f9955dae8eb23a1c053d4a13e609c70b9d29fac4156450fe8f365", "schema_version": 1} +{"protected_inventory_sha256": "6accf43a213ae9a890b1ff76172826831210df455860137ba905e4a7f348f734", "schema_version": 1}