1
0
forked from IQ.Lvbs/IQ.Pilot

IQ.Pilot Release Commit @ 0798119

This commit is contained in:
IQ.Lvbs history cleanup
2026-08-22 23:42:42 -05:00
commit b42569dbca
4529 changed files with 1132125 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
from types import SimpleNamespace
from iqdbc.car.volkswagen import mqbcan, pqcan
from iqdbc.car.volkswagen.carcontroller import CarController
from iqdbc.car.volkswagen.values import CAR, MQB_A0_CARS
def test_is_mqb_a0_car_matches_expected_platforms():
assert CAR.VOLKSWAGEN_POLO_MK6 in MQB_A0_CARS
assert CAR.VOLKSWAGEN_TCROSS_MK1 in MQB_A0_CARS
assert CAR.SKODA_FABIA_MK4 in MQB_A0_CARS
assert CAR.SKODA_KAMIQ_MK1 in MQB_A0_CARS
assert CarController._is_mqb_a0_car(CAR.VOLKSWAGEN_POLO_MK6)
assert CarController._is_mqb_a0_car(CAR.VOLKSWAGEN_TCROSS_MK1)
assert CarController._is_mqb_a0_car(CAR.SKODA_FABIA_MK4)
assert CarController._is_mqb_a0_car(CAR.SKODA_KAMIQ_MK1)
assert not CarController._is_mqb_a0_car(CAR.VOLKSWAGEN_GOLF_MK7)
def test_mqb_steering_torque_scale_only_changes_when_toggle_enabled():
controller = object.__new__(CarController)
controller.CCS = mqbcan
assert controller._get_mqb_steering_torque_scale(0.4, False) == 1.0
assert controller._get_mqb_steering_torque_scale(0.4, True) == 0.8
assert controller._get_mqb_steering_torque_scale(4.0, True) == 1.0
controller.CCS = pqcan
assert controller._get_mqb_steering_torque_scale(0.4, True) == 1.0
def test_mqb_a0_resume_spam_requires_toggle_platform_and_window():
controller = object.__new__(CarController)
controller.CCS = mqbcan
controller.is_mqb_a0 = True
controller.frame = 10
cs = SimpleNamespace(out=SimpleNamespace(standstill=True))
assert controller._should_spam_mqb_a0_resume(cs, True)
controller.frame = 20
assert not controller._should_spam_mqb_a0_resume(cs, True)
controller.frame = 10
controller.is_mqb_a0 = False
assert not controller._should_spam_mqb_a0_resume(cs, True)
controller.is_mqb_a0 = True
assert not controller._should_spam_mqb_a0_resume(cs, False)

View File

@@ -0,0 +1,363 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import math
from types import SimpleNamespace
from iqdbc.car.volkswagen.carcontroller import MQBStandstillManager
def _pitch(grade_pct):
return math.atan(grade_pct / 100.0)
def _mgr():
return MQBStandstillManager(vehicle_mass=1540.0, accel_min=-3.5)
def _cs(*, esp_hold_confirmation=False, esp_stopping=False, rolling_backward=False,
rolling_forward=False, brake_pressed=False, gas_pressed=False, standstill=True, v_ego=0.0,
sum_wegimpulse=0):
out = SimpleNamespace(brakePressed=brake_pressed, gasPressed=gas_pressed, standstill=standstill, vEgo=v_ego)
return SimpleNamespace(out=out, esp_hold_confirmation=esp_hold_confirmation,
esp_stopping=esp_stopping, rolling_backward=rolling_backward,
rolling_forward=rolling_forward, sum_wegimpulse=sum_wegimpulse)
def _run(mgr, cs, *, long_active=True, accel=0.0, stopping=False, starting=False,
max_planned_speed=0.0, grade_pct=0.0, tsk_brake_torque=0.0):
return mgr.update(cs, long_active, accel, stopping, starting, max_planned_speed,
_pitch(grade_pct), tsk_brake_torque)
def _safe_speed(mgr, grade_pct, brake_torque=0.0):
return mgr.get_safe_speed_for_brake_torque(_pitch(grade_pct), brake_torque)
# ── brake press ──────────────────────────────────────────────────────────────
def test_brake_pressed_disables_long_active():
mgr = _mgr()
long_active, *_ = _run(mgr, _cs(brake_pressed=True), long_active=True)
assert not long_active
# ── rollback detection ───────────────────────────────────────────────────────
def test_rollback_detected_on_rolling_backward():
mgr = _mgr()
_run(mgr, _cs(rolling_backward=True))
assert mgr.rollback_detected
def test_rollback_clears_on_rolling_forward():
mgr = _mgr()
_run(mgr, _cs(rolling_backward=True))
assert mgr.rollback_detected
_run(mgr, _cs(rolling_forward=True))
assert not mgr.rollback_detected
def test_rollback_forces_brake():
mgr = _mgr()
_run(mgr, _cs(rolling_backward=True))
_, accel, *_ = _run(mgr, _cs(), accel=-0.5)
assert accel == mgr.accel_min
def test_rollback_cleared_when_rolling_forward_passes_through_accel():
mgr = _mgr()
_run(mgr, _cs(rolling_backward=True))
_, accel, *_ = _run(mgr, _cs(rolling_forward=True, standstill=False), accel=-0.5)
assert accel == -0.5
def test_rollback_forces_brake_with_positive_accel():
mgr = _mgr()
_run(mgr, _cs(rolling_backward=True))
_, accel, stopping, starting, *_ = _run(mgr, _cs(), accel=0.5)
assert accel == mgr.accel_min
assert stopping
assert not starting
# ── safe speed braking ───────────────────────────────────────────────────────
def test_flat_ground_passes_raw_accel():
mgr = _mgr()
_, accel, stopping, starting, *_ = _run(mgr, _cs(v_ego=0.0), accel=-0.55,
stopping=True, starting=False, grade_pct=0.0)
assert accel == -0.55
assert stopping
assert not starting
def test_current_brake_torque_reduces_safe_speed():
mgr = _mgr()
zero_brake_safe_speed = _safe_speed(mgr, 20.0)
current_brake_safe_speed = _safe_speed(mgr, 20.0, 500.0)
assert 0.0 < current_brake_safe_speed < zero_brake_safe_speed
def test_current_brake_torque_can_eliminate_safe_speed():
mgr = _mgr()
assert _safe_speed(mgr, 20.0, 10000.0) == 0.0
def test_safe_speed_is_capped_at_10_kph():
mgr = _mgr()
assert _safe_speed(mgr, 100.0) == MQBStandstillManager.MAX_SAFE_STOPPING_SPEED
def test_esp_stopping_passes_raw_accel_on_flat():
mgr = _mgr()
_, accel, stopping, starting, esp_starting_override, esp_stopping_override = _run(
mgr, _cs(esp_stopping=True, v_ego=0.2), accel=-0.55, stopping=True, starting=False,
)
assert accel == -0.55
assert stopping
assert not starting
assert esp_starting_override is True
assert esp_stopping_override is False
def test_below_safe_speed_blends_brake():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
required_torque = mgr.get_hill_hold_decel_deficit(_pitch(grade), 0.0) * mgr.vehicle_mass * mgr.ASSUMED_WHEEL_RADIUS
_, accel, stopping, starting, *_ = _run(
mgr, _cs(v_ego=safe_speed * 0.5), accel=-0.55, grade_pct=grade,
tsk_brake_torque=required_torque * 0.5,
)
assert mgr.accel_min < accel < -0.55
assert stopping
assert not starting
def test_sufficient_brake_torque_passes_raw_accel():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
_, accel, stopping, starting, *_ = _run(
mgr, _cs(v_ego=safe_speed * 0.5), accel=-0.2, stopping=True, starting=False,
grade_pct=grade, tsk_brake_torque=10000.0,
)
assert accel == -0.2
assert stopping
assert not starting
def test_below_safe_speed_with_low_planned_speed_uses_blended_braking():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
_, accel, stopping, starting, *_ = _run(
mgr, _cs(v_ego=safe_speed * 0.5), accel=-0.55,
max_planned_speed=safe_speed * 0.5, grade_pct=grade,
)
assert mgr.accel_min <= accel < -0.55
assert stopping
assert not starting
def test_below_safe_speed_with_high_planned_speed_and_negative_accel_uses_blended_braking():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
_, accel, stopping, starting, *_ = _run(
mgr, _cs(v_ego=safe_speed * 0.5), accel=-0.55,
max_planned_speed=safe_speed * 2.0, grade_pct=grade,
)
assert mgr.accel_min <= accel < -0.55
assert stopping
assert not starting
def test_below_safe_speed_with_high_planned_speed_and_positive_accel_uses_hill_takeoff():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
_, accel, stopping, starting, *_ = _run(
mgr, _cs(v_ego=safe_speed * 0.5), accel=0.1,
max_planned_speed=safe_speed * 2.0, grade_pct=grade,
)
assert accel == max(0.1, 0.1 * grade, 0.2)
assert starting
assert not stopping
# ── start commit ─────────────────────────────────────────────────────────────
def test_start_commit_on_pre_enable():
mgr = _mgr()
_run(mgr, _cs(esp_hold_confirmation=True))
assert mgr.start_commit_active
def test_start_commit_forces_accel():
mgr = _mgr()
grade = 10.0
expected_min = max(0.1 * grade, 0.2)
_run(mgr, _cs(esp_hold_confirmation=True), grade_pct=grade)
_, accel, stopping, starting, *_ = _run(
mgr, _cs(esp_hold_confirmation=True), accel=0.0, grade_pct=grade,
)
assert accel >= expected_min
assert starting
assert not stopping
def test_start_commit_clears_above_safe_speed_while_moving():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
_run(mgr, _cs(esp_hold_confirmation=True), grade_pct=grade)
assert mgr.start_commit_active
_run(mgr, _cs(v_ego=safe_speed * 2.0, standstill=True), grade_pct=grade)
assert not mgr.start_commit_active
def test_start_commit_persists_below_safe_speed():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
_run(mgr, _cs(esp_hold_confirmation=True), grade_pct=grade)
assert mgr.start_commit_active
_, accel, stopping, starting, *_ = _run(
mgr, _cs(v_ego=safe_speed * 0.5), accel=-0.55, grade_pct=grade,
)
assert mgr.start_commit_active
assert accel == max(-0.55, 0.1 * grade, 0.2)
assert starting
assert not stopping
# ── can_stop_forever / ESP override ──────────────────────────────────────────
def test_can_stop_forever_latches_on_esp_stopping():
mgr = _mgr()
*_, esp_start, esp_stop = _run(mgr, _cs(esp_stopping=True), accel=-1.0, stopping=True)
assert mgr.can_stop_forever
assert esp_start is True
assert esp_stop is False
def test_can_stop_forever_persists():
mgr = _mgr()
_run(mgr, _cs(esp_stopping=True), accel=-1.0, stopping=True)
*_, esp_start, esp_stop = _run(mgr, _cs(), accel=-1.0, stopping=True)
assert mgr.can_stop_forever
assert esp_start is True
assert esp_stop is False
def test_can_stop_forever_cleared_by_hold_confirmation():
mgr = _mgr()
_run(mgr, _cs(esp_stopping=True), accel=-1.0, stopping=True)
assert mgr.can_stop_forever
_run(mgr, _cs(esp_hold_confirmation=True), accel=-1.0, stopping=True)
assert not mgr.can_stop_forever
def test_hold_confirmation_recovers_stop_forever_after_launch_commit():
mgr = _mgr()
grade = 20.0
safe_speed = _safe_speed(mgr, grade)
*_, esp_start, esp_stop = _run(mgr, _cs(esp_hold_confirmation=True, sum_wegimpulse=0),
accel=0.0, grade_pct=grade)
assert mgr.start_commit_active
assert mgr.hold_recovery_active
assert esp_start is True
assert esp_stop is False
*_, esp_start, esp_stop = _run(mgr, _cs(v_ego=safe_speed * 2.0, standstill=False, sum_wegimpulse=1),
accel=0.0, grade_pct=grade)
assert not mgr.start_commit_active
assert mgr.hold_recovery_active
assert esp_start is False
assert esp_stop is True
*_, esp_start, esp_stop = _run(mgr, _cs(esp_stopping=True, v_ego=safe_speed * 2.0, standstill=False,
sum_wegimpulse=2), accel=0.0, grade_pct=grade)
assert mgr.can_stop_forever
assert not mgr.hold_recovery_active
assert esp_start is True
assert esp_stop is False
def test_can_stop_forever_cleared_when_moving():
mgr = _mgr()
_run(mgr, _cs(esp_stopping=True), accel=-1.0, stopping=True)
assert mgr.can_stop_forever
_run(mgr, _cs(v_ego=MQBStandstillManager.ESP_OVERRIDE_SPEED + 0.01), accel=0.5)
assert not mgr.can_stop_forever
def test_can_stop_forever_cleared_when_long_inactive():
mgr = _mgr()
_run(mgr, _cs(esp_stopping=True), accel=-1.0, stopping=True)
assert mgr.can_stop_forever
_run(mgr, _cs(), long_active=False)
assert not mgr.can_stop_forever
def test_esp_override_stop_at_standstill():
mgr = _mgr()
esp_start = esp_stop = None
for _ in range(MQBStandstillManager.WEGIMPULSE_STILLNESS_FRAMES + 1):
*_, esp_start, esp_stop = _run(mgr, _cs(sum_wegimpulse=0), accel=-1.0, stopping=True)
assert esp_start is False
assert esp_stop is True
def test_esp_override_stop_persists_at_standstill():
mgr = _mgr()
for _ in range(MQBStandstillManager.WEGIMPULSE_STILLNESS_FRAMES + 1):
_run(mgr, _cs(sum_wegimpulse=0), accel=-1.0, stopping=True)
*_, esp_start, esp_stop = _run(mgr, _cs(sum_wegimpulse=0), accel=-1.0, stopping=True)
assert esp_start is False
assert esp_stop is True
def test_esp_override_stop_not_sent_while_esp_stopping():
mgr = _mgr()
*_, esp_start, esp_stop = _run(mgr, _cs(esp_stopping=True, sum_wegimpulse=0),
accel=-1.0, stopping=True)
assert mgr.can_stop_forever
assert esp_start is True
assert esp_stop is False
def test_esp_override_stop_not_requested_while_moving():
mgr = _mgr()
esp_start = esp_stop = None
for sum_wegimpulse in range(MQBStandstillManager.WEGIMPULSE_STILLNESS_FRAMES + 1):
*_, esp_start, esp_stop = _run(mgr, _cs(sum_wegimpulse=sum_wegimpulse), accel=-1.0, stopping=True)
assert esp_start is True
assert esp_stop is False
def test_esp_override_default_below_grant_speed_when_inactive():
mgr = _mgr()
*_, esp_start, esp_stop = _run(mgr, _cs(), long_active=False)
assert esp_start is True
assert esp_stop is False
def test_wegimpulse_at_standstill_after_stillness_frames():
mgr = _mgr()
for _ in range(MQBStandstillManager.WEGIMPULSE_STILLNESS_FRAMES + 1):
_run(mgr, _cs(sum_wegimpulse=0))
assert mgr.frames_since_last_wheel_pulse >= MQBStandstillManager.WEGIMPULSE_STILLNESS_FRAMES
def test_wegimpulse_resets_on_change():
mgr = _mgr()
for _ in range(MQBStandstillManager.WEGIMPULSE_STILLNESS_FRAMES):
_run(mgr, _cs(sum_wegimpulse=0))
_run(mgr, _cs(sum_wegimpulse=1))
assert mgr.frames_since_last_wheel_pulse == 0

View File

@@ -0,0 +1,186 @@
"""
Regression guard for the class of bug fixed in carstate.py's Diagnose_1 (cluster clock,
removed) and EPB_1 (stop-and-go hold, now gated on PQ_SNG_ECD): reading a CAN message via
`some_cp.vl["MsgName"]["Signal"]` without declaring it in get_can_parsers_pq() lazily adds
it via VLDict.__getitem__ -> CANParser._add_message(key, freq=None), which is NOT the same
as ignore-alive (that's what math.nan is for -- see
iqdbc/can/tests/test_packer_parser.py::test_lazy_add_not_ignore_alive). An undeclared
message defaults to "assume ~1Hz, must be seen within ~10s", so if the real car never sends
it, CarState.canValid gets stuck False forever.
Rather than fuzzing every VolkswagenFlagsIQ combination (most are unreachable through
interface.py's real detection logic), each fixture below is the CarParams captured from a
real konn3kt route for a real car of that variant. We run CarState.update() once (no CAN
data needs to be fed -- .vl[...] lazily adds and returns default-zero values regardless of
whether the parser has ever seen a real frame) while recording every message name accessed,
then assert that set is a subset of what that real car actually transmits (per its captured
CAN fingerprint). A message accessed but not in the real fingerprint is exactly the bug
class this guards against.
"""
from iqdbc.can.parser import VLDict
from iqdbc.car import structs
from iqdbc.car.volkswagen.carstate import CarState
from iqdbc.car.volkswagen.values import CAR, Bus
class _RecordingVLDict(VLDict):
"""Records every message name read via .vl[...], including ones lazily added for
messages never declared in get_can_parsers_pq's message list. Applied by reclassing
a live CANParser.vl instance in place (rather than replacing it with a fresh object)
so any messages already registered at CANParser construction time, and the parser
back-reference _add_message needs, are preserved."""
accessed: set[str]
def __getitem__(self, key):
if isinstance(key, str):
self.accessed.add(key)
return super().__getitem__(key)
def _make_car_params(car_fingerprint, flags, network_location, transmission_type, enable_bsm, pcm_cruise):
CP = structs.CarParams.new_message()
CP.carFingerprint = car_fingerprint
CP.flags = flags
CP.radarUnavailable = True
CP.networkLocation = network_location
CP.transmissionType = transmission_type
CP.enableBsm = enable_bsm
CP.pcmCruise = pcm_cruise
CP.minSteerSpeed = 0.0
CP_IQ = structs.IQCarParams()
return CP, CP_IQ
# Real per-variant CarParams captured from konn3kt routes (not hand-derived), so each
# fixture reflects an actual car rather than a guess at which flag combos are reachable.
# known_bus1/known_bus2 are the exact message names present in that car's real CAN
# fingerprint. Bus.pt and Bus.aux both listen on physical bus 1 for PQ (CanBus.powertrain
# == CanBus.aux == 1); Bus.cam listens on physical bus 2 (CanBus.cam == 2) -- see CanBus in
# iqdbc/car/volkswagen/values.py. UNK_* (unrecognized DBC addresses) are dropped.
FIXTURES = [
dict(
name="jetta_mk6_base",
route="a0d99b85ff06857b|0000003a--135c88414f",
car_fingerprint=CAR.VOLKSWAGEN_JETTA_MK6,
flags=2,
network_location="gateway",
transmission_type="automatic",
enable_bsm=False,
pcm_cruise=True,
known_bus1={
'ACC_GRA_Anzeige', 'ACC_System', 'AWV', 'Airbag_1', 'Airbag_2', 'BSG_Last', 'Bremse_1',
'Bremse_10', 'Bremse_11', 'Bremse_2', 'Bremse_3', 'Bremse_4', 'Bremse_5', 'Bremse_8',
'Bremse_9', 'Diagnose_1', 'Einheiten_1', 'GRA_Neu', 'Gate_Komf_1', 'Gate_Komf_2',
'Getriebe_1', 'Getriebe_2', 'Getriebe_4', 'Ident', 'Klima_1', 'Kombi_1', 'Kombi_2',
'Kombi_3', 'Lenkhilfe_1', 'Lenkhilfe_2', 'Lenkhilfe_3', 'Lenkwinkel_1', 'Motor_1',
'Motor_10', 'Motor_12', 'Motor_2', 'Motor_3', 'Motor_5', 'Motor_6', 'Motor_7', 'Motor_8',
'Motor_Bremse', 'Motor_Flexia', 'Soll_Verbauliste_neu', 'Systeminfo_1', 'Waehlhebel_1',
},
known_bus2={
'ACC_GRA_Anzeige', 'ACC_System', 'AWV', 'Airbag_1', 'BSG_Last', 'Bremse_1', 'Bremse_10',
'Bremse_11', 'Bremse_2', 'Bremse_3', 'Bremse_5', 'Bremse_8', 'Diagnose_1', 'Einheiten_1',
'GRA_Neu', 'Gate_Komf_1', 'Gate_Komf_2', 'Getriebe_1', 'Ident', 'Kombi_1', 'Kombi_2',
'Kombi_3', 'Lenkhilfe_2', 'Lenkhilfe_3', 'Lenkwinkel_1', 'Motor_1', 'Motor_10', 'Motor_2',
'Motor_3', 'Motor_5', 'Motor_Flexia', 'Soll_Verbauliste_neu', 'Systeminfo_1',
},
),
dict(
name="passat_nms_sng_ecd",
route="0f53129ed44f6920|00000031--0c1aea511e",
car_fingerprint=CAR.VOLKSWAGEN_PASSAT_NMS,
flags=524418, # PQ | IQ_LVBS_ALC_MODULE | IQ_PQ_SNG_ECD
network_location="gateway",
transmission_type="automatic",
enable_bsm=False,
pcm_cruise=False,
known_bus1={
'ACC_GRA_Anzeige', 'ACC_System', 'AWV', 'Airbag_1', 'Airbag_2', 'BSG_Last', 'Bremse_1',
'Bremse_10', 'Bremse_11', 'Bremse_2', 'Bremse_3', 'Bremse_4', 'Bremse_5', 'Bremse_8',
'Bremse_9', 'Diagnose_1', 'EPB_1', 'EPB_2', 'Einheiten_1', 'GRA_Neu', 'Gate_Komf_1',
'Gate_Komf_2', 'Getriebe_1', 'Getriebe_2', 'Ident', 'Klima_1', 'Kombi_1', 'Kombi_2',
'Kombi_3', 'Lenkhilfe_1', 'Lenkhilfe_2', 'Lenkhilfe_3', 'Lenkwinkel_1', 'Motor_1',
'Motor_10', 'Motor_12', 'Motor_13', 'Motor_2', 'Motor_3', 'Motor_5', 'Motor_6', 'Motor_7',
'Motor_8', 'Motor_Bremse', 'Motor_Flexia', 'Soll_Verbauliste_neu', 'Systeminfo_1',
},
known_bus2={
'ACC_GRA_Anzeige', 'ACC_System', 'AWV', 'Airbag_1', 'BSG_Last', 'Bremse_1', 'Bremse_10',
'Bremse_11', 'Bremse_2', 'Bremse_3', 'Bremse_5', 'Bremse_8', 'Diagnose_1', 'EPB_1',
'Einheiten_1', 'GRA_Neu', 'Gate_Komf_1', 'Gate_Komf_2', 'Getriebe_1', 'Ident', 'Kombi_1',
'Kombi_2', 'Kombi_3', 'Lenkhilfe_2', 'Lenkhilfe_3', 'Lenkwinkel_1', 'Motor_1', 'Motor_10',
'Motor_2', 'Motor_3', 'Motor_5', 'Motor_Flexia', 'Soll_Verbauliste_neu', 'Systeminfo_1',
},
),
dict(
name="passat_b7_acc_fts_epb",
route="20e3cd4f0d5f39d1|0000008e--274d762bac",
car_fingerprint=CAR.VOLKSWAGEN_PASSAT_B7,
flags=262146, # PQ | IQ_PQ_ACC_FTS_EPB
network_location="gateway",
transmission_type="automatic",
enable_bsm=True,
pcm_cruise=False,
known_bus1={
'ACC_GRA_Anzeige', 'ACC_System', 'AWV', 'Airbag_1', 'Airbag_2', 'BSG_Last', 'Bremse_1',
'Bremse_10', 'Bremse_11', 'Bremse_2', 'Bremse_3', 'Bremse_4', 'Bremse_5', 'Bremse_8',
'Bremse_9', 'Daempfer_1', 'Diagnose_1', 'EPB_1', 'Einheiten_1', 'GRA_Neu', 'Gate_Komf_1',
'Gate_Komf_2', 'Getriebe_1', 'Getriebe_2', 'Getriebe_4', 'HCA_1', 'Ident', 'Klima_1',
'Kombi_1', 'Kombi_2', 'Kombi_3', 'Lenkhilfe_1', 'Lenkhilfe_2', 'Lenkhilfe_3',
'Lenkwinkel_1', 'Motor_1', 'Motor_10', 'Motor_12', 'Motor_2', 'Motor_3', 'Motor_5',
'Motor_6', 'Motor_7', 'Motor_8', 'Motor_Bremse', 'Motor_Flexia', 'Parkhilfe_01',
'Soll_Verbauliste_neu', 'Systeminfo_1', 'Waehlhebel_1',
},
known_bus2={
'ACC_GRA_Anzeige', 'ACC_System', 'AWV', 'Airbag_1', 'BSG_Last', 'Bremse_1', 'Bremse_10',
'Bremse_11', 'Bremse_2', 'Bremse_3', 'Bremse_5', 'Bremse_8', 'Diagnose_1', 'EPB_1',
'Einheiten_1', 'GRA_Neu', 'Gate_Komf_1', 'Gate_Komf_2', 'Getriebe_1', 'HCA_1', 'Ident',
'Kombi_1', 'Kombi_2', 'Kombi_3', 'LDW_Status', 'Lenkhilfe_2', 'Lenkhilfe_3',
'Lenkwinkel_1', 'Motor_1', 'Motor_10', 'Motor_2', 'Motor_3', 'Motor_5', 'Motor_Flexia',
'Parkhilfe_01', 'RDK_Status', 'SWA_1', 'Soll_Verbauliste_neu', 'Systeminfo_1',
},
),
]
class TestVolkswagenPqCanValid:
def test_only_reads_messages_the_real_car_sends(self, subtests):
for fixture in FIXTURES:
with subtests.test(car=fixture["name"]):
CP, CP_IQ = _make_car_params(
fixture["car_fingerprint"], fixture["flags"], fixture["network_location"],
fixture["transmission_type"], fixture["enable_bsm"], fixture["pcm_cruise"],
)
CS = CarState(CP, CP_IQ)
can_parsers = CS.get_can_parsers(CP, CP_IQ)
recorders = {}
for bus, parser in can_parsers.items():
if parser is None:
continue
parser.vl.__class__ = _RecordingVLDict
parser.vl.accessed = set()
recorders[bus] = parser.vl
# no CAN data is fed: .vl[...] lazily adds and returns default-zero values
# regardless of whether the parser has ever seen a real frame, so this alone
# is enough to harvest every message name update_pq() touches for this variant
CS.update(can_parsers)
accessed_bus1 = recorders[Bus.pt].accessed | recorders[Bus.aux].accessed
accessed_bus2 = recorders[Bus.cam].accessed
extra_bus1 = accessed_bus1 - fixture["known_bus1"]
extra_bus2 = accessed_bus2 - fixture["known_bus2"]
assert not extra_bus1, (
f"{fixture['name']}: code reads {sorted(extra_bus1)} on bus 1 (pt/aux) but the "
f"real car (route {fixture['route']}) never transmits it -- this needs to be "
f"gated behind whatever flag makes it optional, or declared with math.nan if it's "
f"genuinely on-demand/rare, not read unconditionally"
)
assert not extra_bus2, (
f"{fixture['name']}: code reads {sorted(extra_bus2)} on bus 2 (cam) but the "
f"real car (route {fixture['route']}) never transmits it -- this needs to be "
f"gated behind whatever flag makes it optional, or declared with math.nan if it's "
f"genuinely on-demand/rare, not read unconditionally"
)

View File

@@ -0,0 +1,33 @@
import pytest
from iqdbc.car.volkswagen.interface import CarInterface
from iqdbc.car.volkswagen.values import (
CAR, PASSAT_B7_STOP_ACCEL, PASSAT_B7_STOPPING_SPEED, PQ_STOPPING_SPEED,
VolkswagenFlags, apply_pq_stopping_accel, get_longitudinal_stopping_speed_override,
)
@pytest.mark.parametrize("candidate, flags, expected", [
(CAR.VOLKSWAGEN_PASSAT_B7, VolkswagenFlags.PQ, PASSAT_B7_STOPPING_SPEED),
(CAR.VOLKSWAGEN_JETTA_MK6, VolkswagenFlags.PQ, PQ_STOPPING_SPEED),
(CAR.VOLKSWAGEN_GOLF_MK7, 0, 0.0),
(CAR.VOLKSWAGEN_ID4_MK1, VolkswagenFlags.MEB, 0.0),
])
def test_stopping_speed_override(candidate, flags, expected):
assert get_longitudinal_stopping_speed_override(candidate, flags) == expected
def test_passat_b7_stop_accel_is_exact():
assert apply_pq_stopping_accel(CAR.VOLKSWAGEN_PASSAT_B7, -0.2, True) == PASSAT_B7_STOP_ACCEL == -0.55
assert apply_pq_stopping_accel(CAR.VOLKSWAGEN_PASSAT_B7, -0.2, False) == -0.2
assert apply_pq_stopping_accel(CAR.VOLKSWAGEN_JETTA_MK6, -0.2, True) == -0.2
@pytest.mark.parametrize("candidate", [
CAR.VOLKSWAGEN_JETTA_MK6,
CAR.VOLKSWAGEN_PASSAT_NMS,
])
def test_pq_longitudinal_feedforward_uses_active_schema_field(candidate):
fingerprint = {bus: {} for bus in range(8)}
cp = CarInterface.get_params(candidate, fingerprint, [], alpha_long=True, is_release=False, docs=False)
assert cp.longitudinalTuning.kf == pytest.approx(1.2)

View File

@@ -0,0 +1,129 @@
import random
import re
from types import SimpleNamespace
import pytest
from iqdbc.car import structs
from iqdbc.car.volkswagen.carstate import CarState
from iqdbc.car.structs import CarParams
from iqdbc.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI
from iqdbc.car.volkswagen.fingerprints import FW_VERSIONS
Ecu = CarParams.Ecu
CHASSIS_CODE_PATTERN = re.compile('[A-Z0-9]{2}')
# TODO: determine the unknown groups
SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P<gateway>[0-9][0-9A-Z]{2})(?P<unknown>[0-9][0-9A-Z][0-9])(?P<unknown2>[0-9A-Z]{2}[0-9])([A-Z0-9]| )')
class TestVolkswagenPlatformConfigs:
def test_spare_part_fw_pattern(self, subtests):
# Relied on for determining if a FW is likely VW
for platform, ecus in FW_VERSIONS.items():
with subtests.test(platform=platform.value):
for fws in ecus.values():
for fw in fws:
assert SPARE_PART_FW_PATTERN.match(fw) is not None, f"Bad FW: {fw}"
def test_chassis_codes(self, subtests):
for platform in CAR:
with subtests.test(platform=platform.value):
assert len(platform.config.wmis) > 0, "WMIs not set"
assert len(platform.config.chassis_codes) > 0, "Chassis codes not set"
assert all(CHASSIS_CODE_PATTERN.match(cc) for cc in
platform.config.chassis_codes), "Bad chassis codes"
# No two platforms should share chassis codes
for comp in CAR:
if platform == comp:
continue
shared_chassis_codes = platform.config.chassis_codes & comp.config.chassis_codes
if len(shared_chassis_codes) == 0:
continue
# A shared chassis code is unambiguous when the VIN WMI separates the candidates.
if platform.config.wmis.isdisjoint(comp.config.wmis):
continue
platform_model_years = getattr(platform.config, "model_years", set())
comp_model_years = getattr(comp.config, "model_years", set())
if platform_model_years and comp_model_years and platform_model_years.isdisjoint(comp_model_years):
continue
assert set() == shared_chassis_codes, f"Shared chassis codes: {comp}"
def test_custom_fuzzy_fingerprinting(self, subtests):
all_radar_fw = list({fw for ecus in FW_VERSIONS.values() for fw in ecus.get((Ecu.fwdRadar, 0x757, None), [])})
for platform in CAR:
with subtests.test(platform=platform.name):
# Fuzzy matching keys off the radar ECU (CHECK_FUZZY_ECUS), so a platform that
# declares no radar ECU can never be VIN-matched and should never be expected to match.
platform_has_radar = (Ecu.fwdRadar, 0x757, None) in FW_VERSIONS.get(platform, {})
for wmi in WMI:
for chassis_code in platform.config.chassis_codes | {"00"}:
platform_model_years = getattr(platform.config, "model_years", set())
model_years = platform_model_years if platform_model_years else {"0"}
for model_year in model_years | {"0"}:
vin = ["0"] * 17
vin[0:3] = wmi
vin[6:8] = chassis_code
vin[9] = model_year
vin = "".join(vin)
# Check a few FW cases - expected, unexpected
for radar_fw in random.sample(all_radar_fw, 5) + [b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x877H9907572AA\xf1\x890396']:
model_year_match = len(platform_model_years) == 0 or model_year in platform_model_years
should_match = ((wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes and model_year_match) and
radar_fw in all_radar_fw and platform_has_radar)
live_fws = {(0x757, None): [radar_fw]}
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS)
expected_matches = {platform} if should_match else set()
assert expected_matches == matches, "Bad match"
@pytest.mark.parametrize("button_type", (
structs.CarState.ButtonEvent.Type.setCruise,
structs.CarState.ButtonEvent.Type.resumeCruise,
))
def test_button_enable_is_blocked_while_cruise_fault_lateral_mode_is_still_faulted(button_type):
state = object.__new__(CarState)
state.CP = SimpleNamespace(pcmCruise=False)
state.cruise_fault_lateral_active = True
state.cruise_faulted = True
button_events = [structs.CarState.ButtonEvent(pressed=False, type=button_type)]
assert not state.update_button_enable(button_events)
def test_button_enable_recovers_once_cruise_fault_clears():
state = object.__new__(CarState)
state.CP = SimpleNamespace(pcmCruise=False)
state.cruise_fault_lateral_active = True
state.cruise_faulted = False
button_events = [structs.CarState.ButtonEvent(
pressed=False,
type=structs.CarState.ButtonEvent.Type.setCruise,
)]
assert state.update_button_enable(button_events)
def test_pq_hca_ready_does_not_complete_eps_initialization():
state = object.__new__(CarState)
state.eps_init_complete = False
state.frame = 0
assert state.update_hca_state("READY", ready_confirms_init=False) == (True, False)
state.frame = 317
assert state.update_hca_state("FAULT", ready_confirms_init=False) == (True, False)
state.frame = 1001
assert state.update_hca_state("FAULT", ready_confirms_init=False) == (False, True)