IQ.Pilot Release Commit @ bec7652

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-22 21:29:55 -05:00
parent 11cefcb266
commit 7786d94c33
58 changed files with 578 additions and 267 deletions

View File

@@ -16,17 +16,17 @@
},
"python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "94664d1b9abd1c0cb124707bf10f746f23adf0847a890d507df86f1e4a7e8ca9",
"sha256": "50f00d356371a60a50b2395966b6b1646fd3b8306e9ad6fe0229ad0e24186683",
"size": 67616
},
"python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "481235a5685e14f5b8e9216908be23be9743fe6f487f2b38fb21c33dd84e22cf",
"sha256": "57b15e47bcc1084d33bda3908fb50347c2403cde0c9c951dad1b107ef56b06f1",
"size": 67616
},
"python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "75ffbf8212f805b054c97ca4723a56071db822e889cda9d6bec233b2d2c8592a",
"sha256": "1d3611c753cc94d1e22802900a2487a13c445def98fa998385fa72935a0dd110",
"size": 136488
},
"runtime": {
@@ -38,8 +38,8 @@
}
},
"signatures": {
"python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so": "B45oDHpHNmslzjTyeuEVbE4fOytUsC/0po+CiHiLI+T01yWJIb6Dt4yFJm+qVTEqCLJfajedd1FnIQHCyUddDw==",
"python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so": "a+YcE2ik7Oo3btunKGoA+FHN+uSDoric2V6nu5SjtKSM/Nwpg1M9BX0BM55sA56sz6FgPRn93u+yPL7eryLYDw==",
"python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so": "NcdigDJOQ7d9A+HXCewPowezSML1i4cHW6uRyZ1N6vmtkXDorRk9YC4lxlM8uvoS/tB5/jQBkfc/DhkwPXAoAA=="
"python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so": "ZIgAfOcrpjAYBY/iLm9oovGs6jv7dQAToObwc9rrPpPTEgQZ/zTcTOa0TcqGTU/lK0tPhum2WVrPpxwosKUwDg==",
"python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so": "Zv7AsE+0KgGABA4n9Hf40UQVOz7k2ew/vxgKqkUpyFjZKdst6DUecAxyBPbK4Z8owTaKnnbNRxuRGiJtr+sJDA==",
"python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so": "2aLUgznDiiBF3sYwIxZKMjMW2A1S9q+tHZUnyAiEB48QbxAFjDAS6tMHUp5ej6/JAmejOzxQePYPL1jJnxKsBQ=="
}
}

View File

@@ -1 +1 @@
54xdruMwhqg4ouUav9CwbOhcsytYbn/uy/OL1pT6Sd/JRvgsLckfe8+jsG527nyhZ7sBpxPx5dFLX3h/eOaSDg==
D9FZfSC3DUspPq4ewwPjNoCS1N8ai3jcgutVQo94CnOI6Wcx3L1H30hTZ6SS8jw9MfMsVaggFXfMo2QT4yJlBQ==

View File

@@ -41,6 +41,9 @@ if not _os.path.exists("/usr/libexec/iqpilot/runtime_integrity.json"):
"update_mqb_carstate_alc_state": _noop,
"update_mlb_carstate_alc_state": _noop,
"update_pq_carstate_alc_state": _noop,
"update_turn_signals": _noop,
"create_vehicle_odometer_store": lambda *a, **k: _VehicleOdometerStoreStub(),
"vw_driver_override_threshold_cnm": lambda cs, platform, default: default,
})
_stub_module("iqpilot_private.konn3kt.iqlvbs.iqlvbs_commander", {
"update_turn_signals": _noop,
@@ -50,13 +53,20 @@ if not _os.path.exists("/usr/libexec/iqpilot/runtime_integrity.json"):
try:
from iqpilot.system.proprietary_runtime import _verified_import as _vi
_real_import_verified_module = _vi.import_verified_module
def _import_verified_module(bundle, module):
return _sys.modules.get(module) or _real_import_verified_module(bundle, module)
_vi.import_verified_module = _import_verified_module
except Exception:
pass
_vi = _types.ModuleType("iqpilot.system.proprietary_runtime._verified_import")
_sys.modules["iqpilot.system.proprietary_runtime._verified_import"] = _vi
_real_import_verified_module = None
def _import_verified_module(bundle, module):
stub = _sys.modules.get(module)
if stub is not None:
return stub
if _real_import_verified_module is None:
raise ImportError(module)
return _real_import_verified_module(bundle, module)
_vi.import_verified_module = _import_verified_module
# --- end host test harness ---------------------------------------------------
_ABSTRACT_BASES = frozenset(("CarSafetyTest", "AolSafetyTestBase", "SafetyTest", "SafetyTestBase"))

View File

@@ -353,6 +353,7 @@ routes = [
CarTestRoute("79b0a26c081d6d6c/00000083--8407f96a1e", VOLKSWAGEN.VOLKSWAGEN_PASSAT_MK7),
CarTestRoute("cc7e1fc0ce4ec686/00000004--dddc5bf4b6", VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS_PLUS),
CarTestRoute("07667b885add75fd/2021-01-23--19-48-42", VOLKSWAGEN.AUDI_A3_MK3),
CarTestRoute("c8b7d4cd76391e07|00000000--c42dead9c6", VOLKSWAGEN.AUDI_A4_MK4, segment=29),
CarTestRoute("5432d2499e17e646/000000e8--9ce8cf6d58", VOLKSWAGEN.AUDI_Q5_MK1),
CarTestRoute("6c6b466346192818/2021-06-06--14-17-47", VOLKSWAGEN.AUDI_Q2_MK1),
CarTestRoute("0cd0b7f7e31a3853/2021-12-03--03-12-05", VOLKSWAGEN.AUDI_Q3_MK2),

View File

@@ -124,6 +124,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"VOLKSWAGEN_ID3_MK2" = "VOLKSWAGEN_ID4_MK2"
"SKODA_ENYAQ_MK1" = "VOLKSWAGEN_ID4_MK1"
"SKODA_ENYAQ_MK2" = "VOLKSWAGEN_ID4_MK2"
"AUDI_A4_MK4" = "AUDI_Q5_MK1"
"AUDI_Q4_MK1" = "VOLKSWAGEN_ID4_MK1"
"AUDI_Q4_MK2" = "VOLKSWAGEN_ID4_MK2"
"VOLKSWAGEN_GOLF_MK8" = "VOLKSWAGEN_GOLF_MK7"

View File

@@ -205,6 +205,8 @@ class CarController(CarControllerBase):
self._pt_tx_bus = self.CAN.aux
elif CP.flags & VolkswagenFlags.MLB:
self.CCS = mlbcan
if CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_ECAN:
self._pt_tx_bus = self.CAN.aux
elif CP.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO):
self.CCS = mebcan
else:
@@ -648,7 +650,8 @@ class CarController(CarControllerBase):
cancel_cmd = stock_cancel_pressed or self._tap_gra_cancel(CC.cruiseControl.cancel, gra_send_ready)
resume_cmd = CC.cruiseControl.resume or self._should_spam_mqb_a0_resume(CS, iq_mqb_acc_resume)
if gra_send_ready and (cancel_cmd or resume_cmd):
bus_send = self.CAN.aux if self.CP.flags & VolkswagenFlags.PQ else self.CAN.ext
stalk_on_powertrain = self.CP.flags & VolkswagenFlags.PQ or self.CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_ECAN
bus_send = self.CAN.aux if stalk_on_powertrain else self.CAN.ext
can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, bus_send, CS.gra_stock_values,
cancel=cancel_cmd, resume=resume_cmd))

View File

@@ -672,9 +672,13 @@ class CarState(CarStateBase):
ret.gasPressed = pt_cp.vl["Motor_03"]["MO_Fahrpedalrohwert_01"] > 0
if self.CP.carFingerprint == CAR.PORSCHE_MACAN_MK1:
ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["Getriebe_03"]["GE_Waehlhebel"], None))
elif self.CP.transmissionType == TransmissionType.manual:
reverse = bool(pt_cp.vl["Gateway_05"]["BCM1_Rueckfahrlicht_Schalter"])
ret.gearShifter = GearShifter.reverse if reverse else GearShifter.drive
else:
ret.gearShifter = GearShifter.drive
cc_only = bool(self.CP.flags & (VolkswagenFlagsIQ.IQ_CC_ONLY | VolkswagenFlagsIQ.IQ_CC_ONLY_NO_RADAR))
cruise_main_switch = bool(pt_cp.vl["LS_01"]["LS_Hauptschalter"])
if not self.CP.pcmCruise:
ret.cruiseState.available = cruise_main_switch
@@ -687,7 +691,8 @@ class CarState(CarStateBase):
else:
ret.cruiseState.available = pt_cp.vl["TSK_02"]["TSK_Status"] in (0, 1, 2)
ret.cruiseState.enabled = pt_cp.vl["TSK_02"]["TSK_Status"] in (1, 2)
ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw_02"] * CV.KPH_TO_MS
if not cc_only:
ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw_02"] * CV.KPH_TO_MS
ret.accFaulted = pt_cp.vl["TSK_02"]["TSK_Status"] in (3,)
ret.cruiseState.nonAdaptive = bool(pt_cp.vl["LS_01"]["LS_Limiter"])
@@ -758,6 +763,13 @@ class CarState(CarStateBase):
def parse_mlb_mqb_steering_state(self, ret, pt_cp, drive_mode=True):
ret.steeringAngleDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradwinkel"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradwinkel"])]
ret.steeringRateDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradw_Geschw"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradw_Geschw"])]
if self.CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS:
ret.steeringTorque = 0.0
ret.steeringPressed = False
ret.steerFaultTemporary, ret.steerFaultPermanent = False, True
return
ret.steeringTorque = pt_cp.vl["LH_EPS_03"]["EPS_Lenkmoment"] * (1, -1)[int(pt_cp.vl["LH_EPS_03"]["EPS_VZ_Lenkmoment"])]
ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE
@@ -826,13 +838,17 @@ class CarState(CarStateBase):
]
else:
pt_messages += [("Kombi_02", math.nan)] # Auxiliary-bus cluster odometer
if CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS:
pt_messages += [("LH_EPS_03", math.nan)]
if CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT:
cam_messages += [
("HCA_01", 1), # From R242 Driver assistance camera, 50Hz if steering/1Hz if not
]
pt_bus = CanBus(CP).aux if CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_ECAN else CanBus(CP).pt
return {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).pt),
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, pt_bus),
Bus.aux: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).aux),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CanBus(CP).cam),
}

View File

@@ -1046,6 +1046,14 @@ FW_VERSIONS = {
b'\xf1\x875Q0907572P \xf1\x890682',
],
},
CAR.AUDI_A4_MK4: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x878K5907115M \xf1\x890005',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x878K0959655P \xf1\x890610\xf1\x82\x040E0D050G',
],
},
CAR.AUDI_Q2_MK1: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8704E906027JT\xf1\x894145',

View File

@@ -7,7 +7,8 @@ from iqdbc.car.isotp_parallel_query import IsoTpParallelQuery
from iqdbc.car.volkswagen.carcontroller import CarController
from iqdbc.car.volkswagen.carstate import CarState
from iqdbc.car.volkswagen.values import (
CAR, CanBus, DashcamOnlyReason, NetworkLocation, RADAR_DISABLE_STATE, TransmissionType,
CAR, CanBus, DashcamOnlyReason, MLB_ACC_COORDINATOR_MSGS, MLB_GEARBOX_MSGS, MLB_MSG_ACC_10,
MLB_MSG_LH_EPS_03, NetworkLocation, RADAR_DISABLE_STATE, TransmissionType,
VolkswagenFlags, VolkswagenSafetyFlags, VolkswagenFlagsIQ, get_longitudinal_stopping_speed_override,
)
from iqdbc.car.volkswagen.radar_interface import RadarInterface
@@ -94,6 +95,29 @@ class CarInterface(CarInterfaceBase):
ret.transmissionType = TransmissionType.automatic
ret.dashcamOnly = False
ecan_msgs = fingerprint[0] | fingerprint[2]
fingerprinted = bool(ecan_msgs or fingerprint[1])
if fingerprinted and not ecan_msgs:
ret.flags |= VolkswagenFlagsIQ.IQ_MLB_NO_ECAN.value
safety_configs[0].safetyParam |= VolkswagenSafetyFlags.MLB_NO_ECAN.value
ret.dashcamOnly = True
has_acc = any(msg in ecan_msgs for msg in MLB_ACC_COORDINATOR_MSGS)
if fingerprinted and not has_acc:
if MLB_MSG_ACC_10 in ecan_msgs:
ret.flags |= VolkswagenFlagsIQ.IQ_CC_ONLY.value
else:
ret.flags |= VolkswagenFlagsIQ.IQ_CC_ONLY_NO_RADAR.value
pt_msgs = fingerprint[1] if ret.flags & VolkswagenFlagsIQ.IQ_MLB_NO_ECAN else ecan_msgs
if fingerprinted and MLB_MSG_LH_EPS_03 not in pt_msgs:
ret.flags |= VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS.value
if fingerprinted and not any(msg in pt_msgs for msg in MLB_GEARBOX_MSGS):
ret.transmissionType = TransmissionType.manual
elif ret.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO):
if ret.flags & VolkswagenFlags.MEB:
safety_configs = [get_safety_config(structs.CarParams.SafetyModel.volkswagenMeb)]
@@ -209,6 +233,11 @@ class CarInterface(CarInterfaceBase):
ret.longitudinalTuning.kiV = [0.4, 0.]
ret.alphaLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or docs or bool(ret.flags & VolkswagenFlags.DISABLE_RADAR)
if ret.flags & VolkswagenFlags.MLB and ret.flags & (VolkswagenFlagsIQ.IQ_CC_ONLY | VolkswagenFlagsIQ.IQ_CC_ONLY_NO_RADAR):
ret.alphaLongitudinalAvailable = False
alpha_long = False
if alpha_long:
# Proof-of-concept, prep for E2E only. No radar points available. Panda ALLOW_DEBUG firmware required.
ret.openpilotLongitudinalControl = True

View File

@@ -4,11 +4,14 @@ from types import SimpleNamespace
import pytest
from iqdbc.car import structs
from iqdbc.can import CANPacker
from iqdbc.car import Bus, DT_CTRL, structs
from iqdbc.car.volkswagen.carstate import CarState
from iqdbc.car.structs import CarParams
from iqdbc.car.volkswagen.interface import CarInterface
from iqdbc.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI, VolkswagenFlags, VolkswagenFlagsIQ, VolkswagenSafetyFlags
from iqdbc.car.volkswagen.values import (CAR, FW_QUERY_CONFIG, MLB_ACC_COORDINATOR_MSGS, MLB_MSG_ACC_10,
MLB_MSG_LH_EPS_03, WMI, VolkswagenFlags, VolkswagenFlagsIQ,
VolkswagenSafetyFlags)
from iqdbc.car.volkswagen.fingerprints import FW_VERSIONS
Ecu = CarParams.Ecu
@@ -118,6 +121,217 @@ class TestVolkswagenPlatformConfigs:
assert bool(params.safetyConfigs[-1].safetyParam & VolkswagenSafetyFlags.ALLOW_LONG_ACCEL_WITH_GAS_PRESSED) is alpha_long
def _mlb_fingerprint(bus, msgs):
fingerprints = {b: {} for b in range(7)}
fingerprints[bus] = {msg: 8 for msg in msgs}
return fingerprints
A4_MK4_BUS_1 = (0x040, 0x080, 0x081, 0x086, 0x100, 0x101, 0x103, 0x104, 0x105, 0x106, 0x107, 0x10B,
0x10C, 0x10E, 0x114, 0x11D, 0x309, 0x30B, 0x30E, 0x312, 0x391, 0x392, 0x39C, 0x3BF,
0x3C0, 0x440, 0x471, 0x520, 0x585, 0x590, 0x5F0, 0x640, 0x641, 0x643, 0x644, 0x647,
0x670, 0x6B2, 0x6B4, 0x6B7, 0x6B8, 0x6C0, 0x6C1)
MLB_ECAN_GATEWAY = (0x086, 0x09F, 0x102, 0x103, 0x105, 0x106, 0x10B, 0x10C, 0x30B)
MLB_ECAN_CAMERA = (0x109, 0x10D, 0x117, 0x30C, 0x30F)
def test_mlb_no_ecan_car_moves_to_the_powertrain_bus():
fingerprints = _mlb_fingerprint(1, A4_MK4_BUS_1)
params = CarInterface.get_params(CAR.AUDI_A4_MK4, fingerprints, [], alpha_long=True, is_release=False, docs=False)
assert params.flags & VolkswagenFlagsIQ.IQ_MLB_NO_ECAN
assert params.safetyConfigs[-1].safetyParam & VolkswagenSafetyFlags.MLB_NO_ECAN
assert params.flags & VolkswagenFlagsIQ.IQ_CC_ONLY_NO_RADAR
assert params.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS
assert params.transmissionType == CarParams.TransmissionType.manual
assert not params.alphaLongitudinalAvailable
assert not params.openpilotLongitudinalControl
assert params.dashcamOnly
def test_mlb_without_an_extended_can_is_not_controllable():
for msgs in (A4_MK4_BUS_1, A4_MK4_BUS_1 + (0x9F,)):
params = CarInterface.get_params(CAR.AUDI_A4_MK4, _mlb_fingerprint(1, msgs), [],
alpha_long=False, is_release=False, docs=False)
assert params.dashcamOnly
def test_mlb_gateway_car_keeps_the_extended_can():
fingerprints = _mlb_fingerprint(0, MLB_ECAN_GATEWAY)
fingerprints[2] = {msg: 8 for msg in MLB_ECAN_CAMERA}
params = CarInterface.get_params(CAR.AUDI_Q5_MK1, fingerprints, [], alpha_long=False, is_release=False, docs=False)
assert not params.flags & VolkswagenFlagsIQ.IQ_MLB_NO_ECAN
assert not params.flags & (VolkswagenFlagsIQ.IQ_CC_ONLY | VolkswagenFlagsIQ.IQ_CC_ONLY_NO_RADAR)
assert not params.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS
assert params.transmissionType == CarParams.TransmissionType.automatic
assert params.alphaLongitudinalAvailable
assert not params.dashcamOnly
def test_mlb_flags_are_not_inferred_without_a_fingerprint():
fingerprints = {bus: {} for bus in range(7)}
for platform in (CAR.AUDI_Q5_MK1, CAR.PORSCHE_MACAN_MK1, CAR.AUDI_A4_MK4):
params = CarInterface.get_params(platform, fingerprints, [], alpha_long=False, is_release=False, docs=True)
assert not params.flags & VolkswagenFlagsIQ.IQ_MLB_NO_ECAN
assert not params.flags & (VolkswagenFlagsIQ.IQ_CC_ONLY | VolkswagenFlagsIQ.IQ_CC_ONLY_NO_RADAR)
assert not params.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS
assert params.transmissionType == CarParams.TransmissionType.automatic
def _build_mlb_car(platform, fingerprints):
CP = CarInterface.get_params(platform, fingerprints, [], alpha_long=False, is_release=False, docs=False)
CP_IQ = CarInterface.get_params_iq(CP, platform, fingerprints, [], alpha_long=False, is_release_iq=False, docs=False)
return CarInterface(CP, CP_IQ)
def _a4_mk4_car():
return _build_mlb_car(CAR.AUDI_A4_MK4, _mlb_fingerprint(1, A4_MK4_BUS_1))
def _q5_mk1_car():
fingerprints = _mlb_fingerprint(0, MLB_ECAN_GATEWAY)
fingerprints[2] = {msg: 8 for msg in MLB_ECAN_CAMERA}
return _build_mlb_car(CAR.AUDI_Q5_MK1, fingerprints)
def _a4_mk4_frames(packer, reverse=False, eps_torque=None):
msgs = [
packer.make_can_msg("ESP_03", 1, {"ESP_%s_Radgeschw" % s: 30.0 for s in ("VL", "VR", "HL", "HR")}),
packer.make_can_msg("Motor_03", 1, {"MO_Fahrpedalrohwert_01": 0, "MO_BLS": 0}),
packer.make_can_msg("ESP_05", 1, {"ESP_Bremsdruck": 0, "ESP_Fahrer_bremst": 0}),
packer.make_can_msg("ESP_01", 1, {"ESP_Tastung_passiv": 0}),
packer.make_can_msg("ESP_02", 1, {"ESP_Stillstandsflag": 0}),
packer.make_can_msg("TSK_02", 1, {"TSK_Status": 0}),
packer.make_can_msg("LS_01", 1, {"LS_Hauptschalter": 1, "LS_Codierung": 2}),
packer.make_can_msg("LWI_01", 1, {"LWI_Lenkradwinkel": 12.0, "LWI_Lenkradw_Geschw": 4.0}),
packer.make_can_msg("Kombi_01", 1, {"KBI_angez_Geschw": 30.0, "KBI_Handbremse": 0}),
packer.make_can_msg("Kombi_02", 1, {"KBI_Inhalt_Tank": 40, "KBI_Kilometerstand": 100000}),
packer.make_can_msg("Airbag_02", 1, {"AB_Gurtschloss_FA": 3}),
packer.make_can_msg("Gateway_05", 1, {"BCM1_Rueckfahrlicht_Schalter": int(reverse)}),
]
if eps_torque is not None:
msgs.append(packer.make_can_msg("LH_EPS_03", 1, {"EPS_Lenkmoment": abs(eps_torque),
"EPS_VZ_Lenkmoment": eps_torque < 0,
"EPS_HCA_Status": 3}))
return msgs
def _run(car, build, frames=25):
nanos = 0
for _ in range(frames):
nanos += int(DT_CTRL * 1e9)
ret, _ = car.update([(nanos, build())])
return ret
def test_mlb_no_ecan_parsers_read_the_powertrain_bus():
a4 = _a4_mk4_car()
assert a4.CS.get_can_parsers(a4.CP, a4.CP_IQ)[Bus.pt].bus == 1
q5 = _q5_mk1_car()
assert q5.CS.get_can_parsers(q5.CP, q5.CP_IQ)[Bus.pt].bus == 0
def test_mlb_no_ecan_transmits_on_the_powertrain_bus():
a4 = _a4_mk4_car()
packer = CANPacker("vw_mlb")
CC = structs.CarControl()
CC.enabled = True
CC.latActive = True
CC.cruiseControl.cancel = True
CC = CC.as_reader()
CC_IQ = structs.IQCarControl()
sent = []
nanos = 0
for _ in range(20):
nanos += int(DT_CTRL * 1e9)
a4.update([(nanos, _a4_mk4_frames(packer, eps_torque=0))])
_, can_sends = a4.apply(CC, CC_IQ, nanos)
sent.extend(can_sends)
assert {addr for addr, _, _ in sent} == {0x126, 0x397, 0x10B}
assert {bus for _, _, bus in sent} == {1}
def test_mlb_no_hca_eps_reports_a_steer_fault_without_a_can_fault():
a4 = _a4_mk4_car()
assert a4.CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS
packer = CANPacker("vw_mlb")
ret = _run(a4, lambda: _a4_mk4_frames(packer))
assert ret.steerFaultPermanent
assert not ret.steerFaultTemporary
assert ret.steeringTorque == 0.0
assert not ret.steeringPressed
assert ret.steeringAngleDeg == pytest.approx(12.0, abs=0.2)
pt = a4.can_parsers[Bus.pt]
assert pt.message_states[MLB_MSG_LH_EPS_03].ignore_alive
assert all(parser.can_valid for parser in a4.can_parsers.values())
def test_mlb_no_hca_eps_survives_the_parser_aliveness_timeout():
a4 = _a4_mk4_car()
packer = CANPacker("vw_mlb")
pt = a4.can_parsers[Bus.pt]
nanos = 0
for _ in range(int(15.0 / DT_CTRL)):
nanos += int(DT_CTRL * 1e9)
ret, _ = a4.update([(nanos, _a4_mk4_frames(packer))])
pt.vl["LH_EPS_03"]["EPS_HCA_Status"]
assert ret.steerFaultPermanent
assert all(parser.can_valid for parser in a4.can_parsers.values())
assert not any(parser.bus_timeout for parser in a4.can_parsers.values())
def test_mlb_lane_assist_eps_restores_the_normal_torque_path():
fingerprints = _mlb_fingerprint(1, A4_MK4_BUS_1 + (0x9F,))
a4 = _build_mlb_car(CAR.AUDI_A4_MK4, fingerprints)
assert not a4.CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS
packer = CANPacker("vw_mlb")
ret = _run(a4, lambda: _a4_mk4_frames(packer, eps_torque=-140))
assert ret.steeringTorque == pytest.approx(-140, abs=1)
assert ret.steeringPressed
assert not ret.steerFaultPermanent
assert not a4.can_parsers[Bus.pt].message_states[MLB_MSG_LH_EPS_03].ignore_alive
def test_mlb_cc_only_never_reads_the_acc_coordinator():
a4 = _a4_mk4_car()
assert a4.CP.flags & VolkswagenFlagsIQ.IQ_CC_ONLY_NO_RADAR
packer = CANPacker("vw_mlb")
ret = _run(a4, lambda: _a4_mk4_frames(packer))
assert ret.cruiseState.available
assert not ret.cruiseState.enabled
assert ret.cruiseState.speed == 0.0
parsers = a4.can_parsers
for msg in MLB_ACC_COORDINATOR_MSGS + (MLB_MSG_ACC_10,):
for parser in parsers.values():
assert msg not in parser.addresses
def test_mlb_manual_gear_follows_the_reverse_light_switch():
a4 = _a4_mk4_car()
assert a4.CP.transmissionType == CarParams.TransmissionType.manual
packer = CANPacker("vw_mlb")
assert _run(a4, lambda: _a4_mk4_frames(packer, reverse=False)).gearShifter == structs.CarState.GearShifter.drive
assert _run(a4, lambda: _a4_mk4_frames(packer, reverse=True)).gearShifter == structs.CarState.GearShifter.reverse
assert _run(a4, lambda: _a4_mk4_frames(packer, reverse=False)).gearShifter == structs.CarState.GearShifter.drive
@pytest.mark.parametrize("button_type", (
structs.CarState.ButtonEvent.Type.setCruise,
structs.CarState.ButtonEvent.Type.resumeCruise,

View File

@@ -300,6 +300,7 @@ class VolkswagenSafetyFlags(IntFlag):
PQ_NO_CAM_BUS = 128
PQ_ACC_FTS_EPB = 256
PQ_SNG_ECD = 512
MLB_NO_ECAN = 1024
class VolkswagenFlags(IntFlag):
@@ -331,10 +332,25 @@ class VolkswagenFlagsIQ(IntFlag):
IQ_PQ_ACC_FTS_EPB = 1 << 18 # B7 TRW450: ACC FtS + EPB hold, Motor_1 resume spoof on bus 1
IQ_PQ_SNG_ECD = 1 << 19
IQ_PQ_TIMEBOMB = 1 << 20
IQ_MLB_NO_ECAN = 1 << 21
IQ_MLB_NO_HCA_EPS = 1 << 22
RADAR_DISABLE_STATE = {"error": False}
MLB_MSG_LH_EPS_03 = 0x9F
MLB_MSG_GETRIEBE_01 = 0x82
MLB_MSG_GETRIEBE_02 = 0x83
MLB_MSG_GETRIEBE_03 = 0x102
MLB_MSG_GETRIEBE_04 = 0x441
MLB_MSG_ACC_01 = 0x109
MLB_MSG_ACC_05 = 0x10D
MLB_MSG_ACC_02 = 0x30C
MLB_MSG_ACC_10 = 0x117
MLB_ACC_COORDINATOR_MSGS = (MLB_MSG_ACC_01, MLB_MSG_ACC_05, MLB_MSG_ACC_02)
MLB_GEARBOX_MSGS = (MLB_MSG_GETRIEBE_01, MLB_MSG_GETRIEBE_02, MLB_MSG_GETRIEBE_03, MLB_MSG_GETRIEBE_04)
@dataclass
class VolkswagenMLBPlatformConfig(PlatformConfig):
@@ -671,6 +687,12 @@ class CAR(Platforms):
chassis_codes={"8V", "FF"},
wmis={WMI.AUDI_GERMANY_CAR, WMI.AUDI_SPORT},
)
AUDI_A4_MK4 = VolkswagenMLBPlatformConfig(
[VWCarDocs("Audi A4 2013-16", package="Cruise Control")],
VolkswagenCarSpecs(mass=1610, wheelbase=2.81, steerRatio=15.9),
chassis_codes={"8K", "FL"},
wmis={WMI.AUDI_GERMANY_CAR},
)
AUDI_Q2_MK1 = VolkswagenMQBPlatformConfig(
[VWCarDocs("Audi Q2 2018")],
VolkswagenCarSpecs(mass=1205, wheelbase=2.61),

View File

@@ -18,6 +18,8 @@ extern const uint16_t FLAG_VOLKSWAGEN_PQ_ACC_FTS_EPB;
const uint16_t FLAG_VOLKSWAGEN_PQ_ACC_FTS_EPB = 256;
extern const uint16_t FLAG_VOLKSWAGEN_PQ_SNG_ECD;
const uint16_t FLAG_VOLKSWAGEN_PQ_SNG_ECD = 512;
extern const uint16_t FLAG_VOLKSWAGEN_MLB_NO_ECAN;
const uint16_t FLAG_VOLKSWAGEN_MLB_NO_ECAN = 1024;
static uint8_t volkswagen_crc8_lut_8h2f[256]; // Static lookup table for CRC8 poly 0x2F, aka 8H2F/AUTOSAR

View File

@@ -12,6 +12,8 @@
// so a rounding difference on the sending side cannot silently block the whole ACC_01 stream.
#define VOLKSWAGEN_MLB_INACTIVE_ACCEL_TOLERANCE 15
static bool volkswagen_mlb_no_ecan = false;
static bool volkswagen_mlb_long_accel_check(int desired_accel) {
int inactive_delta = desired_accel - VW_IQ_INACTIVE_LONG_ACCEL;
bool inactive = ((inactive_delta >= -VOLKSWAGEN_MLB_INACTIVE_ACCEL_TOLERANCE) &&
@@ -36,6 +38,9 @@ static safety_config volkswagen_mlb_init(uint16_t param) {
static const CanMsg VOLKSWAGEN_MLB_LONG_TX_MSGS[] = {{MSG_HCA_01, 0, 8, .check_relay = true}, {MSG_LDW_02, 0, 8, .check_relay = true},
{MSG_ACC_01, 0, 8, .check_relay = true}, {MSG_ACC_02, 0, 8, .check_relay = true}};
static const CanMsg VOLKSWAGEN_MLB_NO_ECAN_TX_MSGS[] = {{MSG_HCA_01, 1, 8, .check_relay = true}, {MSG_LDW_02, 1, 8, .check_relay = true},
{MSG_LS_01, 1, 4, .check_relay = false}};
static RxCheck volkswagen_mlb_rx_checks[] = {
// TODO: implement checksum validation
{.msg = {{MSG_ESP_03, 0, 8, 50U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
@@ -46,7 +51,17 @@ static safety_config volkswagen_mlb_init(uint16_t param) {
{.msg = {{MSG_LS_01, 0, 4, 10U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
};
static RxCheck volkswagen_mlb_no_ecan_rx_checks[] = {
{.msg = {{MSG_ESP_03, 1, 8, 50U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{MSG_LH_EPS_03, 1, 8, 100U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{MSG_ESP_05, 1, 8, 50U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{MSG_TSK_02, 1, 8, 50U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{MSG_MOTOR_03, 1, 8, 100U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{MSG_LS_01, 1, 4, 10U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
};
volkswagen_common_init();
volkswagen_mlb_no_ecan = GET_FLAG(param, FLAG_VOLKSWAGEN_MLB_NO_ECAN);
#ifdef ALLOW_DEBUG
volkswagen_longitudinal = GET_FLAG(param, FLAG_VOLKSWAGEN_LONG_CONTROL);
@@ -56,11 +71,12 @@ static safety_config volkswagen_mlb_init(uint16_t param) {
#endif
return volkswagen_longitudinal ? BUILD_SAFETY_CFG(volkswagen_mlb_rx_checks, VOLKSWAGEN_MLB_LONG_TX_MSGS) : \
BUILD_SAFETY_CFG(volkswagen_mlb_rx_checks, VOLKSWAGEN_MLB_STOCK_TX_MSGS);
volkswagen_mlb_no_ecan ? BUILD_SAFETY_CFG(volkswagen_mlb_no_ecan_rx_checks, VOLKSWAGEN_MLB_NO_ECAN_TX_MSGS) : \
BUILD_SAFETY_CFG(volkswagen_mlb_rx_checks, VOLKSWAGEN_MLB_STOCK_TX_MSGS);
}
static void volkswagen_mlb_rx_hook(const CANPacket_t *msg) {
if (msg->bus == 0U) {
if (msg->bus == (volkswagen_mlb_no_ecan ? 1U : 0U)) {
// Check all wheel speeds for any movement
// Signals: ESP_03.ESP_[VL|VR|HL|HR]_Radgeschw
if (msg->addr == MSG_ESP_03) {

View File

@@ -216,5 +216,72 @@ class TestVolkswagenMlbLongSafety(TestVolkswagenMlbSafetyBase):
self.assertTrue(self._tx(self._acc_01_msg(0.5)))
class TestVolkswagenMlbNoEcanSafety(TestVolkswagenMlbSafetyBase):
TX_MSGS = [[MSG_HCA_01, 1], [MSG_LDW_02, 1], [MSG_LS_01, 1]]
FWD_BLACKLISTED_ADDRS = {}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
RELAY_MALFUNCTION_ADDRS = {1: (MSG_HCA_01, MSG_LDW_02)}
def setUp(self):
self.packer = CANPackerSafety("vw_mlb")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMlb, VolkswagenSafetyFlags.MLB_NO_ECAN)
self.safety.init_tests()
def _speed_msg(self, speed):
values = {"ESP_%s_Radgeschw" % s: speed for s in ["HL", "HR", "VL", "VR"]}
return self.packer.make_can_msg_safety("ESP_03", 1, values)
def _esp_05_msg(self, brake):
values = {"ESP_Fahrer_bremst": brake}
return self.packer.make_can_msg_safety("ESP_05", 1, values)
def _motor_03_msg(self, brake_signal=False, gas_signal=0):
values = {"MO_BLS": brake_signal, "MO_Fahrpedalrohwert_01": gas_signal}
return self.packer.make_can_msg_safety("Motor_03", 1, values)
def _torque_driver_msg(self, torque):
values = {"EPS_Lenkmoment": abs(torque), "EPS_VZ_Lenkmoment": torque < 0}
return self.packer.make_can_msg_safety("LH_EPS_03", 1, values)
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"HCA_01_LM_Offset": abs(torque),
"HCA_01_LM_OffSign": torque < 0,
"HCA_01_Sendestatus": steer_req,
"HCA_01_Status_HCA": 7 if steer_req else 3}
return self.packer.make_can_msg_safety("HCA_01", 1, values)
def _ls_01_msg(self, cancel=0, resume=0, _set=0, main_switch=1, bus=1):
values = {"LS_Abbrechen": cancel, "LS_Tip_Setzen": _set, "LS_Tip_Wiederaufnahme": resume,
"LS_Hauptschalter": main_switch}
return self.packer.make_can_msg_safety("LS_01", bus, values)
def _tsk_status_msg(self, enable, main_switch=True):
values = {"TSK_Status": 1 if enable else 0 if main_switch else 3}
return self.packer.make_can_msg_safety("TSK_02", 1, values)
def test_spam_cancel_safety_check(self):
self.safety.set_controls_allowed(0)
self.assertTrue(self._tx(self._ls_01_msg(cancel=1)))
self.assertFalse(self._tx(self._ls_01_msg(resume=1)))
self.assertFalse(self._tx(self._ls_01_msg(_set=1)))
self.safety.set_controls_allowed(1)
self.assertTrue(self._tx(self._ls_01_msg(resume=1)))
def test_cancel_button(self):
self._rx(self._tsk_status_msg(False, main_switch=True))
self.safety.set_controls_allowed(1)
self._rx(self._ls_01_msg(cancel=True))
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after cancel")
def test_ecan_bus_is_ignored(self):
self._rx(self._torque_driver_msg(0))
self.safety.set_controls_allowed(1)
self._rx(self.packer.make_can_msg_safety("LS_01", 0, {"LS_Abbrechen": 1, "LS_Hauptschalter": 1}))
self.assertTrue(self.safety.get_controls_allowed(), "bus 0 cancel disengaged a no-ECAN car")
self._rx(self.packer.make_can_msg_safety("LH_EPS_03", 0, {"EPS_Lenkmoment": 200, "EPS_VZ_Lenkmoment": 0}))
self.assertEqual(0, self.safety.get_torque_driver_max(), "bus 0 EPS torque reached the driver sample")
if __name__ == "__main__":
unittest.main()