IQ.Pilot Prebuilt Release @ 7e87bc7

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-31 21:20:43 -05:00
commit 086374214d
2544 changed files with 677676 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import pytest
from iqdbc.car.volkswagen.carcontroller import accel_during_driver_override
@pytest.mark.parametrize("accel", [-3.5, -0.5, 0.0, 0.5, 2.0])
def test_opted_in_driver_override_sends_neutral_accel(accel):
assert accel_during_driver_override(accel, True, True) == 0.0
@pytest.mark.parametrize("gas_pressed,keep_long_active", [(False, False), (False, True), (True, False)])
def test_other_longitudinal_paths_preserve_accel(gas_pressed, keep_long_active):
assert accel_during_driver_override(-0.7, gas_pressed, keep_long_active) == -0.7

View File

@@ -0,0 +1,42 @@
from iqdbc.car.volkswagen.carcontroller import CarController
from iqdbc.car.volkswagen.values import CarControllerParams
class _Tapper:
CCP = CarControllerParams
def __init__(self):
self.gra_cancel_ticks = 0
def tap(self, cancel_req, gra_send_ready=True):
return CarController._tap_gra_cancel(self, cancel_req, gra_send_ready)
def _pattern(tapper, ticks, cancel_req=True):
return "".join("1" if tapper.tap(cancel_req) else "0" for _ in range(ticks))
def test_held_cancel_is_bounded():
held = _pattern(_Tapper(), 2000).count("1")
assert held == CarControllerParams.GRA_CANCEL_TAP_ON * CarControllerParams.GRA_CANCEL_MAX_TAPS
assert held < 50
def test_cancel_is_tapped_not_held():
on, off = CarControllerParams.GRA_CANCEL_TAP_ON, CarControllerParams.GRA_CANCEL_TAP_OFF
expected = ("1" * on + "0" * off) * CarControllerParams.GRA_CANCEL_MAX_TAPS
assert _pattern(_Tapper(), len(expected)) == expected
def test_taps_rearm_after_request_clears():
tapper = _Tapper()
_pattern(tapper, 2000)
assert not tapper.tap(False)
assert _pattern(tapper, CarControllerParams.GRA_CANCEL_TAP_ON) == "1" * CarControllerParams.GRA_CANCEL_TAP_ON
def test_ticks_only_advance_on_stock_counter_change():
tapper = _Tapper()
for _ in range(500):
assert tapper.tap(True, gra_send_ready=False)
assert tapper.gra_cancel_ticks == 0

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,98 @@
"""Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved."""
from iqdbc.can import CANPacker, CANParser
from iqdbc.car.volkswagen import mebcan
from iqdbc.car.volkswagen.carcontroller import ea_blinker_command, ea_send_ready, next_ea_counter
EA_HUD_VALUES = {
"COUNTER": 0,
"EA_Texte": 0,
"ACF_Lampe_Hands_Off": 0,
"EA_Infotainment_Anf": 0,
"EA_Tueren_Anf": 0,
"EA_Innenraumlicht_Anf": 0,
"zFAS_Warnblinken": 0,
"STP_Primaeranz": 0,
"EA_Bremslichtblinken": 0,
"EA_Blinken": 0,
"EA_Unknown": 0,
}
EA_CONTROL_VALUES = {"EA_Funktionsstatus": 2}
def blinker_values(dbc, requests):
packer = CANPacker(dbc)
parser = CANParser(dbc, [("EA_02", 50)], 0)
values = []
for counter, stock_blinker, left, right in requests:
ea_hud_values = {**EA_HUD_VALUES, "COUNTER": counter, "EA_Blinken": stock_blinker}
msg = mebcan.create_blinker_control(
packer, 0, ea_hud_values, EA_CONTROL_VALUES, left, right, False,
)
parser.update([0, [msg]])
values.append((int(parser.vl["EA_02"]["COUNTER"]), int(parser.vl["EA_02"]["EA_Blinken"])))
return values
def blinker_value(dbc, counter, stock_blinker, left, right, override_counter=None):
packer = CANPacker(dbc)
parser = CANParser(dbc, [("EA_02", 50)], 0)
ea_hud_values = {**EA_HUD_VALUES, "COUNTER": counter, "EA_Blinken": stock_blinker}
msg = mebcan.create_blinker_control(
packer, 0, ea_hud_values, EA_CONTROL_VALUES, left, right, False, override_counter,
)
parser.update([0, [msg]])
return int(parser.vl["EA_02"]["COUNTER"]), int(parser.vl["EA_02"]["EA_Blinken"])
def test_nav_blinker_request_remains_asserted_until_released():
requests = [(counter % 16, 0, True, False) for counter in range(100)] + [(4, 0, False, False)]
assert blinker_values("vw_meb", requests) == [(counter % 16, 1) for counter in range(100)] + [(4, 0)]
def test_nav_blinker_directions_on_meb_variants():
requests = [(7, 0, True, False), (8, 0, False, True), (9, 0, False, False)]
for dbc in ("vw_meb", "vw_meb_2024", "vw_mqbevo"):
assert blinker_values(dbc, requests) == [(7, 1), (8, 2), (9, 0)]
def test_stock_blinker_has_priority_over_nav_request():
requests = [(10, 1, False, True), (11, 2, True, False), (12, 3, True, False)]
assert blinker_values("vw_meb", requests) == [(10, 1), (11, 2), (12, 3)]
def test_ea_frame_is_sent_once_per_stock_counter():
last_counter = None
sends = []
for counter in (5, 5, 5, 6, 6, 7):
stock_values = {"COUNTER": counter}
if ea_send_ready(stock_values, last_counter):
sends.append(counter)
last_counter = counter
assert sends == [5, 6, 7]
def test_blinker_counter_can_continue_between_stock_frames():
assert blinker_value("vw_meb", 8, 0, True, False, 9) == (9, 1)
assert blinker_value("vw_meb", 8, 0, True, False, 10) == (10, 1)
def test_stock_blinker_priority_survives_counter_override():
assert blinker_value("vw_meb", 8, 2, True, False, 9) == (9, 2)
def test_nav_blinker_retriggers_only_between_lamp_cycles():
assert ea_blinker_command(True, False, False, False) == (True, False)
assert ea_blinker_command(True, False, True, False) == (False, False)
assert ea_blinker_command(True, False, False, False) == (True, False)
assert ea_blinker_command(False, True, False, True) == (False, False)
def test_blinker_counter_runs_at_20_ms():
tx_counter = None
counters = []
for _ in range(10):
tx_counter = next_ea_counter(tx_counter, 14)
counters.append(tx_counter)
assert counters == [15, 0, 1, 2, 3, 4, 5, 6, 7, 8]

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,26 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import pytest
from iqdbc.can import CANPacker
from iqdbc.car.volkswagen import pqcan
@pytest.mark.parametrize("accel, acc_control, expected", [
(-1.0, 1, False),
(0.0, 1, False),
(0.05, 1, False),
(0.051, 1, True),
(1.0, 1, True),
(1.0, 0, False),
])
def test_positive_acceleration_prevents_fuel_cutoff(accel, acc_control, expected):
packer = CANPacker("vw_pq")
messages = pqcan.create_acc_accel_control(
packer, 0, 1, accel, acc_control, False, False, False, 0.2, 0.3, False,
)
assert len(messages) == 1
_, data, _ = messages[0]
assert bool(data[1] & 0x80) is expected

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,384 @@
import random
import re
from types import SimpleNamespace
import pytest
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, 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
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
radar_ecu = (Ecu.fwdRadar, 0x757, None)
platform_radar_fw = set(FW_VERSIONS.get(platform, {}).get(radar_ecu, []))
comp_radar_fw = set(FW_VERSIONS.get(comp, {}).get(radar_ecu, []))
if not platform_radar_fw or not comp_radar_fw:
continue
assert platform_radar_fw.isdisjoint(comp_radar_fw), f"Ambiguous VIN and radar firmware: {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):
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)
for radar_fw in random.sample(all_radar_fw, 5) + [b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x877H9907572AA\xf1\x890396']:
live_fws = {(0x757, None): [radar_fw]}
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS)
expected_matches = set()
for candidate in CAR:
candidate_model_years = getattr(candidate.config, "model_years", set())
candidate_has_radar = (Ecu.fwdRadar, 0x757, None) in FW_VERSIONS.get(candidate, {})
model_year_match = not candidate_model_years or model_year in candidate_model_years
if (wmi in candidate.config.wmis and chassis_code in candidate.config.chassis_codes and model_year_match and
radar_fw in all_radar_fw and candidate_has_radar):
expected_matches.add(candidate)
assert expected_matches == matches, "Bad match"
@pytest.mark.parametrize("candidate, expected", (
(CAR.VOLKSWAGEN_PASSAT_B7, True),
(CAR.SEAT_ALHAMBRA_MK1, True),
(CAR.VOLKSWAGEN_GOLF_MK7, False),
))
def test_pq_acc_fts_epb_flags(self, candidate, expected):
params = CarInterface.get_params(candidate, {bus: {} for bus in range(7)}, [], alpha_long=False, is_release=False, docs=False)
assert bool(params.flags & VolkswagenFlagsIQ.IQ_PQ_ACC_FTS_EPB) is expected
assert bool(params.safetyConfigs[-1].safetyParam & VolkswagenSafetyFlags.PQ_ACC_FTS_EPB) is expected
@pytest.mark.parametrize("candidate, alpha_long, expected", (
(CAR.VOLKSWAGEN_PASSAT_B7, False, False),
(CAR.VOLKSWAGEN_PASSAT_B7, True, True),
(CAR.VOLKSWAGEN_GOLF_MK7, False, False),
(CAR.VOLKSWAGEN_GOLF_MK7, True, True),
(CAR.AUDI_Q5_MK1, True, False),
(CAR.VOLKSWAGEN_ID4_MK1, True, False),
(CAR.VOLKSWAGEN_GOLF_MK8, True, False),
))
def test_supported_vw_longitudinal_stays_active_during_gas_override(self, candidate, alpha_long, expected):
fingerprints = {bus: {} for bus in range(7)}
params = CarInterface.get_params(candidate, fingerprints, [], alpha_long=alpha_long, is_release=False, docs=False)
params_iq = CarInterface.get_params_iq(params, candidate, fingerprints, [], alpha_long=alpha_long, is_release_iq=False, docs=False)
assert params_iq.longActiveWithGasOverride is expected
if expected:
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)}),
packer.make_can_msg("LH_EPS_01", 1, {}),
]
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,
))
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)
@pytest.mark.parametrize("platform", (CAR.AUDI_Q4_MK1, CAR.VOLKSWAGEN_ID4_MK2))
def test_meb_does_not_infer_mqb_cluster_from_address(platform):
fingerprints = {bus: {} for bus in range(7)}
fingerprints[0][0x30B] = 8
params = CarInterface.get_params(platform, fingerprints, [], alpha_long=False, is_release=False, docs=False)
assert not params.flags & VolkswagenFlags.KOMBI_PRESENT