Files
StarPilot/openpilot/starpilot/tests/test_ford_configuration_admission.py
T
alan-polk 8da0c9d916 Ford: remove the interim BluePilot port; AOL on the stock profiles
StarPilot's angle-only bluepilot_lateral adaptation and the BluePilot-derived curvature strategies (Mach-E,
classic, Explorer, generic CAN FD, new-port curvature, manual-turn latch, curvature preview, pinion
measurement) are removed, with their extended safety profiles 16/32/64 and the interim angle word 128. Ford
lateral is upstream's stock curvature path until the BluePilot lateral library is mounted.

StarPilot's own Ford features stay, re-keyed from the extended words to the stock ones:
- AOL: classic 32 -> 0, Mach-E 18 and the CAN FD trucks 66 -> 2 (+1 with openpilot long); the CAN FD
  retained-display rules apply to word 2. Edge/Mondeo/Transit and the manual bit (256) are unchanged.
- release CAN FD long: word 3 (was 19/67); 19 and 67 are now retired and denied.
- stock cruise switch, CAN FD camera-lead radar handling, hands-free cluster: keyed on the Ford family.
- classic stock ACC (0) keeps the no-ACCDATA TX list the extended classic profile had.

Bits 9-14 of the Ford param are reserved for the BluePilot lateral library's startup bits
(safety/modes/ford_param.h); AOL and pandad mask them out of the profile word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:49:32 -05:00

70 lines
4.0 KiB
Python

"""Ford dual-Panda and transmission admission remains exact and fail-closed."""
from dataclasses import replace
import pytest
from opendbc.car import gen_empty_fingerprint, structs
from opendbc.car.ford.interface import CarInterface
from opendbc.car.ford.aol import qualified
from openpilot.starpilot.aol.vehicle import native_matches_cp, policy_for
from openpilot.starpilot.aol.runtime import native_inventory_matches_cp
from openpilot.starpilot.aol.wire import PandaSlot, SafetyState
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES
@pytest.mark.parametrize('identity,base', FORD_FACTORIES)
@pytest.mark.parametrize('alpha', [False, True])
@pytest.mark.parametrize('manual', [False, True])
def test_exact_dual_owner_and_manual_identity(identity, base, alpha, manual):
fp = gen_empty_fingerprint()
if not manual:
fp[4][0x5A] = 8
fp[6].update({0x3D6: 8, 0x186: 8})
cp = CarInterface.get_params(identity, fp, [], alpha, True, False)
policy = policy_for(cp)
cp.safetyConfigs[-1].safetyParam |= policy.safety_param_addition
cp.alternativeExperience |= policy.alternative_experience_addition
word = base | int(alpha) | (256 if manual else 0)
assert cp.safetyConfigs[-1].safetyParam == word
assert qualified(cp, marked_only=True) and native_matches_cp(cp, 6, word)
# bits 9-14 are the BluePilot lateral library's; bit 15 belongs to no one
bluepilot = cp.as_reader().as_builder()
bluepilot.safetyConfigs[-1].safetyParam = word | 0x4000
assert qualified(bluepilot, marked_only=True) and native_matches_cp(bluepilot, 6, word | 0x4000)
for field, value in [('safetyParam', word ^ 256), ('safetyParam', word | 0x8000), ('safetyModel', structs.CarParams.SafetyModel.noOutput)]:
bad = cp.as_reader().as_builder()
setattr(bad.safetyConfigs[-1], field, value)
assert not qualified(bad) and not native_matches_cp(bad, 6, word)
for field, value in [('safetyParam', 1), ('safetyModel', structs.CarParams.SafetyModel.ford)]:
bad = cp.as_reader().as_builder()
setattr(bad.safetyConfigs[0], field, value)
assert not qualified(bad) and not native_matches_cp(bad, 6, word)
bad = cp.as_reader().as_builder()
bad.safetyConfigs = [cp.safetyConfigs[0], cp.safetyConfigs[0], cp.safetyConfigs[-1]]
assert not qualified(bad)
slots = (PandaSlot(0, 'passive', int(cp.safetyConfigs[0].safetyModel.raw), 0, 32, False, False), PandaSlot(1, 'ford', 6, word, 32, True, False))
state = SafetyState(1, True, 100, 200, 6, word, True, bool(alpha), True, bool(alpha), 'ford', 'session',
sourcePandaStatesMonoTime=100, pandaInventory=slots)
assert native_inventory_matches_cp(state, cp)
assert not native_inventory_matches_cp(replace(state, sourcePandaStatesMonoTime=0, pandaInventory=()), cp)
for index, changes in [(0, {'controlsAllowed': True}), (0, {'safetyParam': 1}), (0, {'hardwareSerial': 'ford'}),
(0, {'heartbeatLost': True}), (1, {'safetyRxChecksInvalid': True}),
(1, {'slotIndex': 0}), (1, {'safetyParam': word ^ 1}), (1, {'alternativeExperience': 0})]:
invalid = list(slots)
invalid[index] = replace(invalid[index], **changes)
assert not native_inventory_matches_cp(replace(state, pandaInventory=tuple(invalid)), cp)
def test_marked_manual_policy_survives_cards_alternative_experience_reset():
fp = gen_empty_fingerprint()
cp = CarInterface.get_params(FORD_FACTORIES[0][0], fp, [], False, False, False)
initial = policy_for(cp)
assert cp.safetyConfigs[-1].safetyParam == 0 and initial.safety_param_addition == 256
cp.safetyConfigs[-1].safetyParam |= initial.safety_param_addition
cp.alternativeExperience |= initial.alternative_experience_addition
assert native_matches_cp(cp, 6, 256)
cp.alternativeExperience = 0 # Car resets this before reapplying saved settings.
recovered = policy_for(cp)
assert recovered.intent_supported and recovered.safety_param_addition == 256
cp.safetyConfigs[-1].safetyParam |= recovered.safety_param_addition
cp.alternativeExperience |= recovered.alternative_experience_addition
assert native_matches_cp(cp, 6, 256)