Restore MQB fixed and second-Panda lateral ownership

This commit is contained in:
firestar5683
2026-10-22 19:04:28 -05:00
parent 43360170dd
commit 4413c29923
13 changed files with 148 additions and 25 deletions
@@ -15,6 +15,9 @@ class CarInterface(CarInterfaceBase):
@staticmethod
def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
CAN = CanBus(fingerprint=fingerprint)
if CAN.pt == 4:
fingerprint = {bus: fingerprint.get(CAN.pt + bus, {}) for bus in range(4)}
ret.brand = "volkswagen"
ret.radarUnavailable = Bus.radar not in DBC[candidate]
@@ -142,7 +145,6 @@ class CarInterface(CarInterfaceBase):
ret.stopAccel = -0.55
ret.autoResumeSng = ret.minEnableSpeed == -1
CAN = CanBus(fingerprint=fingerprint)
if CAN.pt >= 4:
safety_configs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
ret.safetyConfigs = safety_configs
@@ -1,4 +1,4 @@
"""Exact MQB stock-ACC capability; direct drive/system-long remain ordinary."""
"""Exact MQB stock-ACC capability; direct drive remains ordinary."""
from opendbc.car import Bus, structs
from opendbc.car.volkswagen.values import CAR, DBC, VolkswagenFlags
@@ -10,20 +10,33 @@ MQB_CARS = frozenset(car for car in CAR if DBC[car].get(Bus.pt) == "vw_mqb" and
KNOWN_FLAGS = int(VolkswagenFlags.STOCK_HCA_PRESENT | VolkswagenFlags.KOMBI_PRESENT | VolkswagenFlags.HAS_BSM)
def safety_config(cp):
"""One owner, optionally preceded by one quiet noOutput board."""
try:
configs = cp.safetyConfigs
if len(configs) == 1:
return configs[0]
if (len(configs) == 2 and configs[0].safetyModel == structs.CarParams.SafetyModel.noOutput and
configs[0].safetyParam == 0):
return configs[1]
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
pass
return None
def _qualified(cp, *, marked_only=False, longitudinal=False):
if (cp is None or cp.brand != "volkswagen" or cp.carFingerprint not in MQB_CARS or
cp.passive or cp.notCar or cp.dashcamOnly or cp.flags & ~KNOWN_FLAGS or
cp.steerControlType != structs.CarParams.SteerControlType.torque or
cp.transmissionType not in (structs.CarParams.TransmissionType.automatic, structs.CarParams.TransmissionType.manual) or
cp.networkLocation not in (structs.CarParams.NetworkLocation.gateway, structs.CarParams.NetworkLocation.fwdCamera) or
cp.fingerprintSource == structs.CarParams.FingerprintSource.fixed or
bool(cp.openpilotLongitudinalControl) != longitudinal or bool(cp.pcmCruise) == longitudinal or len(cp.safetyConfigs) != 1 or
bool(cp.openpilotLongitudinalControl) != longitudinal or bool(cp.pcmCruise) == longitudinal or
(longitudinal and (cp.networkLocation != structs.CarParams.NetworkLocation.gateway or not cp.alphaLongitudinalAvailable))):
return False
word = AOL_WORD | (MANUAL_WORD if cp.transmissionType == structs.CarParams.TransmissionType.manual else 0)
word |= int(longitudinal)
safety = cp.safetyConfigs[0]
return (safety.safetyModel == structs.CarParams.SafetyModel.volkswagen and
safety = safety_config(cp)
return (safety is not None and safety.safetyModel == structs.CarParams.SafetyModel.volkswagen and
(cp.alternativeExperience == 32 and safety.safetyParam == word or
not marked_only and cp.alternativeExperience == 0 and safety.safetyParam == int(longitudinal)))
@@ -20,7 +20,7 @@ from openpilot.starpilot.aol.vehicle import policy_for
@pytest.mark.parametrize('manual', (False, True))
@pytest.mark.parametrize('gateway,alpha', ((False, False), (True, False), (True, True)))
def test_normal_golf_discovery_and_card_stock_aol_owner(manual, gateway, alpha):
def test_normal_golf_discovery_and_card_stock_aol_owner(manual, gateway, alpha, offset=0, fixed=False):
fixture = {'kind': 'EXPLICIT_SYNTHETIC_FULL_ECU_DISCOVERY_INPUT_FROM_REUSED_INVENTORY_NOT_ROUTE_OR_PHYSICAL',
'identity': 'VOLKSWAGEN_GOLF_MK7',
'vin': 'WVWZZZAUZGW000000',
@@ -47,15 +47,15 @@ def test_normal_golf_discovery_and_card_stock_aol_owner(manual, gateway, alpha):
'origin': 'Synthetic ECU bytes from configuration-matrix.json; actual matcher must verify uniqueness.'}
fw = [structs.CarParams.CarFw(ecu=getattr(structs.CarParams.Ecu, r['ecu']),
address=r['address'], subAddress=r['subAddress'] or 0, fwVersion=bytes.fromhex(r['fwVersion']),
brand='volkswagen', bus=0) for r in fixture['firmware']]
brand='volkswagen', bus=offset) for r in fixture['firmware']]
exact, candidates = match_fw_to_car(fw, fixture['vin'], log=False)
assert exact and candidates == {CAR.VOLKSWAGEN_GOLF_MK7}, (exact, candidates)
packer = CANPacker('vw_mqb')
incoming = [packer.make_can_msg('GRA_ACC_01', 0, {'GRA_Hauptschalter': 1})]
incoming = [packer.make_can_msg('GRA_ACC_01', offset, {'GRA_Hauptschalter': 1})]
if not manual:
incoming.append(packer.make_can_msg('Getriebe_11', 0, {}))
incoming.append(packer.make_can_msg('Getriebe_11', offset, {}))
if gateway:
incoming.append(packer.make_can_msg('ESP_21', 1, {}))
incoming.append(packer.make_can_msg('ESP_21', offset + 1, {}))
frames = [CanData(address=address, dat=data, src=bus) for address, data, bus in incoming]
def receive(wait_for_one=False):
@@ -77,10 +77,12 @@ def test_normal_golf_discovery_and_card_stock_aol_owner(manual, gateway, alpha):
mock.patch.object(car_helpers, 'get_present_ecus', return_value=set()),
mock.patch.object(car_helpers, 'get_fw_versions_ordered', return_value=fw)):
# Normal public get_car: do NOT stub fingerprint, match_fw_to_car or candidate selection.
ci = car_helpers.get_car(receive, lambda *args, **kwargs: None, lambda _: None, alpha, False)
ci = car_helpers.get_car(receive, lambda *args, **kwargs: None, lambda _: None, alpha, False,
forced_candidate=CAR.VOLKSWAGEN_GOLF_MK7 if fixed else None)
cp = ci.CP
assert cp.carFingerprint == CAR.VOLKSWAGEN_GOLF_MK7
assert cp.fingerprintSource == structs.CarParams.FingerprintSource.fw and not cp.fuzzyFingerprint
assert cp.fingerprintSource == (structs.CarParams.FingerprintSource.fixed if fixed else
structs.CarParams.FingerprintSource.fw) and not cp.fuzzyFingerprint
assert cp.carVin == fixture['vin'] and len(cp.carFw) == len(fw)
assert cp.transmissionType == (structs.CarParams.TransmissionType.manual if manual else
structs.CarParams.TransmissionType.automatic)
@@ -92,7 +94,11 @@ def test_normal_golf_discovery_and_card_stock_aol_owner(manual, gateway, alpha):
card = Car(CI=ci, RI=ci.RadarInterface(cp))
try:
assert card.aol_qualified and card.aol_card_intent is not None
assert card.CP.safetyConfigs[0].safetyParam == ((24 if manual else 8) | int(alpha))
assert card.CP.safetyConfigs[-1].safetyParam == ((24 if manual else 8) | int(alpha))
assert len(card.CP.safetyConfigs) == (2 if offset else 1)
if offset:
assert card.CP.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.noOutput
assert card.CP.safetyConfigs[0].safetyParam == 0
assert card.CP.alternativeExperience == 32
with structs.CarParams.from_bytes(params.get('CarParams')) as published:
assert published.to_dict() == card.CP.to_dict()
@@ -0,0 +1,10 @@
"""New fixed and owner-slot discovery contexts; real matcher and public get_car."""
import pytest
from opendbc.car.volkswagen.tests import test_mqb_aol_discovery as discovery
@pytest.mark.parametrize('manual', (False, True))
@pytest.mark.parametrize('gateway,alpha', ((False, False), (True, False), (True, True)))
@pytest.mark.parametrize('offset,fixed', ((0, True), (4, False), (4, True)))
def test_topology_fixed_discovery(manual, gateway, alpha, offset, fixed):
discovery.test_normal_golf_discovery_and_card_stock_aol_owner(manual, gateway, alpha, offset, fixed)
+4 -2
View File
@@ -40,7 +40,8 @@ from opendbc.car.honda.stock_aol import (
qualified as qualified_honda_stock_aol, native_observation as honda_native_observation,
temporary_restriction as honda_temporary_restriction,
)
from opendbc.car.volkswagen.stock_aol import native_observation as volkswagen_native_observation
from opendbc.car.volkswagen.stock_aol import (native_observation as volkswagen_native_observation,
safety_config as volkswagen_aol_safety_config)
from openpilot.starpilot.car.volkswagen.aol import (qualified as qualified_volkswagen_aol,
temporary_restriction as volkswagen_temporary_restriction)
from openpilot.starpilot.car.rivian.aol import retained_brake_lateral_intent as rivian_brake_intent_retained
@@ -386,7 +387,8 @@ class Car:
self.aol_sequence = 0
self.aol_process_fault_context = AolProcessFaultContext() if self.aol_replay else None
if self.aol_qualified:
config = hyundai_aol_safety_config(self.CP) if self.CP.brand == 'hyundai' else self.CP.safetyConfigs[0]
config = (volkswagen_aol_safety_config(self.CP) if self.CP.brand == 'volkswagen' else
(hyundai_aol_safety_config(self.CP) if self.CP.brand == 'hyundai' else self.CP.safetyConfigs[0]))
if config is None:
raise RuntimeError('AOL requires the exact vehicle safety configuration')
config.safetyParam |= aol_policy.safety_param_addition
@@ -130,11 +130,13 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
for prime in range(-110, 0):
ambient_update((10_000_000_000 + prime * 10_000_000) / 1e9, [])
card.slc_producer_session = 'authority-card'
class Publisher:
class Publisher(messaging.PubMaster):
def __init__(self):
self.messages = {}
def send(self, service, message):
self.messages[service] = messaging.log_from_bytes(message.to_bytes())
def send(self, s, dat):
if not isinstance(dat, bytes):
dat = dat.to_bytes()
self.messages[s] = messaging.log_from_bytes(dat)
card.pm = Publisher()
native = libsafety_py.libsafety
cleanup.callback(native.init_tests)
@@ -180,7 +182,7 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
if isinstance(name, str) and name in dbc.name_to_msg:
if tick < 3 and name in ('TSK_06', 'LH_EPS_03'):
continue # Real startup may receive other CAN sources before cruise and EPS.
fields = dict(overrides.get(name, {}))
fields: dict[str, float] = dict(overrides.get(name, {}))
if 'COUNTER' in dbc.name_to_msg[name].sigs:
fields['COUNTER'] = tick % 16
incoming.append(packer.make_can_msg(name, parser.bus, fields))
@@ -0,0 +1,14 @@
"""New topology/fixed contexts through the existing actual caller harness.
Requires source-matched native preloading; no compiler or native permission stub.
"""
import pytest
from openpilot.starpilot.aol.tests import test_mqb_actual_callers as callers
@pytest.mark.parametrize('gearbox', ('automatic', 'manual'))
@pytest.mark.parametrize('alpha', (False, True))
@pytest.mark.parametrize('offset,fixed', ((0, True), (4, False), (4, True)))
@pytest.mark.parametrize('engage_on_set', (False, True))
def test_topology_fixed_actual_callers(gearbox, alpha, offset, fixed, engage_on_set):
callers.test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha, offset, fixed, engage_on_set)
@@ -0,0 +1,66 @@
"""Family CP and ordered native inventory contracts for the new topology contexts."""
from dataclasses import replace
from types import SimpleNamespace
import pytest
from opendbc.car import gen_empty_fingerprint, structs
from opendbc.car.volkswagen.interface import CarInterface
from opendbc.car.volkswagen.stock_aol import MQB_CARS, safety_config
from openpilot.starpilot.aol.runtime import native_inventory_matches_cp
from openpilot.starpilot.aol.vehicle import policy_for, native_matches_cp
from openpilot.starpilot.aol.wire import PandaSlot
from openpilot.starpilot.car.volkswagen.aol import qualified
def params(identity, manual, alpha, offset, fixed):
fp = gen_empty_fingerprint()
if not manual:
fp[offset][0xAD] = 8
fp[offset + 1][0xFD] = 8
cp = CarInterface.get_params(identity, fp, [], alpha, False, False)
cp.fingerprintSource = structs.CarParams.FingerprintSource.fixed if fixed else structs.CarParams.FingerprintSource.fw
return cp
@pytest.mark.parametrize('identity', sorted(MQB_CARS))
@pytest.mark.parametrize('manual', (False, True))
@pytest.mark.parametrize('alpha', (False, True))
@pytest.mark.parametrize('offset,fixed', ((0, True), (4, False), (4, True)))
def test_exact_family_identity_topology_and_owner(identity, manual, alpha, offset, fixed):
cp = params(identity, manual, alpha, offset, fixed)
policy = policy_for(cp)
assert policy.intent_supported
owner = safety_config(cp)
assert owner is not None and owner.safetyModel == structs.CarParams.SafetyModel.volkswagen
owner.safetyParam |= policy.safety_param_addition
cp.alternativeExperience |= policy.alternative_experience_addition
word = (24 if manual else 8) | int(alpha)
assert qualified(cp, marked_only=True) and native_matches_cp(cp, 15, word)
assert cp.pcmCruise != alpha and cp.openpilotLongitudinalControl == alpha
if offset:
assert cp.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.noOutput
assert cp.safetyConfigs[0].safetyParam == 0
assert not native_matches_cp(cp, 15, word ^ 16)
assert not native_matches_cp(cp, 34, word)
def test_exact_ordered_inventory_denies_incoherent_or_active_prefix():
cp = params(sorted(MQB_CARS)[0], False, False, 4, True)
policy = policy_for(cp)
safety_config(cp).safetyParam |= policy.safety_param_addition
cp.alternativeExperience = 32
quiet = PandaSlot(0, 'quiet', int(structs.CarParams.SafetyModel.noOutput), 0, 32, False, False, False, 0)
active = PandaSlot(1, 'owner', 15, 8, 32, True, False, False, 0)
def receipt(slots, serial='owner', stamp=123):
return SimpleNamespace(pandaInventory=slots, pandaSerial=serial, sourcePandaStatesMonoTime=stamp,
safetyModel=15, safetyParam=8)
assert native_inventory_matches_cp(receipt((quiet, active)), cp)
for slots in ((active, quiet), (quiet,), (quiet, active, active),
(replace(quiet, controlsAllowed=True), active),
(replace(quiet, safetyParam=8), active),
(replace(quiet, hardwareSerial='owner'), active),
(quiet, replace(active, heartbeatLost=True)),
(quiet, replace(active, safetyRxChecksInvalid=True))):
assert not native_inventory_matches_cp(receipt(slots), cp)
assert not native_inventory_matches_cp(receipt((quiet, active), serial='quiet'), cp)
assert not native_inventory_matches_cp(receipt((quiet, active), stamp=0), cp)
assert not native_inventory_matches_cp(receipt((), stamp=0), cp)
@@ -110,7 +110,7 @@ def test_gateway_long_profile_rejects_changed_identity_bus_and_safety_context(ma
changes = (
('passive', True), ('notCar', True), ('dashcamOnly', True), ('flags', 1 << 20),
('networkLocation', structs.CarParams.NetworkLocation.fwdCamera),
('fingerprintSource', structs.CarParams.FingerprintSource.fixed),
('brand', 'toyota'),
('transmissionType', structs.CarParams.TransmissionType.direct),
('steerControlType', structs.CarParams.SteerControlType.angle),
('openpilotLongitudinalControl', False), ('pcmCruise', True),
@@ -126,7 +126,8 @@ def test_gateway_long_profile_rejects_changed_identity_bus_and_safety_context(ma
altered.safetyConfigs[0].safetyParam = word
assert not long_qualified(altered)
altered = structs.CarParams(**cp.to_dict())
altered.safetyConfigs = [structs.CarParams.SafetyConfig(safetyModel=structs.CarParams.SafetyModel.noOutput), *altered.safetyConfigs]
altered.safetyConfigs = [structs.CarParams.SafetyConfig(safetyModel=structs.CarParams.SafetyModel.noOutput, safetyParam=1),
*altered.safetyConfigs]
assert not long_qualified(altered)
altered = structs.CarParams(**cp.to_dict())
altered.safetyConfigs[0].safetyModel = structs.CarParams.SafetyModel.volkswagenMeb
+4
View File
@@ -86,6 +86,10 @@ def native_matches_cp(CP, model: int, param: int) -> bool:
config = honda.aol_safety_config(CP)
return bool(config is not None and honda.native_accepts_cp(CP, model, param) and
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
if CP.brand == 'volkswagen':
config = volkswagen.aol_safety_config(CP)
return bool(config is not None and volkswagen.native_accepts_cp(CP, model, param) and
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
return bool(any(port.native_accepts_cp(CP, model, param) for port in _PORTS.values()) and
model == int(CP.safetyConfigs[0].safetyModel.raw) and
param == int(CP.safetyConfigs[-1].safetyParam))
+2 -2
View File
@@ -1,5 +1,5 @@
from opendbc.car import structs
from opendbc.car.volkswagen.stock_aol import qualified as stock_qualified, AOL_WORD, MANUAL_WORD, AOL_WORDS
from opendbc.car.volkswagen.stock_aol import qualified as stock_qualified, safety_config as aol_safety_config, AOL_WORD, MANUAL_WORD, AOL_WORDS
from opendbc.car.volkswagen.long_aol import qualified as long_qualified, WORDS as LONG_WORDS
from openpilot.starpilot.aol.intent import AolCardIntent, AOL_TOGGLE, ButtonType
from openpilot.starpilot.aol.policy import AolVehiclePolicy
@@ -31,7 +31,7 @@ def native_profile_supported(model, param):
def native_accepts_cp(cp, model, param):
return qualified(cp, marked_only=True) and native_profile_supported(model, param) and param == cp.safetyConfigs[0].safetyParam
return qualified(cp, marked_only=True) and native_profile_supported(model, param) and param == aol_safety_config(cp).safetyParam
def native_latch_rejected(cp, native):
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "f40dda21dcd3a36f88cb8ea563fac27b023c7766257dcf2d36027eacb2c668d4", "schema_version": 1}
{"protected_inventory_sha256": "7ad958f6080cde1c5e80081ee1cd24927df9a703189143fcb8d15c8918cc5564", "schema_version": 1}
+3
View File
@@ -71,6 +71,9 @@ AOL_LIFECYCLE_TARGETS = (
"openpilot/starpilot/aol/tests/test_volkswagen.py",
"openpilot/starpilot/aol/tests/test_volkswagen_saved_intent.py",
"openpilot/starpilot/aol/tests/test_mqb_actual_callers.py",
"openpilot/starpilot/aol/tests/test_mqb_topology_callers.py",
"openpilot/starpilot/aol/tests/test_mqb_topology_contract.py",
"opendbc_repo/opendbc/car/volkswagen/tests/test_mqb_topology_discovery.py",
"opendbc_repo/opendbc/car/volkswagen/tests/test_mqb_aol_discovery.py",
"opendbc_repo/opendbc/safety/tests/test_volkswagen_stock_aol.py",
"opendbc_repo/opendbc/safety/tests/test_volkswagen_engagement_arm.py",