forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Prebuilt Release @ 67fd9c2
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,436 @@
|
||||
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_GEARBOX_MSGS, MLB_MSG_ACC_10,
|
||||
MLB_MSG_GATEWAY_05, MLB_MSG_GETRIEBE_01, 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
|
||||
|
||||
|
||||
def _q5_params(bus0, bus1=(), car_fw=()):
|
||||
fingerprints = _mlb_fingerprint(0, bus0)
|
||||
fingerprints[1] = {msg: 8 for msg in bus1}
|
||||
fingerprints[2] = {msg: 8 for msg in MLB_ECAN_CAMERA}
|
||||
return CarInterface.get_params(CAR.AUDI_Q5_MK1, fingerprints, list(car_fw), alpha_long=False, is_release=False, docs=False)
|
||||
|
||||
|
||||
MLB_ECAN_GATEWAY_NO_GEARBOX = tuple(msg for msg in MLB_ECAN_GATEWAY if msg not in MLB_GEARBOX_MSGS)
|
||||
|
||||
|
||||
def test_mlb_gearbox_on_the_powertrain_bus_keeps_automatic():
|
||||
params = _q5_params(MLB_ECAN_GATEWAY_NO_GEARBOX, bus1=(MLB_MSG_GETRIEBE_01, MLB_MSG_GATEWAY_05))
|
||||
assert params.transmissionType == CarParams.TransmissionType.automatic
|
||||
|
||||
|
||||
def test_mlb_transmission_ecu_firmware_keeps_automatic():
|
||||
transmission = CarParams.CarFw.new_message(ecu=Ecu.transmission, address=0x7e1)
|
||||
params = _q5_params(MLB_ECAN_GATEWAY_NO_GEARBOX, bus1=(MLB_MSG_GATEWAY_05,), car_fw=(transmission,))
|
||||
assert params.transmissionType == CarParams.TransmissionType.automatic
|
||||
|
||||
|
||||
def test_mlb_reverse_switch_on_the_extended_can_alone_keeps_automatic():
|
||||
params = _q5_params(MLB_ECAN_GATEWAY_NO_GEARBOX + (MLB_MSG_GATEWAY_05,))
|
||||
assert params.transmissionType == CarParams.TransmissionType.automatic
|
||||
|
||||
|
||||
def test_mlb_manual_needs_no_gearbox_anywhere_and_a_reverse_switch_on_the_powertrain_bus():
|
||||
params = _q5_params(MLB_ECAN_GATEWAY_NO_GEARBOX, bus1=(MLB_MSG_GATEWAY_05,))
|
||||
assert params.transmissionType == CarParams.TransmissionType.manual
|
||||
|
||||
|
||||
def test_mlb_manual_gateway_car_reads_reverse_from_the_powertrain_bus():
|
||||
fingerprints = _mlb_fingerprint(0, MLB_ECAN_GATEWAY_NO_GEARBOX)
|
||||
fingerprints[1] = {MLB_MSG_GATEWAY_05: 8}
|
||||
fingerprints[2] = {msg: 8 for msg in MLB_ECAN_CAMERA}
|
||||
q5 = _build_mlb_car(CAR.AUDI_Q5_MK1, fingerprints)
|
||||
assert q5.CP.transmissionType == CarParams.TransmissionType.manual
|
||||
|
||||
packer = CANPacker("vw_mlb")
|
||||
frames = lambda reverse: [packer.make_can_msg("Gateway_05", 1, {"BCM1_Rueckfahrlicht_Schalter": int(reverse)})]
|
||||
assert _run(q5, lambda: frames(True)).gearShifter == structs.CarState.GearShifter.reverse
|
||||
assert _run(q5, lambda: frames(False)).gearShifter == structs.CarState.GearShifter.drive
|
||||
assert MLB_MSG_GATEWAY_05 not in q5.can_parsers[Bus.pt].addresses
|
||||
|
||||
|
||||
def test_mlb_automatic_never_subscribes_gateway_05():
|
||||
q5 = _q5_mk1_car()
|
||||
q5.update([(int(DT_CTRL * 1e9), [])])
|
||||
assert MLB_MSG_GATEWAY_05 not in q5.can_parsers[Bus.pt].addresses
|
||||
assert MLB_MSG_GATEWAY_05 not in q5.can_parsers[Bus.aux].addresses
|
||||
|
||||
|
||||
@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
|
||||
Reference in New Issue
Block a user