mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
8da0c9d916
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>
172 lines
7.3 KiB
Python
172 lines
7.3 KiB
Python
"""Saved Ford release intent is consumed before construction, with exact admission."""
|
|
from types import SimpleNamespace
|
|
import pytest
|
|
from opendbc.car import gen_empty_fingerprint
|
|
from opendbc.car.ford.interface import CarInterface
|
|
from opendbc.car.ford.values import CAR
|
|
from openpilot.starpilot.car.ford.longitudinal_preferences import prepare_release_long
|
|
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES
|
|
|
|
|
|
def saved(tmp_path, alpha=b'1', safe=b'0'):
|
|
for key, value in [('AlphaLongitudinalEnabled', alpha), ('SafeMode', safe)]:
|
|
if value is not None:
|
|
(tmp_path / key).write_bytes(value)
|
|
return SimpleNamespace(get_param_path=lambda key: str(tmp_path / key))
|
|
|
|
|
|
@pytest.mark.parametrize('identity,base', FORD_FACTORIES)
|
|
@pytest.mark.parametrize('manual', [False, True])
|
|
@pytest.mark.parametrize('offset', [0, 4])
|
|
def test_release_long_changes_only_selected_owner_and_long_selection(identity, base, manual, offset, tmp_path):
|
|
fp = gen_empty_fingerprint()
|
|
if not manual:
|
|
fp[offset][0x5A] = 8
|
|
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
|
stock = CarInterface.get_params(identity, fp, [], False, True, False)
|
|
stock.carVin = '0' * 17
|
|
stock.fuzzyFingerprint = True
|
|
before = stock.to_dict()
|
|
selected = prepare_release_long(stock, saved(tmp_path), is_release=True, enabled=True)
|
|
expected = stock.as_reader().as_builder()
|
|
expected.openpilotLongitudinalControl = True
|
|
expected.safetyConfigs[-1].safetyParam = base | 1
|
|
assert stock.to_dict() == before
|
|
assert selected.to_dict() == expected.to_dict()
|
|
|
|
|
|
@pytest.mark.parametrize('alpha,safe,release,enabled', [
|
|
(None, b'0', True, True), (b'0', b'0', True, True), (b'1junk', b'0', True, True),
|
|
(b'1', b'1', True, True), (b'1', b'bad', True, True), (b'1', b'0', False, True),
|
|
(b'1', b'0', True, False),
|
|
])
|
|
def test_release_opt_in_off_invalid_safe_mode_debug_and_disabled_stay_unchanged(alpha, safe, release, enabled, tmp_path):
|
|
fp = gen_empty_fingerprint()
|
|
fp[0][0x5A] = 8
|
|
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fp, [], False, True, False)
|
|
assert prepare_release_long(cp, saved(tmp_path, alpha, safe), is_release=release, enabled=enabled) is cp
|
|
|
|
|
|
@pytest.mark.parametrize('restriction', ['secoc', 'extra-panda', 'foreign-brand'])
|
|
def test_release_never_promotes_unowned_configuration(restriction, tmp_path):
|
|
fp = gen_empty_fingerprint()
|
|
fp[0][0x5A] = 8
|
|
fp[2].update({0x3D6: 16 if restriction == 'secoc' else 8, 0x186: 16 if restriction == 'secoc' else 8})
|
|
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fp, [], False, True, False)
|
|
if restriction == 'extra-panda':
|
|
cp.safetyConfigs = [cp.safetyConfigs[0], cp.safetyConfigs[0]]
|
|
elif restriction == 'foreign-brand':
|
|
cp.brand = 'hyundai'
|
|
assert prepare_release_long(cp, saved(tmp_path), is_release=True, enabled=True) is cp
|
|
|
|
|
|
class StopApproachFrame(dict):
|
|
def __init__(self):
|
|
from openpilot.starpilot.car.ford.stopped_lead import SOURCES
|
|
super().__init__(carState=SimpleNamespace(vEgo=.8, canValid=True, canTimeout=False, accFaulted=False,
|
|
gasPressed=False, brakePressed=False, gearShifter='drive', standstill=False),
|
|
radarState=SimpleNamespace(leadOne=SimpleNamespace(present=True, radar=True, modelProb=.999,
|
|
dRel=5.5, vLead=0., yRel=0.), leadTwo=SimpleNamespace(present=False)),
|
|
modelV2=SimpleNamespace(), carControl=SimpleNamespace(longActive=True),
|
|
controlsState=SimpleNamespace(forceDecel=False), selfdriveState=SimpleNamespace(enabled=True))
|
|
self.valid = dict.fromkeys(SOURCES, True)
|
|
self.alive = dict.fromkeys(SOURCES, True)
|
|
self.logMonoTime = dict.fromkeys(SOURCES, 1_000_000_000)
|
|
|
|
|
|
def stopped_sample(owner, sm, **changes):
|
|
args = {'now_ns': 1_000_000_000, 'drive_id': 1, 'target': -.05, 'stopping': False, 'accel_min': -3.5, 'active': True}
|
|
args.update(changes)
|
|
return owner.sample(sm, **args)
|
|
|
|
|
|
def primed_stop_approach():
|
|
from openpilot.starpilot.car.ford.stopped_lead import RollingStoppedLead
|
|
owner, sm = RollingStoppedLead(5.5), StopApproachFrame()
|
|
assert stopped_sample(owner, sm) == (-.05, False)
|
|
sm['carState'].vEgo = .4
|
|
return owner, sm
|
|
|
|
|
|
def test_original_dom_rolling_stop_cap_and_stronger_braking_are_retained():
|
|
owner, sm = primed_stop_approach()
|
|
target, stopping = stopped_sample(owner, sm)
|
|
# Dom's original6m STOP_DISTANCE-.5 +3m margin, with its unchanged brake terms.
|
|
assert target == pytest.approx(-.21272727272727274) and stopping
|
|
assert stopped_sample(owner, sm, target=-3.5, stopping=True) == (-3.5, True)
|
|
|
|
|
|
@pytest.mark.parametrize('defect', ['gas', 'brake', 'stale', 'future', 'invalid', 'off', 'disabled', 'force',
|
|
'inactive', 'can', 'gear', 'nan', 'drive'])
|
|
def test_stop_approach_cannot_override_driver_invalid_source_or_new_drive(defect):
|
|
owner, sm = primed_stop_approach()
|
|
changes = {}
|
|
if defect in ('gas', 'brake'):
|
|
setattr(sm['carState'], defect + 'Pressed', True)
|
|
elif defect == 'stale':
|
|
sm.logMonoTime['radarState'] -= 250_000_001
|
|
elif defect == 'future':
|
|
sm.logMonoTime['radarState'] += 1
|
|
elif defect == 'invalid':
|
|
sm.valid['radarState'] = False
|
|
elif defect == 'off':
|
|
sm['carControl'].longActive = False
|
|
elif defect == 'disabled':
|
|
sm['selfdriveState'].enabled = False
|
|
elif defect == 'force':
|
|
sm['controlsState'].forceDecel = True
|
|
elif defect == 'inactive':
|
|
changes['active'] = False
|
|
elif defect == 'can':
|
|
sm['carState'].canValid = False
|
|
elif defect == 'gear':
|
|
sm['carState'].gearShifter = 'reverse'
|
|
elif defect == 'nan':
|
|
sm['radarState'].leadOne.dRel = float('nan')
|
|
elif defect == 'drive':
|
|
changes['drive_id'] = 2
|
|
assert stopped_sample(owner, sm, **changes) == (-.05, False)
|
|
assert not owner.rolling
|
|
|
|
|
|
@pytest.mark.parametrize('defect', ['moving', 'far', 'offcenter', 'uncertain', 'absent'])
|
|
def test_stop_approach_keeps_normal_moving_and_unqualified_leads_unchanged(defect):
|
|
owner, sm = primed_stop_approach()
|
|
lead = sm['radarState'].leadOne
|
|
if defect == 'moving':
|
|
lead.vLead = .450001
|
|
elif defect == 'far':
|
|
lead.dRel = 8.500001
|
|
elif defect == 'offcenter':
|
|
lead.yRel = 1.750001
|
|
elif defect == 'uncertain':
|
|
lead.radar, lead.modelProb = False, .949999
|
|
else:
|
|
lead.present = False
|
|
assert stopped_sample(owner, sm) == (-.05, False)
|
|
|
|
|
|
def test_actual_standstill_and_normal_departure_keep_existing_release_owner():
|
|
owner, sm = primed_stop_approach()
|
|
assert stopped_sample(owner, sm)[1]
|
|
sm['carState'].standstill, sm['carState'].vEgo = True, 0.
|
|
assert stopped_sample(owner, sm, stopping=True) == (-.05, True)
|
|
sm['carState'].standstill, sm['carState'].vEgo = False, .2
|
|
sm['radarState'].leadOne.vLead = .2
|
|
assert stopped_sample(owner, sm, target=.3) == (.3, False)
|
|
owner.reset()
|
|
assert stopped_sample(owner, sm, target=.3) == (.3, False)
|
|
|
|
|
|
def test_stopped_lead_owner_requires_existing_mache_open_long_configuration():
|
|
from openpilot.starpilot.car.ford.stopped_lead import owner_for
|
|
fp = gen_empty_fingerprint()
|
|
fp[0][0x5A] = 8
|
|
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fp, [], True, False, False)
|
|
assert owner_for(cp, 5.5) is not None
|
|
for field, value in (('openpilotLongitudinalControl', False), ('passive', True), ('dashcamOnly', True),
|
|
('notCar', True), ('brand', 'hyundai'), ('carFingerprint', CAR.FORD_F_150_MK14)):
|
|
changed = cp.as_reader().as_builder()
|
|
setattr(changed, field, value)
|
|
assert owner_for(changed, 5.5) is None
|