mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-12 07:23:45 +08:00
2dfa4b6fe1
Revert the Bolt-specific admission change from 4b754298 while retaining
native authority, receive-state, relay, heartbeat and actuator checks.
Keep raw diagnostic reports and all other profile policies unchanged.
Add actual Card coverage and restore exact diagnostic policy regressions.
206 lines
11 KiB
Python
206 lines
11 KiB
Python
"""Source-prepared exact GM admission regressions; root executes after candidate review."""
|
|
from dataclasses import replace
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
|
|
from opendbc.car.gm.tests.test_bolt_pedal import params
|
|
from opendbc.car.gm.values import CAR
|
|
from openpilot.starpilot.aol.runtime import current_native, native_inventory_matches_cp, decide_axes
|
|
from openpilot.starpilot.aol.wire import PandaSlot, SafetyState
|
|
from openpilot.starpilot.aol.tests.test_panda_inventory import sm_for
|
|
from openpilot.starpilot.aol.tests.test_runtime import car_state
|
|
|
|
NOW = 10_000_000_000
|
|
|
|
|
|
def owner(label):
|
|
cp = sdgm_params(alpha=False, sascm=True, radar=False) if label == 'volt' else params(
|
|
CAR.CHEVROLET_BOLT_CC_2018_2021, pedal=True, camera=True)
|
|
cp.alternativeExperience = 32
|
|
assert cp.safetyConfigs[0].safetyParam == (0x1005 if label == 'volt' else 0x9D)
|
|
assert cp.openpilotLongitudinalControl == (label == 'bolt')
|
|
assert cp.pcmCruise == (label == 'volt')
|
|
config = cp.safetyConfigs[0]
|
|
slot = PandaSlot(0, label, config.safetyModel.raw, config.safetyParam, 32, False)
|
|
state = SafetyState(1, True, NOW, NOW + 200_000_000, config.safetyModel.raw, config.safetyParam,
|
|
False, False, False, False, label, 'session', NOW, (slot,))
|
|
return cp, state
|
|
|
|
|
|
@pytest.mark.parametrize('label', ('volt', 'bolt'))
|
|
@pytest.mark.parametrize('mask', (0, 1 << 2, 1 << 3, 1 << 18, 12))
|
|
def test_diagnostic_receipt_does_not_invent_native_authority(label, mask):
|
|
cp, state = owner(label)
|
|
state = replace(state, pandaInventory=(replace(state.pandaInventory[0], faults=mask),))
|
|
admitted = mask == 0 or (label == 'volt' and mask == 1 << 18) or (label == 'bolt' and mask in (1 << 2, 1 << 3, 12))
|
|
assert native_inventory_matches_cp(state, cp) == admitted
|
|
accepted = current_native(sm_for(cp, state), cp, now_ns=NOW, axis_session_id='session')
|
|
if not admitted:
|
|
assert accepted is None
|
|
return
|
|
assert accepted == state
|
|
intent = SimpleNamespace(allowedLatch=True, pauseLateral=False, pauseLongitudinal=False)
|
|
decision = decide_axes(standard_lateral=True, standard_longitudinal=cp.openpilotLongitudinalControl,
|
|
intent=intent, native=accepted, car_state=car_state(), initialized=True,
|
|
model_ready=True, no_entry=False, immediate_disable=False, dm_lockout=False, pause_brake_mps=0.)
|
|
assert not decision.lateral_active and not decision.longitudinal_active
|
|
|
|
|
|
@pytest.mark.parametrize('label', ('volt', 'bolt'))
|
|
@pytest.mark.parametrize('bit', (0, 1, 4, 5, 7, 8, 15, 20, 21, 22, 23, 25, 26, 27, 31))
|
|
def test_authority_faults_and_unknown_bits_still_block(label, bit):
|
|
cp, state = owner(label)
|
|
baseline = replace(state, pandaInventory=(replace(state.pandaInventory[0], faults=(1 << 18 if label == 'volt' else 12)),))
|
|
assert current_native(sm_for(cp, baseline), cp, now_ns=NOW, axis_session_id='session') == baseline
|
|
state = replace(baseline, pandaInventory=(replace(baseline.pandaInventory[0], faults=baseline.pandaInventory[0].faults | (1 << bit)),))
|
|
assert current_native(sm_for(cp, state), cp, now_ns=NOW, axis_session_id='session') is None
|
|
|
|
|
|
@pytest.mark.parametrize('label', ('volt', 'bolt'))
|
|
@pytest.mark.parametrize('bad', ('rx', 'heartbeat', 'word', 'ae', 'serial', 'session', 'expired'))
|
|
def test_diagnostic_bits_cannot_bypass_other_vetoes(label, bad):
|
|
cp, state = owner(label)
|
|
slot = replace(state.pandaInventory[0], faults=(1 << 18 if label == 'volt' else 12))
|
|
baseline = replace(state, pandaInventory=(slot,))
|
|
assert current_native(sm_for(cp, baseline), cp, now_ns=NOW, axis_session_id='session') == baseline
|
|
mutations = {'rx': {'safetyRxChecksInvalid': True}, 'heartbeat': {'heartbeatLost': True},
|
|
'word': {'safetyParam': slot.safetyParam ^ 1}, 'ae': {'alternativeExperience': 0},
|
|
'serial': {'hardwareSerial': 'wrong'}}
|
|
state = replace(state, pandaInventory=(replace(slot, **mutations.get(bad, {})),))
|
|
assert current_native(sm_for(cp, state), cp, now_ns=NOW + (200_000_001 if bad == 'expired' else 0),
|
|
axis_session_id='wrong' if bad == 'session' else 'session') is None
|
|
|
|
|
|
def test_other_brands_keep_existing_all_fault_veto():
|
|
from openpilot.starpilot.aol.panda_faults import blocking_fault_mask, blocking_fault_list
|
|
from openpilot.cereal import log
|
|
cp = SimpleNamespace(brand='hyundai')
|
|
assert blocking_fault_mask(12, cp)
|
|
assert blocking_fault_list([log.PandaState.FaultType.interruptRateCan1], cp)
|
|
|
|
|
|
@pytest.mark.parametrize('word', (0x1405, 0x5007, 0x5407, 0x5087, 0xD107, 0xE500, 0x205, 0x4007, 0x20))
|
|
def test_other_volt_configs_do_not_borrow_stock_register_policy(word):
|
|
from openpilot.starpilot.aol.panda_faults import blocking_fault_mask
|
|
cp, _ = owner('volt')
|
|
cp.safetyConfigs[0].safetyParam = word
|
|
assert blocking_fault_mask(1 << 18, cp)
|
|
|
|
|
|
@pytest.mark.parametrize('identity', (CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2022_2023,
|
|
CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL))
|
|
def test_other_bolt_generations_do_not_borrow_irq_policy(identity):
|
|
from openpilot.starpilot.aol.panda_faults import blocking_fault_mask
|
|
cp = params(identity, pedal=True, camera=True)
|
|
cp.alternativeExperience = 32
|
|
assert blocking_fault_mask(12, cp)
|
|
|
|
|
|
@pytest.mark.parametrize('label', ('volt', 'bolt'))
|
|
def test_latest_raw_faults_use_same_exact_policy_and_retain_native_veto(label):
|
|
from openpilot.cereal import messaging
|
|
from openpilot.starpilot.aol.transport_pause import healthy_transport
|
|
cp, state = owner(label)
|
|
allowed = 'registerDivergent' if label == 'volt' else 'interruptRateCan2'
|
|
mask = 1 << 18 if label == 'volt' else 1 << 3
|
|
state = replace(state, pandaInventory=(replace(state.pandaInventory[0], faults=mask),))
|
|
sm = sm_for(cp, state)
|
|
assert current_native(sm, cp, now_ns=NOW, axis_session_id='session') == state
|
|
event = messaging.new_message('pandaStates', 1, valid=True, logMonoTime=NOW)
|
|
event.pandaStates[0] = sm['pandaStates'][0]
|
|
event.pandaStates[0].faults = [allowed]
|
|
sm.update_msgs(NOW / 1e9, [event.as_reader()])
|
|
assert healthy_transport(sm, cp, car_state(), 'session', NOW)
|
|
event.pandaStates[0].faults = [allowed, 'relayMalfunction']
|
|
sm.update_msgs(NOW / 1e9, [event.as_reader()])
|
|
assert not healthy_transport(sm, cp, car_state(), 'session', NOW)
|
|
event.pandaStates[0].faults = [allowed]
|
|
event.pandaStates[0].heartbeatLost = True
|
|
sm.update_msgs(NOW / 1e9, [event.as_reader()])
|
|
assert not healthy_transport(sm, cp, car_state(), 'session', NOW)
|
|
|
|
|
|
@pytest.mark.parametrize('label', ('volt', 'bolt'))
|
|
def test_raw_enum_policy_does_not_borrow_other_config_diagnostics(label):
|
|
from openpilot.cereal import log
|
|
from openpilot.starpilot.aol.panda_faults import blocking_fault_list
|
|
cp, _ = owner(label)
|
|
allowed = [log.PandaState.FaultType.registerDivergent] if label == 'volt' else [
|
|
log.PandaState.FaultType.interruptRateCan1, log.PandaState.FaultType.interruptRateCan2]
|
|
assert not blocking_fault_list(allowed, cp)
|
|
denied = (log.PandaState.FaultType.interruptRateCan1 if label == 'volt' else log.PandaState.FaultType.registerDivergent)
|
|
assert blocking_fault_list([*allowed, denied], cp)
|
|
assert blocking_fault_list([*allowed, log.PandaState.FaultType.interruptRateCan3], cp)
|
|
assert blocking_fault_list([*allowed, log.PandaState.FaultType.relayMalfunction], cp)
|
|
assert blocking_fault_list([*allowed, 31], cp)
|
|
|
|
|
|
@pytest.mark.parametrize('label', ('volt', 'bolt'))
|
|
def test_real_native_permission_and_brake_withdrawal_remain_authoritative(label):
|
|
from opendbc.can import CANPacker
|
|
from opendbc.car import Bus
|
|
from opendbc.car.gm.interface import CarInterface
|
|
from opendbc.car.gm.values import DBC, CruiseButtons
|
|
from opendbc.car.gm.bolt_cc import button_bytes
|
|
from opendbc.car.gm.gmcan import pedal_crc
|
|
from opendbc.car.gm.tests.test_bolt_cc import feed
|
|
from opendbc.safety.tests.libsafety import libsafety_py
|
|
cp, state = owner(label)
|
|
ci = CarInterface(cp)
|
|
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
|
|
safety = libsafety_py.libsafety
|
|
safety.init_tests()
|
|
safety.set_alternative_experience(32)
|
|
assert safety.set_safety_hooks(cp.safetyConfigs[0].safetyModel.raw, cp.safetyConfigs[0].safetyParam) == 0
|
|
for tick in range(32):
|
|
now = NOW + tick * 10_000_000
|
|
_, packets = feed(SimpleNamespace(update=lambda _: None), packer, now, counter=tick % 4,
|
|
active=label == 'volt', speed=20., camera=True, brake=tick == 31)
|
|
packets = [packet for packet in packets if packet[0] != 0x1E1]
|
|
button = CruiseButtons.DECEL_SET if tick == 20 else CruiseButtons.UNPRESS
|
|
packets.append((0x1E1, button_bytes(button, tick % 4), 0))
|
|
if label == 'volt':
|
|
# Stock SDGM requires camera ACC status and its selected PT brake source.
|
|
packets = [packet for packet in packets if packet[0] != 0xBE]
|
|
packets.append(packer.make_can_msg('ECMAcceleratorPos', 0, {'BrakePedalPos': 12 if tick == 31 else 0}))
|
|
packets.append(packer.make_can_msg('ASCMActiveCruiseControlStatus', 2,
|
|
{'ACCCruiseState': 3, 'ACCSpeedSetpoint': 60}))
|
|
if label == 'bolt':
|
|
packets = [packet for packet in packets if packet[0] != 0x1F5]
|
|
packets.append(packer.make_can_msg('ECMPRDNL2', 0, {'PRNDL2': 6}))
|
|
sensor = bytearray(packer.make_can_msg('GAS_SENSOR', 0, {
|
|
'INTERCEPTOR_GAS': 0., 'INTERCEPTOR_GAS2': 0., 'STATE': 0, 'COUNTER_PEDAL': tick % 16})[1])
|
|
sensor[5] = pedal_crc(sensor)
|
|
packets.append((0x201, bytes(sensor), 0))
|
|
parsed = ci.update([(now, packets)])
|
|
safety.set_timer(now // 1000)
|
|
for address, data, bus in packets:
|
|
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data))
|
|
safety.safety_tick()
|
|
# Transport instrumentation supplies heartbeat/request, never a permission or controlsAllowed setter.
|
|
safety.set_aol_test_heartbeat(True)
|
|
request = 1 if label == 'volt' else 3
|
|
safety.aol_set_host_request(request)
|
|
mask = safety.aol_get_permission_mask()
|
|
if tick == 30:
|
|
assert parsed.canValid
|
|
assert mask == request
|
|
if tick == 31:
|
|
assert parsed.brakePressed
|
|
assert not mask & 2
|
|
slot = replace(state.pandaInventory[0], controlsAllowed=bool(safety.get_controls_allowed()),
|
|
faults=1 << 18 if label == 'volt' else 12)
|
|
receipt = replace(state, observedMonoTime=now, validUntilMonoTime=now + 200_000_000,
|
|
lateralAllowed=bool(mask & 1), longitudinalAllowed=bool(mask & 2),
|
|
requestedLateral=bool(request & 1), requestedLongitudinal=bool(request & 2),
|
|
pandaInventory=(slot,))
|
|
# Admission of a classified diagnostic does not overwrite native LONG withdrawal.
|
|
assert native_inventory_matches_cp(receipt, cp)
|
|
intent = SimpleNamespace(allowedLatch=True, pauseLateral=False, pauseLongitudinal=False)
|
|
decision = decide_axes(standard_lateral=True, standard_longitudinal=cp.openpilotLongitudinalControl,
|
|
intent=intent, native=receipt, car_state=parsed, initialized=True, model_ready=True,
|
|
no_entry=False, immediate_disable=False, dm_lockout=False, pause_brake_mps=0.)
|
|
assert not decision.longitudinal_active
|
|
safety.set_alternative_experience(0)
|