IQ.Pilot Prebuilt Release @ 27f668a
This commit is contained in:
372
artifacts/package_runtime/iqdbc/safety/tests/aol_common.py
Normal file
372
artifacts/package_runtime/iqdbc/safety/tests/aol_common.py
Normal file
@@ -0,0 +1,372 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from parameterized import parameterized
|
||||
import abc
|
||||
import unittest
|
||||
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
class AolSafetyTestBase(unittest.TestCase):
|
||||
safety: libsafety_py.LibSafety
|
||||
|
||||
@abc.abstractmethod
|
||||
def _lkas_button_msg(self, enabled):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def _acc_state_msg(self, enabled):
|
||||
raise NotImplementedError
|
||||
|
||||
def tearDown(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_aol_button_press(-1)
|
||||
self.safety.set_controls_allowed_lat(False)
|
||||
self.safety.set_controls_requested_lat(False)
|
||||
self.safety.set_acc_main_on(False)
|
||||
self.safety.set_aol_params(False, False, False)
|
||||
self.safety.set_heartbeat_engaged_aol(True)
|
||||
|
||||
def test_heartbeat_engaged_aol_check(self):
|
||||
"""Test AOL heartbeat engaged check behavior"""
|
||||
for boolean in (True, False):
|
||||
# If boolean is True, the heartbeat is engaged and should remain engaged, otherwise it should disengage.
|
||||
with self.subTest(heartbeat_engaged=boolean, should_remain_engaged=boolean):
|
||||
# Setup initial conditions
|
||||
self.safety.set_aol_params(True, False, False) # Enable AOL
|
||||
self.safety.set_controls_allowed_lat(True)
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Set heartbeat engaged state based on test case
|
||||
self.safety.set_heartbeat_engaged_aol(boolean)
|
||||
|
||||
# Call the heartbeat check function multiple times
|
||||
# We know from the implementation that it takes 3 mismatches to disengage
|
||||
for _ in range(4): # More than 3 times to ensure we pass the threshold
|
||||
self.safety.aol_heartbeat_engaged_check()
|
||||
|
||||
# Verify engagement state matches expectation
|
||||
self.assertEqual(self.safety.get_controls_allowed_lat(), boolean,
|
||||
f"Expected controls_allowed_lat to be [{boolean}] but got [{self.safety.get_controls_allowed_lat()}]")
|
||||
|
||||
def test_enable_control_allowed_with_aol_button(self):
|
||||
"""Toggle AOL with AOL button"""
|
||||
try:
|
||||
self._lkas_button_msg(False)
|
||||
except NotImplementedError as err:
|
||||
raise unittest.SkipTest("Skipping test because AOL button is not supported") from err
|
||||
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
self.assertEqual(enable_aol, self.safety.get_enable_aol())
|
||||
|
||||
self._rx(self._lkas_button_msg(True))
|
||||
self._rx(self._lkas_button_msg(False))
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_enable_control_allowed_with_manual_acc_main_on_state(self):
|
||||
try:
|
||||
self._acc_state_msg(False)
|
||||
except NotImplementedError as err:
|
||||
raise unittest.SkipTest("Skipping test because _acc_state_msg is not implemented for this car") from err
|
||||
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
self._rx(self._acc_state_msg(True))
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_enable_control_allowed_with_manual_aol_button_state(self):
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
for aol_button_press in (-1, 0, 1):
|
||||
with self.subTest("aol_button_press", button_state=aol_button_press):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
self.safety.set_aol_button_press(aol_button_press)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertEqual(enable_aol and aol_button_press == 1, self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_enable_control_allowed_from_acc_main_on(self):
|
||||
"""Test that lateral controls are allowed when ACC main is enabled and disabled when ACC main is disabled"""
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
for acc_main_on in (True, False):
|
||||
with self.subTest("initial_acc_main", initial_acc_main=acc_main_on):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
# Set initial state
|
||||
self.safety.set_acc_main_on(acc_main_on)
|
||||
self._rx(self._speed_msg(0))
|
||||
expected_lat = enable_aol and acc_main_on
|
||||
self.assertEqual(expected_lat, self.safety.get_controls_allowed_lat(),
|
||||
f"Expected lat: [{expected_lat}] when acc_main_on goes to [{acc_main_on}]")
|
||||
|
||||
# Test transition to opposite state
|
||||
self.safety.set_acc_main_on(not acc_main_on)
|
||||
self._rx(self._speed_msg(0))
|
||||
expected_lat = enable_aol and not acc_main_on
|
||||
self.assertEqual(expected_lat, self.safety.get_controls_allowed_lat(),
|
||||
f"Expected lat: [{expected_lat}] when acc_main_on goes from [{acc_main_on}] to [{not acc_main_on}]")
|
||||
|
||||
# Test transition back to initial state
|
||||
self.safety.set_acc_main_on(acc_main_on)
|
||||
self._rx(self._speed_msg(0))
|
||||
expected_lat = enable_aol and acc_main_on
|
||||
self.assertEqual(expected_lat, self.safety.get_controls_allowed_lat(),
|
||||
f"Expected lat: [{expected_lat}] when acc_main_on goes from [{not acc_main_on}] to [{acc_main_on}]")
|
||||
|
||||
def test_aol_with_acc_main_on(self):
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
self.safety.set_acc_main_on(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
self.safety.set_acc_main_on(False)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_pause_lateral_on_brake_setup(self):
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", enable_aol=enable_aol):
|
||||
for pause_lateral_on_brake in (True, False):
|
||||
with self.subTest("pause_lateral_on_brake", pause_lateral_on_brake=pause_lateral_on_brake):
|
||||
self.safety.set_aol_params(enable_aol, False, pause_lateral_on_brake)
|
||||
self.assertEqual(enable_aol and pause_lateral_on_brake, self.safety.get_pause_lateral_on_brake())
|
||||
|
||||
def test_pause_lateral_on_brake(self):
|
||||
self.safety.set_aol_params(True, False, True)
|
||||
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.safety.set_controls_requested_lat(True)
|
||||
self.safety.set_controls_allowed_lat(True)
|
||||
|
||||
self._rx(self._user_brake_msg(True))
|
||||
# Test we pause lateral
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
# Make sure we can re-gain lateral actuation
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_no_pause_lateral_on_brake(self):
|
||||
self.safety.set_aol_params(True, False, False)
|
||||
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.safety.set_controls_requested_lat(True)
|
||||
self.safety.set_controls_allowed_lat(True)
|
||||
|
||||
self._rx(self._user_brake_msg(True))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
@parameterized.expand(["aol_button", "acc_main_on"])
|
||||
def test_engage_with_brake_pressed(self, engage_method):
|
||||
if engage_method == "aol_button":
|
||||
try:
|
||||
self._lkas_button_msg(False)
|
||||
except NotImplementedError as err:
|
||||
raise unittest.SkipTest("Skipping test because AOL button is not supported") from err
|
||||
elif engage_method == "acc_main_on":
|
||||
try:
|
||||
self._acc_state_msg(False)
|
||||
except NotImplementedError as err:
|
||||
raise unittest.SkipTest("Skipping test because ACC main is not supported") from err
|
||||
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", enable_aol=enable_aol):
|
||||
for pause_lateral_on_brake in (True, False):
|
||||
with self.subTest("pause_lateral_on_brake", pause_lateral_on_brake=pause_lateral_on_brake):
|
||||
with self.subTest(engage_method):
|
||||
self.safety.set_aol_params(enable_aol, False, pause_lateral_on_brake)
|
||||
|
||||
# Brake press rising edge
|
||||
self._rx(self._user_brake_msg(True))
|
||||
|
||||
if engage_method == "aol_button":
|
||||
self._rx(self._lkas_button_msg(True))
|
||||
elif engage_method == "acc_main_on":
|
||||
self.safety.set_acc_main_on(True)
|
||||
self.assertTrue(self.safety.get_acc_main_on())
|
||||
else:
|
||||
raise ValueError(f"Invalid engage_method: {engage_method}")
|
||||
self._rx(self._speed_msg(0))
|
||||
|
||||
self.assertEqual(enable_aol and not pause_lateral_on_brake, self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Continuous braking after the first frame of brake press rising edge
|
||||
for _ in range(400):
|
||||
self.assertEqual(enable_aol and not pause_lateral_on_brake, self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_pause_lateral_on_brake_with_pressed_and_released(self):
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", enable_aol=enable_aol):
|
||||
for pause_lateral_on_brake in (True, False):
|
||||
with self.subTest("pause_lateral_on_brake", pause_lateral_on_brake=pause_lateral_on_brake):
|
||||
self.safety.set_aol_params(enable_aol, False, pause_lateral_on_brake)
|
||||
|
||||
# Set controls_allowed_lat rising edge
|
||||
self.safety.set_controls_requested_lat(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
# User brake press, validate controls_allowed_lat is false
|
||||
self._rx(self._user_brake_msg(True))
|
||||
self.assertEqual(enable_aol and not pause_lateral_on_brake, self.safety.get_controls_allowed_lat())
|
||||
|
||||
# User brake release, validate controls_allowed_lat is true
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_pause_lateral_on_brake_persistent_control_allowed_off(self):
|
||||
self.safety.set_aol_params(True, False, True)
|
||||
|
||||
self.safety.set_controls_requested_lat(True)
|
||||
|
||||
# Vehicle moving, validate controls_allowed_lat is true
|
||||
for _ in range(10):
|
||||
self._rx(self._speed_msg(10))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# User braked, vehicle slowed down in 10 frames, then stopped for 10 frames
|
||||
# Validate controls_allowed_lat is false
|
||||
self._rx(self._user_brake_msg(True))
|
||||
for _ in range(10):
|
||||
self._rx(self._speed_msg(5))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
for _ in range(10):
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_enable_lateral_control_with_controls_allowed_rising_edge(self):
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", enable_aol=enable_aol):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
self.safety.set_controls_allowed(False)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
def test_enable_control_allowed_with_aol_button_and_disable_with_main_cruise(self):
|
||||
"""Tests main cruise and AOL button state transitions.
|
||||
|
||||
Sequence:
|
||||
1. Main cruise off -> on
|
||||
2. AOL button engage
|
||||
3. Main cruise off
|
||||
|
||||
"""
|
||||
try:
|
||||
self._lkas_button_msg(False)
|
||||
except NotImplementedError as err:
|
||||
raise unittest.SkipTest("Skipping test because AOL button is not supported") from err
|
||||
|
||||
try:
|
||||
self._acc_state_msg(False)
|
||||
except NotImplementedError as err:
|
||||
raise unittest.SkipTest("Skipping test because _acc_state_msg is not implemented for this car") from err
|
||||
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", enable_aol=enable_aol):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
self._rx(self._lkas_button_msg(True))
|
||||
self._rx(self._lkas_button_msg(False))
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
self._rx(self._acc_state_msg(True))
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
self._rx(self._acc_state_msg(False))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_brake_disengage_with_control_request(self):
|
||||
"""Tests behavior when controls are requested while brake is engaged
|
||||
|
||||
Sequence:
|
||||
1. Enable AOL with pause lateral on brake
|
||||
2. Brake to pause lateral control
|
||||
3. Set control request while braking
|
||||
4. Release brake
|
||||
5. Verify controls become allowed
|
||||
"""
|
||||
self.safety.set_aol_params(True, False, True) # enable AOL with pause lateral on brake
|
||||
|
||||
# Initial state
|
||||
self.safety.set_controls_allowed_lat(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Brake press disengages lateral
|
||||
self._rx(self._user_brake_msg(True))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Request controls while braking
|
||||
self.safety.set_controls_requested_lat(True)
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Release brake - should enable since controls were requested
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_brake_disengage_with_acc_main_off(self):
|
||||
"""Tests behavior when ACC main is turned off while brake is engaged
|
||||
|
||||
Sequence:
|
||||
1. Enable AOL with pause lateral on brake
|
||||
2. Brake to pause lateral control
|
||||
3. Turn ACC main off while braking
|
||||
4. Release brake
|
||||
5. Verify controls remain disengaged
|
||||
"""
|
||||
self.safety.set_aol_params(True, False, True) # enable AOL with pause lateral on brake
|
||||
|
||||
# Initial state - enable with ACC main
|
||||
self.safety.set_acc_main_on(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Brake press disengages lateral
|
||||
self._rx(self._user_brake_msg(True))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Turn ACC main off while braking
|
||||
self.safety.set_acc_main_on(False)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Release brake - should remain disabled since ACC main is off
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_steering_disengage_with_control_request(self):
|
||||
self.safety.set_aol_params(True, False, False)
|
||||
|
||||
self.safety.set_controls_allowed_lat(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
self.safety.set_steering_disengage(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_disengage_on_brake(self):
|
||||
for disengage_on_brake in (True, False):
|
||||
self.safety.set_aol_params(True, disengage_on_brake, False)
|
||||
|
||||
self.safety.set_controls_allowed_lat(True)
|
||||
self._rx(self._speed_msg(0))
|
||||
self.assertTrue(self.safety.get_controls_allowed_lat())
|
||||
|
||||
self._rx(self._user_brake_msg(True))
|
||||
self.assertEqual(not disengage_on_brake, self.safety.get_controls_allowed_lat())
|
||||
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.assertEqual(not disengage_on_brake, self.safety.get_controls_allowed_lat())
|
||||
|
||||
# TODO-IQ: controls_allowed and controls_allowed_lat check for steering safety tests
|
||||
1185
artifacts/package_runtime/iqdbc/safety/tests/common.py
Normal file
1185
artifacts/package_runtime/iqdbc/safety/tests/common.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
from iqdbc.safety.tests.common import CANPackerSafety, CarSafetyTest
|
||||
|
||||
|
||||
class GasInterceptorSafetyTest(CarSafetyTest):
|
||||
|
||||
INTERCEPTOR_THRESHOLD = 0
|
||||
|
||||
cnt_gas_cmd = 0
|
||||
cnt_user_gas = 0
|
||||
|
||||
packer: CANPackerSafety
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "GasInterceptorSafetyTest" or cls.__name__.endswith("Base"):
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
|
||||
def _interceptor_gas_cmd(self, gas: int):
|
||||
values: dict[str, float | int] = {"PEDAL_COUNTER": self.__class__.cnt_gas_cmd & 0xF}
|
||||
if gas > 0:
|
||||
values["GAS_COMMAND"] = gas * 255.
|
||||
values["GAS_COMMAND2"] = gas * 255.
|
||||
self.__class__.cnt_gas_cmd += 1
|
||||
return self.packer.make_can_msg_safety("GAS_COMMAND", 0, values)
|
||||
|
||||
def _interceptor_user_gas(self, gas: int):
|
||||
values = {"INTERCEPTOR_GAS": gas, "INTERCEPTOR_GAS2": gas,
|
||||
"PEDAL_COUNTER": self.__class__.cnt_user_gas}
|
||||
self.__class__.cnt_user_gas += 1
|
||||
return self.packer.make_can_msg_safety("GAS_SENSOR", 0, values)
|
||||
|
||||
# Skip non-interceptor user gas tests
|
||||
def test_prev_gas(self):
|
||||
pass
|
||||
|
||||
def test_no_disengage_on_gas(self):
|
||||
pass
|
||||
|
||||
def test_prev_gas_interceptor(self):
|
||||
self._rx(self._interceptor_user_gas(0x0))
|
||||
self.assertFalse(self.safety.get_gas_interceptor_prev())
|
||||
self._rx(self._interceptor_user_gas(0x1000))
|
||||
self.assertTrue(self.safety.get_gas_interceptor_prev())
|
||||
self._rx(self._interceptor_user_gas(0x0))
|
||||
|
||||
def test_no_disengage_on_gas_interceptor(self):
|
||||
self.safety.set_controls_allowed(True)
|
||||
for g in range(0x1000):
|
||||
self._rx(self._interceptor_user_gas(g))
|
||||
# Test we allow lateral, but not longitudinal
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self.assertEqual(g <= self.INTERCEPTOR_THRESHOLD, self.safety.get_longitudinal_brake_allowed())
|
||||
self.assertTrue(self.safety.get_longitudinal_gas_allowed())
|
||||
# Make sure we can re-gain longitudinal actuation
|
||||
self._rx(self._interceptor_user_gas(0))
|
||||
self.assertTrue(self.safety.get_longitudinal_brake_allowed())
|
||||
self.assertTrue(self.safety.get_longitudinal_gas_allowed())
|
||||
|
||||
def test_allow_engage_with_gas_interceptor_pressed(self):
|
||||
self._rx(self._interceptor_user_gas(0x1000))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._rx(self._interceptor_user_gas(0x1000))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self._rx(self._interceptor_user_gas(0))
|
||||
|
||||
def test_gas_interceptor_safety_check(self):
|
||||
for gas in np.arange(0, 4000, 100):
|
||||
for controls_allowed in [True, False]:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
if controls_allowed:
|
||||
send = True
|
||||
else:
|
||||
send = gas == 0
|
||||
self.assertEqual(send, self._tx(self._interceptor_gas_cmd(gas)))
|
||||
@@ -0,0 +1,46 @@
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
def packet(addr: int, bus: int, length: int, updates: dict[int, int] | None = None):
|
||||
data = bytearray(length)
|
||||
for index, value in (updates or {}).items():
|
||||
data[index] = value
|
||||
return libsafety_py.make_CANPacket(addr, bus, data)
|
||||
|
||||
|
||||
def classic_steer(torque: int, request: bool = True):
|
||||
value = torque + 1024
|
||||
word = (value << 16) | (int(request) << 27)
|
||||
return packet(0x340, 0, 8, {i: (word >> (8 * i)) & 0xFF for i in range(4)})
|
||||
|
||||
|
||||
def canfd_steer(addr: int, length: int, torque: int, request: bool = True):
|
||||
value = torque + 1024
|
||||
return packet(addr, 0, length, {
|
||||
5: (value & 0x7F) << 1,
|
||||
6: ((value >> 7) & 0xF) | (int(request) << 4),
|
||||
})
|
||||
|
||||
|
||||
def classic_accel(accel: int, *, aeb_decel: int = 0, aeb_request: bool = False):
|
||||
value = accel + 1023
|
||||
return packet(0x421, 0, 8, {
|
||||
2: aeb_decel,
|
||||
3: value & 0xFF,
|
||||
4: ((value >> 8) & 0x7) | ((value & 0x7) << 5),
|
||||
5: (value >> 3) & 0xFF,
|
||||
6: int(aeb_request) << 6,
|
||||
})
|
||||
|
||||
|
||||
def canfd_accel(accel: int, *, acc_mode: int = 0, bus: int = 0):
|
||||
value = accel + 1023
|
||||
return packet(0x1A0, bus, 32, {
|
||||
8: (acc_mode & 0x7) << 4,
|
||||
16: value & 0xFF,
|
||||
17: ((value >> 8) & 0x7) | ((value & 0xF) << 4),
|
||||
18: (value >> 4) & 0xFF,
|
||||
})
|
||||
|
||||
|
||||
TESTER_PRESENT = bytes.fromhex("023e800000000000")
|
||||
@@ -0,0 +1,120 @@
|
||||
import os
|
||||
from cffi import FFI
|
||||
|
||||
from iqdbc.safety import LEN_TO_DLC
|
||||
|
||||
libsafety_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
libsafety_fn = os.path.join(libsafety_dir, "libsafety.so")
|
||||
|
||||
ffi = FFI()
|
||||
|
||||
ffi.cdef("""
|
||||
typedef struct {
|
||||
unsigned char fd : 1;
|
||||
unsigned char bus : 3;
|
||||
unsigned char data_len_code : 4;
|
||||
unsigned char rejected : 1;
|
||||
unsigned char returned : 1;
|
||||
unsigned char extended : 1;
|
||||
unsigned int addr : 29;
|
||||
unsigned char checksum;
|
||||
unsigned char data[64];
|
||||
} CANPacket_t;
|
||||
""", packed=True)
|
||||
class CANPacket:
|
||||
pass
|
||||
|
||||
ffi.cdef("""
|
||||
bool safety_rx_hook(CANPacket_t *msg);
|
||||
bool safety_tx_hook(CANPacket_t *msg);
|
||||
int safety_fwd_hook(int bus_num, int addr);
|
||||
int set_safety_hooks(uint16_t mode, uint16_t param);
|
||||
|
||||
void set_controls_allowed(bool c);
|
||||
bool get_controls_allowed(void);
|
||||
void set_heartbeat_engaged(bool c);
|
||||
bool get_longitudinal_allowed(void);
|
||||
bool get_longitudinal_gas_allowed(void);
|
||||
bool get_longitudinal_brake_allowed(void);
|
||||
void set_alternative_experience(int mode);
|
||||
int get_alternative_experience(void);
|
||||
void set_relay_malfunction(bool c);
|
||||
bool get_relay_malfunction(void);
|
||||
bool get_gas_pressed_prev(void);
|
||||
void set_gas_pressed_prev(bool);
|
||||
bool get_brake_pressed_prev(void);
|
||||
bool get_regen_braking_prev(void);
|
||||
bool get_steering_disengage_prev(void);
|
||||
bool get_acc_main_on(void);
|
||||
float get_vehicle_speed_min(void);
|
||||
float get_vehicle_speed_max(void);
|
||||
int get_current_safety_mode(void);
|
||||
int get_current_safety_param(void);
|
||||
|
||||
void set_torque_meas(int min, int max);
|
||||
int get_torque_meas_min(void);
|
||||
int get_torque_meas_max(void);
|
||||
void set_torque_driver(int min, int max);
|
||||
int get_torque_driver_min(void);
|
||||
int get_torque_driver_max(void);
|
||||
void set_desired_torque_last(int t);
|
||||
void set_rt_torque_last(int t);
|
||||
void set_desired_angle_last(int t);
|
||||
int get_desired_angle_last();
|
||||
void set_angle_meas(int min, int max);
|
||||
int get_angle_meas_min(void);
|
||||
int get_angle_meas_max(void);
|
||||
|
||||
bool get_cruise_engaged_prev(void);
|
||||
void set_cruise_engaged_prev(bool engaged);
|
||||
bool get_vehicle_moving(void);
|
||||
void set_timer(uint32_t t);
|
||||
|
||||
void safety_tick_current_safety_config();
|
||||
bool safety_config_valid();
|
||||
|
||||
void init_tests(void);
|
||||
|
||||
void set_honda_fwd_brake(bool c);
|
||||
bool get_honda_fwd_brake(void);
|
||||
void set_honda_alt_brake_msg(bool c);
|
||||
void set_honda_bosch_long(bool c);
|
||||
int get_honda_hw(void);
|
||||
|
||||
bool get_lat_active(void);
|
||||
bool get_controls_allowed_lat(void);
|
||||
bool get_controls_requested_lat(void);
|
||||
void set_current_safety_param_iq(uint16_t param);
|
||||
uint16_t get_current_safety_param_iq(void);
|
||||
bool get_enable_aol(void);
|
||||
bool get_disengage_lateral_on_brake(void);
|
||||
bool get_pause_lateral_on_brake(void);
|
||||
void set_aol_button_press(int aol_button_press);
|
||||
void set_controls_allowed_lat(bool c);
|
||||
void set_controls_requested_lat(bool c);
|
||||
bool get_aol_acc_main(void);
|
||||
void set_acc_main_on(bool c);
|
||||
int get_aol_button_press(void);
|
||||
void aol_set_current_disengage_reason(int reason);
|
||||
int aol_get_current_disengage_reason(void);
|
||||
int get_temp_debug(void);
|
||||
uint32_t get_acc_main_on_mismatches(void);
|
||||
void set_aol_params(bool enable_aol, bool disengage_lateral_on_brake, bool pause_lateral_on_brake);
|
||||
void set_heartbeat_engaged_aol(bool c);
|
||||
void aol_heartbeat_engaged_check(void);
|
||||
void set_steering_disengage(bool c);
|
||||
int get_gas_interceptor_prev(void);
|
||||
""")
|
||||
|
||||
class LibSafety:
|
||||
pass
|
||||
libsafety: LibSafety = ffi.dlopen(libsafety_fn)
|
||||
|
||||
def make_CANPacket(addr: int, bus: int, dat):
|
||||
ret = ffi.new('CANPacket_t *')
|
||||
ret[0].extended = 1 if addr >= 0x800 else 0
|
||||
ret[0].addr = addr
|
||||
ret[0].data_len_code = LEN_TO_DLC[len(dat)]
|
||||
ret[0].bus = bus
|
||||
ret[0].data = bytes(dat)
|
||||
return ret
|
||||
5
artifacts/package_runtime/iqdbc/safety/tests/misra/.gitignore
vendored
Normal file
5
artifacts/package_runtime/iqdbc/safety/tests/misra/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
*.pdf
|
||||
*.txt
|
||||
.output.log
|
||||
new_table
|
||||
cppcheck/
|
||||
456
artifacts/package_runtime/iqdbc/safety/tests/misra/checkers.txt
Normal file
456
artifacts/package_runtime/iqdbc/safety/tests/misra/checkers.txt
Normal file
@@ -0,0 +1,456 @@
|
||||
Cppcheck checkers list from test_misra.sh:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TEST variant options:
|
||||
--enable=all --enable=unusedFunction --addon=misra -DCANFD /iqdbc/safety/main.c
|
||||
|
||||
|
||||
Critical errors
|
||||
---------------
|
||||
No critical errors encountered.
|
||||
Note: There might still have been non-critical bailouts which might lead to false negatives.
|
||||
|
||||
|
||||
Open source checkers
|
||||
--------------------
|
||||
Yes Check64BitPortability::pointerassignment
|
||||
Yes CheckAssert::assertWithSideEffects
|
||||
Yes CheckAutoVariables::assignFunctionArg
|
||||
Yes CheckAutoVariables::autoVariables
|
||||
Yes CheckAutoVariables::checkVarLifetime
|
||||
No CheckBool::checkAssignBoolToFloat require:style,c++
|
||||
Yes CheckBool::checkAssignBoolToPointer
|
||||
No CheckBool::checkBitwiseOnBoolean require:style,inconclusive
|
||||
Yes CheckBool::checkComparisonOfBoolExpressionWithInt
|
||||
No CheckBool::checkComparisonOfBoolWithBool require:style,c++
|
||||
No CheckBool::checkComparisonOfBoolWithInt require:warning,c++
|
||||
No CheckBool::checkComparisonOfFuncReturningBool require:style,c++
|
||||
Yes CheckBool::checkIncrementBoolean
|
||||
Yes CheckBool::pointerArithBool
|
||||
Yes CheckBool::returnValueOfFunctionReturningBool
|
||||
Yes CheckBufferOverrun::analyseWholeProgram
|
||||
Yes CheckBufferOverrun::argumentSize
|
||||
Yes CheckBufferOverrun::arrayIndex
|
||||
Yes CheckBufferOverrun::arrayIndexThenCheck
|
||||
Yes CheckBufferOverrun::bufferOverflow
|
||||
Yes CheckBufferOverrun::negativeArraySize
|
||||
Yes CheckBufferOverrun::objectIndex
|
||||
Yes CheckBufferOverrun::pointerArithmetic
|
||||
No CheckBufferOverrun::stringNotZeroTerminated require:warning,inconclusive
|
||||
Yes CheckClass::analyseWholeProgram
|
||||
No CheckClass::checkConst require:style,inconclusive
|
||||
No CheckClass::checkConstructors require:style,warning
|
||||
No CheckClass::checkCopyConstructors require:warning
|
||||
No CheckClass::checkDuplInheritedMembers require:warning
|
||||
No CheckClass::checkExplicitConstructors require:style
|
||||
No CheckClass::checkMemset
|
||||
No CheckClass::checkMissingOverride require:style,c++03
|
||||
No CheckClass::checkReturnByReference require:performance
|
||||
No CheckClass::checkSelfInitialization
|
||||
No CheckClass::checkThisUseAfterFree require:warning
|
||||
No CheckClass::checkUnsafeClassRefMember require:warning,safeChecks
|
||||
No CheckClass::checkUselessOverride require:style
|
||||
No CheckClass::checkVirtualFunctionCallInConstructor require:warning
|
||||
No CheckClass::initializationListUsage require:performance
|
||||
No CheckClass::initializerListOrder require:style,inconclusive
|
||||
No CheckClass::operatorEqRetRefThis require:style
|
||||
No CheckClass::operatorEqToSelf require:warning
|
||||
No CheckClass::privateFunctions require:style
|
||||
No CheckClass::thisSubtraction require:warning
|
||||
No CheckClass::virtualDestructor
|
||||
Yes CheckCondition::alwaysTrueFalse
|
||||
Yes CheckCondition::assignIf
|
||||
Yes CheckCondition::checkAssignmentInCondition
|
||||
Yes CheckCondition::checkBadBitmaskCheck
|
||||
Yes CheckCondition::checkCompareValueOutOfTypeRange
|
||||
Yes CheckCondition::checkDuplicateConditionalAssign
|
||||
Yes CheckCondition::checkIncorrectLogicOperator
|
||||
Yes CheckCondition::checkInvalidTestForOverflow
|
||||
Yes CheckCondition::checkModuloAlwaysTrueFalse
|
||||
Yes CheckCondition::checkPointerAdditionResultNotNull
|
||||
Yes CheckCondition::clarifyCondition
|
||||
Yes CheckCondition::comparison
|
||||
Yes CheckCondition::duplicateCondition
|
||||
Yes CheckCondition::multiCondition
|
||||
Yes CheckCondition::multiCondition2
|
||||
No CheckExceptionSafety::checkCatchExceptionByValue require:style
|
||||
No CheckExceptionSafety::checkRethrowCopy require:style
|
||||
No CheckExceptionSafety::deallocThrow require:warning
|
||||
No CheckExceptionSafety::destructors require:warning
|
||||
No CheckExceptionSafety::nothrowThrows
|
||||
No CheckExceptionSafety::rethrowNoCurrentException
|
||||
No CheckExceptionSafety::unhandledExceptionSpecification require:style,inconclusive
|
||||
Yes CheckFunctions::checkIgnoredReturnValue
|
||||
Yes CheckFunctions::checkMathFunctions
|
||||
Yes CheckFunctions::checkMissingReturn
|
||||
Yes CheckFunctions::checkProhibitedFunctions
|
||||
Yes CheckFunctions::invalidFunctionUsage
|
||||
Yes CheckFunctions::memsetInvalid2ndParam
|
||||
Yes CheckFunctions::memsetZeroBytes
|
||||
No CheckFunctions::returnLocalStdMove require:performance,c++11
|
||||
Yes CheckFunctions::useStandardLibrary
|
||||
No CheckIO::checkCoutCerrMisusage require:c
|
||||
Yes CheckIO::checkFileUsage
|
||||
Yes CheckIO::checkWrongPrintfScanfArguments
|
||||
Yes CheckIO::invalidScanf
|
||||
Yes CheckLeakAutoVar::check
|
||||
No CheckMemoryLeakInClass::check
|
||||
Yes CheckMemoryLeakInFunction::checkReallocUsage
|
||||
Yes CheckMemoryLeakNoVar::check
|
||||
No CheckMemoryLeakNoVar::checkForUnsafeArgAlloc
|
||||
Yes CheckMemoryLeakStructMember::check
|
||||
Yes CheckNullPointer::analyseWholeProgram
|
||||
Yes CheckNullPointer::arithmetic
|
||||
Yes CheckNullPointer::nullConstantDereference
|
||||
Yes CheckNullPointer::nullPointer
|
||||
No CheckOther::checkAccessOfMovedVariable require:c++11,warning
|
||||
Yes CheckOther::checkCastIntToCharAndBack
|
||||
Yes CheckOther::checkCharVariable
|
||||
Yes CheckOther::checkComparePointers
|
||||
Yes CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse
|
||||
Yes CheckOther::checkConstPointer
|
||||
No CheckOther::checkConstVariable require:style,c++
|
||||
No CheckOther::checkDuplicateBranch require:style,inconclusive
|
||||
Yes CheckOther::checkDuplicateExpression
|
||||
Yes CheckOther::checkEvaluationOrder
|
||||
Yes CheckOther::checkFuncArgNamesDifferent
|
||||
No CheckOther::checkIncompleteArrayFill require:warning,portability,inconclusive
|
||||
Yes CheckOther::checkIncompleteStatement
|
||||
No CheckOther::checkInterlockedDecrement require:windows-platform
|
||||
Yes CheckOther::checkInvalidFree
|
||||
Yes CheckOther::checkKnownArgument
|
||||
Yes CheckOther::checkKnownPointerToBool
|
||||
No CheckOther::checkMisusedScopedObject require:style,c++
|
||||
Yes CheckOther::checkModuloOfOne
|
||||
Yes CheckOther::checkNanInArithmeticExpression
|
||||
Yes CheckOther::checkNegativeBitwiseShift
|
||||
Yes CheckOther::checkOverlappingWrite
|
||||
No CheckOther::checkPassByReference require:performance,c++
|
||||
Yes CheckOther::checkRedundantAssignment
|
||||
No CheckOther::checkRedundantCopy require:c++,performance,inconclusive
|
||||
Yes CheckOther::checkRedundantPointerOp
|
||||
Yes CheckOther::checkShadowVariables
|
||||
Yes CheckOther::checkSignOfUnsignedVariable
|
||||
No CheckOther::checkSuspiciousCaseInSwitch require:warning,inconclusive
|
||||
No CheckOther::checkSuspiciousSemicolon require:warning,inconclusive
|
||||
Yes CheckOther::checkUnreachableCode
|
||||
Yes CheckOther::checkUnusedLabel
|
||||
Yes CheckOther::checkVarFuncNullUB
|
||||
Yes CheckOther::checkVariableScope
|
||||
Yes CheckOther::checkZeroDivision
|
||||
Yes CheckOther::clarifyCalculation
|
||||
Yes CheckOther::clarifyStatement
|
||||
Yes CheckOther::invalidPointerCast
|
||||
Yes CheckOther::redundantBitwiseOperationInSwitch
|
||||
Yes CheckOther::suspiciousFloatingPointCast
|
||||
No CheckOther::warningOldStylePointerCast require:style,c++
|
||||
No CheckPostfixOperator::postfixOperator require:performance
|
||||
Yes CheckSizeof::checkSizeofForArrayParameter
|
||||
Yes CheckSizeof::checkSizeofForNumericParameter
|
||||
Yes CheckSizeof::checkSizeofForPointerSize
|
||||
Yes CheckSizeof::sizeofCalculation
|
||||
Yes CheckSizeof::sizeofFunction
|
||||
Yes CheckSizeof::sizeofVoid
|
||||
Yes CheckSizeof::sizeofsizeof
|
||||
No CheckSizeof::suspiciousSizeofCalculation require:warning,inconclusive
|
||||
No CheckStl::checkDereferenceInvalidIterator require:warning
|
||||
No CheckStl::checkDereferenceInvalidIterator2
|
||||
No CheckStl::checkFindInsert require:performance
|
||||
No CheckStl::checkMutexes require:warning
|
||||
No CheckStl::erase
|
||||
No CheckStl::eraseIteratorOutOfBounds
|
||||
No CheckStl::if_find require:warning,performance
|
||||
No CheckStl::invalidContainer
|
||||
No CheckStl::iterators
|
||||
No CheckStl::knownEmptyContainer require:style
|
||||
No CheckStl::misMatchingContainerIterator
|
||||
No CheckStl::misMatchingContainers
|
||||
No CheckStl::missingComparison require:warning
|
||||
No CheckStl::negativeIndex
|
||||
No CheckStl::outOfBounds
|
||||
No CheckStl::outOfBoundsIndexExpression
|
||||
No CheckStl::redundantCondition require:style
|
||||
No CheckStl::size require:performance,c++03
|
||||
No CheckStl::stlBoundaries
|
||||
No CheckStl::stlOutOfBounds
|
||||
No CheckStl::string_c_str
|
||||
No CheckStl::useStlAlgorithm require:style
|
||||
No CheckStl::uselessCalls require:performance,warning
|
||||
Yes CheckString::checkAlwaysTrueOrFalseStringCompare
|
||||
Yes CheckString::checkIncorrectStringCompare
|
||||
Yes CheckString::checkSuspiciousStringCompare
|
||||
Yes CheckString::overlappingStrcmp
|
||||
Yes CheckString::sprintfOverlappingData
|
||||
Yes CheckString::strPlusChar
|
||||
Yes CheckString::stringLiteralWrite
|
||||
Yes CheckType::checkFloatToIntegerOverflow
|
||||
Yes CheckType::checkIntegerOverflow
|
||||
Yes CheckType::checkLongCast
|
||||
Yes CheckType::checkSignConversion
|
||||
Yes CheckType::checkTooBigBitwiseShift
|
||||
Yes CheckUninitVar::analyseWholeProgram
|
||||
Yes CheckUninitVar::check
|
||||
Yes CheckUninitVar::valueFlowUninit
|
||||
Yes CheckUnusedFunctions::check
|
||||
Yes CheckUnusedVar::checkFunctionVariableUsage
|
||||
Yes CheckUnusedVar::checkStructMemberUsage
|
||||
Yes CheckVaarg::va_list_usage
|
||||
Yes CheckVaarg::va_start_argument
|
||||
|
||||
|
||||
Premium checkers
|
||||
----------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Autosar
|
||||
-------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Cert C
|
||||
------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Cert C++
|
||||
--------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Misra C 2012
|
||||
------------
|
||||
No Misra C 2012: Dir 1.1
|
||||
No Misra C 2012: Dir 2.1
|
||||
No Misra C 2012: Dir 3.1
|
||||
No Misra C 2012: Dir 4.1
|
||||
No Misra C 2012: Dir 4.2
|
||||
No Misra C 2012: Dir 4.3
|
||||
No Misra C 2012: Dir 4.4
|
||||
No Misra C 2012: Dir 4.5
|
||||
No Misra C 2012: Dir 4.6 amendment:3
|
||||
No Misra C 2012: Dir 4.7
|
||||
No Misra C 2012: Dir 4.8
|
||||
No Misra C 2012: Dir 4.9 amendment:3
|
||||
No Misra C 2012: Dir 4.10
|
||||
No Misra C 2012: Dir 4.11 amendment:3
|
||||
No Misra C 2012: Dir 4.12
|
||||
No Misra C 2012: Dir 4.13
|
||||
No Misra C 2012: Dir 4.14 amendment:2
|
||||
No Misra C 2012: Dir 4.15 amendment:3
|
||||
No Misra C 2012: Dir 5.1 amendment:4
|
||||
No Misra C 2012: Dir 5.2 amendment:4
|
||||
No Misra C 2012: Dir 5.3 amendment:4
|
||||
Yes Misra C 2012: 1.1
|
||||
Yes Misra C 2012: 1.2
|
||||
Yes Misra C 2012: 1.3
|
||||
Yes Misra C 2012: 1.4 amendment:2
|
||||
No Misra C 2012: 1.5 amendment:3 require:premium
|
||||
Yes Misra C 2012: 2.1
|
||||
Yes Misra C 2012: 2.2
|
||||
Yes Misra C 2012: 2.3
|
||||
Yes Misra C 2012: 2.4
|
||||
Yes Misra C 2012: 2.5
|
||||
Yes Misra C 2012: 2.6
|
||||
Yes Misra C 2012: 2.7
|
||||
Yes Misra C 2012: 2.8
|
||||
Yes Misra C 2012: 3.1
|
||||
Yes Misra C 2012: 3.2
|
||||
Yes Misra C 2012: 4.1
|
||||
Yes Misra C 2012: 4.2
|
||||
Yes Misra C 2012: 5.1
|
||||
Yes Misra C 2012: 5.2
|
||||
Yes Misra C 2012: 5.3
|
||||
Yes Misra C 2012: 5.4
|
||||
Yes Misra C 2012: 5.5
|
||||
Yes Misra C 2012: 5.6
|
||||
Yes Misra C 2012: 5.7
|
||||
Yes Misra C 2012: 5.8
|
||||
Yes Misra C 2012: 5.9
|
||||
Yes Misra C 2012: 6.1
|
||||
Yes Misra C 2012: 6.2
|
||||
No Misra C 2012: 6.3
|
||||
Yes Misra C 2012: 7.1
|
||||
Yes Misra C 2012: 7.2
|
||||
Yes Misra C 2012: 7.3
|
||||
Yes Misra C 2012: 7.4
|
||||
No Misra C 2012: 7.5
|
||||
No Misra C 2012: 7.6
|
||||
Yes Misra C 2012: 8.1
|
||||
Yes Misra C 2012: 8.2
|
||||
No Misra C 2012: 8.3
|
||||
Yes Misra C 2012: 8.4
|
||||
Yes Misra C 2012: 8.5
|
||||
Yes Misra C 2012: 8.6
|
||||
Yes Misra C 2012: 8.7
|
||||
Yes Misra C 2012: 8.8
|
||||
Yes Misra C 2012: 8.9
|
||||
Yes Misra C 2012: 8.10
|
||||
Yes Misra C 2012: 8.11
|
||||
Yes Misra C 2012: 8.12
|
||||
Yes Misra C 2012: 8.13
|
||||
Yes Misra C 2012: 8.14
|
||||
No Misra C 2012: 8.15
|
||||
No Misra C 2012: 8.16
|
||||
No Misra C 2012: 8.17
|
||||
Yes Misra C 2012: 9.1
|
||||
Yes Misra C 2012: 9.2
|
||||
Yes Misra C 2012: 9.3
|
||||
Yes Misra C 2012: 9.4
|
||||
Yes Misra C 2012: 9.5
|
||||
No Misra C 2012: 9.6
|
||||
No Misra C 2012: 9.7
|
||||
Yes Misra C 2012: 10.1
|
||||
Yes Misra C 2012: 10.2
|
||||
Yes Misra C 2012: 10.3
|
||||
Yes Misra C 2012: 10.4
|
||||
Yes Misra C 2012: 10.5
|
||||
Yes Misra C 2012: 10.6
|
||||
Yes Misra C 2012: 10.7
|
||||
Yes Misra C 2012: 10.8
|
||||
Yes Misra C 2012: 11.1
|
||||
Yes Misra C 2012: 11.2
|
||||
Yes Misra C 2012: 11.3
|
||||
Yes Misra C 2012: 11.4
|
||||
Yes Misra C 2012: 11.5
|
||||
Yes Misra C 2012: 11.6
|
||||
Yes Misra C 2012: 11.7
|
||||
Yes Misra C 2012: 11.8
|
||||
Yes Misra C 2012: 11.9
|
||||
No Misra C 2012: 11.10
|
||||
Yes Misra C 2012: 12.1
|
||||
Yes Misra C 2012: 12.2
|
||||
Yes Misra C 2012: 12.3
|
||||
Yes Misra C 2012: 12.4
|
||||
Yes Misra C 2012: 12.5 amendment:1
|
||||
No Misra C 2012: 12.6 amendment:4 require:premium
|
||||
Yes Misra C 2012: 13.1
|
||||
No Misra C 2012: 13.2
|
||||
Yes Misra C 2012: 13.3
|
||||
Yes Misra C 2012: 13.4
|
||||
Yes Misra C 2012: 13.5
|
||||
Yes Misra C 2012: 13.6
|
||||
Yes Misra C 2012: 14.1
|
||||
Yes Misra C 2012: 14.2
|
||||
Yes Misra C 2012: 14.3
|
||||
Yes Misra C 2012: 14.4
|
||||
Yes Misra C 2012: 15.1
|
||||
Yes Misra C 2012: 15.2
|
||||
Yes Misra C 2012: 15.3
|
||||
Yes Misra C 2012: 15.4
|
||||
Yes Misra C 2012: 15.5
|
||||
Yes Misra C 2012: 15.6
|
||||
Yes Misra C 2012: 15.7
|
||||
Yes Misra C 2012: 16.1
|
||||
Yes Misra C 2012: 16.2
|
||||
Yes Misra C 2012: 16.3
|
||||
Yes Misra C 2012: 16.4
|
||||
Yes Misra C 2012: 16.5
|
||||
Yes Misra C 2012: 16.6
|
||||
Yes Misra C 2012: 16.7
|
||||
Yes Misra C 2012: 17.1
|
||||
Yes Misra C 2012: 17.2
|
||||
Yes Misra C 2012: 17.3
|
||||
No Misra C 2012: 17.4
|
||||
Yes Misra C 2012: 17.5
|
||||
Yes Misra C 2012: 17.6
|
||||
Yes Misra C 2012: 17.7
|
||||
Yes Misra C 2012: 17.8
|
||||
No Misra C 2012: 17.9
|
||||
No Misra C 2012: 17.10
|
||||
No Misra C 2012: 17.11
|
||||
No Misra C 2012: 17.12
|
||||
No Misra C 2012: 17.13
|
||||
Yes Misra C 2012: 18.1
|
||||
Yes Misra C 2012: 18.2
|
||||
Yes Misra C 2012: 18.3
|
||||
Yes Misra C 2012: 18.4
|
||||
Yes Misra C 2012: 18.5
|
||||
Yes Misra C 2012: 18.6
|
||||
Yes Misra C 2012: 18.7
|
||||
Yes Misra C 2012: 18.8
|
||||
No Misra C 2012: 18.9
|
||||
No Misra C 2012: 18.10
|
||||
Yes Misra C 2012: 19.1
|
||||
Yes Misra C 2012: 19.2
|
||||
Yes Misra C 2012: 20.1
|
||||
Yes Misra C 2012: 20.2
|
||||
Yes Misra C 2012: 20.3
|
||||
Yes Misra C 2012: 20.4
|
||||
Yes Misra C 2012: 20.5
|
||||
Yes Misra C 2012: 20.6
|
||||
Yes Misra C 2012: 20.7
|
||||
Yes Misra C 2012: 20.8
|
||||
Yes Misra C 2012: 20.9
|
||||
Yes Misra C 2012: 20.10
|
||||
Yes Misra C 2012: 20.11
|
||||
Yes Misra C 2012: 20.12
|
||||
Yes Misra C 2012: 20.13
|
||||
Yes Misra C 2012: 20.14
|
||||
Yes Misra C 2012: 21.1
|
||||
Yes Misra C 2012: 21.2
|
||||
Yes Misra C 2012: 21.3
|
||||
Yes Misra C 2012: 21.4
|
||||
Yes Misra C 2012: 21.5
|
||||
Yes Misra C 2012: 21.6
|
||||
Yes Misra C 2012: 21.7
|
||||
Yes Misra C 2012: 21.8
|
||||
Yes Misra C 2012: 21.9
|
||||
Yes Misra C 2012: 21.10
|
||||
Yes Misra C 2012: 21.11
|
||||
Yes Misra C 2012: 21.12
|
||||
Yes Misra C 2012: 21.13 amendment:1
|
||||
Yes Misra C 2012: 21.14 amendment:1
|
||||
Yes Misra C 2012: 21.15 amendment:1
|
||||
Yes Misra C 2012: 21.16 amendment:1
|
||||
Yes Misra C 2012: 21.17 amendment:1
|
||||
Yes Misra C 2012: 21.18 amendment:1
|
||||
Yes Misra C 2012: 21.19 amendment:1
|
||||
Yes Misra C 2012: 21.20 amendment:1
|
||||
Yes Misra C 2012: 21.21 amendment:3
|
||||
No Misra C 2012: 21.22 amendment:3 require:premium
|
||||
No Misra C 2012: 21.23 amendment:3 require:premium
|
||||
No Misra C 2012: 21.24 amendment:3 require:premium
|
||||
No Misra C 2012: 21.25 amendment:4 require:premium
|
||||
No Misra C 2012: 21.26 amendment:4 require:premium
|
||||
Yes Misra C 2012: 22.1
|
||||
Yes Misra C 2012: 22.2
|
||||
Yes Misra C 2012: 22.3
|
||||
Yes Misra C 2012: 22.4
|
||||
Yes Misra C 2012: 22.5
|
||||
Yes Misra C 2012: 22.6
|
||||
Yes Misra C 2012: 22.7 amendment:1
|
||||
Yes Misra C 2012: 22.8 amendment:1
|
||||
Yes Misra C 2012: 22.9 amendment:1
|
||||
Yes Misra C 2012: 22.10 amendment:1
|
||||
No Misra C 2012: 22.11 amendment:4 require:premium
|
||||
No Misra C 2012: 22.12 amendment:4 require:premium
|
||||
No Misra C 2012: 22.13 amendment:4 require:premium
|
||||
No Misra C 2012: 22.14 amendment:4 require:premium
|
||||
No Misra C 2012: 22.15 amendment:4 require:premium
|
||||
No Misra C 2012: 22.16 amendment:4 require:premium
|
||||
No Misra C 2012: 22.17 amendment:4 require:premium
|
||||
No Misra C 2012: 22.18 amendment:4 require:premium
|
||||
No Misra C 2012: 22.19 amendment:4 require:premium
|
||||
No Misra C 2012: 22.20 amendment:4 require:premium
|
||||
No Misra C 2012: 23.1 amendment:3 require:premium
|
||||
No Misra C 2012: 23.2 amendment:3 require:premium
|
||||
No Misra C 2012: 23.3 amendment:3 require:premium
|
||||
No Misra C 2012: 23.4 amendment:3 require:premium
|
||||
No Misra C 2012: 23.5 amendment:3 require:premium
|
||||
No Misra C 2012: 23.6 amendment:3 require:premium
|
||||
No Misra C 2012: 23.7 amendment:3 require:premium
|
||||
No Misra C 2012: 23.8 amendment:3 require:premium
|
||||
|
||||
|
||||
Misra C++ 2008
|
||||
--------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
|
||||
|
||||
Misra C++ 2023
|
||||
--------------
|
||||
Not available, Cppcheck Premium is not used
|
||||
25
artifacts/package_runtime/iqdbc/safety/tests/misra/install.sh
Executable file
25
artifacts/package_runtime/iqdbc/safety/tests/misra/install.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
: "${CPPCHECK_DIR:=$DIR/cppcheck/}"
|
||||
|
||||
# skip if we're running in parallel with test_mutation.py
|
||||
if [ ! -z "$OPENDBC_ROOT" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -d "$CPPCHECK_DIR" ]; then
|
||||
git clone https://github.com/danmar/cppcheck.git $CPPCHECK_DIR
|
||||
fi
|
||||
|
||||
cd $CPPCHECK_DIR
|
||||
|
||||
VERS="2.19.1"
|
||||
if [ "$(git describe --tags --always)" != "$VERS" ]; then
|
||||
git fetch --all --tags --force
|
||||
git checkout $VERS
|
||||
fi
|
||||
|
||||
#make clean
|
||||
make MATCHCOMPILTER=yes CXXFLAGS="-O2" -j8
|
||||
@@ -0,0 +1,21 @@
|
||||
# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well
|
||||
misra-c2012-11.4
|
||||
# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well
|
||||
misra-c2012-11.5
|
||||
# Advisory: as stated in the Misra document, use of goto statements in accordance to 15.2 and 15.3 is ok
|
||||
misra-c2012-15.1
|
||||
# Advisory: union types can be used
|
||||
misra-c2012-19.2
|
||||
# Advisory: The # and ## preprocessor operators should not be used
|
||||
misra-c2012-20.10
|
||||
|
||||
# needed since not all of these suppressions are applicable to all builds
|
||||
unmatchedSuppression
|
||||
|
||||
# All interrupt handlers are defined, including ones we don't use
|
||||
unusedFunction:*/interrupt_handlers*.h
|
||||
|
||||
# all of the below suppressions are from new checks introduced after updating
|
||||
# cppcheck from 2.5 -> 2.13. they are listed here to separate the update from
|
||||
# fixing the violations and all are intended to be removed soon after
|
||||
misra-c2012-2.5 # unused macros. a few legit, rest aren't common between F4/H7 builds. should we do this in the unusedFunction pass?
|
||||
71
artifacts/package_runtime/iqdbc/safety/tests/misra/test_misra.sh
Executable file
71
artifacts/package_runtime/iqdbc/safety/tests/misra/test_misra.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
cd $DIR
|
||||
|
||||
source ../../../../setup.sh
|
||||
|
||||
GREEN="\e[1;32m"
|
||||
YELLOW="\e[1;33m"
|
||||
RED="\e[1;31m"
|
||||
NC='\033[0m'
|
||||
|
||||
: "${CPPCHECK_DIR:=$DIR/cppcheck/}"
|
||||
|
||||
# ensure checked in coverage table is up to date
|
||||
python3 $CPPCHECK_DIR/addons/misra.py -generate-table > coverage_table
|
||||
if ! git diff --quiet coverage_table; then
|
||||
echo -e "${YELLOW}MISRA coverage table doesn't match. Update and commit:${NC}"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
cd $BASEDIR
|
||||
|
||||
CHECKLIST=$(mktemp)
|
||||
echo "Cppcheck checkers list from test_misra.sh:" > $CHECKLIST
|
||||
|
||||
cppcheck() {
|
||||
# get all gcc defines: arm-none-eabi-gcc -dM -E - < /dev/null
|
||||
COMMON_DEFINES="-D__GNUC__=9"
|
||||
|
||||
# note that cppcheck build cache results in inconsistent results as of v2.13.0
|
||||
OUTPUT=$(mktemp)
|
||||
|
||||
echo -e "\n\n\n\n\nTEST variant options:" >> $CHECKLIST
|
||||
echo -e ""${@//$BASEDIR/}"\n\n" >> $CHECKLIST # (absolute path removed)
|
||||
|
||||
OPENDBC_ROOT=${OPENDBC_ROOT:-$BASEDIR}
|
||||
$CPPCHECK_DIR/cppcheck --inline-suppr -I $OPENDBC_ROOT \
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppressions-list=$DIR/suppressions.txt \
|
||||
--error-exitcode=2 --check-level=exhaustive --safety \
|
||||
--platform=arm32-wchar_t4 $COMMON_DEFINES --checkers-report=$CHECKLIST.tmp \
|
||||
--std=c11 "$@" 2>&1 | tee $OUTPUT
|
||||
|
||||
cat $CHECKLIST.tmp >> $CHECKLIST
|
||||
rm $CHECKLIST.tmp
|
||||
# cppcheck bug: some MISRA errors won't result in the error exit code,
|
||||
# so check the output (https://trac.cppcheck.net/ticket/12440#no1)
|
||||
if grep -e "misra violation" -e "error" -e "style: " $OUTPUT > /dev/null; then
|
||||
printf "${RED}** FAILED: MISRA violations found!${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
OPTS=" --enable=all --enable=unusedFunction --addon=misra"
|
||||
|
||||
printf "\n${GREEN}** Safety **${NC}\n"
|
||||
cppcheck $OPTS $BASEDIR/iqdbc/safety/tests/misra/main.c
|
||||
|
||||
printf "\n${GREEN}Success!${NC} took $SECONDS seconds\n"
|
||||
|
||||
# ensure list of checkers is up to date
|
||||
if [ -z "$OPENDBC_ROOT" ]; then
|
||||
cd $DIR
|
||||
if ! git diff --quiet $CHECKLIST; then
|
||||
echo -e "\n${YELLOW}WARNING: Cppcheck checkers.txt report has changed. Review and commit...${NC}"
|
||||
mv $CHECKLIST $DIR/checkers.txt
|
||||
exit 4
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import glob
|
||||
import pytest
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import random
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
ROOT = os.path.join(HERE, "../../../../")
|
||||
|
||||
IGNORED_PATHS = (
|
||||
'iqdbc/safety/main.c',
|
||||
'iqdbc/safety/tests/',
|
||||
)
|
||||
|
||||
mutations = [
|
||||
# no mutation, should pass
|
||||
(None, None, lambda s: s, False),
|
||||
]
|
||||
|
||||
patterns = [
|
||||
("misra-c2012-10.3", lambda s: s + "\nvoid test(float len) { for (float j = 0; j < len; j++) {;} }\n"),
|
||||
("misra-c2012-13.3", lambda s: s + "\nvoid test(int tmp) { int tmp2 = tmp++ + 2; if (tmp2) {;}}\n"),
|
||||
("misra-c2012-13.4", lambda s: s + "\nint test(int x, int y) { return (x=2) && (y=2); }\n"),
|
||||
("misra-c2012-13.5", lambda s: s + "\nvoid test(int tmp) { if (true && tmp++) {;} }\n"),
|
||||
("misra-c2012-13.6", lambda s: s + "\nvoid test(int tmp) { if (sizeof(tmp++)) {;} }\n"),
|
||||
("misra-c2012-14.2", lambda s: s + "\nvoid test(int cnt) { for (cnt=0;;cnt++) {;} }\n"),
|
||||
("misra-c2012-14.4", lambda s: s + "\nvoid test(int len) { if (len - 8) {;} }\n"),
|
||||
("misra-c2012-16.4", lambda s: s + "\nvoid test(int temp) {switch (temp) { case 1: ; }}\n"),
|
||||
("misra-c2012-20.4", lambda s: s + "\n#define auto 1\n"),
|
||||
("misra-c2012-20.5", lambda s: s + "\n#define TEST 1\n#undef TEST\n"),
|
||||
]
|
||||
|
||||
all_files = glob.glob('iqdbc/safety/**', root_dir=ROOT, recursive=True)
|
||||
files = [f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS)]
|
||||
assert len(files) > 20, files
|
||||
|
||||
for p in patterns:
|
||||
mutations.append((random.choice(files), *p, True))
|
||||
|
||||
mutations = random.sample(mutations, 2) # can remove this once cppcheck is faster
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fn, rule, transform, should_fail", mutations)
|
||||
def test_misra_mutation(fn, rule, transform, should_fail):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
shutil.copytree(ROOT, tmp, dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns('.venv', 'cppcheck', '.git', '*.ctu-info', '.hypothesis'))
|
||||
|
||||
# apply patch
|
||||
if fn is not None:
|
||||
with open(os.path.join(tmp, fn), 'r+') as f:
|
||||
content = f.read()
|
||||
f.seek(0)
|
||||
f.write(transform(content))
|
||||
|
||||
# run test
|
||||
r = subprocess.run(f"OPENDBC_ROOT={tmp} iqdbc/safety/tests/misra/test_misra.sh",
|
||||
stdout=subprocess.PIPE, cwd=ROOT, shell=True, encoding='utf8')
|
||||
print(r.stdout) # helpful for debugging failures
|
||||
failed = r.returncode != 0
|
||||
assert failed == should_fail
|
||||
if should_fail:
|
||||
assert rule in r.stdout, "MISRA test failed but not for the correct violation"
|
||||
20
artifacts/package_runtime/iqdbc/safety/tests/mutation.sh
Executable file
20
artifacts/package_runtime/iqdbc/safety/tests/mutation.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
|
||||
source $DIR/../../../setup.sh
|
||||
|
||||
GIT_REF="${GIT_REF:-origin/master}"
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
cat > $GIT_ROOT/mull.yml <<EOF
|
||||
mutators: [cxx_increment, cxx_decrement, cxx_comparison, cxx_boundary, cxx_bitwise_assignment, cxx_bitwise, cxx_arithmetic_assignment, cxx_arithmetic, cxx_remove_negation]
|
||||
timeout: 1000000
|
||||
gitDiffRef: $GIT_REF
|
||||
gitProjectRoot: $GIT_ROOT
|
||||
EOF
|
||||
|
||||
scons -j4 -D
|
||||
|
||||
mull-runner-18 --debug --ld-search-path /lib/x86_64-linux-gnu/ ./libsafety/libsafety.so -test-program=pytest -- -n8 --ignore-glob=misra/*
|
||||
@@ -0,0 +1,111 @@
|
||||
from iqdbc.car.ford.values import FordSafetyFlags
|
||||
from iqdbc.car.hyundai.values import HyundaiSafetyFlags
|
||||
from iqdbc.car.toyota.values import ToyotaSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
def to_signed(d, bits):
|
||||
ret = d
|
||||
if d >= (1 << (bits - 1)):
|
||||
ret = d - (1 << bits)
|
||||
return ret
|
||||
|
||||
|
||||
def is_steering_msg(mode, param, addr):
|
||||
ret = False
|
||||
if mode in (CarParams.SafetyModel.hondaNidec, CarParams.SafetyModel.hondaBosch):
|
||||
ret = (addr == 0xE4) or (addr == 0x194) or (addr == 0x33D) or (addr == 0x33DA) or (addr == 0x33DB)
|
||||
elif mode == CarParams.SafetyModel.toyota:
|
||||
ret = addr == (0x191 if param & ToyotaSafetyFlags.LTA else 0x2E4)
|
||||
elif mode == CarParams.SafetyModel.gm:
|
||||
ret = addr == 384
|
||||
elif mode in (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy):
|
||||
ret = addr == 832
|
||||
elif mode == CarParams.SafetyModel.hyundaiCanfd:
|
||||
ret = addr == (0x110 if param & HyundaiSafetyFlags.CANFD_LKA_STEERING_ALT else
|
||||
0x50 if param & HyundaiSafetyFlags.CANFD_LKA_STEERING else
|
||||
0x12A)
|
||||
elif mode == CarParams.SafetyModel.chrysler:
|
||||
ret = addr == 0x292
|
||||
elif mode == CarParams.SafetyModel.subaru:
|
||||
ret = addr == 0x122
|
||||
elif mode == CarParams.SafetyModel.ford:
|
||||
ret = addr == 0x3d6 if param & FordSafetyFlags.CANFD else addr == 0x3d3
|
||||
elif mode == CarParams.SafetyModel.nissan:
|
||||
ret = addr == 0x169
|
||||
elif mode == CarParams.SafetyModel.rivian:
|
||||
ret = addr == 0x120
|
||||
elif mode == CarParams.SafetyModel.tesla:
|
||||
ret = addr == 0x488
|
||||
return ret
|
||||
|
||||
|
||||
def get_steer_value(mode, param, msg):
|
||||
# TODO: use CANParser
|
||||
torque, angle = 0, 0
|
||||
if mode in (CarParams.SafetyModel.hondaNidec, CarParams.SafetyModel.hondaBosch):
|
||||
torque = (msg.data[0] << 8) | msg.data[1]
|
||||
torque = to_signed(torque, 16)
|
||||
elif mode == CarParams.SafetyModel.toyota:
|
||||
if param & ToyotaSafetyFlags.LTA:
|
||||
angle = (msg.data[1] << 8) | msg.data[2]
|
||||
angle = to_signed(angle, 16)
|
||||
else:
|
||||
torque = (msg.data[1] << 8) | (msg.data[2])
|
||||
torque = to_signed(torque, 16)
|
||||
elif mode == CarParams.SafetyModel.gm:
|
||||
torque = ((msg.data[0] & 0x7) << 8) | msg.data[1]
|
||||
torque = to_signed(torque, 11)
|
||||
elif mode in (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy):
|
||||
torque = (((msg.data[3] & 0x7) << 8) | msg.data[2]) - 1024
|
||||
elif mode == CarParams.SafetyModel.hyundaiCanfd:
|
||||
torque = ((msg.data[5] >> 1) | (msg.data[6] & 0xF) << 7) - 1024
|
||||
elif mode == CarParams.SafetyModel.chrysler:
|
||||
torque = (((msg.data[0] & 0x7) << 8) | msg.data[1]) - 1024
|
||||
elif mode == CarParams.SafetyModel.subaru:
|
||||
torque = ((msg.data[3] & 0x1F) << 8) | msg.data[2]
|
||||
torque = -to_signed(torque, 13)
|
||||
elif mode == CarParams.SafetyModel.ford:
|
||||
if param & FordSafetyFlags.CANFD:
|
||||
angle = ((msg.data[2] << 3) | (msg.data[3] >> 5)) - 1000
|
||||
else:
|
||||
angle = ((msg.data[0] << 3) | (msg.data[1] >> 5)) - 1000
|
||||
elif mode == CarParams.SafetyModel.nissan:
|
||||
angle = (msg.data[0] << 10) | (msg.data[1] << 2) | (msg.data[2] >> 6)
|
||||
angle = -angle + (1310 * 100)
|
||||
elif mode == CarParams.SafetyModel.rivian:
|
||||
torque = ((msg.data[2] << 3) | (msg.data[3] >> 5)) - 1024
|
||||
elif mode == CarParams.SafetyModel.tesla:
|
||||
angle = (((msg.data[0] & 0x7F) << 8) | (msg.data[1])) - 16384 # ceil(1638.35/0.1)
|
||||
return torque, angle
|
||||
|
||||
|
||||
def package_can_msg(msg):
|
||||
return libsafety_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
|
||||
|
||||
def init_segment(safety, msgs, mode, param):
|
||||
sendcan = (msg for msg in msgs if msg.which() == 'sendcan')
|
||||
steering_msgs = (can for msg in sendcan for can in msg.sendcan if is_steering_msg(mode, param, can.address))
|
||||
|
||||
msg = next(steering_msgs, None)
|
||||
if msg is None:
|
||||
print("no steering msgs found!")
|
||||
return
|
||||
|
||||
msg = package_can_msg(msg)
|
||||
torque, angle = get_steer_value(mode, param, msg)
|
||||
if torque != 0:
|
||||
safety.set_controls_allowed(1)
|
||||
safety.set_controls_allowed_lat(1)
|
||||
safety.set_desired_torque_last(torque)
|
||||
safety.set_rt_torque_last(torque)
|
||||
safety.set_torque_meas(torque, torque)
|
||||
safety.set_torque_driver(torque, torque)
|
||||
elif angle != 0:
|
||||
safety.set_controls_allowed(1)
|
||||
safety.set_controls_allowed_lat(1)
|
||||
safety.set_desired_angle_last(angle)
|
||||
safety.set_angle_meas(angle, angle)
|
||||
assert safety.safety_tx_hook(msg), "failed to initialize safety for segment"
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
from collections import Counter, defaultdict
|
||||
from tqdm import tqdm
|
||||
|
||||
from iqdbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
from iqdbc.car.carlog import carlog
|
||||
from iqdbc.safety.tests.safety_replay.helpers import package_can_msg, init_segment
|
||||
|
||||
# Define debug variables and their getter methods
|
||||
DEBUG_VARS = {
|
||||
'lat_active': lambda safety: safety.get_lat_active(),
|
||||
'controls_allowed': lambda safety: safety.get_controls_allowed(),
|
||||
'controls_requested_lat': lambda safety: safety.get_controls_requested_lat(),
|
||||
'controls_allowed_lat': lambda safety: safety.get_controls_allowed_lat(),
|
||||
'current_disengage_reason': lambda safety: safety.aol_get_current_disengage_reason(),
|
||||
'stock_acc_main': lambda safety: safety.get_acc_main_on(),
|
||||
'aol_acc_main': lambda safety: safety.get_aol_acc_main(),
|
||||
}
|
||||
|
||||
|
||||
# replay a drive to check for safety violations
|
||||
def replay_drive(msgs, safety_mode, param, alternative_experience, param_iq):
|
||||
safety = libsafety_py.libsafety
|
||||
msgs.sort(key=lambda m: m.logMonoTime)
|
||||
|
||||
safety.set_current_safety_param_iq(param_iq)
|
||||
err = safety.set_safety_hooks(safety_mode, param)
|
||||
assert err == 0, "invalid safety mode: %d" % safety_mode
|
||||
safety.set_alternative_experience(alternative_experience)
|
||||
|
||||
_enable_aol = bool(alternative_experience & ALTERNATIVE_EXPERIENCE.ENABLE_AOL)
|
||||
_disengage_lateral_on_brake = bool(alternative_experience & ALTERNATIVE_EXPERIENCE.AOL_DISENGAGE_LATERAL_ON_BRAKE)
|
||||
_pause_lateral_on_brake = bool(alternative_experience & ALTERNATIVE_EXPERIENCE.AOL_PAUSE_LATERAL_ON_BRAKE)
|
||||
safety.set_aol_params(_enable_aol, _disengage_lateral_on_brake, _pause_lateral_on_brake)
|
||||
print("alternative experience:")
|
||||
print(f" enable aol: {_enable_aol}")
|
||||
print(f" disengage lateral on brake: {_disengage_lateral_on_brake}")
|
||||
print(f" pause lateral on brake: {_pause_lateral_on_brake}")
|
||||
|
||||
init_segment(safety, msgs, safety_mode, param)
|
||||
|
||||
rx_tot, rx_invalid, tx_tot, tx_blocked, tx_controls, tx_controls_lat, tx_controls_blocked, tx_controls_lat_blocked, aol_mismatch = 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
safety_tick_rx_invalid = False
|
||||
blocked_addrs = Counter()
|
||||
invalid_addrs = set()
|
||||
|
||||
# Track last good state for each address
|
||||
last_good_states = defaultdict(lambda: {
|
||||
'timestamp': None,
|
||||
**{var: None for var in DEBUG_VARS}
|
||||
})
|
||||
|
||||
can_msgs = [m for m in msgs if m.which() in ('can', 'sendcan')]
|
||||
start_t = can_msgs[0].logMonoTime
|
||||
end_t = can_msgs[-1].logMonoTime
|
||||
for msg in tqdm(can_msgs):
|
||||
safety.set_timer((msg.logMonoTime // 1000) % 0xFFFFFFFF)
|
||||
|
||||
# skip start and end of route, warm up/down period
|
||||
if msg.logMonoTime - start_t > 1e9 and end_t - msg.logMonoTime > 1e9:
|
||||
safety.safety_tick_current_safety_config()
|
||||
safety_tick_rx_invalid |= not safety.safety_config_valid() or safety_tick_rx_invalid
|
||||
|
||||
if msg.which() == 'sendcan':
|
||||
for canmsg in msg.sendcan:
|
||||
_msg = package_can_msg(canmsg)
|
||||
sent = safety.safety_tx_hook(_msg)
|
||||
|
||||
# mismatched
|
||||
if safety.get_controls_allowed() and not safety.get_controls_allowed_lat():
|
||||
aol_mismatch += 1
|
||||
print(f"controls allowed but not controls allowed lat [{aol_mismatch}]")
|
||||
print(f"msg:{canmsg.address} ({hex(canmsg.address)})")
|
||||
for var, getter in DEBUG_VARS.items():
|
||||
print(f" {var}: {getter(safety)}")
|
||||
if not sent:
|
||||
tx_blocked += 1
|
||||
tx_controls_blocked += safety.get_controls_allowed()
|
||||
tx_controls_lat_blocked += safety.get_controls_allowed_lat()
|
||||
blocked_addrs[canmsg.address] += 1
|
||||
|
||||
carlog.debug("blocked bus %d msg %d at %f" % (canmsg.src, canmsg.address, (msg.logMonoTime - start_t) / 1e9))
|
||||
|
||||
if "DEBUG" in os.environ:
|
||||
last_good = last_good_states[canmsg.address]
|
||||
print(f"\nBlocked message at {(msg.logMonoTime - start_t) / 1e9:.3f}s:")
|
||||
print(f"Address: {hex(canmsg.address)} (bus {canmsg.src})")
|
||||
print("Current state:")
|
||||
for var, getter in DEBUG_VARS.items():
|
||||
print(f" {var}: {getter(safety)}")
|
||||
|
||||
if last_good['timestamp'] is not None:
|
||||
print(f"\nLast good state ({last_good['timestamp']:.3f}s):")
|
||||
for var in DEBUG_VARS:
|
||||
print(f" {var}: {last_good[var]}")
|
||||
else:
|
||||
print("\nNo previous good state found for this address")
|
||||
print("-" * 80)
|
||||
else: # Update last good state if message is allowed
|
||||
last_good_states[canmsg.address].update({
|
||||
'timestamp': (msg.logMonoTime - start_t) / 1e9,
|
||||
**{var: getter(safety) for var, getter in DEBUG_VARS.items()}
|
||||
})
|
||||
|
||||
tx_controls += safety.get_controls_allowed()
|
||||
tx_controls_lat += safety.get_controls_allowed_lat()
|
||||
tx_tot += 1
|
||||
elif msg.which() == 'can':
|
||||
# ignore msgs we sent
|
||||
for canmsg in filter(lambda m: m.src < 128, msg.can):
|
||||
safety.safety_fwd_hook(canmsg.src, canmsg.address)
|
||||
_msg = package_can_msg(canmsg)
|
||||
recv = safety.safety_rx_hook(_msg)
|
||||
if not recv:
|
||||
rx_invalid += 1
|
||||
invalid_addrs.add(canmsg.address)
|
||||
rx_tot += 1
|
||||
|
||||
print("\nRX")
|
||||
print("total rx msgs:", rx_tot)
|
||||
print("invalid rx msgs:", rx_invalid)
|
||||
print("safety tick rx invalid:", safety_tick_rx_invalid)
|
||||
print("invalid addrs:", invalid_addrs)
|
||||
print("\nTX")
|
||||
print("total openpilot msgs:", tx_tot)
|
||||
print("total msgs with controls allowed:", tx_controls)
|
||||
print("total msgs with controls_lat allowed:", tx_controls_lat)
|
||||
print("blocked msgs:", tx_blocked)
|
||||
print("blocked with controls allowed:", tx_controls_blocked)
|
||||
print("blocked with controls_lat allowed:", tx_controls_lat_blocked)
|
||||
print("blocked addrs:", blocked_addrs)
|
||||
print("aol enabled:", safety.get_enable_aol())
|
||||
|
||||
return tx_controls_blocked == 0 and tx_controls_lat_blocked == 0 and rx_invalid == 0 and not safety_tick_rx_invalid
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from iqpilot.tools.lib.logreader import LogReader
|
||||
|
||||
parser = argparse.ArgumentParser(description="Replay CAN messages from a route or segment through a safety mode",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("route_or_segment_name", nargs='+')
|
||||
parser.add_argument("--mode", type=int, help="Override the safety mode from the log")
|
||||
parser.add_argument("--param", type=int, help="Override the safety param from the log")
|
||||
parser.add_argument("--alternative-experience", type=int, help="Override the alternative experience from the log")
|
||||
parser.add_argument("--param-sp", type=int, help="Override the iqpilot safety param from the log")
|
||||
args = parser.parse_args()
|
||||
|
||||
lr = LogReader(args.route_or_segment_name[0])
|
||||
|
||||
if None in (args.mode, args.param, args.alternative_experience, args.param_iq):
|
||||
CP = lr.first('carParams')
|
||||
CP_IQ = lr.first('iqCarParams')
|
||||
if args.mode is None:
|
||||
args.mode = CP.safetyConfigs[-1].safetyModel.raw
|
||||
if args.param is None:
|
||||
args.param = CP.safetyConfigs[-1].safetyParam
|
||||
if args.alternative_experience is None:
|
||||
args.alternative_experience = CP.alternativeExperience
|
||||
if args.param_iq is None:
|
||||
_param_iq = CP_IQ.safetyParam if hasattr(CP_IQ, 'safetyParam') else 0
|
||||
args.param_iq = _param_iq
|
||||
|
||||
print(f"replaying {args.route_or_segment_name[0]} with safety mode {args.mode}, param {args.param}, alternative experience {args.alternative_experience}, " +
|
||||
f"param_iq {args.param_iq}")
|
||||
replay_drive(list(lr), args.mode, args.param, args.alternative_experience, args.param_iq)
|
||||
36
artifacts/package_runtime/iqdbc/safety/tests/test.sh
Executable file
36
artifacts/package_runtime/iqdbc/safety/tests/test.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
|
||||
source ../../../setup.sh
|
||||
|
||||
# reset coverage data and generate gcc note file
|
||||
rm -f ./libsafety/*.gcda
|
||||
scons -j$(nproc) -D
|
||||
|
||||
# run safety tests and generate coverage data
|
||||
pytest -n8 --ignore-glob=misra/*
|
||||
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
GCOV_EXEC="/opt/homebrew/opt/llvm@18/bin/llvm-cov gcov"
|
||||
else
|
||||
GCOV_EXEC="llvm-cov-18 gcov"
|
||||
fi
|
||||
|
||||
# generate and open report
|
||||
if [ "$1" == "--report" ]; then
|
||||
mkdir -p coverage-out
|
||||
gcovr -r ../ --gcov-executable "$GCOV_EXEC" --html-nested coverage-out/index.html
|
||||
sensible-browser coverage-out/index.html
|
||||
fi
|
||||
|
||||
# test coverage
|
||||
GCOV="gcovr -r $DIR/../ --gcov-executable \"$GCOV_EXEC\" -d --fail-under-line=100 -e ^libsafety"
|
||||
if ! GCOV_OUTPUT="$(eval $GCOV)"; then
|
||||
echo -e "FAILED:\n$GCOV_OUTPUT"
|
||||
exit 1
|
||||
else
|
||||
echo "SUCCESS: All checked files have 100% coverage!"
|
||||
fi
|
||||
60
artifacts/package_runtime/iqdbc/safety/tests/test_body.py
Normal file
60
artifacts/package_runtime/iqdbc/safety/tests/test_body.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.structs import CarParams
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
|
||||
class TestBody(common.SafetyTest):
|
||||
TX_MSGS = [[0x250, 0], [0x251, 0],
|
||||
[0x1, 0], [0x1, 1], [0x1, 2], [0x1, 3]]
|
||||
FWD_BUS_LOOKUP = {}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("comma_body")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.body, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _motors_data_msg(self, speed_l, speed_r):
|
||||
values = {"SPEED_L": speed_l, "SPEED_R": speed_r}
|
||||
return self.packer.make_can_msg_safety("MOTORS_DATA", 0, values)
|
||||
|
||||
def _torque_cmd_msg(self, torque_l, torque_r):
|
||||
values = {"TORQUE_L": torque_l, "TORQUE_R": torque_r}
|
||||
return self.packer.make_can_msg_safety("TORQUE_CMD", 0, values)
|
||||
|
||||
def _max_motor_rpm_cmd_msg(self, max_rpm_l, max_rpm_r):
|
||||
values = {"MAX_RPM_L": max_rpm_l, "MAX_RPM_R": max_rpm_r}
|
||||
return self.packer.make_can_msg_safety("MAX_MOTOR_RPM_CMD", 0, values)
|
||||
|
||||
def test_rx_hook(self):
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
# controls allowed when we get MOTORS_DATA message
|
||||
self.assertTrue(self._rx(self._torque_cmd_msg(0, 0)))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertTrue(self._rx(self._motors_data_msg(0, 0)))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
def test_tx_hook(self):
|
||||
self.assertFalse(self._tx(self._torque_cmd_msg(0, 0)))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._tx(self._torque_cmd_msg(0, 0)))
|
||||
|
||||
def test_can_flasher(self):
|
||||
# CAN flasher always allowed
|
||||
self.safety.set_controls_allowed(False)
|
||||
self.assertTrue(self._tx(common.make_msg(0, 0x1, 8)))
|
||||
|
||||
# 0xdeadfaceU allowed for CAN flashing mode
|
||||
self.assertTrue(self._tx(common.make_msg(0, 0x250, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a')))
|
||||
self.assertFalse(self._tx(common.make_msg(0, 0x250, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0'))) # not correct data/len
|
||||
self.assertFalse(self._tx(common.make_msg(0, 0x251, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a'))) # wrong address
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
159
artifacts/package_runtime/iqdbc/safety/tests/test_chrysler.py
Normal file
159
artifacts/package_runtime/iqdbc/safety/tests/test_chrysler.py
Normal file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.chrysler.values import ChryslerSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
|
||||
class TestChryslerSafety(common.CarSafetyTest, common.MotorTorqueSteeringSafetyTest):
|
||||
TX_MSGS = [[0x23B, 0], [0x292, 0], [0x2A6, 0], [0x2D9, 0]]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x292, 0x2A6, 0x2D9)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x292, 0x2A6, 0x2D9]}
|
||||
|
||||
MAX_RATE_UP = 3
|
||||
MAX_RATE_DOWN = 3
|
||||
MAX_TORQUE_LOOKUP = [0], [261]
|
||||
MAX_RT_DELTA = 112
|
||||
MAX_TORQUE_ERROR = 80
|
||||
|
||||
LKAS_ACTIVE_VALUE = 1
|
||||
|
||||
DAS_BUS = 0
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("chrysler_pacifica_2017_hybrid_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.chrysler, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _button_msg(self, cancel=False, resume=False, accel=False, decel=False):
|
||||
values = {"ACC_Cancel": cancel, "ACC_Resume": resume, "ACC_Accel": accel, "ACC_Decel": decel}
|
||||
return self.packer.make_can_msg_safety("CRUISE_BUTTONS", self.DAS_BUS, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"ACC_ACTIVE": enable}
|
||||
return self.packer.make_can_msg_safety("DAS_3", self.DAS_BUS, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"SPEED_LEFT": speed, "SPEED_RIGHT": speed}
|
||||
return self.packer.make_can_msg_safety("SPEED_1", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"Accelerator_Position": gas}
|
||||
return self.packer.make_can_msg_safety("ECM_5", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"Brake_Pedal_State": 1 if brake else 0}
|
||||
return self.packer.make_can_msg_safety("ESP_1", 0, values)
|
||||
|
||||
def _torque_meas_msg(self, torque):
|
||||
values = {"EPS_TORQUE_MOTOR": torque}
|
||||
return self.packer.make_can_msg_safety("EPS_2", 0, values)
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"STEERING_TORQUE": torque, "LKAS_CONTROL_BIT": self.LKAS_ACTIVE_VALUE if steer_req else 0}
|
||||
return self.packer.make_can_msg_safety("LKAS_COMMAND", 0, values)
|
||||
|
||||
def test_buttons(self):
|
||||
for controls_allowed in (True, False):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
|
||||
# resume/accel/decel only while controls allowed
|
||||
self.assertEqual(controls_allowed, self._tx(self._button_msg(resume=True)))
|
||||
self.assertEqual(controls_allowed, self._tx(self._button_msg(accel=True)))
|
||||
self.assertEqual(controls_allowed, self._tx(self._button_msg(decel=True)))
|
||||
|
||||
# can always cancel
|
||||
self.assertTrue(self._tx(self._button_msg(cancel=True)))
|
||||
|
||||
# invalid: more than one button pressed
|
||||
combos = [
|
||||
# 2 buttons
|
||||
{"cancel": True, "resume": True},
|
||||
{"cancel": True, "accel": True},
|
||||
{"cancel": True, "decel": True},
|
||||
{"resume": True, "accel": True},
|
||||
{"resume": True, "decel": True},
|
||||
{"accel": True, "decel": True},
|
||||
|
||||
# 3 buttons
|
||||
{"cancel": True, "resume": True, "accel": True},
|
||||
{"cancel": True, "resume": True, "decel": True},
|
||||
{"cancel": True, "accel": True, "decel": True},
|
||||
{"resume": True, "accel": True, "decel": True},
|
||||
|
||||
# all 4 buttons
|
||||
{"cancel": True, "resume": True, "accel": True, "decel": True},
|
||||
]
|
||||
|
||||
for combo in combos:
|
||||
with self.subTest(combo=combo):
|
||||
self.assertFalse(self._tx(self._button_msg(**combo)))
|
||||
|
||||
def _lkas_button_msg(self, enabled):
|
||||
values = {"TOGGLE_LKAS": enabled}
|
||||
return self.packer.make_can_msg_safety("TRACTION_BUTTON", 0, values)
|
||||
|
||||
|
||||
class TestChryslerRamDTSafety(TestChryslerSafety):
|
||||
TX_MSGS = [[0xB1, 2], [0xA6, 0], [0xFA, 0]]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xA6, 0xFA)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0xA6, 0xFA]}
|
||||
|
||||
MAX_RATE_UP = 6
|
||||
MAX_RATE_DOWN = 6
|
||||
MAX_TORQUE_LOOKUP = [0], [350]
|
||||
|
||||
DAS_BUS = 2
|
||||
|
||||
LKAS_ACTIVE_VALUE = 2
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("chrysler_ram_dt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.chrysler, ChryslerSafetyFlags.RAM_DT)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"Vehicle_Speed": speed}
|
||||
return self.packer.make_can_msg_safety("ESP_8", 0, values)
|
||||
|
||||
def _lkas_button_msg(self, enabled):
|
||||
values = {"LKAS_Button": enabled}
|
||||
return self.packer.make_can_msg_safety("Center_Stack_2", 0, values)
|
||||
|
||||
|
||||
class TestChryslerRamHDSafety(TestChryslerSafety):
|
||||
TX_MSGS = [[0x275, 0], [0x276, 0], [0x23A, 2]]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x276, 0x275)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x275, 0x276]}
|
||||
|
||||
MAX_TORQUE_LOOKUP = [0], [361]
|
||||
MAX_RATE_UP = 14
|
||||
MAX_RATE_DOWN = 14
|
||||
MAX_RT_DELTA = 182
|
||||
|
||||
DAS_BUS = 2
|
||||
|
||||
LKAS_ACTIVE_VALUE = 2
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("chrysler_ram_hd_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.chrysler, ChryslerSafetyFlags.RAM_HD)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"Vehicle_Speed": speed}
|
||||
return self.packer.make_can_msg_safety("ESP_8", 0, values)
|
||||
|
||||
def _lkas_button_msg(self, enabled):
|
||||
values = {"LKAS_Button": enabled}
|
||||
return self.packer.make_can_msg_safety("Center_Stack_2", 0, values)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
class TestDefaultRxHookBase(common.SafetyTest):
|
||||
FWD_BUS_LOOKUP = {}
|
||||
|
||||
def test_rx_hook(self):
|
||||
# default rx hook allows all msgs
|
||||
for bus in range(4):
|
||||
for addr in self.SCANNED_ADDRS:
|
||||
self.assertTrue(self._rx(common.make_msg(bus, addr, 8)), f"failed RX {addr=}")
|
||||
|
||||
|
||||
class TestNoOutput(TestDefaultRxHookBase):
|
||||
TX_MSGS = []
|
||||
|
||||
def setUp(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.noOutput, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestSilent(TestNoOutput):
|
||||
"""SILENT uses same hooks as NOOUTPUT"""
|
||||
|
||||
def setUp(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.silent, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestAllOutput(TestDefaultRxHookBase):
|
||||
# Allow all messages
|
||||
TX_MSGS = [[addr, bus] for addr in common.SafetyTest.SCANNED_ADDRS
|
||||
for bus in range(4)]
|
||||
|
||||
def setUp(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.allOutput, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_spam_can_buses(self):
|
||||
# asserts tx allowed for all scanned addrs
|
||||
for bus in range(4):
|
||||
for addr in self.SCANNED_ADDRS:
|
||||
should_tx = [addr, bus] in self.TX_MSGS
|
||||
self.assertEqual(should_tx, self._tx(common.make_msg(bus, addr, 8)), f"allowed TX {addr=} {bus=}")
|
||||
|
||||
def test_default_controls_not_allowed(self):
|
||||
# controls always allowed
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
def test_tx_hook_on_wrong_safety_mode(self):
|
||||
# No point, since we allow all messages
|
||||
pass
|
||||
|
||||
|
||||
class TestAllOutputPassthrough(TestAllOutput):
|
||||
FWD_BLACKLISTED_ADDRS = {}
|
||||
FWD_BUS_LOOKUP = {0: 2, 2: 0}
|
||||
|
||||
def setUp(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.allOutput, 1)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
52
artifacts/package_runtime/iqdbc/safety/tests/test_elm327.py
Normal file
52
artifacts/package_runtime/iqdbc/safety/tests/test_elm327.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety import DLC_TO_LEN
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
from iqdbc.safety.tests.test_defaults import TestDefaultRxHookBase
|
||||
|
||||
GM_CAMERA_DIAG_ADDR = 0x24B
|
||||
|
||||
|
||||
class TestElm327(TestDefaultRxHookBase):
|
||||
TX_MSGS = [[addr, bus] for addr in [GM_CAMERA_DIAG_ADDR, *range(0x600, 0x800),
|
||||
*range(0x18DA00F1, 0x18DB00F1, 0x100), # 29-bit UDS physical addressing
|
||||
*[0x18DB33F1], # 29-bit UDS functional address
|
||||
] for bus in range(4)]
|
||||
FWD_BUS_LOOKUP = {}
|
||||
|
||||
def setUp(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.elm327, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_tx_hook(self):
|
||||
# ensure we can transmit arbitrary data on allowed addresses
|
||||
for bus in range(4):
|
||||
for addr in self.SCANNED_ADDRS:
|
||||
should_tx = [addr, bus] in self.TX_MSGS
|
||||
self.assertEqual(should_tx, self._tx(common.make_msg(bus, addr, 8)))
|
||||
|
||||
# ELM only allows 8 byte UDS/KWP messages under ISO 15765-4
|
||||
for msg_len in DLC_TO_LEN:
|
||||
should_tx = msg_len == 8
|
||||
self.assertEqual(should_tx, self._tx(common.make_msg(0, 0x700, msg_len)))
|
||||
|
||||
# TODO: perform this check for all addresses
|
||||
# 4 to 15 are reserved ISO-TP frame types (https://en.wikipedia.org/wiki/ISO_15765-2)
|
||||
for byte in range(0xff):
|
||||
should_tx = (byte >> 4) <= 3
|
||||
self.assertEqual(should_tx, self._tx(common.make_msg(0, GM_CAMERA_DIAG_ADDR, dat=bytes([byte] * 8))))
|
||||
|
||||
# test GM camera diagnostic address with malformed length
|
||||
self.assertEqual(False, self._tx(common.make_msg(0, GM_CAMERA_DIAG_ADDR, dat=bytes([0x00] * 7))))
|
||||
|
||||
def test_tx_hook_on_wrong_safety_mode(self):
|
||||
# No point, since we allow many diagnostic addresses
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
511
artifacts/package_runtime/iqdbc/safety/tests/test_ford.py
Normal file
511
artifacts/package_runtime/iqdbc/safety/tests/test_ford.py
Normal file
@@ -0,0 +1,511 @@
|
||||
#!/usr/bin/env python3
|
||||
import numpy as np
|
||||
import random
|
||||
import unittest
|
||||
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.car.ford.values import FordSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
MSG_EngBrakeData = 0x165 # RX from PCM, for driver brake pedal and cruise state
|
||||
MSG_EngVehicleSpThrottle = 0x204 # RX from PCM, for driver throttle input
|
||||
MSG_BrakeSysFeatures = 0x415 # RX from ABS, for vehicle speed
|
||||
MSG_EngVehicleSpThrottle2 = 0x202 # RX from PCM, for second vehicle speed
|
||||
MSG_Yaw_Data_FD1 = 0x91 # RX from RCM, for yaw rate
|
||||
MSG_Steering_Data_FD1 = 0x083 # TX by OP, various driver switches and LKAS/CC buttons
|
||||
MSG_ACCDATA = 0x186 # TX by OP, ACC controls
|
||||
MSG_ACCDATA_3 = 0x18A # TX by OP, ACC/TJA user interface
|
||||
MSG_Lane_Assist_Data1 = 0x3CA # TX by OP, Lane Keep Assist
|
||||
MSG_LateralMotionControl = 0x3D3 # TX by OP, Lateral Control message
|
||||
MSG_LateralMotionControl2 = 0x3D6 # TX by OP, alternate Lateral Control message
|
||||
MSG_IPMA_Data = 0x3D8 # TX by OP, IPMA and LKAS user interface
|
||||
|
||||
SAFETY_ISO_LATERAL_ACCEL = 5.0
|
||||
EARTH_G = 9.81
|
||||
AVERAGE_ROAD_ROLL = 0.06
|
||||
MAX_LATERAL_ACCEL = SAFETY_ISO_LATERAL_ACCEL - (EARTH_G * AVERAGE_ROAD_ROLL)
|
||||
|
||||
|
||||
def checksum(msg):
|
||||
addr, dat, bus = msg
|
||||
ret = bytearray(dat)
|
||||
|
||||
if addr == MSG_Yaw_Data_FD1:
|
||||
chksum = dat[0] + dat[1] # VehRol_W_Actl
|
||||
chksum += dat[2] + dat[3] # VehYaw_W_Actl
|
||||
chksum += dat[5] # VehRollYaw_No_Cnt
|
||||
chksum += dat[6] >> 6 # VehRolWActl_D_Qf
|
||||
chksum += (dat[6] >> 4) & 0x3 # VehYawWActl_D_Qf
|
||||
chksum = 0xff - (chksum & 0xff)
|
||||
ret[4] = chksum
|
||||
|
||||
elif addr == MSG_BrakeSysFeatures:
|
||||
chksum = dat[0] + dat[1] # Veh_V_ActlBrk
|
||||
chksum += (dat[2] >> 2) & 0xf # VehVActlBrk_No_Cnt
|
||||
chksum += dat[2] >> 6 # VehVActlBrk_D_Qf
|
||||
chksum = 0xff - (chksum & 0xff)
|
||||
ret[3] = chksum
|
||||
|
||||
elif addr == MSG_EngVehicleSpThrottle2:
|
||||
chksum = (dat[2] >> 3) & 0xf # VehVActlEng_No_Cnt
|
||||
chksum += (dat[4] >> 5) & 0x3 # VehVActlEng_D_Qf
|
||||
chksum += dat[6] + dat[7] # Veh_V_ActlEng
|
||||
chksum = 0xff - (chksum & 0xff)
|
||||
ret[1] = chksum
|
||||
|
||||
return addr, ret, bus
|
||||
|
||||
|
||||
class Buttons:
|
||||
CANCEL = 0
|
||||
RESUME = 1
|
||||
TJA_TOGGLE = 2
|
||||
|
||||
|
||||
# Ford safety has four different configurations tested here:
|
||||
# * CAN with openpilot longitudinal
|
||||
# * CAN FD with stock longitudinal
|
||||
# * CAN FD with openpilot longitudinal
|
||||
|
||||
class TestFordSafetyBase(common.CarSafetyTest, common.SecondSpeedSafetyTest):
|
||||
STANDSTILL_THRESHOLD = 1
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
|
||||
MSG_LateralMotionControl2, MSG_IPMA_Data)}
|
||||
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
|
||||
MSG_LateralMotionControl2, MSG_IPMA_Data]}
|
||||
|
||||
STEER_MESSAGE = 0
|
||||
|
||||
# Curvature control limits
|
||||
DEG_TO_CAN = 50000 # 1 / (2e-5) rad to can
|
||||
MAX_CURVATURE = 0.02
|
||||
MAX_CURVATURE_ERROR = 0.002
|
||||
CURVATURE_ERROR_MIN_SPEED = 10.0 # m/s
|
||||
|
||||
ANGLE_RATE_BP = [5., 25., 25.]
|
||||
ANGLE_RATE_UP = [0.00045, 0.0001, 0.0001] # windup limit
|
||||
ANGLE_RATE_DOWN = [0.00045, 0.00015, 0.00015] # unwind limit
|
||||
|
||||
cnt_speed = 0
|
||||
cnt_speed_2 = 0
|
||||
cnt_yaw_rate = 0
|
||||
|
||||
packer: CANPackerSafety
|
||||
safety: libsafety_py.LibSafety
|
||||
|
||||
def get_canfd_curvature_limits(self, speed):
|
||||
# Round it in accordance with the safety
|
||||
curvature_accel_limit = MAX_LATERAL_ACCEL / (max(speed, 1) ** 2)
|
||||
curvature_accel_limit_lower = int(curvature_accel_limit * self.DEG_TO_CAN - 1) / self.DEG_TO_CAN
|
||||
curvature_accel_limit_upper = int(curvature_accel_limit * self.DEG_TO_CAN + 1) / self.DEG_TO_CAN
|
||||
return curvature_accel_limit_lower, curvature_accel_limit_upper
|
||||
|
||||
def _set_prev_desired_angle(self, t):
|
||||
t = round(t * self.DEG_TO_CAN)
|
||||
self.safety.set_desired_angle_last(t)
|
||||
|
||||
def _reset_curvature_measurement(self, curvature, speed):
|
||||
for _ in range(6):
|
||||
self._rx(self._speed_msg(speed))
|
||||
self._rx(self._yaw_rate_msg(curvature, speed))
|
||||
|
||||
# Driver brake pedal
|
||||
def _user_brake_msg(self, brake: bool):
|
||||
# brake pedal and cruise state share same message, so we have to send
|
||||
# the other signal too
|
||||
enable = self.safety.get_controls_allowed()
|
||||
values = {
|
||||
"BpedDrvAppl_D_Actl": 2 if brake else 1,
|
||||
"CcStat_D_Actl": 5 if enable else 0,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("EngBrakeData", 0, values)
|
||||
|
||||
# ABS vehicle speed
|
||||
def _speed_msg(self, speed: float, quality_flag=True):
|
||||
values = {"Veh_V_ActlBrk": speed * 3.6, "VehVActlBrk_D_Qf": 3 if quality_flag else 0, "VehVActlBrk_No_Cnt": self.cnt_speed % 16}
|
||||
self.__class__.cnt_speed += 1
|
||||
return self.packer.make_can_msg_safety("BrakeSysFeatures", 0, values, fix_checksum=checksum)
|
||||
|
||||
# PCM vehicle speed
|
||||
def _speed_msg_2(self, speed: float, quality_flag=True):
|
||||
# Ford relies on speed for driver curvature limiting, so it checks two sources
|
||||
values = {"Veh_V_ActlEng": speed * 3.6, "VehVActlEng_D_Qf": 3 if quality_flag else 0, "VehVActlEng_No_Cnt": self.cnt_speed_2 % 16}
|
||||
self.__class__.cnt_speed_2 += 1
|
||||
return self.packer.make_can_msg_safety("EngVehicleSpThrottle2", 0, values, fix_checksum=checksum)
|
||||
|
||||
# Standstill state
|
||||
def _vehicle_moving_msg(self, speed: float):
|
||||
values = {"VehStop_D_Stat": 1 if speed <= self.STANDSTILL_THRESHOLD else random.choice((0, 2, 3))}
|
||||
return self.packer.make_can_msg_safety("DesiredTorqBrk", 0, values)
|
||||
|
||||
# Current curvature
|
||||
def _yaw_rate_msg(self, curvature: float, speed: float, quality_flag=True):
|
||||
values = {"VehYaw_W_Actl": curvature * speed, "VehYawWActl_D_Qf": 3 if quality_flag else 0,
|
||||
"VehRollYaw_No_Cnt": self.cnt_yaw_rate % 256}
|
||||
self.__class__.cnt_yaw_rate += 1
|
||||
return self.packer.make_can_msg_safety("Yaw_Data_FD1", 0, values, fix_checksum=checksum)
|
||||
|
||||
# Drive throttle input
|
||||
def _user_gas_msg(self, gas: float):
|
||||
values = {"ApedPos_Pc_ActlArb": gas}
|
||||
return self.packer.make_can_msg_safety("EngVehicleSpThrottle", 0, values)
|
||||
|
||||
# Cruise status
|
||||
def _pcm_status_msg(self, enable: bool):
|
||||
# brake pedal and cruise state share same message, so we have to send
|
||||
# the other signal too
|
||||
brake = self.safety.get_brake_pressed_prev()
|
||||
values = {
|
||||
"BpedDrvAppl_D_Actl": 2 if brake else 1,
|
||||
"CcStat_D_Actl": 5 if enable else 0,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("EngBrakeData", 0, values)
|
||||
|
||||
# LKAS command
|
||||
def _lkas_command_msg(self, action: int):
|
||||
values = {
|
||||
"LkaActvStats_D2_Req": action,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("Lane_Assist_Data1", 0, values)
|
||||
|
||||
# LCA command
|
||||
def _lat_ctl_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float):
|
||||
if self.STEER_MESSAGE == MSG_LateralMotionControl:
|
||||
values = {
|
||||
"LatCtl_D_Rq": 1 if enabled else 0,
|
||||
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
|
||||
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
|
||||
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
|
||||
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
|
||||
}
|
||||
return self.packer.make_can_msg_safety("LateralMotionControl", 0, values)
|
||||
elif self.STEER_MESSAGE == MSG_LateralMotionControl2:
|
||||
values = {
|
||||
"LatCtl_D2_Rq": 1 if enabled else 0,
|
||||
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
|
||||
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
|
||||
"LatCtlCrv_NoRate2_Actl": curvature_rate, # Curvature rate [-0.001024|0.001023] 1/meter^2
|
||||
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
|
||||
}
|
||||
return self.packer.make_can_msg_safety("LateralMotionControl2", 0, values)
|
||||
|
||||
# Cruise control buttons
|
||||
def _acc_button_msg(self, button: int, bus: int):
|
||||
values = {
|
||||
"CcAslButtnCnclPress": 1 if button == Buttons.CANCEL else 0,
|
||||
"CcAsllButtnResPress": 1 if button == Buttons.RESUME else 0,
|
||||
"TjaButtnOnOffPress": 1 if button == Buttons.TJA_TOGGLE else 0,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("Steering_Data_FD1", bus, values)
|
||||
|
||||
def test_rx_hook(self):
|
||||
# checksum, counter, and quality flag checks
|
||||
for quality_flag in [True, False]:
|
||||
for msg_type in ["speed", "speed_2", "yaw"]:
|
||||
self.safety.set_controls_allowed(True)
|
||||
# send multiple times to verify counter checks
|
||||
for _ in range(10):
|
||||
if msg_type == "speed":
|
||||
msg = self._speed_msg(0, quality_flag=quality_flag)
|
||||
elif msg_type == "speed_2":
|
||||
msg = self._speed_msg_2(0, quality_flag=quality_flag)
|
||||
elif msg_type == "yaw":
|
||||
msg = self._yaw_rate_msg(0, 0, quality_flag=quality_flag)
|
||||
|
||||
self.assertEqual(quality_flag, self._rx(msg))
|
||||
self.assertEqual(quality_flag, self.safety.get_controls_allowed())
|
||||
|
||||
# Mess with checksum to make it fail, checksum is not checked for 2nd speed
|
||||
msg[0].data[3] = 0 # Speed checksum & half of yaw signal
|
||||
should_rx = msg_type == "speed_2" and quality_flag
|
||||
self.assertEqual(should_rx, self._rx(msg))
|
||||
self.assertEqual(should_rx, self.safety.get_controls_allowed())
|
||||
|
||||
def test_angle_measurements(self):
|
||||
"""Tests rx hook correctly parses the curvature measurement from the vehicle speed and yaw rate"""
|
||||
for speed in np.arange(0.5, 40, 0.5):
|
||||
for curvature in np.arange(0, self.MAX_CURVATURE * 2, 2e-3):
|
||||
self._rx(self._speed_msg(speed))
|
||||
for c in (curvature, -curvature, 0, 0, 0, 0):
|
||||
self._rx(self._yaw_rate_msg(c, speed))
|
||||
|
||||
self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN))
|
||||
self.assertEqual(self.safety.get_angle_meas_max(), round(curvature * self.DEG_TO_CAN))
|
||||
|
||||
self._rx(self._yaw_rate_msg(0, speed))
|
||||
self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN))
|
||||
self.assertEqual(self.safety.get_angle_meas_max(), 0)
|
||||
|
||||
self._rx(self._yaw_rate_msg(0, speed))
|
||||
self.assertEqual(self.safety.get_angle_meas_min(), 0)
|
||||
self.assertEqual(self.safety.get_angle_meas_max(), 0)
|
||||
|
||||
def test_max_lateral_acceleration(self):
|
||||
# Ford CAN FD can achieve a higher max lateral acceleration than CAN so we limit curvature based on speed
|
||||
for speed in np.arange(0, 40, 0.5):
|
||||
# Clip so we test curvature limiting at low speed due to low max curvature
|
||||
_, curvature_accel_limit_upper = self.get_canfd_curvature_limits(speed)
|
||||
curvature_accel_limit_upper = np.clip(curvature_accel_limit_upper, -self.MAX_CURVATURE, self.MAX_CURVATURE)
|
||||
|
||||
for sign in (-1, 1):
|
||||
# Test above and below the lateral by 20%, max is clipped since
|
||||
# max curvature at low speed is higher than the signal max
|
||||
for curvature in np.arange(curvature_accel_limit_upper * 0.8, min(curvature_accel_limit_upper * 1.2, self.MAX_CURVATURE), 1 / self.DEG_TO_CAN):
|
||||
curvature = sign * round(curvature * self.DEG_TO_CAN) / self.DEG_TO_CAN # fix np rounding errors
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._set_prev_desired_angle(curvature)
|
||||
self._reset_curvature_measurement(curvature, speed)
|
||||
|
||||
should_tx = abs(curvature) <= curvature_accel_limit_upper
|
||||
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, curvature, 0)))
|
||||
|
||||
def test_steer_allowed(self):
|
||||
path_offsets = np.arange(-5.12, 5.11, 2.5).round()
|
||||
path_angles = np.arange(-0.5, 0.5235, 0.25).round(1)
|
||||
curvature_rates = np.arange(-0.001024, 0.00102375, 0.001).round(3)
|
||||
curvatures = np.arange(-0.02, 0.02094, 0.01).round(2)
|
||||
|
||||
for speed in (self.CURVATURE_ERROR_MIN_SPEED - 1,
|
||||
self.CURVATURE_ERROR_MIN_SPEED + 1):
|
||||
_, curvature_accel_limit_upper = self.get_canfd_curvature_limits(speed)
|
||||
for controls_allowed in (True, False):
|
||||
for steer_control_enabled in (True, False):
|
||||
for path_offset in path_offsets:
|
||||
for path_angle in path_angles:
|
||||
for curvature_rate in curvature_rates:
|
||||
for curvature in curvatures:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self._set_prev_desired_angle(curvature)
|
||||
self._reset_curvature_measurement(curvature, speed)
|
||||
|
||||
should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0
|
||||
# when request bit is 0, only allow curvature of 0 since the signal range
|
||||
# is not large enough to enforce it tracking measured
|
||||
should_tx = should_tx and (controls_allowed if steer_control_enabled else curvature == 0)
|
||||
|
||||
# Only CAN FD has the max lateral acceleration limit
|
||||
if self.STEER_MESSAGE == MSG_LateralMotionControl2:
|
||||
should_tx = should_tx and abs(curvature) <= curvature_accel_limit_upper
|
||||
|
||||
with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled,
|
||||
path_offset=float(path_offset), path_angle=float(path_angle), curvature_rate=float(curvature_rate),
|
||||
curvature=float(curvature)):
|
||||
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate)))
|
||||
|
||||
def test_curvature_rate_limits(self):
|
||||
"""
|
||||
When the curvature error is exceeded, commanded curvature must start moving towards meas respecting rate limits.
|
||||
Since safety allows higher rate limits to avoid false positives, we need to allow a lower rate to move towards meas.
|
||||
"""
|
||||
self.safety.set_controls_allowed(True)
|
||||
# safety fudges the speed (1 m/s) and rate limits (1 CAN unit) to avoid false positives
|
||||
small_curvature = 1 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary
|
||||
|
||||
for speed in np.arange(0, 40, 0.5):
|
||||
curvature_accel_limit_lower, curvature_accel_limit_upper = self.get_canfd_curvature_limits(speed)
|
||||
limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED
|
||||
# ensure our limits match the safety's rounded limits
|
||||
max_delta_up = int(np.interp(speed - 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) * self.DEG_TO_CAN + 1) / self.DEG_TO_CAN
|
||||
max_delta_up_lower = int(np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) * self.DEG_TO_CAN - 1) / self.DEG_TO_CAN
|
||||
|
||||
max_delta_down = int(np.interp(speed - 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) * self.DEG_TO_CAN + 1 + 1e-3) / self.DEG_TO_CAN
|
||||
max_delta_down_lower = int(np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) * self.DEG_TO_CAN - 1 + 1e-3) / self.DEG_TO_CAN
|
||||
|
||||
up_cases = (self.MAX_CURVATURE_ERROR * 2, [
|
||||
(not limit_command, 0, 0),
|
||||
(not limit_command, 0, max_delta_up_lower - small_curvature),
|
||||
(True, 1e-9, max_delta_down), # TODO: safety should not allow down limits at 0
|
||||
(not limit_command, 1e-9, max_delta_up_lower), # TODO: safety should not allow down limits at 0
|
||||
(True, 0, max_delta_up_lower),
|
||||
(True, 0, max_delta_up),
|
||||
(False, 0, max_delta_up + small_curvature),
|
||||
# stay at boundary limit
|
||||
(True, self.MAX_CURVATURE_ERROR - small_curvature, self.MAX_CURVATURE_ERROR - small_curvature),
|
||||
# 1 unit below boundary limit
|
||||
(not limit_command, self.MAX_CURVATURE_ERROR - small_curvature * 2, self.MAX_CURVATURE_ERROR - small_curvature * 2),
|
||||
# shouldn't allow command to move outside the boundary limit if last was inside
|
||||
(not limit_command, self.MAX_CURVATURE_ERROR - small_curvature, self.MAX_CURVATURE_ERROR - small_curvature * 2),
|
||||
])
|
||||
|
||||
down_cases = (self.MAX_CURVATURE - self.MAX_CURVATURE_ERROR * 2, [
|
||||
(not limit_command, self.MAX_CURVATURE, self.MAX_CURVATURE),
|
||||
(not limit_command, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down_lower + small_curvature),
|
||||
(True, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down_lower),
|
||||
(True, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down),
|
||||
(False, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down - small_curvature),
|
||||
])
|
||||
|
||||
for sign in (-1, 1):
|
||||
for angle_meas, cases in (up_cases, down_cases):
|
||||
self._reset_curvature_measurement(sign * angle_meas, speed)
|
||||
for should_tx, initial_curvature, desired_curvature in cases:
|
||||
|
||||
# Only CAN FD has the max lateral acceleration limit
|
||||
if self.STEER_MESSAGE == MSG_LateralMotionControl2:
|
||||
if should_tx:
|
||||
# can not send if the curvature is above the max lateral acceleration
|
||||
should_tx = should_tx and abs(desired_curvature) <= curvature_accel_limit_upper
|
||||
else:
|
||||
# if desired curvature violates driver curvature error, it can only send if
|
||||
# the curvature is being limited by max lateral acceleration
|
||||
should_tx = should_tx or curvature_accel_limit_lower <= abs(desired_curvature) <= curvature_accel_limit_upper
|
||||
|
||||
# small curvature ensures we're using up limits. at 0, safety allows down limits to allow to account for rounding errors
|
||||
curvature_offset = small_curvature if initial_curvature == 0 else 0
|
||||
self._set_prev_desired_angle(sign * (curvature_offset + initial_curvature))
|
||||
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (curvature_offset + desired_curvature), 0)))
|
||||
|
||||
def test_prevent_lkas_action(self):
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertFalse(self._tx(self._lkas_command_msg(1)))
|
||||
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertFalse(self._tx(self._lkas_command_msg(1)))
|
||||
|
||||
def test_acc_buttons(self):
|
||||
for allowed in (0, 1):
|
||||
self.safety.set_controls_allowed(allowed)
|
||||
for enabled in (True, False):
|
||||
self._rx(self._pcm_status_msg(enabled))
|
||||
self.assertTrue(self._tx(self._acc_button_msg(Buttons.TJA_TOGGLE, 2)))
|
||||
|
||||
for allowed in (0, 1):
|
||||
self.safety.set_controls_allowed(allowed)
|
||||
for bus in (0, 2):
|
||||
self.assertEqual(allowed, self._tx(self._acc_button_msg(Buttons.RESUME, bus)))
|
||||
|
||||
for enabled in (True, False):
|
||||
self._rx(self._pcm_status_msg(enabled))
|
||||
for bus in (0, 2):
|
||||
self.assertEqual(enabled, self._tx(self._acc_button_msg(Buttons.CANCEL, bus)))
|
||||
|
||||
def test_enable_control_allowed_from_acc_main_on(self):
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
for main_button_msg_valid in (True, False):
|
||||
with self.subTest("main_button_msg_valid", state_valid=main_button_msg_valid):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
self._rx(self._pcm_status_msg(main_button_msg_valid))
|
||||
self.assertEqual(enable_aol and main_button_msg_valid, self.safety.get_controls_allowed_lat())
|
||||
|
||||
|
||||
class TestFordCANFDStockSafety(TestFordSafetyBase):
|
||||
STEER_MESSAGE = MSG_LateralMotionControl2
|
||||
|
||||
TX_MSGS = [
|
||||
[MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
|
||||
[MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0],
|
||||
]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
|
||||
MSG_IPMA_Data)}
|
||||
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
|
||||
MSG_IPMA_Data]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("ford_lincoln_base_pt")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.ford, FordSafetyFlags.CANFD)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestFordLongitudinalSafetyBase(TestFordSafetyBase):
|
||||
MAX_ACCEL = 2.0 # accel is used for brakes, but openpilot can set positive values
|
||||
MIN_ACCEL = -3.5
|
||||
INACTIVE_ACCEL = 0.0
|
||||
|
||||
MAX_GAS = 2.0
|
||||
MIN_GAS = -0.5
|
||||
INACTIVE_GAS = -5.0
|
||||
|
||||
# ACC command
|
||||
def _acc_command_msg(self, gas: float, brake: float, brake_actuation: bool, cmbb_deny: bool = False):
|
||||
values = {
|
||||
"AccPrpl_A_Rq": gas, # [-5|5.23] m/s^2
|
||||
"AccPrpl_A_Pred": gas, # [-5|5.23] m/s^2
|
||||
"AccBrkTot_A_Rq": brake, # [-20|11.9449] m/s^2
|
||||
"AccBrkPrchg_B_Rq": 1 if brake_actuation else 0, # Pre-charge brake request: 0=No, 1=Yes
|
||||
"AccBrkDecel_B_Rq": 1 if brake_actuation else 0, # Deceleration request: 0=Inactive, 1=Active
|
||||
"CmbbDeny_B_Actl": 1 if cmbb_deny else 0, # [0|1] deny AEB actuation
|
||||
}
|
||||
return self.packer.make_can_msg_safety("ACCDATA", 0, values)
|
||||
|
||||
def test_stock_aeb(self):
|
||||
# Test that CmbbDeny_B_Actl is never 1, it prevents the ABS module from actuating AEB requests from ACCDATA_2
|
||||
for controls_allowed in (True, False):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
for cmbb_deny in (True, False):
|
||||
should_tx = not cmbb_deny
|
||||
self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, self.INACTIVE_ACCEL, controls_allowed, cmbb_deny)))
|
||||
should_tx = controls_allowed and not cmbb_deny
|
||||
self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.MAX_GAS, self.MAX_ACCEL, controls_allowed, cmbb_deny)))
|
||||
|
||||
def test_gas_safety_check(self):
|
||||
for controls_allowed in (True, False):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
for gas in np.concatenate((np.arange(self.MIN_GAS - 2, self.MAX_GAS + 2, 0.05), [self.INACTIVE_GAS])):
|
||||
gas = round(gas, 2) # floats might not hit exact boundary conditions without rounding
|
||||
should_tx = (controls_allowed and self.MIN_GAS <= gas <= self.MAX_GAS) or gas == self.INACTIVE_GAS
|
||||
self.assertEqual(should_tx, self._tx(self._acc_command_msg(gas, self.INACTIVE_ACCEL, controls_allowed)))
|
||||
|
||||
def test_brake_safety_check(self):
|
||||
for controls_allowed in (True, False):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
for brake_actuation in (True, False):
|
||||
for brake in np.arange(self.MIN_ACCEL - 2, self.MAX_ACCEL + 2, 0.05):
|
||||
brake = round(brake, 2) # floats might not hit exact boundary conditions without rounding
|
||||
should_tx = (controls_allowed and self.MIN_ACCEL <= brake <= self.MAX_ACCEL) or brake == self.INACTIVE_ACCEL
|
||||
should_tx = should_tx and (controls_allowed or not brake_actuation)
|
||||
self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake, brake_actuation)))
|
||||
|
||||
|
||||
class TestFordLongitudinalSafety(TestFordLongitudinalSafetyBase):
|
||||
STEER_MESSAGE = MSG_LateralMotionControl
|
||||
|
||||
TX_MSGS = [
|
||||
[MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
|
||||
[MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0],
|
||||
]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
|
||||
MSG_IPMA_Data)}
|
||||
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
|
||||
MSG_IPMA_Data]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("ford_lincoln_base_pt")
|
||||
self.safety = libsafety_py.libsafety
|
||||
# Make sure we enforce long safety even without long flag for CAN
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.ford, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_max_lateral_acceleration(self):
|
||||
# CAN does not limit curvature from lateral acceleration
|
||||
pass
|
||||
|
||||
|
||||
class TestFordCANFDLongitudinalSafety(TestFordLongitudinalSafetyBase):
|
||||
STEER_MESSAGE = MSG_LateralMotionControl2
|
||||
|
||||
TX_MSGS = [
|
||||
[MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
|
||||
[MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0],
|
||||
]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
|
||||
MSG_IPMA_Data)}
|
||||
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
|
||||
MSG_IPMA_Data]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("ford_lincoln_base_pt")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.ford, FordSafetyFlags.LONG_CONTROL | FordSafetyFlags.CANFD)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
254
artifacts/package_runtime/iqdbc/safety/tests/test_gm.py
Normal file
254
artifacts/package_runtime/iqdbc/safety/tests/test_gm.py
Normal file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.gm.values import GMSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
# GM_PARAM_IQ_NON_ACC in safety/modes/gm.h (IQ safety framework flag)
|
||||
GM_PARAM_IQ_NON_ACC = 1
|
||||
|
||||
|
||||
class Buttons:
|
||||
UNPRESS = 1
|
||||
RES_ACCEL = 2
|
||||
DECEL_SET = 3
|
||||
CANCEL = 6
|
||||
|
||||
|
||||
class GmLongitudinalBase(common.CarSafetyTest, common.LongitudinalGasBrakeSafetyTest):
|
||||
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB), 2: (0x184,)} # ASCMLKASteeringCmd, ASCMGasRegenCmd, PSCMStatus
|
||||
|
||||
MAX_POSSIBLE_BRAKE = 2 ** 12
|
||||
MAX_BRAKE = 400
|
||||
|
||||
MAX_POSSIBLE_GAS = 4000 # reasonably excessive limits, not signal max
|
||||
MIN_POSSIBLE_GAS = -4000
|
||||
|
||||
PCM_CRUISE = False # openpilot can control the PCM state if longitudinal
|
||||
|
||||
def _send_brake_msg(self, brake):
|
||||
values = {"FrictionBrakeCmd": -brake}
|
||||
return self.packer_chassis.make_can_msg_safety("EBCMFrictionBrakeCmd", self.BRAKE_BUS, values)
|
||||
|
||||
def _send_gas_msg(self, gas):
|
||||
values = {"GasRegenCmd": gas}
|
||||
return self.packer.make_can_msg_safety("ASCMGasRegenCmd", 0, values)
|
||||
|
||||
# override these tests from CarSafetyTest, GM longitudinal uses button enable
|
||||
def _pcm_status_msg(self, enable):
|
||||
raise NotImplementedError
|
||||
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_enable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_cruise_engaged_prev(self):
|
||||
pass
|
||||
|
||||
def test_set_resume_buttons(self):
|
||||
"""
|
||||
SET and RESUME enter controls allowed on their falling and rising edges, respectively.
|
||||
"""
|
||||
for btn_prev in range(8):
|
||||
for btn_cur in range(8):
|
||||
with self.subTest(btn_prev=btn_prev, btn_cur=btn_cur):
|
||||
self._rx(self._button_msg(btn_prev))
|
||||
self.safety.set_controls_allowed(0)
|
||||
for _ in range(10):
|
||||
self._rx(self._button_msg(btn_cur))
|
||||
|
||||
should_enable = btn_cur != Buttons.DECEL_SET and btn_prev == Buttons.DECEL_SET
|
||||
should_enable = should_enable or (btn_cur == Buttons.RES_ACCEL and btn_prev != Buttons.RES_ACCEL)
|
||||
should_enable = should_enable and btn_cur != Buttons.CANCEL
|
||||
self.assertEqual(should_enable, self.safety.get_controls_allowed())
|
||||
|
||||
def test_cancel_button(self):
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._button_msg(Buttons.CANCEL))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
|
||||
class TestGmSafetyBase(common.CarSafetyTest, common.DriverTorqueSteeringSafetyTest):
|
||||
STANDSTILL_THRESHOLD = 10 * 0.0311
|
||||
# Ensures ASCM is off on ASCM cars, and relay is not malfunctioning for camera-ACC cars
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x180,), 2: (0x184,)} # ASCMLKASteeringCmd, PSCMStatus
|
||||
BUTTONS_BUS = 0 # rx or tx
|
||||
BRAKE_BUS = 0 # tx only
|
||||
|
||||
MAX_RATE_UP = 10
|
||||
MAX_RATE_DOWN = 15
|
||||
MAX_TORQUE_LOOKUP = [0], [300]
|
||||
MAX_RT_DELTA = 128
|
||||
DRIVER_TORQUE_ALLOWANCE = 65
|
||||
DRIVER_TORQUE_FACTOR = 4
|
||||
|
||||
PCM_CRUISE = True # openpilot is tied to the PCM state if not longitudinal
|
||||
|
||||
EXTRA_SAFETY_PARAM = 0
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("gm_global_a_powertrain_generated")
|
||||
self.packer_chassis = CANPackerSafety("gm_global_a_chassis")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.gm, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
if self.PCM_CRUISE:
|
||||
values = {"CruiseState": enable}
|
||||
return self.packer.make_can_msg_safety("AcceleratorPedal2", 0, values)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"%sWheelSpd" % s: speed for s in ["RL", "RR"]}
|
||||
return self.packer.make_can_msg_safety("EBCMWheelSpdRear", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
# GM safety has a brake threshold of 8
|
||||
values = {"BrakePedalPos": 8 if brake else 0}
|
||||
return self.packer.make_can_msg_safety("ECMAcceleratorPos", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"AcceleratorPedal2": 1 if gas else 0}
|
||||
if self.PCM_CRUISE:
|
||||
# Fill CruiseState with expected value if the safety mode reads cruise state from gas msg
|
||||
values["CruiseState"] = self.safety.get_controls_allowed()
|
||||
return self.packer.make_can_msg_safety("AcceleratorPedal2", 0, values)
|
||||
|
||||
def _torque_driver_msg(self, torque):
|
||||
# Safety tests assume driver torque is an int, use DBC factor
|
||||
values = {"LKADriverAppldTrq": torque * 0.01}
|
||||
return self.packer.make_can_msg_safety("PSCMStatus", 0, values)
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"LKASteeringCmd": torque, "LKASteeringCmdActive": steer_req}
|
||||
return self.packer.make_can_msg_safety("ASCMLKASteeringCmd", 0, values)
|
||||
|
||||
def _button_msg(self, buttons):
|
||||
values = {"ACCButtons": buttons}
|
||||
return self.packer.make_can_msg_safety("ASCMSteeringButton", self.BUTTONS_BUS, values)
|
||||
|
||||
|
||||
class TestGmEVSafetyBase(TestGmSafetyBase):
|
||||
EXTRA_SAFETY_PARAM = GMSafetyFlags.EV
|
||||
|
||||
# existence of _user_regen_msg adds regen tests
|
||||
def _user_regen_msg(self, regen):
|
||||
values = {"RegenPaddle": 2 if regen else 0}
|
||||
return self.packer.make_can_msg_safety("EBCMRegenPaddle", 0, values)
|
||||
|
||||
|
||||
class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase):
|
||||
TX_MSGS = [[0x180, 0], [0x409, 0], [0x40A, 0], [0x2CB, 0], [0x370, 0], # pt bus
|
||||
[0xA1, 1], [0x306, 1], [0x308, 1], [0x310, 1], # obs bus
|
||||
[0x315, 2]] # ch bus
|
||||
FWD_BLACKLISTED_ADDRS: dict[int, list[int]] = {}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB)} # ASCMLKASteeringCmd, ASCMGasRegenCmd
|
||||
FWD_BUS_LOOKUP: dict[int, int] = {}
|
||||
BRAKE_BUS = 2
|
||||
|
||||
MAX_GAS = 1018
|
||||
MIN_GAS = -650 # maximum regen
|
||||
INACTIVE_GAS = -650
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("gm_global_a_powertrain_generated")
|
||||
self.packer_chassis = CANPackerSafety("gm_global_a_chassis")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.gm, self.EXTRA_SAFETY_PARAM)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestGmAscmEVSafety(TestGmAscmSafety, TestGmEVSafetyBase):
|
||||
pass
|
||||
|
||||
|
||||
class TestGmCameraSafetyBase(TestGmSafetyBase):
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"BrakePressed": brake}
|
||||
return self.packer.make_can_msg_safety("ECMEngineStatus", 0, values)
|
||||
|
||||
|
||||
class TestGmCameraSafety(TestGmCameraSafetyBase):
|
||||
TX_MSGS = [[0x180, 0], # pt bus
|
||||
[0x184, 2]] # camera bus
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x180], 0: [0x184]} # block LKAS message and PSCMStatus
|
||||
BUTTONS_BUS = 2 # tx only
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("gm_global_a_powertrain_generated")
|
||||
self.packer_chassis = CANPackerSafety("gm_global_a_chassis")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.gm, GMSafetyFlags.HW_CAM | self.EXTRA_SAFETY_PARAM)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_buttons(self):
|
||||
# Only CANCEL button is allowed while cruise is enabled
|
||||
self.safety.set_controls_allowed(0)
|
||||
for btn in range(8):
|
||||
self.assertFalse(self._tx(self._button_msg(btn)))
|
||||
|
||||
self.safety.set_controls_allowed(1)
|
||||
for btn in range(8):
|
||||
self.assertFalse(self._tx(self._button_msg(btn)))
|
||||
|
||||
for enabled in (True, False):
|
||||
self._rx(self._pcm_status_msg(enabled))
|
||||
self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL)))
|
||||
|
||||
|
||||
class TestGmCameraEVSafety(TestGmCameraSafety, TestGmEVSafetyBase):
|
||||
pass
|
||||
|
||||
|
||||
class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase):
|
||||
TX_MSGS = [[0x180, 0], [0x315, 0], [0x2CB, 0], [0x370, 0], # pt bus
|
||||
[0x184, 2]] # camera bus
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x180, 0x2CB, 0x370, 0x315], 0: [0x184]} # block LKAS, ACC messages and PSCMStatus
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB, 0x370, 0x315), 2: (0x184,)}
|
||||
BUTTONS_BUS = 0 # rx only
|
||||
|
||||
MAX_GAS = 1346
|
||||
MIN_GAS = -540 # maximum regen
|
||||
INACTIVE_GAS = -500
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("gm_global_a_powertrain_generated")
|
||||
self.packer_chassis = CANPackerSafety("gm_global_a_chassis")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.gm, GMSafetyFlags.HW_CAM | GMSafetyFlags.HW_CAM_LONG | self.EXTRA_SAFETY_PARAM)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestGmCameraLongitudinalEVSafety(TestGmCameraLongitudinalSafety, TestGmEVSafetyBase):
|
||||
pass
|
||||
|
||||
|
||||
class TestGmCameraNonACCSafety(TestGmCameraSafety):
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("gm_global_a_powertrain_generated")
|
||||
self.packer_chassis = CANPackerSafety("gm_global_a_chassis")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(GM_PARAM_IQ_NON_ACC)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.gm, GMSafetyFlags.HW_CAM | self.EXTRA_SAFETY_PARAM)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"CruiseActive": enable}
|
||||
return self.packer.make_can_msg_safety("ECMCruiseControl", 0, values)
|
||||
|
||||
|
||||
class TestGmCameraEVNonACCSafety(TestGmCameraNonACCSafety, TestGmEVSafetyBase):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
799
artifacts/package_runtime/iqdbc/safety/tests/test_honda.py
Normal file
799
artifacts/package_runtime/iqdbc/safety/tests/test_honda.py
Normal file
@@ -0,0 +1,799 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
from iqdbc.car.honda.values import HondaSafetyFlags
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.common import CANPackerSafety, MAX_WRONG_COUNTERS
|
||||
from iqdbc.safety.tests.gas_interceptor_common import GasInterceptorSafetyTest
|
||||
|
||||
from iqdbc.lvbs.car.honda.iq_values import HondaSafetyFlagsIQ
|
||||
|
||||
HONDA_N_COMMON_TX_MSGS = [[0xE4, 0], [0x194, 0], [0x1FA, 0], [0x30C, 0], [0x33D, 0]]
|
||||
|
||||
|
||||
class Btn:
|
||||
NONE = 0
|
||||
MAIN = 1
|
||||
CANCEL = 2
|
||||
SET = 3
|
||||
RESUME = 4
|
||||
|
||||
# Honda safety has several different configurations tested here:
|
||||
# * Nidec
|
||||
# * normal (PCM-enable)
|
||||
# * alt SCM messages (PCM-enable)
|
||||
# * gas interceptor (button-enable)
|
||||
# * gas interceptor with alt SCM messages (button-enable)
|
||||
# * Bosch
|
||||
# * Bosch with Longitudinal Support
|
||||
# * Bosch Radarless
|
||||
# * Bosch Radarless with Longitudinal Support
|
||||
# * Bosch CANFD
|
||||
# * Bosch CANFD with Longitudinal Support
|
||||
|
||||
|
||||
class HondaButtonEnableBase(common.CarSafetyTest):
|
||||
|
||||
# override these inherited tests since we're using button enable
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_enable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_cruise_engaged_prev(self):
|
||||
pass
|
||||
|
||||
def test_buttons_with_main_off(self):
|
||||
for btn in (Btn.SET, Btn.RESUME, Btn.CANCEL):
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._acc_state_msg(False))
|
||||
self._rx(self._button_msg(btn, main_on=False))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_set_resume_buttons(self):
|
||||
"""
|
||||
Both SET and RES should enter controls allowed on their falling edge.
|
||||
"""
|
||||
for main_on in (True, False):
|
||||
self._rx(self._acc_state_msg(main_on))
|
||||
for btn_prev in range(8):
|
||||
for btn_cur in range(8):
|
||||
self._rx(self._button_msg(Btn.NONE))
|
||||
self.safety.set_controls_allowed(0)
|
||||
for _ in range(10):
|
||||
self._rx(self._button_msg(btn_prev))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
# should enter controls allowed on falling edge and not transitioning to cancel or main
|
||||
should_enable = (main_on and
|
||||
btn_cur != btn_prev and
|
||||
btn_prev in (Btn.RESUME, Btn.SET) and
|
||||
btn_cur not in (Btn.CANCEL, Btn.MAIN))
|
||||
|
||||
self._rx(self._button_msg(btn_cur, main_on=main_on))
|
||||
self.assertEqual(should_enable, self.safety.get_controls_allowed(), msg=f"{main_on=} {btn_prev=} {btn_cur=}")
|
||||
|
||||
def test_main_cancel_buttons(self):
|
||||
"""
|
||||
Both MAIN and CANCEL should exit controls immediately.
|
||||
"""
|
||||
for btn in (Btn.MAIN, Btn.CANCEL):
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._button_msg(btn, main_on=True))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_disengage_on_main(self):
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._acc_state_msg(True))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self._rx(self._acc_state_msg(False))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_rx_hook(self):
|
||||
|
||||
# TODO: move this test to common
|
||||
# checksum checks
|
||||
for msg_type in ["btn", "gas", "speed"]:
|
||||
self.safety.set_controls_allowed(1)
|
||||
if msg_type == "btn":
|
||||
msg = self._button_msg(Btn.SET)
|
||||
if msg_type == "gas":
|
||||
msg = self._user_gas_msg(0)
|
||||
if msg_type == "speed":
|
||||
msg = self._speed_msg(0)
|
||||
self.assertTrue(self._rx(msg))
|
||||
if msg_type != "btn":
|
||||
msg[0].data[4] = 0 # invalidate checksum
|
||||
msg[0].data[5] = 0
|
||||
msg[0].data[6] = 0
|
||||
msg[0].data[7] = 0
|
||||
self.assertFalse(self._rx(msg))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
# counter
|
||||
# reset wrong_counters to zero by sending valid messages
|
||||
for i in range(MAX_WRONG_COUNTERS + 1):
|
||||
self.__class__.cnt_speed += 1
|
||||
self.__class__.cnt_button += 1
|
||||
self.__class__.cnt_powertrain_data += 1
|
||||
if i < MAX_WRONG_COUNTERS:
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._button_msg(Btn.SET))
|
||||
self._rx(self._speed_msg(0))
|
||||
self._rx(self._user_gas_msg(0))
|
||||
else:
|
||||
self.assertFalse(self._rx(self._button_msg(Btn.SET)))
|
||||
self.assertFalse(self._rx(self._speed_msg(0)))
|
||||
self.assertFalse(self._rx(self._user_gas_msg(0)))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
# restore counters for future tests with a couple of good messages
|
||||
for _ in range(2):
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._button_msg(Btn.SET, main_on=True))
|
||||
self._rx(self._speed_msg(0))
|
||||
self._rx(self._user_gas_msg(0))
|
||||
self._rx(self._button_msg(Btn.SET, main_on=True))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
|
||||
class HondaPcmEnableBase(common.CarSafetyTest):
|
||||
|
||||
def test_buttons(self):
|
||||
"""
|
||||
Buttons should only cancel in this configuration,
|
||||
since our state is tied to the PCM's cruise state.
|
||||
"""
|
||||
for controls_allowed in (True, False):
|
||||
for main_on in (True, False):
|
||||
# not a valid state
|
||||
if controls_allowed and not main_on:
|
||||
continue
|
||||
|
||||
for btn in (Btn.SET, Btn.RESUME, Btn.CANCEL):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self._rx(self._acc_state_msg(main_on))
|
||||
|
||||
# btn + none for falling edge
|
||||
self._rx(self._button_msg(btn, main_on=main_on))
|
||||
self._rx(self._button_msg(Btn.NONE, main_on=main_on))
|
||||
|
||||
if btn == Btn.CANCEL:
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
else:
|
||||
self.assertEqual(controls_allowed, self.safety.get_controls_allowed())
|
||||
|
||||
|
||||
class HondaBase(common.CarSafetyTest):
|
||||
MAX_BRAKE = 255
|
||||
PT_BUS: int | None = None # must be set when inherited
|
||||
STEER_BUS: int | None = None # must be set when inherited
|
||||
BUTTONS_BUS: int | None = None # must be set when inherited, tx on this bus, rx on PT_BUS
|
||||
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x194)} # STEERING_CONTROL
|
||||
|
||||
cnt_speed = 0
|
||||
cnt_button = 0
|
||||
cnt_brake = 0
|
||||
cnt_powertrain_data = 0
|
||||
cnt_acc_state = 0
|
||||
|
||||
def _powertrain_data_msg(self, cruise_on=None, brake_pressed=None, gas_pressed=None):
|
||||
# preserve the state
|
||||
if cruise_on is None:
|
||||
# or'd with controls allowed since the tests use it to "enable" cruise
|
||||
cruise_on = self.safety.get_cruise_engaged_prev() or self.safety.get_controls_allowed()
|
||||
if brake_pressed is None:
|
||||
brake_pressed = self.safety.get_brake_pressed_prev()
|
||||
if gas_pressed is None:
|
||||
gas_pressed = self.safety.get_gas_pressed_prev()
|
||||
|
||||
values = {
|
||||
"ACC_STATUS": cruise_on,
|
||||
"BRAKE_PRESSED": brake_pressed,
|
||||
"PEDAL_GAS": gas_pressed,
|
||||
"COUNTER": self.cnt_powertrain_data % 4
|
||||
}
|
||||
self.__class__.cnt_powertrain_data += 1
|
||||
return self.packer.make_can_msg_safety("POWERTRAIN_DATA", self.PT_BUS, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
return self._powertrain_data_msg(cruise_on=enable)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"XMISSION_SPEED": speed, "COUNTER": self.cnt_speed % 4}
|
||||
self.__class__.cnt_speed += 1
|
||||
return self.packer.make_can_msg_safety("ENGINE_DATA", self.PT_BUS, values)
|
||||
|
||||
def _acc_state_msg(self, main_on):
|
||||
values = {"MAIN_ON": main_on, "COUNTER": self.cnt_acc_state % 4}
|
||||
self.__class__.cnt_acc_state += 1
|
||||
return self.packer.make_can_msg_safety("SCM_FEEDBACK", self.PT_BUS, values)
|
||||
|
||||
def _button_msg(self, buttons, main_on=False, bus=None):
|
||||
bus = self.PT_BUS if bus is None else bus
|
||||
values = {"CRUISE_BUTTONS": buttons, "COUNTER": self.cnt_button % 4}
|
||||
self.__class__.cnt_button += 1
|
||||
return self.packer.make_can_msg_safety("SCM_BUTTONS", bus, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
return self._powertrain_data_msg(brake_pressed=brake)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
return self._powertrain_data_msg(gas_pressed=gas)
|
||||
|
||||
def _send_steer_msg(self, steer):
|
||||
values = {"STEER_TORQUE": steer}
|
||||
return self.packer.make_can_msg_safety("STEERING_CONTROL", self.STEER_BUS, values)
|
||||
|
||||
def _send_brake_msg(self, brake):
|
||||
# must be implemented when inherited
|
||||
raise NotImplementedError
|
||||
|
||||
def test_disengage_on_brake(self):
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._user_brake_msg(1))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_steer_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._send_steer_msg(0x0000)))
|
||||
self.assertFalse(self._tx(self._send_steer_msg(0x1000)))
|
||||
|
||||
def _lkas_button_msg(self, lkas_button=False, setting_btn=0):
|
||||
values = {"CRUISE_SETTING": 1 if lkas_button else setting_btn, "COUNTER": self.cnt_button % 4}
|
||||
self.__class__.cnt_button += 1
|
||||
return self.packer.make_can_msg_safety("SCM_BUTTONS", self.PT_BUS, values)
|
||||
|
||||
def test_enable_control_allowed_with_aol_button(self):
|
||||
"""Tests AOL button state transitions and internal button press state."""
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
# Verify initial state
|
||||
self._rx(self._lkas_button_msg(False, 0))
|
||||
self.assertEqual(0, self.safety.get_aol_button_press()) # NOT_PRESSED
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Verify press sets correct internal state
|
||||
self._rx(self._lkas_button_msg(False, 1))
|
||||
self.assertEqual(1, self.safety.get_aol_button_press()) # PRESSED
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Verify release sets correct internal state
|
||||
self._rx(self._lkas_button_msg(False, 0))
|
||||
self.assertEqual(0, self.safety.get_aol_button_press()) # NOT_PRESSED
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Test invalid values - should not change button press state
|
||||
for invalid_setting in (2, 3):
|
||||
self._rx(self._lkas_button_msg(False, invalid_setting))
|
||||
self.assertEqual(0, self.safety.get_aol_button_press()) # Should remain NOT_PRESSED
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
# Verify we can still transition after invalid values
|
||||
self._rx(self._lkas_button_msg(False, 1))
|
||||
self.assertEqual(1, self.safety.get_aol_button_press())
|
||||
self._rx(self._lkas_button_msg(False, 0))
|
||||
self.assertEqual(0, self.safety.get_aol_button_press())
|
||||
|
||||
|
||||
# ********************* Honda Nidec **********************
|
||||
|
||||
|
||||
class TestHondaNidecSafetyBase(HondaBase):
|
||||
TX_MSGS = HONDA_N_COMMON_TX_MSGS
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x194, 0x33D, 0x30C]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x194, 0x33D, 0x30C)}
|
||||
|
||||
PT_BUS = 0
|
||||
STEER_BUS = 0
|
||||
BUTTONS_BUS = 0
|
||||
|
||||
MAX_GAS = 198
|
||||
|
||||
BRAKE_SIG = "COMPUTER_BRAKE"
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("honda_civic_touring_2016_can_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _send_brake_msg(self, brake, aeb_req=0, bus=0):
|
||||
values = {self.BRAKE_SIG: brake, "AEB_REQ_1": aeb_req}
|
||||
return self.packer.make_can_msg_safety("BRAKE_COMMAND", bus, values)
|
||||
|
||||
def _rx_brake_msg(self, brake, aeb_req=0):
|
||||
return self._send_brake_msg(brake, aeb_req, bus=2)
|
||||
|
||||
def _send_acc_hud_msg(self, pcm_gas, pcm_speed):
|
||||
# Used to control ACC on Nidec without pedal
|
||||
values = {"PCM_GAS": pcm_gas, "PCM_SPEED": pcm_speed}
|
||||
return self.packer.make_can_msg_safety("ACC_HUD", 0, values)
|
||||
|
||||
def test_acc_hud_safety_check(self):
|
||||
for controls_allowed in [True, False]:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
for pcm_gas in range(255):
|
||||
for pcm_speed in range(100):
|
||||
send = (controls_allowed and pcm_gas <= self.MAX_GAS) or (pcm_gas == 0 and pcm_speed == 0)
|
||||
self.assertEqual(send, self._tx(self._send_acc_hud_msg(pcm_gas, pcm_speed)))
|
||||
|
||||
def test_fwd_hook(self):
|
||||
# normal operation, not forwarding AEB
|
||||
self.FWD_BLACKLISTED_ADDRS[2].append(0x1FA)
|
||||
self.safety.set_honda_fwd_brake(False)
|
||||
super().test_fwd_hook()
|
||||
|
||||
# forwarding AEB brake signal
|
||||
self.FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x194, 0x33D, 0x30C]}
|
||||
self.safety.set_honda_fwd_brake(True)
|
||||
super().test_fwd_hook()
|
||||
|
||||
def test_honda_fwd_brake_latching(self):
|
||||
# Shouldn't fwd stock Honda requesting brake without AEB
|
||||
self.assertTrue(self._rx(self._rx_brake_msg(self.MAX_BRAKE, aeb_req=0)))
|
||||
self.assertFalse(self.safety.get_honda_fwd_brake())
|
||||
|
||||
# Now allow controls and request some brake
|
||||
openpilot_brake = round(self.MAX_BRAKE / 2.0)
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._tx(self._send_brake_msg(openpilot_brake)))
|
||||
|
||||
# Still shouldn't fwd stock Honda brake until it's more than openpilot's
|
||||
for stock_honda_brake in range(self.MAX_BRAKE + 1):
|
||||
self.assertTrue(self._rx(self._rx_brake_msg(stock_honda_brake, aeb_req=1)))
|
||||
should_fwd_brake = stock_honda_brake >= openpilot_brake
|
||||
self.assertEqual(should_fwd_brake, self.safety.get_honda_fwd_brake())
|
||||
|
||||
# Shouldn't stop fwding until AEB event is over
|
||||
for stock_honda_brake in range(self.MAX_BRAKE + 1)[::-1]:
|
||||
self.assertTrue(self._rx(self._rx_brake_msg(stock_honda_brake, aeb_req=1)))
|
||||
self.assertTrue(self.safety.get_honda_fwd_brake())
|
||||
|
||||
self.assertTrue(self._rx(self._rx_brake_msg(0, aeb_req=0)))
|
||||
self.assertFalse(self.safety.get_honda_fwd_brake())
|
||||
|
||||
def test_brake_safety_check(self):
|
||||
for fwd_brake in [False, True]:
|
||||
self.safety.set_honda_fwd_brake(fwd_brake)
|
||||
for brake in np.arange(0, self.MAX_BRAKE + 10, 1):
|
||||
for controls_allowed in [True, False]:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
if fwd_brake:
|
||||
send = False # block openpilot brake msg when fwd'ing stock msg
|
||||
elif controls_allowed:
|
||||
send = self.MAX_BRAKE >= brake >= 0
|
||||
else:
|
||||
send = brake == 0
|
||||
self.assertEqual(send, self._tx(self._send_brake_msg(brake)))
|
||||
|
||||
# Inactive brake must pass when gas blocks longitudinal actuation
|
||||
self.safety.set_honda_fwd_brake(False)
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.safety.set_gas_pressed_prev(True)
|
||||
self.assertFalse(self.safety.get_longitudinal_allowed())
|
||||
self.assertTrue(self._tx(self._send_brake_msg(0)))
|
||||
self.assertFalse(self._tx(self._send_brake_msg(1)))
|
||||
|
||||
|
||||
class TestHondaNidecPcmSafety(HondaPcmEnableBase, TestHondaNidecSafetyBase):
|
||||
"""
|
||||
Covers the Honda Nidec safety mode
|
||||
"""
|
||||
|
||||
# Nidec doesn't disengage on falling edge of cruise. See comment in safety_honda.h
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestHondaNidecGasInterceptorSafety(GasInterceptorSafetyTest, HondaButtonEnableBase, TestHondaNidecSafetyBase):
|
||||
"""
|
||||
Covers the Honda Nidec safety mode with a gas interceptor, switches to a button-enable car
|
||||
"""
|
||||
|
||||
TX_MSGS = HONDA_N_COMMON_TX_MSGS + [[0x200, 0]]
|
||||
INTERCEPTOR_THRESHOLD = 492
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("honda_civic_touring_2016_can_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(HondaSafetyFlagsIQ.GAS_INTERCEPTOR)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestHondaNidecPcmAltSafety(TestHondaNidecPcmSafety):
|
||||
"""
|
||||
Covers the Honda Nidec safety mode with alt SCM messages
|
||||
"""
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("acura_ilx_2016_can_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, HondaSafetyFlags.NIDEC_ALT)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _acc_state_msg(self, main_on):
|
||||
values = {"MAIN_ON": main_on, "COUNTER": self.cnt_acc_state % 4}
|
||||
self.__class__.cnt_acc_state += 1
|
||||
return self.packer.make_can_msg_safety("SCM_BUTTONS", self.PT_BUS, values)
|
||||
|
||||
def _button_msg(self, buttons, main_on=False, bus=None):
|
||||
bus = self.PT_BUS if bus is None else bus
|
||||
values = {"CRUISE_BUTTONS": buttons, "MAIN_ON": main_on, "COUNTER": self.cnt_button % 4}
|
||||
self.__class__.cnt_button += 1
|
||||
return self.packer.make_can_msg_safety("SCM_BUTTONS", bus, values)
|
||||
|
||||
|
||||
class TestHondaNidecAltGasInterceptorSafety(GasInterceptorSafetyTest, HondaButtonEnableBase, TestHondaNidecSafetyBase):
|
||||
"""
|
||||
Covers the Honda Nidec safety mode with alt SCM messages and gas interceptor, switches to a button-enable car
|
||||
"""
|
||||
|
||||
TX_MSGS = HONDA_N_COMMON_TX_MSGS + [[0x200, 0]]
|
||||
INTERCEPTOR_THRESHOLD = 492
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("acura_ilx_2016_can_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(HondaSafetyFlagsIQ.GAS_INTERCEPTOR)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, HondaSafetyFlags.NIDEC_ALT)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _acc_state_msg(self, main_on):
|
||||
values = {"MAIN_ON": main_on, "COUNTER": self.cnt_acc_state % 4}
|
||||
self.__class__.cnt_acc_state += 1
|
||||
return self.packer.make_can_msg_safety("SCM_BUTTONS", self.PT_BUS, values)
|
||||
|
||||
def _button_msg(self, buttons, main_on=False, bus=None):
|
||||
bus = self.PT_BUS if bus is None else bus
|
||||
values = {"CRUISE_BUTTONS": buttons, "MAIN_ON": main_on, "COUNTER": self.cnt_button % 4}
|
||||
self.__class__.cnt_button += 1
|
||||
return self.packer.make_can_msg_safety("SCM_BUTTONS", bus, values)
|
||||
|
||||
|
||||
# ********************* Honda Bosch **********************
|
||||
|
||||
|
||||
class TestHondaBoschSafetyBase(HondaBase):
|
||||
PT_BUS = 1
|
||||
STEER_BUS = 0
|
||||
BUTTONS_BUS = 1
|
||||
|
||||
TX_MSGS = [[0xE4, 0], [0xE5, 0], [0x296, 1], [0x33D, 0], [0x33DA, 0], [0x33DB, 0]]
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB)} # STEERING_CONTROL, BOSCH_SUPPLEMENTAL_1
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("honda_civic_hatchback_ex_2017_can_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
|
||||
def _alt_brake_msg(self, brake):
|
||||
values = {"BRAKE_PRESSED": brake, "COUNTER": self.cnt_brake % 4}
|
||||
self.__class__.cnt_brake += 1
|
||||
return self.packer.make_can_msg_safety("BRAKE_MODULE", self.PT_BUS, values)
|
||||
|
||||
def _send_brake_msg(self, brake):
|
||||
pass
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._button_msg(Btn.CANCEL, bus=self.BUTTONS_BUS)))
|
||||
self.assertFalse(self._tx(self._button_msg(Btn.RESUME, bus=self.BUTTONS_BUS)))
|
||||
self.assertFalse(self._tx(self._button_msg(Btn.SET, bus=self.BUTTONS_BUS)))
|
||||
# do not block resume if we are engaged already
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._button_msg(Btn.RESUME, bus=self.BUTTONS_BUS)))
|
||||
|
||||
|
||||
class TestHondaBoschAltBrakeSafetyBase(TestHondaBoschSafetyBase):
|
||||
"""
|
||||
Base Bosch safety test class with an alternate brake message
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.ALT_BRAKE)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
return self._alt_brake_msg(brake)
|
||||
|
||||
def test_alt_brake_rx_hook(self):
|
||||
self.safety.set_honda_alt_brake_msg(1)
|
||||
self.safety.set_controls_allowed(1)
|
||||
msg = self._alt_brake_msg(0)
|
||||
self.assertTrue(self._rx(msg))
|
||||
msg[0].data[2] = msg[0].data[2] & 0xF0 # invalidate checksum
|
||||
self.assertFalse(self._rx(msg))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_alt_disengage_on_brake(self):
|
||||
self.safety.set_honda_alt_brake_msg(1)
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._alt_brake_msg(1))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
self.safety.set_honda_alt_brake_msg(0)
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._alt_brake_msg(1))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
|
||||
class TestHondaBoschSafety(HondaPcmEnableBase, TestHondaBoschSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch safety mode with stock longitudinal
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestHondaBoschAltBrakeSafety(HondaPcmEnableBase, TestHondaBoschAltBrakeSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch safety mode with stock longitudinal and an alternate brake message
|
||||
"""
|
||||
|
||||
|
||||
class TestHondaBoschLongSafety(HondaButtonEnableBase, TestHondaBoschSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch safety mode with longitudinal control
|
||||
"""
|
||||
NO_GAS = -30000
|
||||
MAX_GAS = 2200
|
||||
MAX_ACCEL = 2.0 # accel is used for brakes, but openpilot can set positive values
|
||||
MIN_ACCEL = -3.5
|
||||
|
||||
STEER_BUS = 1
|
||||
TX_MSGS = [[0xE4, 1], [0x1DF, 1], [0x1EF, 1], [0x1FA, 1], [0x30C, 1], [0x33D, 1], [0x33DA, 1], [0x33DB, 1], [0x39F, 1], [0x18DAB0F1, 1]]
|
||||
FWD_BLACKLISTED_ADDRS = {}
|
||||
# 0x1DF is to test that radar is disabled
|
||||
RELAY_MALFUNCTION_ADDRS = {1: (0xE4, 0x1DF, 0x33D, 0x33DA, 0x33DB)} # STEERING_CONTROL, ACC_CONTROL
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.BOSCH_LONG)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _send_gas_brake_msg(self, gas, accel):
|
||||
values = {
|
||||
"GAS_COMMAND": gas,
|
||||
"ACCEL_COMMAND": accel,
|
||||
"BRAKE_REQUEST": accel < 0,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("ACC_CONTROL", self.PT_BUS, values)
|
||||
|
||||
# Longitudinal doesn't need to send buttons
|
||||
def test_spam_cancel_safety_check(self):
|
||||
pass
|
||||
|
||||
def test_diagnostics(self):
|
||||
tester_present = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x02\x3E\x80\x00\x00\x00\x00\x00")
|
||||
self.assertTrue(self._tx(tester_present))
|
||||
|
||||
not_tester_present = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\xAA\xAA\x00\x00\x00\x00\x00")
|
||||
self.assertFalse(self._tx(not_tester_present))
|
||||
|
||||
# the radar disable requests are only allowed on CANFD
|
||||
ext_diag = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x02\x10\x03\x00\x00\x00\x00\x00")
|
||||
self.assertFalse(self._tx(ext_diag))
|
||||
comm_control_disable = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\x28\x83\x03\x00\x00\x00\x00")
|
||||
self.assertFalse(self._tx(comm_control_disable))
|
||||
|
||||
def test_gas_safety_check(self):
|
||||
for controls_allowed in [True, False]:
|
||||
for gas in np.arange(self.NO_GAS, self.MAX_GAS + 2000, 100):
|
||||
accel = 0 if gas < 0 else min(gas / 1000, self.MAX_ACCEL)
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
send = (controls_allowed and 0 <= gas <= self.MAX_GAS) or gas == self.NO_GAS
|
||||
self.assertEqual(send, self._tx(self._send_gas_brake_msg(gas, accel)), (controls_allowed, gas, accel))
|
||||
|
||||
def test_brake_safety_check(self):
|
||||
for controls_allowed in [True, False]:
|
||||
for accel in np.arange(self.MIN_ACCEL - 1, self.MAX_ACCEL + 1, 0.01):
|
||||
accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
send = self.MIN_ACCEL <= accel <= self.MAX_ACCEL if controls_allowed else accel == 0
|
||||
self.assertEqual(send, self._tx(self._send_gas_brake_msg(self.NO_GAS, accel)), (controls_allowed, accel))
|
||||
|
||||
|
||||
class TestHondaBoschRadarlessSafetyBase(TestHondaBoschSafetyBase):
|
||||
"""Base class for radarless Honda Bosch"""
|
||||
PT_BUS = 0
|
||||
STEER_BUS = 0
|
||||
BUTTONS_BUS = 2 # camera controls ACC, need to send buttons on bus 2
|
||||
|
||||
TX_MSGS = [[0xE4, 0], [0x296, 2], [0x33D, 0]]
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x33D]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x33D)} # STEERING_CONTROL
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("honda_bosch_radarless_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
|
||||
|
||||
class TestHondaBoschRadarlessSafety(HondaPcmEnableBase, TestHondaBoschRadarlessSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch Radarless safety mode with stock longitudinal
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.RADARLESS)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestHondaBoschRadarlessAltBrakeSafety(HondaPcmEnableBase, TestHondaBoschRadarlessSafetyBase, TestHondaBoschAltBrakeSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch Radarless safety mode with stock longitudinal and an alternate brake message
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.RADARLESS | HondaSafetyFlags.ALT_BRAKE)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestHondaBoschRadarlessLongSafety(common.LongitudinalAccelSafetyTest, HondaButtonEnableBase,
|
||||
TestHondaBoschRadarlessSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch Radarless safety mode with longitudinal control
|
||||
"""
|
||||
TX_MSGS = [[0xE4, 0], [0x33D, 0], [0x1C8, 0], [0x30C, 0]]
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x33D, 0x1C8, 0x30C]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x1C8, 0x30C, 0x33D)}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.RADARLESS | HondaSafetyFlags.BOSCH_LONG)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _accel_msg(self, accel):
|
||||
values = {
|
||||
"ACCEL_COMMAND": accel,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("ACC_CONTROL", self.PT_BUS, values)
|
||||
|
||||
# Longitudinal doesn't need to send buttons
|
||||
def test_spam_cancel_safety_check(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestHondaBoschCANFDSafetyBase(TestHondaBoschSafetyBase):
|
||||
"""Base class for CANFD Honda Bosch"""
|
||||
PT_BUS = 0
|
||||
STEER_BUS = 0
|
||||
BUTTONS_BUS = 0
|
||||
|
||||
TX_MSGS = [[0xE4, 0], [0x296, 0], [0x296, 2], [0x33D, 0]]
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x33D]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x33D)}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("honda_common_canfd_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
|
||||
def test_buttons_fwd(self):
|
||||
# SCM_BUTTONS (0x296) forwards to the camera unless OP's replacement button stream is flowing
|
||||
# (engaged + a recent OP SCM_BUTTONS tx on the camera bus); see the radarless variant of this test
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertEqual(2, self.safety.safety_fwd_hook(0, 0x296))
|
||||
|
||||
self.assertTrue(self._tx(self._button_msg(Btn.NONE, bus=2)))
|
||||
self.assertEqual(-1, self.safety.safety_fwd_hook(0, 0x296))
|
||||
|
||||
self.safety.set_controls_allowed(False)
|
||||
self.assertEqual(2, self.safety.safety_fwd_hook(0, 0x296))
|
||||
|
||||
self.safety.set_controls_allowed(True)
|
||||
for _ in range(10):
|
||||
self._rx(self._button_msg(Btn.NONE, main_on=True))
|
||||
self.assertEqual(2, self.safety.safety_fwd_hook(0, 0x296))
|
||||
|
||||
def test_radar_diag_response_fwd(self):
|
||||
# the radar's UDS responses (0x18DAF1B0) never forward to the camera: the radar disable handshake
|
||||
# happens after the relay is open on CAN FD
|
||||
self.safety.set_controls_allowed(False)
|
||||
self.assertEqual(-1, self.safety.safety_fwd_hook(0, 0x18DAF1B0))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertEqual(-1, self.safety.safety_fwd_hook(0, 0x18DAF1B0))
|
||||
|
||||
def test_buttons_tx_camera_bus(self):
|
||||
# Buttons to the camera (bus 2): cancel-only while disengaged, any button while engaged
|
||||
# (OP takes over SCM_BUTTONS towards the camera when engaged)
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._button_msg(Btn.CANCEL, bus=2)))
|
||||
self.assertFalse(self._tx(self._button_msg(Btn.RESUME, bus=2)))
|
||||
self.assertFalse(self._tx(self._button_msg(Btn.SET, bus=2)))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._button_msg(Btn.NONE, bus=2)))
|
||||
self.assertTrue(self._tx(self._button_msg(Btn.RESUME, bus=2)))
|
||||
|
||||
|
||||
class TestHondaBoschCANFDSafety(HondaPcmEnableBase, TestHondaBoschCANFDSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch CANFD safety mode with stock longitudinal
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.BOSCH_CANFD)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestHondaBoschCANFDAltBrakeSafety(HondaPcmEnableBase, TestHondaBoschCANFDSafetyBase, TestHondaBoschAltBrakeSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch CANFD safety mode with stock longitudinal and an alternate brake message
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.BOSCH_CANFD | HondaSafetyFlags.ALT_BRAKE)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestHondaBoschCANFDLongSafety(TestHondaBoschLongSafety, TestHondaBoschCANFDSafetyBase):
|
||||
"""
|
||||
Covers the Honda Bosch CANFD safety mode with longitudinal control
|
||||
"""
|
||||
|
||||
PT_BUS = 0
|
||||
STEER_BUS = 0
|
||||
BUTTONS_BUS = 0
|
||||
|
||||
# the radar look-alikes are sent on both the powertrain bus (0) and the camera bus (2)
|
||||
TX_MSGS = [[0xE4, 0], [0x1DF, 0], [0x1EF, 0], [0x30C, 0], [0x33D, 0], [0x39F, 0], [0x296, 2], [0x18DAB0F1, 0],
|
||||
[0x310, 0], [0x6CD5558, 0], [0x6CD5559, 0], [0xF31AA52, 0], [0xF31AA5C, 0], [0x1A45AA4E, 0],
|
||||
[0x310, 2], [0x6CD5558, 2], [0x6CD5559, 2], [0xF31AA52, 2], [0xF31AA5C, 2], [0x1A45AA4E, 2]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [0x6CD5558, 0xF31AA5C], 2: [0xE4, 0x1DF, 0x33D, 0x6CD5558, 0xF31AA5C]}
|
||||
# STEERING_CONTROL, ACC_CONTROL, LKAS_HUD on the pt bus; the radar's LANE_PATH and RADAR_LEAD are
|
||||
# additionally blocked from forwarding to the camera in both directions
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x1DF, 0x33D, 0x6CD5558, 0xF31AA5C), 2: (0x6CD5558, 0xF31AA5C)}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.BOSCH_CANFD | HondaSafetyFlags.BOSCH_LONG)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_diagnostics(self):
|
||||
# CAN FD silences the radar from CarController after the relay opens, so exactly the extended
|
||||
# diagnostic session and the suppressed-response CommunicationControl disable are allowed too
|
||||
tester_present = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x02\x3E\x80\x00\x00\x00\x00\x00")
|
||||
self.assertTrue(self._tx(tester_present))
|
||||
ext_diag = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x02\x10\x03\x00\x00\x00\x00\x00")
|
||||
self.assertTrue(self._tx(ext_diag))
|
||||
comm_control_disable = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\x28\x83\x03\x00\x00\x00\x00")
|
||||
self.assertTrue(self._tx(comm_control_disable))
|
||||
|
||||
# anything else stays blocked, including re-enabling the radar and non-zero trailing bytes
|
||||
comm_control_enable = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\x28\x80\x03\x00\x00\x00\x00")
|
||||
self.assertFalse(self._tx(comm_control_enable))
|
||||
not_tester_present = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\xAA\xAA\x00\x00\x00\x00\x00")
|
||||
self.assertFalse(self._tx(not_tester_present))
|
||||
trailing_bytes = libsafety_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x02\x10\x03\x00\x00\x00\x00\x01")
|
||||
self.assertFalse(self._tx(trailing_bytes))
|
||||
|
||||
|
||||
class TestHondaNidecHybridSafety(TestHondaNidecPcmSafety):
|
||||
"""
|
||||
Covers the Honda Nidec safety mode with hybrid brake
|
||||
"""
|
||||
|
||||
BRAKE_SIG = "COMPUTER_BRAKE_HYBRID"
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("honda_clarity_hybrid_2018_can_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(HondaSafetyFlagsIQ.NIDEC_HYBRID)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
107
artifacts/package_runtime/iqdbc/safety/tests/test_hyundai.py
Normal file
107
artifacts/package_runtime/iqdbc/safety/tests/test_hyundai.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import pytest
|
||||
|
||||
from iqdbc.car.hyundai.values import HyundaiSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.hyundai_common import TESTER_PRESENT, classic_accel, classic_steer, packet
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def safety():
|
||||
return libsafety_py.libsafety
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy))
|
||||
@pytest.mark.parametrize("param", (
|
||||
0,
|
||||
HyundaiSafetyFlags.EV_GAS,
|
||||
HyundaiSafetyFlags.HYBRID_GAS,
|
||||
HyundaiSafetyFlags.LONG,
|
||||
HyundaiSafetyFlags.CAMERA_SCC,
|
||||
HyundaiSafetyFlags.ALT_LIMITS,
|
||||
HyundaiSafetyFlags.FCEV_GAS,
|
||||
HyundaiSafetyFlags.ALT_LIMITS_2,
|
||||
))
|
||||
def test_classic_safety_configurations_initialize(safety, mode, param):
|
||||
assert safety.set_safety_hooks(mode, param) == 0
|
||||
safety.init_tests()
|
||||
assert safety.get_current_safety_param() == param
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy))
|
||||
def test_classic_tx_whitelist_and_steering_limits(safety, mode):
|
||||
safety.set_safety_hooks(mode, 0)
|
||||
safety.init_tests()
|
||||
|
||||
assert not safety.safety_tx_hook(packet(0x123, 0, 8))
|
||||
assert not safety.safety_tx_hook(packet(0x340, 1, 8))
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
assert safety.safety_tx_hook(classic_steer(0, False))
|
||||
assert not safety.safety_tx_hook(classic_steer(1))
|
||||
|
||||
safety.set_controls_allowed(True)
|
||||
assert safety.safety_tx_hook(classic_steer(10))
|
||||
safety.set_desired_torque_last(512)
|
||||
safety.set_rt_torque_last(512)
|
||||
assert safety.safety_tx_hook(classic_steer(512))
|
||||
assert not safety.safety_tx_hook(classic_steer(513))
|
||||
assert not safety.safety_tx_hook(classic_steer(-513))
|
||||
|
||||
|
||||
def test_classic_alt_limits_2(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.ALT_LIMITS_2)
|
||||
safety.init_tests()
|
||||
safety.set_controls_allowed(True)
|
||||
safety.set_desired_torque_last(170)
|
||||
safety.set_rt_torque_last(170)
|
||||
assert safety.safety_tx_hook(classic_steer(170))
|
||||
assert not safety.safety_tx_hook(classic_steer(171))
|
||||
|
||||
|
||||
def test_classic_longitudinal_accel_and_aeb_guards(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.LONG)
|
||||
safety.init_tests()
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
assert safety.safety_tx_hook(classic_accel(0))
|
||||
assert not safety.safety_tx_hook(classic_accel(1))
|
||||
|
||||
safety.set_controls_allowed(True)
|
||||
for accel in (-400, 0, 250):
|
||||
assert safety.safety_tx_hook(classic_accel(accel))
|
||||
for accel in (-401, 251):
|
||||
assert not safety.safety_tx_hook(classic_accel(accel))
|
||||
assert not safety.safety_tx_hook(classic_accel(0, aeb_decel=1))
|
||||
assert not safety.safety_tx_hook(classic_accel(0, aeb_request=True))
|
||||
|
||||
assert safety.safety_tx_hook(packet(0x38D, 0, 8))
|
||||
assert not safety.safety_tx_hook(packet(0x38D, 0, 8, {1: 1}))
|
||||
assert not safety.safety_tx_hook(packet(0x38D, 0, 8, {2: 1 << 4}))
|
||||
assert not safety.safety_tx_hook(packet(0x38D, 0, 8, {3: 1 << 7}))
|
||||
|
||||
|
||||
def test_classic_buttons(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0)
|
||||
safety.init_tests()
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
assert not safety.safety_tx_hook(packet(0x4F1, 0, 4, {0: 1}))
|
||||
assert not safety.safety_tx_hook(packet(0x4F1, 0, 4, {0: 2}))
|
||||
assert not safety.safety_tx_hook(packet(0x4F1, 0, 4, {0: 4}))
|
||||
|
||||
safety.set_controls_allowed(True)
|
||||
assert safety.safety_tx_hook(packet(0x4F1, 0, 4, {0: 1}))
|
||||
assert not safety.safety_tx_hook(packet(0x4F1, 0, 4, {0: 2}))
|
||||
assert safety.safety_tx_hook(packet(0x4F1, 0, 4, {0: 4}))
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
safety.set_cruise_engaged_prev(True)
|
||||
assert safety.safety_tx_hook(packet(0x4F1, 0, 4, {0: 4}))
|
||||
|
||||
|
||||
def test_classic_diagnostic_payload_is_restricted(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.LONG)
|
||||
safety.init_tests()
|
||||
assert safety.safety_tx_hook(packet(0x7D0, 0, 8, dict(enumerate(TESTER_PRESENT))))
|
||||
assert not safety.safety_tx_hook(packet(0x7D0, 0, 8, {0: 3, 1: 0x22}))
|
||||
@@ -0,0 +1,146 @@
|
||||
import pytest
|
||||
|
||||
from iqdbc.car.hyundai.values import HyundaiSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.hyundai_common import TESTER_PRESENT, canfd_accel, canfd_steer, packet
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def safety():
|
||||
return libsafety_py.libsafety
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param", (
|
||||
0,
|
||||
HyundaiSafetyFlags.EV_GAS,
|
||||
HyundaiSafetyFlags.HYBRID_GAS,
|
||||
HyundaiSafetyFlags.LONG,
|
||||
HyundaiSafetyFlags.CAMERA_SCC,
|
||||
HyundaiSafetyFlags.CANFD_LKA_STEERING,
|
||||
HyundaiSafetyFlags.CANFD_ALT_BUTTONS,
|
||||
HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.CANFD_LKA_STEERING_ALT,
|
||||
HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.LONG,
|
||||
HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.CANFD_ALT_BUTTONS,
|
||||
))
|
||||
def test_canfd_safety_configurations_initialize(safety, param):
|
||||
assert safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, param) == 0
|
||||
safety.init_tests()
|
||||
assert safety.get_current_safety_param() == param
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("param", "addr", "length"), (
|
||||
(0, 0x12A, 16),
|
||||
(HyundaiSafetyFlags.CANFD_LKA_STEERING, 0x50, 16),
|
||||
(HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.CANFD_LKA_STEERING_ALT, 0x110, 32),
|
||||
(HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.LONG, 0x12A, 16),
|
||||
))
|
||||
def test_canfd_steering_limits(safety, param, addr, length):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, param)
|
||||
safety.init_tests()
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
assert safety.safety_tx_hook(canfd_steer(addr, length, 0, False))
|
||||
assert not safety.safety_tx_hook(canfd_steer(addr, length, 1))
|
||||
|
||||
safety.set_controls_allowed(True)
|
||||
assert safety.safety_tx_hook(canfd_steer(addr, length, 2))
|
||||
safety.set_desired_torque_last(270)
|
||||
safety.set_rt_torque_last(270)
|
||||
assert safety.safety_tx_hook(canfd_steer(addr, length, 270))
|
||||
assert not safety.safety_tx_hook(canfd_steer(addr, length, 271))
|
||||
assert not safety.safety_tx_hook(canfd_steer(addr, length, -271))
|
||||
|
||||
|
||||
def test_canfd_tx_whitelist_and_buttons(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, 0)
|
||||
safety.init_tests()
|
||||
assert not safety.safety_tx_hook(packet(0x123, 0, 8))
|
||||
assert not safety.safety_tx_hook(packet(0x12A, 1, 16))
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 0, 8, {2: 1}))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 0, 8, {2: 2}))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 0, 8, {2: 4}))
|
||||
|
||||
safety.set_controls_allowed(True)
|
||||
assert safety.safety_tx_hook(packet(0x1CF, 0, 8, {2: 1}))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 0, 8, {2: 2}))
|
||||
assert safety.safety_tx_hook(packet(0x1CF, 0, 8, {2: 4}))
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
safety.set_cruise_engaged_prev(True)
|
||||
assert safety.safety_tx_hook(packet(0x1CF, 0, 8, {2: 4}))
|
||||
|
||||
|
||||
def test_canfd_camera_scc_button_passthrough(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CAMERA_SCC)
|
||||
safety.init_tests()
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
assert safety.safety_tx_hook(packet(0x1CF, 2, 8))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 0, 8))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 2, 8, {2: 1}))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 2, 8, {2: 2}))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 2, 8, {2: 4}))
|
||||
|
||||
safety.set_controls_allowed(True)
|
||||
assert safety.safety_tx_hook(packet(0x1CF, 2, 8, {2: 1}))
|
||||
assert not safety.safety_tx_hook(packet(0x1CF, 2, 8, {2: 2}))
|
||||
assert safety.safety_tx_hook(packet(0x1CF, 2, 8, {2: 4}))
|
||||
|
||||
|
||||
def test_canfd_camera_scc_single_owner_forwarding(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CAMERA_SCC)
|
||||
safety.init_tests()
|
||||
safety.set_timer(1_000_000)
|
||||
|
||||
assert safety.safety_fwd_hook(2, 0x12A) == -1
|
||||
assert safety.safety_fwd_hook(2, 0x1E0) == -1
|
||||
assert safety.safety_fwd_hook(2, 0x1A0) == 0
|
||||
assert safety.safety_fwd_hook(0, 0xEA) == 2
|
||||
assert not safety.safety_tx_hook(packet(0xEA, 2, 24))
|
||||
assert not safety.safety_tx_hook(packet(0x2AF, 2, 8))
|
||||
|
||||
|
||||
def test_canfd_camera_scc_longitudinal_single_owner_forwarding(safety):
|
||||
param = HyundaiSafetyFlags.CAMERA_SCC | HyundaiSafetyFlags.LONG
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, param)
|
||||
safety.init_tests()
|
||||
safety.set_timer(1_000_000)
|
||||
|
||||
assert safety.safety_fwd_hook(2, 0x12A) == -1
|
||||
assert safety.safety_fwd_hook(2, 0x1A0) == -1
|
||||
assert safety.safety_fwd_hook(0, 0x175) == -1
|
||||
assert safety.safety_fwd_hook(0, 0xEA) == 2
|
||||
|
||||
|
||||
def test_canfd_stock_longitudinal_only_allows_cancel(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, 0)
|
||||
safety.init_tests()
|
||||
assert safety.safety_tx_hook(canfd_accel(0, acc_mode=4))
|
||||
assert not safety.safety_tx_hook(canfd_accel(1, acc_mode=4))
|
||||
assert not safety.safety_tx_hook(canfd_accel(0, acc_mode=0))
|
||||
|
||||
|
||||
def test_canfd_longitudinal_accel_limits(safety):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.LONG)
|
||||
safety.init_tests()
|
||||
|
||||
safety.set_controls_allowed(False)
|
||||
assert safety.safety_tx_hook(canfd_accel(0))
|
||||
assert not safety.safety_tx_hook(canfd_accel(1))
|
||||
|
||||
safety.set_controls_allowed(True)
|
||||
for accel in (-400, 0, 250):
|
||||
assert safety.safety_tx_hook(canfd_accel(accel))
|
||||
for accel in (-401, 251):
|
||||
assert not safety.safety_tx_hook(canfd_accel(accel))
|
||||
|
||||
|
||||
def test_canfd_hda2_diagnostic_payload_is_restricted(safety):
|
||||
param = HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.LONG
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, param)
|
||||
safety.init_tests()
|
||||
assert safety.safety_tx_hook(packet(0x730, 1, 8, dict(enumerate(TESTER_PRESENT))))
|
||||
assert not safety.safety_tx_hook(packet(0x730, 1, 8, {0: 3, 1: 0x22}))
|
||||
@@ -0,0 +1,60 @@
|
||||
import pytest
|
||||
|
||||
from iqdbc.car import gen_empty_fingerprint, structs
|
||||
from iqdbc.car.hyundai.interface import CarInterface
|
||||
from iqdbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
@pytest.mark.parametrize("candidate", list(CAR), ids=lambda candidate: candidate.value)
|
||||
@pytest.mark.parametrize("alpha_long", (False, True), ids=("stock_long", "openpilot_long"))
|
||||
def test_controller_frames_match_configured_safety(candidate, alpha_long, monkeypatch, tmp_path):
|
||||
"""Run real controller output through the safety configuration selected for every HKG platform."""
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path / candidate.value))
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
cp = CarInterface.get_params(candidate, fingerprint, [], alpha_long, False, False)
|
||||
cp_iq = CarInterface.get_params_iq(cp, candidate, fingerprint, [], alpha_long, False, False)
|
||||
interface = CarInterface(cp, cp_iq)
|
||||
interface.update([])
|
||||
|
||||
safety_config = cp.safetyConfigs[-1]
|
||||
safety = libsafety_py.libsafety
|
||||
assert safety.set_safety_hooks(safety_config.safetyModel.raw, safety_config.safetyParam) == 0
|
||||
safety.init_tests()
|
||||
safety.set_controls_allowed(True)
|
||||
|
||||
control = structs.CarControl.new_message()
|
||||
control.enabled = True
|
||||
control.latActive = True
|
||||
control.longActive = alpha_long
|
||||
control.actuators.torque = 0.01
|
||||
control.actuators.accel = 0.0
|
||||
|
||||
for frame in range(20):
|
||||
_, can_sends = interface.apply(control.as_reader(), structs.IQCarControl())
|
||||
assert isinstance(can_sends, list)
|
||||
for address, data, bus in can_sends:
|
||||
packet = libsafety_py.make_CANPacket(address, bus, data)
|
||||
rejection = f"{candidate.value} frame {frame}: safety rejected address={address:#x} bus={bus} data={data.hex()}"
|
||||
assert safety.safety_tx_hook(packet), rejection
|
||||
|
||||
|
||||
def test_ev6_camera_scc_has_one_lfa_sender(monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
cp = CarInterface.get_params(CAR.KIA_EV6, fingerprint, [], False, False, False)
|
||||
cp.flags &= ~HyundaiFlags.CANFD_HDA2.value
|
||||
cp.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value
|
||||
cp_iq = CarInterface.get_params_iq(cp, CAR.KIA_EV6, fingerprint, [], False, False, False)
|
||||
interface = CarInterface(cp, cp_iq)
|
||||
interface.update([])
|
||||
|
||||
control = structs.CarControl.new_message()
|
||||
control.enabled = True
|
||||
control.latActive = True
|
||||
control.actuators.torque = 0.01
|
||||
|
||||
for _ in range(20):
|
||||
_, can_sends = interface.apply(control.as_reader(), structs.IQCarControl())
|
||||
assert [(address, bus) for address, _, bus in can_sends if address == 0x12A] == [(0x12A, 0)]
|
||||
assert all(address not in (0xEA, 0x2AF) for address, _, _ in can_sends)
|
||||
85
artifacts/package_runtime/iqdbc/safety/tests/test_mazda.py
Normal file
85
artifacts/package_runtime/iqdbc/safety/tests/test_mazda.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
|
||||
class TestMazdaSafety(common.CarSafetyTest, common.DriverTorqueSteeringSafetyTest):
|
||||
|
||||
TX_MSGS = [[0x243, 0], [0x09d, 0], [0x440, 0]]
|
||||
STANDSTILL_THRESHOLD = .1
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x243, 0x440)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x243, 0x440]}
|
||||
|
||||
MAX_RATE_UP = 10
|
||||
MAX_RATE_DOWN = 25
|
||||
MAX_TORQUE_LOOKUP = [0], [800]
|
||||
|
||||
MAX_RT_DELTA = 300
|
||||
|
||||
DRIVER_TORQUE_ALLOWANCE = 15
|
||||
DRIVER_TORQUE_FACTOR = 1
|
||||
|
||||
# Mazda actually does not set any bit when requesting torque
|
||||
NO_STEER_REQ_BIT = True
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("mazda_2017")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.mazda, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _torque_meas_msg(self, torque):
|
||||
values = {"STEER_TORQUE_MOTOR": torque}
|
||||
return self.packer.make_can_msg_safety("STEER_TORQUE", 0, values)
|
||||
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"STEER_TORQUE_SENSOR": torque}
|
||||
return self.packer.make_can_msg_safety("STEER_TORQUE", 0, values)
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"LKAS_REQUEST": torque}
|
||||
return self.packer.make_can_msg_safety("CAM_LKAS", 0, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"SPEED": speed}
|
||||
return self.packer.make_can_msg_safety("ENGINE_DATA", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"BRAKE_ON": brake}
|
||||
return self.packer.make_can_msg_safety("PEDALS", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"PEDAL_GAS": gas}
|
||||
return self.packer.make_can_msg_safety("ENGINE_DATA", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"CRZ_ACTIVE": enable}
|
||||
return self.packer.make_can_msg_safety("CRZ_CTRL", 0, values)
|
||||
|
||||
def _button_msg(self, resume=False, cancel=False):
|
||||
values = {
|
||||
"CAN_OFF": cancel,
|
||||
"CAN_OFF_INV": (cancel + 1) % 2,
|
||||
"RES": resume,
|
||||
"RES_INV": (resume + 1) % 2,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("CRZ_BTNS", 0, values)
|
||||
|
||||
def test_buttons(self):
|
||||
# only cancel allows while controls not allowed
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._button_msg(cancel=True)))
|
||||
self.assertFalse(self._tx(self._button_msg(resume=True)))
|
||||
|
||||
# do not block resume if we are engaged already
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._button_msg(cancel=True)))
|
||||
self.assertTrue(self._tx(self._button_msg(resume=True)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
132
artifacts/package_runtime/iqdbc/safety/tests/test_nissan.py
Normal file
132
artifacts/package_runtime/iqdbc/safety/tests/test_nissan.py
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.nissan.values import NissanSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
|
||||
# NISSAN_PARAM_IQ_LEAF in safety/modes/nissan.h (IQ safety framework flag)
|
||||
NISSAN_PARAM_IQ_LEAF = 1
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
|
||||
class TestNissanSafety(common.CarSafetyTest, common.AngleSteeringSafetyTest):
|
||||
|
||||
TX_MSGS = [[0x169, 0], [0x2b1, 0], [0x4cc, 0], [0x20b, 2], [0x280, 2]]
|
||||
GAS_PRESSED_THRESHOLD = 3
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x169, 0x2b1, 0x4cc), 2: (0x280,)}
|
||||
FWD_BLACKLISTED_ADDRS = {0: [0x280], 2: [0x169, 0x2b1, 0x4cc]}
|
||||
|
||||
EPS_BUS = 0
|
||||
CRUISE_BUS = 2
|
||||
ACC_MAIN_BUS = 1
|
||||
|
||||
# Angle control limits
|
||||
STEER_ANGLE_MAX = 600 # deg, reasonable limit
|
||||
DEG_TO_CAN = 100
|
||||
|
||||
ANGLE_RATE_BP = [0., 5., 15.]
|
||||
ANGLE_RATE_UP = [5., .8, .15] # windup limit
|
||||
ANGLE_RATE_DOWN = [5., 3.5, .4] # unwind limit
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("nissan_x_trail_2017_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.nissan, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _angle_cmd_msg(self, angle: float, enabled: bool):
|
||||
values = {"DESIRED_ANGLE": angle, "LKA_ACTIVE": 1 if enabled else 0}
|
||||
return self.packer.make_can_msg_safety("LKAS", 0, values)
|
||||
|
||||
def _angle_meas_msg(self, angle: float):
|
||||
values = {"STEER_ANGLE": angle}
|
||||
return self.packer.make_can_msg_safety("STEER_ANGLE_SENSOR", self.EPS_BUS, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"CRUISE_ENABLED": enable}
|
||||
return self.packer.make_can_msg_safety("CRUISE_STATE", self.CRUISE_BUS, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"WHEEL_SPEED_%s" % s: speed * 3.6 for s in ["RR", "RL"]}
|
||||
return self.packer.make_can_msg_safety("WHEEL_SPEEDS_REAR", self.EPS_BUS, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"USER_BRAKE_PRESSED": brake}
|
||||
return self.packer.make_can_msg_safety("DOORS_LIGHTS", self.EPS_BUS, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"GAS_PEDAL": gas}
|
||||
return self.packer.make_can_msg_safety("GAS_PEDAL", self.EPS_BUS, values)
|
||||
|
||||
def _acc_state_msg(self, main_on):
|
||||
values = {"CRUISE_ON": main_on}
|
||||
return self.packer.make_can_msg_safety("PRO_PILOT", self.ACC_MAIN_BUS, values)
|
||||
|
||||
def _acc_button_cmd(self, cancel=0, propilot=0, flw_dist=0, _set=0, res=0):
|
||||
no_button = not any([cancel, propilot, flw_dist, _set, res])
|
||||
values = {"CANCEL_BUTTON": cancel, "PROPILOT_BUTTON": propilot,
|
||||
"FOLLOW_DISTANCE_BUTTON": flw_dist, "SET_BUTTON": _set,
|
||||
"RES_BUTTON": res, "NO_BUTTON_PRESSED": no_button}
|
||||
return self.packer.make_can_msg_safety("CRUISE_THROTTLE", 2, values)
|
||||
|
||||
def test_acc_buttons(self):
|
||||
btns = [
|
||||
("cancel", True),
|
||||
("propilot", False),
|
||||
("flw_dist", False),
|
||||
("_set", False),
|
||||
("res", False),
|
||||
(None, False),
|
||||
]
|
||||
for controls_allowed in (True, False):
|
||||
for btn, should_tx in btns:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
args = {} if btn is None else {btn: 1}
|
||||
tx = self._tx(self._acc_button_cmd(**args))
|
||||
self.assertEqual(tx, should_tx)
|
||||
|
||||
|
||||
class TestNissanSafetyAltEpsBus(TestNissanSafety):
|
||||
"""Altima uses different buses"""
|
||||
|
||||
EPS_BUS = 1
|
||||
CRUISE_BUS = 1
|
||||
ACC_MAIN_BUS = 2
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("nissan_x_trail_2017_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.nissan, NissanSafetyFlags.ALT_EPS_BUS)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestNissanLeafSafety(TestNissanSafety):
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("nissan_leaf_2018_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(NISSAN_PARAM_IQ_LEAF)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.nissan, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"USER_BRAKE_PRESSED": brake}
|
||||
return self.packer.make_can_msg_safety("CRUISE_THROTTLE", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"GAS_PEDAL": gas}
|
||||
return self.packer.make_can_msg_safety("CRUISE_THROTTLE", 0, values)
|
||||
|
||||
def _acc_state_msg(self, main_on):
|
||||
values = {"CRUISE_AVAILABLE": main_on}
|
||||
return self.packer.make_can_msg_safety("CRUISE_THROTTLE", 0, values)
|
||||
|
||||
# TODO: leaf should use its own safety param
|
||||
def test_acc_buttons(self):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
90
artifacts/package_runtime/iqdbc/safety/tests/test_psa.py
Normal file
90
artifacts/package_runtime/iqdbc/safety/tests/test_psa.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
LANE_KEEP_ASSIST = 0x3F2
|
||||
|
||||
|
||||
class TestPsaSafetyBase(common.CarSafetyTest, common.AngleSteeringSafetyTest):
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (LANE_KEEP_ASSIST,)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [LANE_KEEP_ASSIST]}
|
||||
TX_MSGS = [[1010, 0]]
|
||||
|
||||
MAIN_BUS = 0
|
||||
ADAS_BUS = 1
|
||||
CAM_BUS = 2
|
||||
|
||||
STEER_ANGLE_MAX = 390
|
||||
DEG_TO_CAN = 10
|
||||
|
||||
ANGLE_RATE_BP = [0., 5., 25.]
|
||||
ANGLE_RATE_UP = [2.5, 1.5, .2]
|
||||
ANGLE_RATE_DOWN = [5., 2., .3]
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("psa_aee2010_r3")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.psa, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _angle_cmd_msg(self, angle: float, enabled: bool):
|
||||
values = {"SET_ANGLE": angle, "TORQUE_FACTOR": 100 if enabled else 0}
|
||||
return self.packer.make_can_msg_safety("LANE_KEEP_ASSIST", self.MAIN_BUS, values)
|
||||
|
||||
def _angle_meas_msg(self, angle: float):
|
||||
values = {"ANGLE": angle}
|
||||
return self.packer.make_can_msg_safety("STEERING_ALT", self.MAIN_BUS, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"RVV_ACC_ACTIVATION_REQ": enable}
|
||||
return self.packer.make_can_msg_safety("HS2_DAT_MDD_CMD_452", self.ADAS_BUS, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"VITESSE_VEHICULE_ROUES": speed * 3.6}
|
||||
return self.packer.make_can_msg_safety("HS2_DYN_ABR_38D", self.MAIN_BUS, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"P013_MainBrake": brake}
|
||||
return self.packer.make_can_msg_safety("Dat_BSI", self.CAM_BUS, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"P002_Com_rAPP": int(gas * 100)}
|
||||
return self.packer.make_can_msg_safety("Dyn_CMM", self.MAIN_BUS, values)
|
||||
|
||||
def test_rx_hook(self):
|
||||
# speed
|
||||
for _ in range(10):
|
||||
self.assertTrue(self._rx(self._speed_msg(0)))
|
||||
msg = self._speed_msg(0)
|
||||
# invalidate checksum
|
||||
msg[0].data[5] = 0x00
|
||||
self.assertFalse(self._rx(msg))
|
||||
|
||||
# cruise
|
||||
for _ in range(10):
|
||||
self.assertTrue(self._rx(self._pcm_status_msg(0)))
|
||||
msg = self._pcm_status_msg(0)
|
||||
# invalidate checksum
|
||||
msg[0].data[5] = 0x00
|
||||
self.assertFalse(self._rx(msg))
|
||||
msg = self._pcm_status_msg(0)
|
||||
# write to unused payload byte
|
||||
msg[0].data[6] = 0xAB
|
||||
self.assertTrue(self._rx(msg))
|
||||
|
||||
|
||||
class TestPsaStockSafety(TestPsaSafetyBase):
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("psa_aee2010_r3")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.psa, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
146
artifacts/package_runtime/iqdbc/safety/tests/test_rivian.py
Normal file
146
artifacts/package_runtime/iqdbc/safety/tests/test_rivian.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
from iqdbc.car.rivian.values import RivianSafetyFlags
|
||||
from iqdbc.car.rivian.riviancan import checksum as _checksum
|
||||
|
||||
|
||||
def checksum(msg):
|
||||
addr, dat, bus = msg
|
||||
ret = bytearray(dat)
|
||||
|
||||
# ESP_Status
|
||||
if addr == 0x208:
|
||||
ret[0] = _checksum(ret[1:], 0x1D, 0xB1)
|
||||
elif addr == 0x150:
|
||||
ret[0] = _checksum(ret[1:], 0x1D, 0x9A)
|
||||
|
||||
return addr, ret, bus
|
||||
|
||||
|
||||
class TestRivianSafetyBase(common.CarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.LongitudinalAccelSafetyTest,
|
||||
common.VehicleSpeedSafetyTest, common.SecondSpeedSafetyTest):
|
||||
|
||||
TX_MSGS = [[0x120, 0], [0x321, 2], [0x162, 2]]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x120,), 2: (0x321, 0x162)}
|
||||
FWD_BLACKLISTED_ADDRS = {0: [0x321, 0x162], 2: [0x120]}
|
||||
|
||||
MAX_TORQUE_LOOKUP = [9, 17], [350, 250]
|
||||
DYNAMIC_MAX_TORQUE = True
|
||||
MAX_RATE_UP = 3
|
||||
MAX_RATE_DOWN = 5
|
||||
|
||||
MAX_RT_DELTA = 125
|
||||
|
||||
DRIVER_TORQUE_ALLOWANCE = 100
|
||||
DRIVER_TORQUE_FACTOR = 2
|
||||
|
||||
cnt_speed = 0
|
||||
cnt_speed_2 = 0
|
||||
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"EPAS_TorsionBarTorque": torque / 100.0}
|
||||
return self.packer.make_can_msg_safety("EPAS_SystemStatus", 0, values)
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"ACM_lkaStrToqReq": torque, "ACM_lkaActToi": steer_req}
|
||||
return self.packer.make_can_msg_safety("ACM_lkaHbaCmd", 0, values)
|
||||
|
||||
def _speed_msg(self, speed, quality_flag=True):
|
||||
values = {"ESP_Vehicle_Speed": speed * 3.6, "ESP_Status_Counter": self.cnt_speed % 15,
|
||||
"ESP_Vehicle_Speed_Q": 1 if quality_flag else 0}
|
||||
self.__class__.cnt_speed += 1
|
||||
return self.packer.make_can_msg_safety("ESP_Status", 0, values, fix_checksum=checksum)
|
||||
|
||||
def _speed_msg_2(self, speed, quality_flag=True):
|
||||
# Rivian has a dynamic max torque limit based on speed, so it checks two sources
|
||||
return self._user_gas_msg(0, speed, quality_flag)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"iBESP2_BrakePedalApplied": brake}
|
||||
return self.packer.make_can_msg_safety("iBESP2", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas, speed=0, quality_flag=True):
|
||||
values = {"VDM_AcceleratorPedalPosition": gas, "VDM_VehicleSpeed": speed * 3.6,
|
||||
"VDM_PropStatus_Counter": self.cnt_speed_2 % 15, "VDM_VehicleSpeedQ": 1 if quality_flag else 0}
|
||||
self.__class__.cnt_speed_2 += 1
|
||||
return self.packer.make_can_msg_safety("VDM_PropStatus", 0, values, fix_checksum=checksum)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"ACM_FeatureStatus": enable, "ACM_Unkown1": 1}
|
||||
return self.packer.make_can_msg_safety("ACM_Status", 2, values)
|
||||
|
||||
def _accel_msg(self, accel: float):
|
||||
values = {"ACM_AccelerationRequest": accel}
|
||||
return self.packer.make_can_msg_safety("ACM_longitudinalRequest", 0, values)
|
||||
|
||||
def test_wheel_touch(self):
|
||||
# For hiding hold wheel alert on engage
|
||||
for controls_allowed in (True, False):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
values = {
|
||||
"SCCM_WheelTouch_HandsOn": 1 if controls_allowed else 0,
|
||||
"SCCM_WheelTouch_CapacitiveValue": 100 if controls_allowed else 0,
|
||||
"SETME_X52": 100,
|
||||
}
|
||||
self.assertTrue(self._tx(self.packer.make_can_msg_safety("SCCM_WheelTouch", 2, values)))
|
||||
|
||||
def test_rx_hook(self):
|
||||
# checksum, counter, and quality flag checks
|
||||
for quality_flag in (True, False):
|
||||
for msg_type in ("speed", "speed_2"):
|
||||
self.safety.set_controls_allowed(True)
|
||||
# send multiple times to verify counter checks
|
||||
for _ in range(10):
|
||||
if msg_type == "speed":
|
||||
msg = self._speed_msg(0, quality_flag=quality_flag)
|
||||
elif msg_type == "speed_2":
|
||||
msg = self._speed_msg_2(0, quality_flag=quality_flag)
|
||||
|
||||
self.assertEqual(quality_flag, self._rx(msg))
|
||||
self.assertEqual(quality_flag, self.safety.get_controls_allowed())
|
||||
|
||||
# Mess with checksum to make it fail
|
||||
msg[0].data[0] = 0xff
|
||||
self.assertFalse(self._rx(msg))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
|
||||
class TestRivianStockSafety(TestRivianSafetyBase):
|
||||
|
||||
LONGITUDINAL = False
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("rivian_primary_actuator")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.rivian, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_adas_status(self):
|
||||
# For canceling stock ACC
|
||||
for controls_allowed in (True, False):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
for interface_status in range(4):
|
||||
values = {"VDM_AdasInterfaceStatus": interface_status}
|
||||
self.assertTrue(self._tx(self.packer.make_can_msg_safety("VDM_AdasSts", 2, values)))
|
||||
|
||||
|
||||
class TestRivianLongitudinalSafety(TestRivianSafetyBase):
|
||||
|
||||
TX_MSGS = [[0x120, 0], [0x321, 2], [0x160, 0]]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x120, 0x160), 2: (0x321,)}
|
||||
FWD_BLACKLISTED_ADDRS = {0: [0x321], 2: [0x120, 0x160]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("rivian_primary_actuator")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.rivian, RivianSafetyFlags.LONG_CONTROL)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
252
artifacts/package_runtime/iqdbc/safety/tests/test_subaru.py
Normal file
252
artifacts/package_runtime/iqdbc/safety/tests/test_subaru.py
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
import enum
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.subaru.values import SubaruSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
from functools import partial
|
||||
|
||||
|
||||
class SubaruMsg(enum.IntEnum):
|
||||
Brake_Status = 0x13c
|
||||
CruiseControl = 0x240
|
||||
Throttle = 0x40
|
||||
Steering_Torque = 0x119
|
||||
Wheel_Speeds = 0x13a
|
||||
ES_LKAS = 0x122
|
||||
ES_LKAS_ANGLE = 0x124
|
||||
ES_Brake = 0x220
|
||||
ES_Distance = 0x221
|
||||
ES_Status = 0x222
|
||||
ES_DashStatus = 0x321
|
||||
ES_LKAS_State = 0x322
|
||||
ES_Infotainment = 0x323
|
||||
ES_UDS_Request = 0x787
|
||||
ES_HighBeamAssist = 0x22A
|
||||
ES_STATIC_1 = 0x325
|
||||
ES_STATIC_2 = 0x121
|
||||
|
||||
|
||||
SUBARU_MAIN_BUS = 0
|
||||
SUBARU_ALT_BUS = 1
|
||||
SUBARU_CAM_BUS = 2
|
||||
|
||||
|
||||
def lkas_tx_msgs(alt_bus, lkas_msg=SubaruMsg.ES_LKAS):
|
||||
return [[lkas_msg, SUBARU_MAIN_BUS],
|
||||
[SubaruMsg.ES_Distance, alt_bus],
|
||||
[SubaruMsg.ES_DashStatus, SUBARU_MAIN_BUS],
|
||||
[SubaruMsg.ES_LKAS_State, SUBARU_MAIN_BUS],
|
||||
[SubaruMsg.ES_Infotainment, SUBARU_MAIN_BUS]]
|
||||
|
||||
|
||||
def long_tx_msgs(alt_bus):
|
||||
return [[SubaruMsg.ES_Brake, alt_bus],
|
||||
[SubaruMsg.ES_Status, alt_bus]]
|
||||
|
||||
|
||||
def gen2_long_additional_tx_msgs():
|
||||
return [[SubaruMsg.ES_UDS_Request, SUBARU_CAM_BUS],
|
||||
[SubaruMsg.ES_HighBeamAssist, SUBARU_MAIN_BUS],
|
||||
[SubaruMsg.ES_STATIC_1, SUBARU_MAIN_BUS],
|
||||
[SubaruMsg.ES_STATIC_2, SUBARU_MAIN_BUS]]
|
||||
|
||||
|
||||
def fwd_blacklisted_addr(lkas_msg=SubaruMsg.ES_LKAS):
|
||||
return {SUBARU_CAM_BUS: [lkas_msg, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State, SubaruMsg.ES_Infotainment]}
|
||||
|
||||
|
||||
class TestSubaruSafetyBase(common.CarSafetyTest):
|
||||
FLAGS = 0
|
||||
RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
|
||||
SubaruMsg.ES_Infotainment)}
|
||||
FWD_BLACKLISTED_ADDRS = fwd_blacklisted_addr()
|
||||
|
||||
MAX_RT_DELTA = 940
|
||||
|
||||
DRIVER_TORQUE_ALLOWANCE = 60
|
||||
DRIVER_TORQUE_FACTOR = 50
|
||||
|
||||
ALT_MAIN_BUS = SUBARU_MAIN_BUS
|
||||
ALT_CAM_BUS = SUBARU_CAM_BUS
|
||||
|
||||
DEG_TO_CAN = 100
|
||||
|
||||
INACTIVE_GAS = 1818
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("subaru_global_2017_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.subaru, self.FLAGS)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _set_prev_torque(self, t):
|
||||
self.safety.set_desired_torque_last(t)
|
||||
self.safety.set_rt_torque_last(t)
|
||||
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"Steer_Torque_Sensor": torque}
|
||||
return self.packer.make_can_msg_safety("Steering_Torque", 0, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {s: speed for s in ["FR", "FL", "RR", "RL"]}
|
||||
return self.packer.make_can_msg_safety("Wheel_Speeds", self.ALT_MAIN_BUS, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"Brake": brake}
|
||||
return self.packer.make_can_msg_safety("Brake_Status", self.ALT_MAIN_BUS, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"Throttle_Pedal": gas}
|
||||
return self.packer.make_can_msg_safety("Throttle", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"Cruise_Activated": enable}
|
||||
return self.packer.make_can_msg_safety("CruiseControl", self.ALT_MAIN_BUS, values)
|
||||
|
||||
def _lkas_button_msg(self, lkas_pressed=False, lkas_hud=0):
|
||||
values = {"LKAS_Dash_State": 2 if lkas_pressed else lkas_hud}
|
||||
return self.packer.make_can_msg_safety("ES_LKAS_State", SUBARU_CAM_BUS, values)
|
||||
|
||||
def test_enable_control_allowed_with_aol_button(self):
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
for aol_button_press in range(4):
|
||||
with self.subTest("aol_button_press", button_state=aol_button_press):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
self._rx(self._lkas_button_msg(False, aol_button_press))
|
||||
self.assertEqual(enable_aol and aol_button_press in range(1, 4),
|
||||
self.safety.get_controls_allowed_lat())
|
||||
|
||||
|
||||
class TestSubaruStockLongitudinalSafetyBase(TestSubaruSafetyBase):
|
||||
def _cancel_msg(self, cancel, cruise_throttle=0):
|
||||
values = {"Cruise_Cancel": cancel, "Cruise_Throttle": cruise_throttle}
|
||||
return self.packer.make_can_msg_safety("ES_Distance", self.ALT_MAIN_BUS, values)
|
||||
|
||||
def test_cancel_message(self):
|
||||
# test that we can only send the cancel message (ES_Distance) with inactive throttle (1818) and Cruise_Cancel=1
|
||||
for cancel in [True, False]:
|
||||
self._generic_limit_safety_check(partial(self._cancel_msg, cancel), self.INACTIVE_GAS, self.INACTIVE_GAS, 0, 2**12, 1, self.INACTIVE_GAS, cancel)
|
||||
|
||||
|
||||
class TestSubaruLongitudinalSafetyBase(TestSubaruSafetyBase, common.LongitudinalGasBrakeSafetyTest):
|
||||
MIN_GAS = 808
|
||||
MAX_GAS = 3400
|
||||
INACTIVE_GAS = 1818
|
||||
MAX_POSSIBLE_GAS = 2**13
|
||||
|
||||
MIN_BRAKE = 0
|
||||
MAX_BRAKE = 600
|
||||
MAX_POSSIBLE_BRAKE = 2**16
|
||||
|
||||
MIN_RPM = 0
|
||||
MAX_RPM = 3600
|
||||
MAX_POSSIBLE_RPM = 2**13
|
||||
|
||||
FWD_BLACKLISTED_ADDRS = {2: [SubaruMsg.ES_LKAS, SubaruMsg.ES_Brake, SubaruMsg.ES_Distance,
|
||||
SubaruMsg.ES_Status, SubaruMsg.ES_DashStatus,
|
||||
SubaruMsg.ES_LKAS_State, SubaruMsg.ES_Infotainment]}
|
||||
|
||||
def test_rpm_safety_check(self):
|
||||
self._generic_limit_safety_check(self._send_rpm_msg, self.MIN_RPM, self.MAX_RPM, 0, self.MAX_POSSIBLE_RPM, 1)
|
||||
|
||||
def _send_brake_msg(self, brake):
|
||||
values = {"Brake_Pressure": brake}
|
||||
return self.packer.make_can_msg_safety("ES_Brake", self.ALT_MAIN_BUS, values)
|
||||
|
||||
def _send_gas_msg(self, gas):
|
||||
values = {"Cruise_Throttle": gas}
|
||||
return self.packer.make_can_msg_safety("ES_Distance", self.ALT_MAIN_BUS, values)
|
||||
|
||||
def _send_rpm_msg(self, rpm):
|
||||
values = {"Cruise_RPM": rpm}
|
||||
return self.packer.make_can_msg_safety("ES_Status", self.ALT_MAIN_BUS, values)
|
||||
|
||||
|
||||
class TestSubaruTorqueSafetyBase(TestSubaruSafetyBase, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
|
||||
MAX_RATE_UP = 50
|
||||
MAX_RATE_DOWN = 70
|
||||
MAX_TORQUE_LOOKUP = [0], [2047]
|
||||
|
||||
# Safety around steering req bit
|
||||
MIN_VALID_STEERING_FRAMES = 7
|
||||
MAX_INVALID_STEERING_FRAMES = 1
|
||||
STEER_STEP = 2
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"LKAS_Output": torque, "LKAS_Request": steer_req}
|
||||
return self.packer.make_can_msg_safety("ES_LKAS", SUBARU_MAIN_BUS, values)
|
||||
|
||||
|
||||
class TestSubaruGen1TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruTorqueSafetyBase):
|
||||
FLAGS = 0
|
||||
TX_MSGS = lkas_tx_msgs(SUBARU_MAIN_BUS)
|
||||
|
||||
|
||||
class TestSubaruGen2TorqueSafetyBase(TestSubaruTorqueSafetyBase):
|
||||
ALT_MAIN_BUS = SUBARU_ALT_BUS
|
||||
ALT_CAM_BUS = SUBARU_ALT_BUS
|
||||
|
||||
MAX_RATE_UP = 35
|
||||
MAX_RATE_DOWN = 50
|
||||
MAX_TORQUE_LOOKUP = [0], [1500]
|
||||
|
||||
|
||||
class TestSubaruGen2TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase):
|
||||
FLAGS = SubaruSafetyFlags.GEN2
|
||||
TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS)
|
||||
|
||||
|
||||
class TestSubaruGen1LongitudinalSafety(TestSubaruLongitudinalSafetyBase, TestSubaruTorqueSafetyBase):
|
||||
FLAGS = SubaruSafetyFlags.LONG
|
||||
TX_MSGS = lkas_tx_msgs(SUBARU_MAIN_BUS) + long_tx_msgs(SUBARU_MAIN_BUS)
|
||||
RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
|
||||
SubaruMsg.ES_Infotainment, SubaruMsg.ES_Brake, SubaruMsg.ES_Status,
|
||||
SubaruMsg.ES_Distance)}
|
||||
|
||||
|
||||
class TestSubaruGen2LongitudinalSafety(TestSubaruLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase):
|
||||
FLAGS = SubaruSafetyFlags.LONG | SubaruSafetyFlags.GEN2
|
||||
TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS) + long_tx_msgs(SUBARU_ALT_BUS) + gen2_long_additional_tx_msgs()
|
||||
FWD_BLACKLISTED_ADDRS = {2: [SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
|
||||
SubaruMsg.ES_Infotainment]}
|
||||
RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
|
||||
SubaruMsg.ES_Infotainment),
|
||||
SUBARU_ALT_BUS: (SubaruMsg.ES_Brake, SubaruMsg.ES_Status, SubaruMsg.ES_Distance)}
|
||||
|
||||
def _rdbi_msg(self, did: int):
|
||||
return b'\x03\x22' + did.to_bytes(2) + b'\x00\x00\x00\x00'
|
||||
|
||||
def _es_uds_msg(self, msg: bytes):
|
||||
return libsafety_py.make_CANPacket(SubaruMsg.ES_UDS_Request, 2, msg)
|
||||
|
||||
def test_es_uds_message(self):
|
||||
tester_present = b'\x02\x3E\x80\x00\x00\x00\x00\x00'
|
||||
not_tester_present = b"\x03\xAA\xAA\x00\x00\x00\x00\x00"
|
||||
|
||||
button_did = 0x1130
|
||||
|
||||
# Tester present is allowed for gen2 long to keep eyesight disabled
|
||||
self.assertTrue(self._tx(self._es_uds_msg(tester_present)))
|
||||
|
||||
# Non-Tester present is not allowed
|
||||
self.assertFalse(self._tx(self._es_uds_msg(not_tester_present)))
|
||||
|
||||
# Only button_did is allowed to be read via UDS
|
||||
for did in range(0xFFFF):
|
||||
should_tx = (did == button_did)
|
||||
self.assertEqual(self._tx(self._es_uds_msg(self._rdbi_msg(did))), should_tx)
|
||||
|
||||
# any other msg is not allowed
|
||||
for sid in range(0xFF):
|
||||
msg = b'\x03' + sid.to_bytes(1) + b'\x00' * 6
|
||||
self.assertFalse(self._tx(self._es_uds_msg(msg)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.subaru.values import SubaruSafetyFlags
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
|
||||
class TestSubaruPreglobalSafety(common.CarSafetyTest, common.DriverTorqueSteeringSafetyTest):
|
||||
FLAGS = 0
|
||||
DBC = "subaru_outback_2015_generated"
|
||||
TX_MSGS = [[0x161, 0], [0x164, 0]]
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x164, 0x161)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x161, 0x164]}
|
||||
|
||||
MAX_RATE_UP = 50
|
||||
MAX_RATE_DOWN = 70
|
||||
MAX_TORQUE_LOOKUP = [0], [2047]
|
||||
|
||||
MAX_RT_DELTA = 940
|
||||
|
||||
DRIVER_TORQUE_ALLOWANCE = 75
|
||||
DRIVER_TORQUE_FACTOR = 10
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety(self.DBC)
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.subaruPreglobal, self.FLAGS)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _set_prev_torque(self, t):
|
||||
self.safety.set_desired_torque_last(t)
|
||||
self.safety.set_rt_torque_last(t)
|
||||
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"Steer_Torque_Sensor": torque}
|
||||
return self.packer.make_can_msg_safety("Steering_Torque", 0, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
# subaru safety doesn't use the scaled value, so undo the scaling
|
||||
values = {s: speed*0.0592 for s in ["FR", "FL", "RR", "RL"]}
|
||||
return self.packer.make_can_msg_safety("Wheel_Speeds", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"Brake_Pedal": brake}
|
||||
return self.packer.make_can_msg_safety("Brake_Pedal", 0, values)
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"LKAS_Command": torque, "LKAS_Active": steer_req}
|
||||
return self.packer.make_can_msg_safety("ES_LKAS", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"Throttle_Pedal": gas}
|
||||
return self.packer.make_can_msg_safety("Throttle", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"Cruise_Activated": enable}
|
||||
return self.packer.make_can_msg_safety("CruiseControl", 0, values)
|
||||
|
||||
|
||||
class TestSubaruPreglobalReversedDriverTorqueSafety(TestSubaruPreglobalSafety):
|
||||
FLAGS = SubaruSafetyFlags.PREGLOBAL_REVERSED_DRIVER_TORQUE
|
||||
DBC = "subaru_outback_2019_generated"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
502
artifacts/package_runtime/iqdbc/safety/tests/test_tesla.py
Normal file
502
artifacts/package_runtime/iqdbc/safety/tests/test_tesla.py
Normal file
@@ -0,0 +1,502 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
import pytest
|
||||
try:
|
||||
from iqdbc.car.tesla.carcontroller import get_safety_CP
|
||||
from iqdbc.lvbs.car.tesla.values import TeslaSafetyFlagsIQ
|
||||
except ImportError:
|
||||
pytest.skip("requires openpilot dependencies", allow_module_level=True)
|
||||
|
||||
from iqdbc.car.lateral import get_max_angle_delta_vm, get_max_angle_vm
|
||||
from iqdbc.car.tesla.values import CarControllerParams, TeslaSafetyFlags, CANBUS
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.vehicle_model import VehicleModel
|
||||
from iqdbc.can import CANDefine
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety, MAX_SPEED_DELTA, MAX_WRONG_COUNTERS, away_round, round_speed
|
||||
|
||||
MSG_DAS_steeringControl = 0x488
|
||||
MSG_APS_eacMonitor = 0x27d
|
||||
MSG_DAS_Control = 0x2b9
|
||||
MSG_DAS_bodyControls = 0x3E9
|
||||
|
||||
|
||||
def round_angle(apply_angle, can_offset=0):
|
||||
apply_angle_can = (apply_angle + 1638.35) / 0.1 + can_offset
|
||||
# 0.49999_ == 0.5
|
||||
rnd_offset = 1e-5 if apply_angle >= 0 else -1e-5
|
||||
return away_round(apply_angle_can + rnd_offset) * 0.1 - 1638.35
|
||||
|
||||
|
||||
class TestTeslaSafetyBase(common.CarSafetyTest, common.AngleSteeringSafetyTest, common.LongitudinalAccelSafetyTest):
|
||||
SAFETY_PARAM = 0
|
||||
STEER_TYPE_SHIFT = 0 # legacy firmware uses a 2-bit field, one bit up from the 3-bit signal
|
||||
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_APS_eacMonitor)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_APS_eacMonitor]}
|
||||
TX_MSGS = [[MSG_DAS_steeringControl, 0], [MSG_APS_eacMonitor, 0], [MSG_DAS_Control, 0]]
|
||||
|
||||
STANDSTILL_THRESHOLD = 0.1
|
||||
GAS_PRESSED_THRESHOLD = 3
|
||||
|
||||
# Angle control limits
|
||||
STEER_ANGLE_MAX = 360 # deg
|
||||
DEG_TO_CAN = 10
|
||||
|
||||
# Tesla uses get_max_angle_delta_vm and get_max_angle_vm for real lateral accel and jerk limits
|
||||
# TODO: integrate this into AngleSteeringSafetyTest
|
||||
ANGLE_RATE_BP = None
|
||||
ANGLE_RATE_UP = None
|
||||
ANGLE_RATE_DOWN = None
|
||||
|
||||
# Real time limits
|
||||
LATERAL_FREQUENCY = 50 # Hz
|
||||
|
||||
# Long control limits
|
||||
MAX_ACCEL = 2.0
|
||||
MIN_ACCEL = -3.48
|
||||
INACTIVE_ACCEL = 0.0
|
||||
|
||||
cnt_epas = 0
|
||||
cnt_angle_cmd = 0
|
||||
|
||||
packer: CANPackerSafety
|
||||
|
||||
def _get_steer_cmd_angle_max(self, speed):
|
||||
return get_max_angle_vm(max(speed, 1), self.VM, CarControllerParams)
|
||||
|
||||
def setUp(self):
|
||||
self.VM = VehicleModel(get_safety_CP())
|
||||
self.packer = CANPackerSafety("tesla_model3_party")
|
||||
self.define = CANDefine("tesla_model3_party")
|
||||
self.acc_states = {d: v for v, d in self.define.dv["DAS_control"]["DAS_accState"].items()}
|
||||
self.autopark_states = {d: v for v, d in self.define.dv["DI_state"]["DI_autoparkState"].items()}
|
||||
self.active_autopark_states = [self.autopark_states[s] for s in ('ACTIVE', 'COMPLETE', 'SELFPARK_STARTED')]
|
||||
|
||||
self.steer_control_types = {d: v for v, d in self.define.dv["DAS_steeringControl"]["DAS_steeringControlType"].items()}
|
||||
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(0)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.tesla, self.SAFETY_PARAM)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _angle_cmd_msg(self, angle: float, state: bool | int, increment_timer: bool = True, bus: int = 0):
|
||||
values = {"DAS_steeringAngleRequest": angle, "DAS_steeringControlType": int(state) << self.STEER_TYPE_SHIFT}
|
||||
if increment_timer:
|
||||
self.safety.set_timer(self.cnt_angle_cmd * int(1e6 / self.LATERAL_FREQUENCY))
|
||||
self.__class__.cnt_angle_cmd += 1
|
||||
return self.packer.make_can_msg_safety("DAS_steeringControl", bus, values)
|
||||
|
||||
def _angle_meas_msg(self, angle: float, hands_on_level: int = 0, eac_status: int = 1, eac_error_code: int = 0):
|
||||
values = {"EPAS3S_internalSAS": angle, "EPAS3S_handsOnLevel": hands_on_level,
|
||||
"EPAS3S_eacStatus": eac_status, "EPAS3S_eacErrorCode": eac_error_code,
|
||||
"EPAS3S_sysStatusCounter": self.cnt_epas % 16}
|
||||
self.__class__.cnt_epas += 1
|
||||
return self.packer.make_can_msg_safety("EPAS3S_sysStatus", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake, quality_flag: bool = True):
|
||||
values = {"ESP_driverBrakeApply": 2 if brake else 1}
|
||||
if not quality_flag:
|
||||
values["ESP_driverBrakeApply"] = random.choice((0, 3)) # NotInit_orOff, Faulty_SNA
|
||||
return self.packer.make_can_msg_safety("ESP_status", 0, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"DI_vehicleSpeed": speed * 3.6}
|
||||
return self.packer.make_can_msg_safety("DI_speed", 0, values)
|
||||
|
||||
def _speed_msg_2(self, speed, quality_flag=True):
|
||||
values = {"ESP_vehicleSpeed": speed * 3.6, "ESP_wheelSpeedsQF": quality_flag}
|
||||
return self.packer.make_can_msg_safety("ESP_B", 0, values)
|
||||
|
||||
def _vehicle_moving_msg(self, speed: float, quality_flag=True):
|
||||
values = {"ESP_vehicleStandstillSts": 1 if speed <= self.STANDSTILL_THRESHOLD else 0,
|
||||
"ESP_wheelSpeedsQF": quality_flag}
|
||||
return self.packer.make_can_msg_safety("ESP_B", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"DI_accelPedalPos": gas}
|
||||
return self.packer.make_can_msg_safety("DI_systemStatus", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable, autopark_state=0):
|
||||
values = {
|
||||
"DI_cruiseState": 2 if enable else 0,
|
||||
"DI_autoparkState": autopark_state,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("DI_state", 0, values)
|
||||
|
||||
def _long_control_msg(self, set_speed, acc_state=0, jerk_limits=(0, 0), accel_limits=(0, 0), aeb_event=0, bus=0):
|
||||
values = {
|
||||
"DAS_setSpeed": set_speed,
|
||||
"DAS_accState": acc_state,
|
||||
"DAS_aebEvent": aeb_event,
|
||||
"DAS_jerkMin": jerk_limits[0],
|
||||
"DAS_jerkMax": jerk_limits[1],
|
||||
"DAS_accelMin": accel_limits[0],
|
||||
"DAS_accelMax": accel_limits[1],
|
||||
}
|
||||
return self.packer.make_can_msg_safety("DAS_control", bus, values)
|
||||
|
||||
def _accel_msg(self, accel: float):
|
||||
# For common.LongitudinalAccelSafetyTest
|
||||
return self._long_control_msg(10, accel_limits=(accel, max(accel, 0)))
|
||||
|
||||
def test_rx_hook(self):
|
||||
# counter check
|
||||
for msg_type in ("angle", "long", "speed", "speed_2"):
|
||||
# send multiple times to verify counter checks
|
||||
for i in range(10):
|
||||
if msg_type == "angle":
|
||||
msg = self._angle_cmd_msg(0, True, bus=2)
|
||||
elif msg_type == "long":
|
||||
msg = self._long_control_msg(0, bus=2)
|
||||
elif msg_type == "speed":
|
||||
msg = self._speed_msg(0)
|
||||
elif msg_type == "speed_2":
|
||||
msg = self._speed_msg_2(0)
|
||||
|
||||
should_rx = i >= 5
|
||||
if not should_rx:
|
||||
# mess with checksums
|
||||
if msg_type == "angle":
|
||||
msg[0].data[3] = 0
|
||||
elif msg_type == "long":
|
||||
msg[0].data[7] = 0
|
||||
elif msg_type == "speed":
|
||||
msg[0].data[0] = 0
|
||||
elif msg_type == "speed_2":
|
||||
msg[0].data[7] = 0
|
||||
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertEqual(should_rx, self._rx(msg))
|
||||
self.assertEqual(should_rx, self.safety.get_controls_allowed())
|
||||
|
||||
# Send static counters
|
||||
for i in range(MAX_WRONG_COUNTERS + 1):
|
||||
should_rx = i + 1 < MAX_WRONG_COUNTERS
|
||||
self.assertEqual(should_rx, self._rx(msg))
|
||||
self.assertEqual(should_rx, self.safety.get_controls_allowed())
|
||||
|
||||
def test_vehicle_speed_measurements(self):
|
||||
# OVERRIDDEN: 79.1667 is the max speed in m/s
|
||||
self._common_measurement_test(self._speed_msg, 0, 285 / 3.6, 1,
|
||||
self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max)
|
||||
|
||||
def test_rx_hook_speed_mismatch(self):
|
||||
# TODO: overridden because of custom rounding
|
||||
# Tesla relies on speed for lateral limits close to ISO 11270, so it checks two sources
|
||||
for speed in np.arange(0, 40, 0.5):
|
||||
# match signal rounding on CAN
|
||||
speed = away_round(speed / 0.08 * 3.6) * 0.08 / 3.6
|
||||
for speed_delta in np.arange(-5, 5, 0.1):
|
||||
speed_2 = max(speed + speed_delta, 0)
|
||||
speed_2 = away_round(speed_2 * 2 * 3.6) / 2 / 3.6
|
||||
|
||||
# Set controls allowed in between rx since first message can reset it
|
||||
self.assertTrue(self._rx(self._speed_msg(speed)))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._rx(self._speed_msg_2(speed_2)))
|
||||
|
||||
within_delta = abs(speed - speed_2) <= MAX_SPEED_DELTA
|
||||
self.assertEqual(self.safety.get_controls_allowed(), within_delta)
|
||||
|
||||
# Test ESP_B quality flag
|
||||
for quality_flag in (True, False):
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._rx(self._speed_msg(0)))
|
||||
self.assertEqual(quality_flag, self._rx(self._speed_msg_2(0, quality_flag=quality_flag)))
|
||||
self.assertEqual(quality_flag, self.safety.get_controls_allowed())
|
||||
|
||||
def test_rt_limits(self):
|
||||
self._test_rt_limits()
|
||||
|
||||
def test_user_brake_quality_flag(self):
|
||||
for quality_flag in (True, False):
|
||||
msg = self._user_brake_msg(True, quality_flag=quality_flag)
|
||||
self.assertEqual(quality_flag, self._rx(msg))
|
||||
|
||||
def test_steering_wheel_disengage(self):
|
||||
# Tesla disengages when the user forcibly overrides the locked-in angle steering control
|
||||
# Either when the hands on level is high, or if there is a high angle rate fault
|
||||
for hands_on_level in range(4):
|
||||
for eac_status in range(8):
|
||||
for eac_error_code in range(16):
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
should_disengage = hands_on_level >= 3 or (eac_status == 0 and eac_error_code == 9)
|
||||
self.assertTrue(self._rx(self._angle_meas_msg(0, hands_on_level=hands_on_level, eac_status=eac_status,
|
||||
eac_error_code=eac_error_code)))
|
||||
self.assertNotEqual(should_disengage, self.safety.get_controls_allowed())
|
||||
self.assertEqual(should_disengage, self.safety.get_steering_disengage_prev())
|
||||
|
||||
# Should not recover
|
||||
self.assertTrue(self._rx(self._angle_meas_msg(0, hands_on_level=0, eac_status=1, eac_error_code=0)))
|
||||
self.assertNotEqual(should_disengage, self.safety.get_controls_allowed())
|
||||
self.assertFalse(self.safety.get_steering_disengage_prev())
|
||||
|
||||
def test_autopark_summon_while_enabled(self):
|
||||
# We should not respect Autopark that activates while controls are allowed
|
||||
self._rx(self._pcm_status_msg(True, 0))
|
||||
|
||||
self._rx(self._pcm_status_msg(True, self.autopark_states["SELFPARK_STARTED"]))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
|
||||
self.assertTrue(self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
|
||||
|
||||
# We should still not respect Autopark if we disengage cruise
|
||||
self._rx(self._pcm_status_msg(False, self.autopark_states["SELFPARK_STARTED"]))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(0, False)))
|
||||
self.assertTrue(self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
|
||||
|
||||
def test_autopark_summon_behavior(self):
|
||||
for autopark_state in range(16):
|
||||
self._rx(self._pcm_status_msg(False, 0))
|
||||
|
||||
# We shouldn't allow controls if Autopark is an active state
|
||||
autopark_active = autopark_state in self.active_autopark_states
|
||||
self._rx(self._pcm_status_msg(False, autopark_state))
|
||||
self._rx(self._pcm_status_msg(True, autopark_state))
|
||||
self.assertNotEqual(autopark_active, self.safety.get_controls_allowed())
|
||||
|
||||
# We should also start blocking all inactive/active openpilot msgs
|
||||
self.assertNotEqual(autopark_active, self._tx(self._angle_cmd_msg(0, False)))
|
||||
self.assertNotEqual(autopark_active, self._tx(self._angle_cmd_msg(0, True)))
|
||||
self.assertNotEqual(autopark_active, self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
|
||||
self.assertNotEqual(autopark_active or not self.LONGITUDINAL, self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_ON"])))
|
||||
|
||||
# Regain controls when Autopark disables
|
||||
self._rx(self._pcm_status_msg(True, 0))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(0, False)))
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
|
||||
self.assertTrue(self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
|
||||
self.assertEqual(self.LONGITUDINAL, self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_ON"])))
|
||||
|
||||
def test_steering_control_type(self):
|
||||
# Only angle control is allowed (no LANE_KEEP_ASSIST or EMERGENCY_LANE_KEEP)
|
||||
self.safety.set_controls_allowed(True)
|
||||
for steer_control_type in range(4):
|
||||
should_tx = steer_control_type in (self.steer_control_types["NONE"],
|
||||
self.steer_control_types["ANGLE_CONTROL"],
|
||||
self.steer_control_types["LANE_KEEP_ASSIST"])
|
||||
self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(0, state=steer_control_type)))
|
||||
|
||||
def test_stock_lkas_passthrough(self):
|
||||
for control_type in ('ANGLE_CONTROL', 'LANE_KEEP_ASSIST'):
|
||||
no_lkas_msg = self._angle_cmd_msg(0, state=False)
|
||||
no_lkas_msg_cam = self._angle_cmd_msg(0, state=self.steer_control_types['NONE'], bus=2)
|
||||
lkas_msg_cam = self._angle_cmd_msg(0, state=self.steer_control_types[control_type], bus=2)
|
||||
|
||||
self.assertEqual(1, self._rx(no_lkas_msg_cam))
|
||||
self.assertEqual(-1, self.safety.safety_fwd_hook(2, no_lkas_msg_cam.addr))
|
||||
self.assertTrue(self._tx(no_lkas_msg))
|
||||
|
||||
self.assertEqual(1, self._rx(lkas_msg_cam))
|
||||
self.assertEqual(0, self.safety.safety_fwd_hook(2, lkas_msg_cam.addr))
|
||||
self.assertFalse(self._tx(no_lkas_msg))
|
||||
|
||||
def test_fsd_visualization(self):
|
||||
self.safety.set_current_safety_param_iq(TeslaSafetyFlagsIQ.FSD_VISUALIZATION)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.tesla, self.SAFETY_PARAM)
|
||||
self.safety.init_tests()
|
||||
|
||||
stock_msg = self._angle_cmd_msg(0, state=self.steer_control_types['ANGLE_CONTROL'], bus=2)
|
||||
self.assertEqual(-1, self.safety.safety_fwd_hook(2, stock_msg.addr))
|
||||
self.assertEqual(1, self._rx(stock_msg))
|
||||
self.assertEqual(-1, self.safety.safety_fwd_hook(2, stock_msg.addr))
|
||||
|
||||
self.safety.set_controls_allowed(True)
|
||||
iq_msg = self._angle_cmd_msg(0, state=self.steer_control_types['ANGLE_CONTROL'])
|
||||
self.assertTrue(self._tx(iq_msg))
|
||||
|
||||
def test_angle_cmd_when_enabled(self):
|
||||
# We properly test lateral acceleration and jerk below
|
||||
pass
|
||||
|
||||
def test_lateral_accel_limit(self):
|
||||
for speed in np.linspace(0, 40, 100):
|
||||
speed = max(speed, 1)
|
||||
# match DI_vehicleSpeed rounding on CAN
|
||||
speed = round_speed(away_round(speed / 0.08 * 3.6) * 0.08 / 3.6)
|
||||
for sign in (-1, 1):
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._reset_speed_measurement(speed + 1) # safety fudges the speed
|
||||
|
||||
# angle signal can't represent 0, so it biases one unit down
|
||||
angle_unit_offset = -1 if sign == -1 else 0
|
||||
|
||||
# at limit (safety tolerance adds 1)
|
||||
max_angle = round_angle(get_max_angle_vm(speed, self.VM, CarControllerParams), angle_unit_offset + 1) * sign
|
||||
max_angle = np.clip(max_angle, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX)
|
||||
self.safety.set_desired_angle_last(round(max_angle * self.DEG_TO_CAN))
|
||||
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(max_angle, True)))
|
||||
|
||||
# 1 unit above limit
|
||||
max_angle_raw = round_angle(get_max_angle_vm(speed, self.VM, CarControllerParams), angle_unit_offset + 2) * sign
|
||||
max_angle = np.clip(max_angle_raw, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX)
|
||||
self._tx(self._angle_cmd_msg(max_angle, True))
|
||||
|
||||
# at low speeds max angle is above 360, so adding 1 has no effect
|
||||
should_tx = abs(max_angle_raw) >= self.STEER_ANGLE_MAX
|
||||
self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(max_angle, True)))
|
||||
|
||||
def test_lateral_jerk_limit(self):
|
||||
for speed in np.linspace(0, 40, 100):
|
||||
speed = max(speed, 1)
|
||||
# match DI_vehicleSpeed rounding on CAN
|
||||
speed = round_speed(away_round(speed / 0.08 * 3.6) * 0.08 / 3.6)
|
||||
for sign in (-1, 1): # (-1, 1):
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._reset_speed_measurement(speed + 1) # safety fudges the speed
|
||||
self._tx(self._angle_cmd_msg(0, True))
|
||||
|
||||
# angle signal can't represent 0, so it biases one unit down
|
||||
angle_unit_offset = 1 if sign == -1 else 0
|
||||
|
||||
# Stay within limits
|
||||
# Up
|
||||
max_angle_delta = round_angle(get_max_angle_delta_vm(speed, self.VM, CarControllerParams), angle_unit_offset) * sign
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
|
||||
|
||||
# Don't change
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
|
||||
|
||||
# Down
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
|
||||
|
||||
# Inject too high rates
|
||||
# Up
|
||||
max_angle_delta = round_angle(get_max_angle_delta_vm(speed, self.VM, CarControllerParams), angle_unit_offset + 1) * sign
|
||||
self.assertFalse(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
|
||||
|
||||
# Don't change
|
||||
self.safety.set_desired_angle_last(round(max_angle_delta * self.DEG_TO_CAN))
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
|
||||
|
||||
# Down
|
||||
self.assertFalse(self._tx(self._angle_cmd_msg(0, True)))
|
||||
|
||||
# Recover
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
|
||||
|
||||
|
||||
class TestTeslaStockSafety(TestTeslaSafetyBase):
|
||||
|
||||
LONGITUDINAL = False
|
||||
|
||||
def test_cancel(self):
|
||||
for acc_state in range(16):
|
||||
self.safety.set_controls_allowed(True)
|
||||
should_tx = acc_state == self.acc_states["ACC_CANCEL_GENERIC_SILENT"]
|
||||
self.assertFalse(self._tx(self._long_control_msg(0, acc_state=acc_state, accel_limits=(self.MIN_ACCEL, self.MAX_ACCEL))))
|
||||
self.assertEqual(should_tx, self._tx(self._long_control_msg(0, acc_state=acc_state)))
|
||||
|
||||
def test_no_aeb(self):
|
||||
for aeb_event in range(4):
|
||||
should_tx = aeb_event == 0
|
||||
ret = self._tx(self._long_control_msg(10, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"], aeb_event=aeb_event))
|
||||
self.assertEqual(ret, should_tx)
|
||||
|
||||
def test_stock_aeb_no_cancel(self):
|
||||
# No passthrough logic since we always forward DAS_control,
|
||||
# but ensure we can't send cancel cmd while stock AEB is active
|
||||
no_aeb_msg = self._long_control_msg(10, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"], aeb_event=0)
|
||||
no_aeb_msg_cam = self._long_control_msg(10, aeb_event=0, bus=2)
|
||||
aeb_msg_cam = self._long_control_msg(10, aeb_event=1, bus=2)
|
||||
|
||||
# stock system sends no AEB -> no forwarding, and OP is allowed to TX
|
||||
self.assertEqual(1, self._rx(no_aeb_msg_cam))
|
||||
self.assertEqual(0, self.safety.safety_fwd_hook(2, no_aeb_msg_cam.addr))
|
||||
self.assertTrue(self._tx(no_aeb_msg))
|
||||
|
||||
# stock system sends AEB -> forwarding, and OP is not allowed to TX
|
||||
self.assertEqual(1, self._rx(aeb_msg_cam))
|
||||
self.assertEqual(0, self.safety.safety_fwd_hook(2, aeb_msg_cam.addr))
|
||||
self.assertFalse(self._tx(no_aeb_msg))
|
||||
|
||||
|
||||
class TestTeslaLegacyDasSteeringStockSafety(TestTeslaStockSafety):
|
||||
SAFETY_PARAM = TeslaSafetyFlags.LEGACY_DAS_STEERING
|
||||
STEER_TYPE_SHIFT = 1
|
||||
|
||||
|
||||
class TestTeslaLongitudinalSafety(TestTeslaSafetyBase):
|
||||
SAFETY_PARAM = TeslaSafetyFlags.LONG_CONTROL
|
||||
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_APS_eacMonitor, MSG_DAS_Control)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_APS_eacMonitor, MSG_DAS_Control]}
|
||||
|
||||
def test_no_aeb(self):
|
||||
for aeb_event in range(4):
|
||||
self.assertEqual(self._tx(self._long_control_msg(10, aeb_event=aeb_event)), aeb_event == 0)
|
||||
|
||||
def test_stock_aeb_passthrough(self):
|
||||
no_aeb_msg = self._long_control_msg(10, aeb_event=0)
|
||||
no_aeb_msg_cam = self._long_control_msg(10, aeb_event=0, bus=2)
|
||||
aeb_msg_cam = self._long_control_msg(10, aeb_event=1, bus=2)
|
||||
|
||||
# stock system sends no AEB -> no forwarding, and OP is allowed to TX
|
||||
self.assertEqual(1, self._rx(no_aeb_msg_cam))
|
||||
self.assertEqual(-1, self.safety.safety_fwd_hook(2, no_aeb_msg_cam.addr))
|
||||
self.assertTrue(self._tx(no_aeb_msg))
|
||||
|
||||
# stock system sends AEB -> forwarding, and OP is not allowed to TX
|
||||
self.assertEqual(1, self._rx(aeb_msg_cam))
|
||||
self.assertEqual(0, self.safety.safety_fwd_hook(2, aeb_msg_cam.addr))
|
||||
self.assertFalse(self._tx(no_aeb_msg))
|
||||
|
||||
def test_prevent_reverse(self):
|
||||
# Note: Tesla can reverse while at a standstill if both accel_min and accel_max are negative.
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
# accel_min and accel_max are positive
|
||||
self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(1.1, 0.8))))
|
||||
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(1.1, 0.8))))
|
||||
|
||||
# accel_min and accel_max are both zero
|
||||
self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(0, 0))))
|
||||
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0, 0))))
|
||||
|
||||
# accel_min and accel_max have opposing signs
|
||||
self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(-0.8, 1.3))))
|
||||
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0.8, -1.3))))
|
||||
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0, -1.3))))
|
||||
|
||||
# accel_min and accel_max are negative
|
||||
self.assertFalse(self._tx(self._long_control_msg(set_speed=10, accel_limits=(-1.1, -0.6))))
|
||||
self.assertFalse(self._tx(self._long_control_msg(set_speed=0, accel_limits=(-0.6, -1.1))))
|
||||
self.assertFalse(self._tx(self._long_control_msg(set_speed=0, accel_limits=(-0.1, -0.1))))
|
||||
|
||||
|
||||
class TestTeslaLegacyDasSteeringLongitudinalSafety(TestTeslaLongitudinalSafety):
|
||||
SAFETY_PARAM = TeslaSafetyFlags.LONG_CONTROL | TeslaSafetyFlags.LEGACY_DAS_STEERING
|
||||
STEER_TYPE_SHIFT = 1
|
||||
|
||||
|
||||
class TestTeslaVehicleBusSafety(TestTeslaSafetyBase):
|
||||
|
||||
LONGITUDINAL = False
|
||||
|
||||
# With the vehicle bus harness, DAS_bodyControls is also TX'd on bus 1 (blinker MITM)
|
||||
TX_MSGS = [*TestTeslaSafetyBase.TX_MSGS, [MSG_DAS_bodyControls, 1]]
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.packer_adas = CANPackerSafety("tesla_model3_vehicle")
|
||||
self.safety.set_current_safety_param_iq(TeslaSafetyFlagsIQ.HAS_VEHICLE_BUS)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.tesla, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _lkas_button_msg(self, enabled):
|
||||
values = {"UI_activeTouchPoints": 3 if enabled else 0}
|
||||
return self.packer_adas.make_can_msg_safety("UI_status2", CANBUS.vehicle, values)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
540
artifacts/package_runtime/iqdbc/safety/tests/test_toyota.py
Normal file
540
artifacts/package_runtime/iqdbc/safety/tests/test_toyota.py
Normal file
@@ -0,0 +1,540 @@
|
||||
#!/usr/bin/env python3
|
||||
import numpy as np
|
||||
import random
|
||||
import unittest
|
||||
import itertools
|
||||
|
||||
from iqdbc.car.toyota.values import ToyotaSafetyFlags
|
||||
from iqdbc.lvbs.car.toyota.values import ToyotaSafetyFlagsIQ
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety, parameterized_safety_class
|
||||
from iqdbc.safety.tests.gas_interceptor_common import GasInterceptorSafetyTest
|
||||
|
||||
TOYOTA_COMMON_TX_MSGS = [[0x2E4, 0], [0x191, 0], [0x412, 0], [0x343, 0], [0x1D2, 0]] # LKAS + LTA + ACC & PCM cancel cmds
|
||||
TOYOTA_SECOC_TX_MSGS = [[0x131, 0], [0x183, 0]] + TOYOTA_COMMON_TX_MSGS
|
||||
TOYOTA_COMMON_LONG_TX_MSGS = [[0x283, 0], [0x2E6, 0], [0x2E7, 0], [0x33E, 0], [0x344, 0], [0x365, 0], [0x366, 0], [0x4CB, 0], # DSU bus 0
|
||||
[0x128, 1], [0x141, 1], [0x160, 1], [0x161, 1], [0x470, 1], # DSU bus 1
|
||||
[0x411, 0], # PCS_HUD
|
||||
[0x750, 0]] # radar diagnostic address
|
||||
GAS_INTERCEPTOR_TX_MSGS = [[0x200, 0]]
|
||||
|
||||
UNSUPPORTED_DSU = [
|
||||
{"SAFETY_PARAM_IQ": ToyotaSafetyFlagsIQ.LKAS_HUD},
|
||||
{"SAFETY_PARAM_IQ": ToyotaSafetyFlagsIQ.UNSUPPORTED_DSU | ToyotaSafetyFlagsIQ.LKAS_HUD},
|
||||
]
|
||||
|
||||
|
||||
class TestToyotaSafetyBase(common.CarSafetyTest, common.LongitudinalAccelSafetyTest):
|
||||
|
||||
TX_MSGS = TOYOTA_COMMON_TX_MSGS + TOYOTA_COMMON_LONG_TX_MSGS
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x191, 0x412, 0x343)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191, 0x343]}
|
||||
EPS_SCALE = 73
|
||||
|
||||
SAFETY_PARAM_IQ: int = ToyotaSafetyFlagsIQ.LKAS_HUD
|
||||
|
||||
packer: CANPackerSafety
|
||||
safety: libsafety_py.LibSafety
|
||||
|
||||
def _torque_meas_msg(self, torque: int, driver_torque: int | None = None):
|
||||
values = {"STEER_TORQUE_EPS": (torque / self.EPS_SCALE) * 100.}
|
||||
if driver_torque is not None:
|
||||
values["STEER_TORQUE_DRIVER"] = driver_torque
|
||||
return self.packer.make_can_msg_safety("STEER_TORQUE_SENSOR", 0, values)
|
||||
|
||||
# Both torque and angle safety modes test with each other's steering commands
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"STEER_TORQUE_CMD": torque, "STEER_REQUEST": steer_req}
|
||||
return self.packer.make_can_msg_safety("STEERING_LKA", 0, values)
|
||||
|
||||
def _angle_meas_msg(self, angle: float, steer_angle_initializing: bool = False):
|
||||
# This creates a steering torque angle message. Not set on all platforms,
|
||||
# relative to init angle on some older TSS2 platforms. Only to be used with LTA
|
||||
values = {"STEER_ANGLE": angle, "STEER_ANGLE_INITIALIZING": int(steer_angle_initializing)}
|
||||
return self.packer.make_can_msg_safety("STEER_TORQUE_SENSOR", 0, values)
|
||||
|
||||
def _angle_cmd_msg(self, angle: float, enabled: bool):
|
||||
return self._lta_msg(int(enabled), int(enabled), angle, torque_wind_down=100 if enabled else 0)
|
||||
|
||||
def _lta_msg(self, req, req2, angle_cmd, torque_wind_down=100):
|
||||
values = {"STEER_REQUEST": req, "STEER_REQUEST_2": req2, "STEER_ANGLE_CMD": angle_cmd, "TORQUE_WIND_DOWN": torque_wind_down}
|
||||
return self.packer.make_can_msg_safety("STEERING_LTA", 0, values)
|
||||
|
||||
def _accel_msg_343(self, accel, cancel_req=0):
|
||||
values = {"ACCEL_CMD": accel, "CANCEL_REQ": cancel_req}
|
||||
return self.packer.make_can_msg_safety("ACC_CONTROL", 0, values)
|
||||
|
||||
def _accel_msg(self, accel, cancel_req=0):
|
||||
return self._accel_msg_343(accel, cancel_req)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {("WHEEL_SPEED_%s" % n): speed * 3.6 for n in ["FR", "FL", "RR", "RL"]}
|
||||
return self.packer.make_can_msg_safety("WHEEL_SPEEDS", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"BRAKE_PRESSED": brake}
|
||||
return self.packer.make_can_msg_safety("BRAKE_MODULE", 0, values)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
cruise_active = self.safety.get_controls_allowed()
|
||||
values = {"GAS_RELEASED": not gas, "CRUISE_ACTIVE": cruise_active}
|
||||
return self.packer.make_can_msg_safety("PCM_CRUISE", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
values = {"CRUISE_ACTIVE": enable}
|
||||
return self.packer.make_can_msg_safety("PCM_CRUISE", 0, values)
|
||||
|
||||
def _acc_state_msg(self, enabled):
|
||||
msg = "DSU_CRUISE" if self.SAFETY_PARAM_IQ & ToyotaSafetyFlagsIQ.UNSUPPORTED_DSU else "PCM_CRUISE_2"
|
||||
values = {"MAIN_ON": enabled}
|
||||
return self.packer.make_can_msg_safety(msg, 0, values)
|
||||
|
||||
def _lkas_button_msg(self, lkas_button=False, lda_value=None):
|
||||
values = {"LDA_ON_MESSAGE": (1 if lkas_button else 0) if lda_value is None else lda_value}
|
||||
return self.packer.make_can_msg_safety("LKAS_HUD", 2, values)
|
||||
|
||||
def test_enable_control_allowed_with_aol_button(self):
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ | ToyotaSafetyFlagsIQ.LKAS_HUD)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.safety.get_current_safety_param())
|
||||
self.safety.init_tests()
|
||||
for enable_aol in (True, False):
|
||||
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
||||
self.safety.set_aol_params(enable_aol, False, False)
|
||||
|
||||
self._rx(self._lkas_button_msg(False))
|
||||
self.assertEqual(0, self.safety.get_aol_button_press())
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
self._rx(self._lkas_button_msg(True))
|
||||
self.assertEqual(1, self.safety.get_aol_button_press())
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
self._rx(self._lkas_button_msg(False))
|
||||
self.assertEqual(0, self.safety.get_aol_button_press())
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
|
||||
self.safety.set_controls_allowed_lat(False)
|
||||
self._rx(self._lkas_button_msg(False, lda_value=2))
|
||||
self.assertEqual(1, self.safety.get_aol_button_press())
|
||||
self.assertEqual(enable_aol, self.safety.get_controls_allowed_lat())
|
||||
self._rx(self._lkas_button_msg(False))
|
||||
self.safety.set_controls_allowed_lat(False)
|
||||
self.safety.set_aol_params(False, False, False)
|
||||
|
||||
def test_lkas_button_requires_lkas_hud(self):
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ & ~ToyotaSafetyFlagsIQ.LKAS_HUD)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.safety.get_current_safety_param())
|
||||
self.safety.init_tests()
|
||||
self.safety.set_aol_params(True, False, False)
|
||||
button_state = self.safety.get_aol_button_press()
|
||||
self._rx(self._lkas_button_msg(True))
|
||||
self.assertEqual(button_state, self.safety.get_aol_button_press())
|
||||
self.assertFalse(self.safety.get_controls_allowed_lat())
|
||||
|
||||
def test_diagnostics(self, stock_longitudinal: bool = False, ecu_disabled: bool = True):
|
||||
for should_tx, msg in ((False, b"\x6D\x02\x3E\x00\x00\x00\x00\x00"), # fwdCamera tester present
|
||||
(False, b"\x0F\x03\xAA\xAA\x00\x00\x00\x00"), # non-tester present
|
||||
(True, b"\x0F\x02\x3E\x00\x00\x00\x00\x00")):
|
||||
tester_present = libsafety_py.make_CANPacket(0x750, 0, msg)
|
||||
self.assertEqual(should_tx and ecu_disabled and not stock_longitudinal, self._tx(tester_present))
|
||||
|
||||
def test_block_aeb(self, stock_longitudinal: bool = False):
|
||||
for controls_allowed in (True, False):
|
||||
for bad in (True, False):
|
||||
for _ in range(10):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
dat = [random.randint(1, 255) for _ in range(7)]
|
||||
if not bad:
|
||||
dat = [0]*6 + dat[-1:]
|
||||
msg = libsafety_py.make_CANPacket(0x283, 0, bytes(dat))
|
||||
self.assertEqual(not bad and not stock_longitudinal, self._tx(msg))
|
||||
|
||||
# Only allow LTA msgs with no actuation
|
||||
def test_lta_steer_cmd(self):
|
||||
for engaged, req, req2, torque_wind_down, angle in itertools.product([True, False],
|
||||
[0, 1], [0, 1],
|
||||
[0, 50, 100],
|
||||
np.linspace(-20, 20, 5)):
|
||||
self.safety.set_controls_allowed(engaged)
|
||||
|
||||
should_tx = not req and not req2 and angle == 0 and torque_wind_down == 0
|
||||
self.assertEqual(should_tx, self._tx(self._lta_msg(req, req2, angle, torque_wind_down)),
|
||||
f"{req=} {req2=} {angle=} {torque_wind_down=}")
|
||||
|
||||
def test_rx_hook(self):
|
||||
# checksum checks
|
||||
for msg in ["trq", "pcm"]:
|
||||
self.safety.set_controls_allowed(1)
|
||||
if msg == "trq":
|
||||
msg = self._torque_meas_msg(0)
|
||||
if msg == "pcm":
|
||||
msg = self._pcm_status_msg(True)
|
||||
self.assertTrue(self._rx(msg))
|
||||
msg[0].data[4] = 0
|
||||
msg[0].data[5] = 0
|
||||
msg[0].data[6] = 0
|
||||
msg[0].data[7] = 0
|
||||
self.assertFalse(self._rx(msg))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
|
||||
class TestToyotaSafetyGasInterceptorBase(GasInterceptorSafetyTest, TestToyotaSafetyBase):
|
||||
|
||||
TX_MSGS = TOYOTA_COMMON_TX_MSGS + TOYOTA_COMMON_LONG_TX_MSGS + GAS_INTERCEPTOR_TX_MSGS
|
||||
INTERCEPTOR_THRESHOLD = 805
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ | ToyotaSafetyFlagsIQ.GAS_INTERCEPTOR)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.safety.get_current_safety_param())
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_stock_longitudinal(self):
|
||||
# If stock longitudinal is set, the gas interceptor safety param should not be respected
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ | ToyotaSafetyFlagsIQ.GAS_INTERCEPTOR)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.safety.get_current_safety_param() | ToyotaSafetyFlags.STOCK_LONGITUDINAL)
|
||||
self.safety.init_tests()
|
||||
|
||||
# Spot check a few gas interceptor tests: (1) reading interceptor,
|
||||
# (2) behavior around interceptor, and (3) txing interceptor msgs
|
||||
for test in (self.test_prev_gas_interceptor, self.test_no_disengage_on_gas_interceptor,
|
||||
self.test_gas_interceptor_safety_check):
|
||||
with self.subTest(test=test.__name__):
|
||||
with self.assertRaises(AssertionError):
|
||||
test()
|
||||
|
||||
|
||||
@parameterized_safety_class(UNSUPPORTED_DSU)
|
||||
class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
|
||||
|
||||
MAX_RATE_UP = 15
|
||||
MAX_RATE_DOWN = 25
|
||||
MAX_TORQUE_LOOKUP = [0], [1500]
|
||||
MAX_RT_DELTA = 450
|
||||
MAX_TORQUE_ERROR = 350
|
||||
TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conservative for rounding
|
||||
|
||||
# Safety around steering req bit
|
||||
MIN_VALID_STEERING_FRAMES = 17
|
||||
MAX_INVALID_STEERING_FRAMES = 1
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "TestToyotaSafetyTorque":
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_nodsu_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.EPS_SCALE)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
@parameterized_safety_class(UNSUPPORTED_DSU)
|
||||
class TestToyotaSafetyTorqueGasInterceptor(TestToyotaSafetyGasInterceptorBase, TestToyotaSafetyTorque):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "TestToyotaSafetyTorqueGasInterceptor":
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
|
||||
|
||||
class TestToyotaSafetyAngle(TestToyotaSafetyBase, common.AngleSteeringSafetyTest):
|
||||
|
||||
# Angle control limits
|
||||
STEER_ANGLE_MAX = 94.9461 # deg
|
||||
DEG_TO_CAN = 17.452007 # 1 / 0.0573 deg to can
|
||||
|
||||
ANGLE_RATE_BP = [5., 25., 25.]
|
||||
ANGLE_RATE_UP = [0.3, 0.15, 0.15] # windup limit
|
||||
ANGLE_RATE_DOWN = [0.36, 0.26, 0.26] # unwind limit
|
||||
|
||||
MAX_LTA_ANGLE = 94.9461 # PCS faults if commanding above this, deg
|
||||
MAX_MEAS_TORQUE = 1500 # max allowed measured EPS torque before wind down
|
||||
MAX_LTA_DRIVER_TORQUE = 150 # max allowed driver torque before wind down
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_nodsu_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.EPS_SCALE | ToyotaSafetyFlags.LTA)
|
||||
self.safety.init_tests()
|
||||
|
||||
# Only allow LKA msgs with no actuation
|
||||
def test_lka_steer_cmd(self):
|
||||
for engaged, steer_req, torque in itertools.product([True, False],
|
||||
[0, 1],
|
||||
np.linspace(-1500, 1500, 7)):
|
||||
self.safety.set_controls_allowed(engaged)
|
||||
torque = int(torque)
|
||||
self.safety.set_rt_torque_last(torque)
|
||||
self.safety.set_torque_meas(torque, torque)
|
||||
self.safety.set_desired_torque_last(torque)
|
||||
|
||||
should_tx = not steer_req and torque == 0
|
||||
self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(torque, steer_req)))
|
||||
|
||||
def test_lta_steer_cmd(self):
|
||||
"""
|
||||
Tests the LTA steering command message
|
||||
controls_allowed:
|
||||
* STEER_REQUEST and STEER_REQUEST_2 do not mismatch
|
||||
* TORQUE_WIND_DOWN is only set to 0 or 100 when STEER_REQUEST and STEER_REQUEST_2 are both 1
|
||||
* Full torque messages are blocked if either EPS torque or driver torque is above the threshold
|
||||
|
||||
not controls_allowed:
|
||||
* STEER_REQUEST, STEER_REQUEST_2, and TORQUE_WIND_DOWN are all 0
|
||||
"""
|
||||
for controls_allowed in (True, False):
|
||||
for angle in np.arange(-90, 90, 1):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self._reset_angle_measurement(angle)
|
||||
self._set_prev_desired_angle(angle)
|
||||
|
||||
self.assertTrue(self._tx(self._lta_msg(0, 0, angle, 0)))
|
||||
if controls_allowed:
|
||||
# Test the two steer request bits and TORQUE_WIND_DOWN torque wind down signal
|
||||
for req, req2, torque_wind_down in itertools.product([0, 1], [0, 1], [0, 50, 100]):
|
||||
mismatch = not (req or req2) and torque_wind_down != 0
|
||||
should_tx = req == req2 and (torque_wind_down in (0, 100)) and not mismatch
|
||||
self.assertEqual(should_tx, self._tx(self._lta_msg(req, req2, angle, torque_wind_down)))
|
||||
|
||||
# Test max EPS torque and driver override thresholds
|
||||
cases = itertools.product(
|
||||
(0, self.MAX_MEAS_TORQUE - 1, self.MAX_MEAS_TORQUE, self.MAX_MEAS_TORQUE + 1, self.MAX_MEAS_TORQUE * 2),
|
||||
(0, self.MAX_LTA_DRIVER_TORQUE - 1, self.MAX_LTA_DRIVER_TORQUE, self.MAX_LTA_DRIVER_TORQUE + 1, self.MAX_LTA_DRIVER_TORQUE * 2)
|
||||
)
|
||||
|
||||
for eps_torque, driver_torque in cases:
|
||||
for sign in (-1, 1):
|
||||
for _ in range(6):
|
||||
self._rx(self._torque_meas_msg(sign * eps_torque, sign * driver_torque))
|
||||
|
||||
# Toyota adds 1 to EPS torque since it is rounded after EPS factor
|
||||
should_tx = (eps_torque - 1) <= self.MAX_MEAS_TORQUE and driver_torque <= self.MAX_LTA_DRIVER_TORQUE
|
||||
self.assertEqual(should_tx, self._tx(self._lta_msg(1, 1, angle, 100)))
|
||||
self.assertTrue(self._tx(self._lta_msg(1, 1, angle, 0))) # should tx if we wind down torque
|
||||
|
||||
else:
|
||||
# Controls not allowed
|
||||
for req, req2, torque_wind_down in itertools.product([0, 1], [0, 1], [0, 50, 100]):
|
||||
should_tx = not (req or req2) and torque_wind_down == 0
|
||||
self.assertEqual(should_tx, self._tx(self._lta_msg(req, req2, angle, torque_wind_down)))
|
||||
|
||||
def test_angle_measurements(self):
|
||||
"""
|
||||
* Tests angle meas quality flag dictates whether angle measurement is parsed, and if rx is valid
|
||||
* Tests rx hook correctly clips the angle measurement, since it is to be compared to LTA cmd when inactive
|
||||
"""
|
||||
for steer_angle_initializing in (True, False):
|
||||
for angle in np.arange(0, self.STEER_ANGLE_MAX * 2, 1):
|
||||
# If init flag is set, do not rx or parse any angle measurements
|
||||
for a in (angle, -angle, 0, 0, 0, 0):
|
||||
self.assertEqual(not steer_angle_initializing,
|
||||
self._rx(self._angle_meas_msg(a, steer_angle_initializing)))
|
||||
|
||||
final_angle = 0 if steer_angle_initializing else round(angle * self.DEG_TO_CAN)
|
||||
self.assertEqual(self.safety.get_angle_meas_min(), -final_angle)
|
||||
self.assertEqual(self.safety.get_angle_meas_max(), final_angle)
|
||||
|
||||
self._rx(self._angle_meas_msg(0))
|
||||
self.assertEqual(self.safety.get_angle_meas_min(), -final_angle)
|
||||
self.assertEqual(self.safety.get_angle_meas_max(), 0)
|
||||
|
||||
self._rx(self._angle_meas_msg(0))
|
||||
self.assertEqual(self.safety.get_angle_meas_min(), 0)
|
||||
self.assertEqual(self.safety.get_angle_meas_max(), 0)
|
||||
|
||||
|
||||
class TestToyotaSafetyAngleGasInterceptor(TestToyotaSafetyGasInterceptorBase, TestToyotaSafetyAngle):
|
||||
pass
|
||||
|
||||
|
||||
@parameterized_safety_class(UNSUPPORTED_DSU)
|
||||
class TestToyotaAltBrakeSafety(TestToyotaSafetyTorque):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "TestToyotaAltBrakeSafety":
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_new_mc_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.EPS_SCALE | ToyotaSafetyFlags.ALT_BRAKE)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
values = {"BRAKE_PRESSED": brake}
|
||||
return self.packer.make_can_msg_safety("BRAKE_MODULE", 0, values)
|
||||
|
||||
# No LTA message in the DBC
|
||||
def test_lta_steer_cmd(self):
|
||||
pass
|
||||
|
||||
|
||||
@parameterized_safety_class(UNSUPPORTED_DSU)
|
||||
class TestToyotaAltBrakeSafetyGasInterceptor(TestToyotaSafetyGasInterceptorBase, TestToyotaAltBrakeSafety):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "TestToyotaAltBrakeSafetyGasInterceptor":
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
|
||||
# No LTA message in the DBC
|
||||
def test_lta_steer_cmd(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestToyotaStockLongitudinalBase(TestToyotaSafetyBase):
|
||||
|
||||
TX_MSGS = TOYOTA_COMMON_TX_MSGS
|
||||
# Base addresses minus ACC_CONTROL (0x343)
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x191, 0x412)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191]}
|
||||
|
||||
LONGITUDINAL = False
|
||||
|
||||
def test_diagnostics(self, stock_longitudinal: bool = True, ecu_disabled: bool = True):
|
||||
super().test_diagnostics(stock_longitudinal=stock_longitudinal, ecu_disabled=ecu_disabled)
|
||||
|
||||
def test_block_aeb(self, stock_longitudinal: bool = True):
|
||||
super().test_block_aeb(stock_longitudinal=stock_longitudinal)
|
||||
|
||||
def test_acc_cancel(self):
|
||||
"""
|
||||
Regardless of controls allowed, never allow ACC_CONTROL if cancel bit isn't set
|
||||
"""
|
||||
for controls_allowed in [True, False]:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
for accel in np.arange(self.MIN_ACCEL - 1, self.MAX_ACCEL + 1, 0.1):
|
||||
self.assertFalse(self._tx(self._accel_msg_343(accel)))
|
||||
should_tx = np.isclose(accel, self.INACTIVE_ACCEL, atol=0.0001)
|
||||
self.assertEqual(should_tx, self._tx(self._accel_msg_343(accel, cancel_req=1)))
|
||||
|
||||
|
||||
@parameterized_safety_class(UNSUPPORTED_DSU)
|
||||
class TestToyotaStockLongitudinalTorque(TestToyotaStockLongitudinalBase, TestToyotaSafetyTorque):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "TestToyotaStockLongitudinalTorque":
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_nodsu_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.EPS_SCALE | ToyotaSafetyFlags.STOCK_LONGITUDINAL)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestToyotaStockLongitudinalAngle(TestToyotaStockLongitudinalBase, TestToyotaSafetyAngle):
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_nodsu_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota,
|
||||
self.EPS_SCALE | ToyotaSafetyFlags.STOCK_LONGITUDINAL | ToyotaSafetyFlags.LTA)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestToyotaSecOcSafetyBase(TestToyotaSafetyBase):
|
||||
|
||||
TX_MSGS = TOYOTA_SECOC_TX_MSGS
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x191, 0x412, 0x131)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x191, 0x412, 0x131]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_secoc_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota,
|
||||
self.EPS_SCALE | ToyotaSafetyFlags.SECOC)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_diagnostics(self, ecu_disabled: bool = False):
|
||||
super().test_diagnostics(ecu_disabled=ecu_disabled)
|
||||
|
||||
# This platform also has alternate brake and PCM messages, but same naming in the DBC, so same packers work
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"GAS_PEDAL_USER": gas}
|
||||
return self.packer.make_can_msg_safety("GAS_PEDAL", 0, values)
|
||||
|
||||
# This platform sends both STEERING_LTA (same as other Toyota) and STEERING_LTA_2 (SecOC signed)
|
||||
# STEERING_LTA is checked for no-actuation by the base class, STEERING_LTA_2 is checked for no-actuation below
|
||||
|
||||
def _lta_2_msg(self, req, req2, angle_cmd, torque_wind_down=100):
|
||||
values = {"STEER_REQUEST": req, "STEER_REQUEST_2": req2, "STEER_ANGLE_CMD": angle_cmd}
|
||||
return self.packer.make_can_msg_safety("STEERING_LTA_2", 0, values)
|
||||
|
||||
def test_lta_2_steer_cmd(self):
|
||||
for engaged, req, req2, angle in itertools.product([True, False], [0, 1], [0, 1], np.linspace(-20, 20, 5)):
|
||||
self.safety.set_controls_allowed(engaged)
|
||||
|
||||
should_tx = not req and not req2 and angle == 0
|
||||
self.assertEqual(should_tx, self._tx(self._lta_2_msg(req, req2, angle)), f"{req=} {req2=} {angle=}")
|
||||
|
||||
def _accel_msg_183(self, accel):
|
||||
values = {"ACCEL_CMD": accel}
|
||||
return self.packer.make_can_msg_safety("ACC_CONTROL_2", 0, values)
|
||||
|
||||
def _accel_msg(self, accel, cancel_req=0):
|
||||
return self._accel_msg_183(accel)
|
||||
|
||||
|
||||
class TestToyotaSecOcSafetyStockLongitudinal(TestToyotaSecOcSafetyBase, TestToyotaStockLongitudinalBase):
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_secoc_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota,
|
||||
self.EPS_SCALE | ToyotaSafetyFlags.STOCK_LONGITUDINAL | ToyotaSafetyFlags.SECOC)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestToyotaSecOcSafety(TestToyotaSecOcSafetyBase):
|
||||
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x191, 0x412, 0x131, 0x343, 0x183)}
|
||||
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x191, 0x412, 0x131, 0x343, 0x183]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("toyota_secoc_pt_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_current_safety_param_iq(self.SAFETY_PARAM_IQ)
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.EPS_SCALE | ToyotaSafetyFlags.SECOC)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_block_aeb(self, stock_longitudinal: bool = False):
|
||||
for data in (bytes(8), bytes([1] * 8), bytes([255] * 8)):
|
||||
self.assertFalse(self._tx(libsafety_py.make_CANPacket(0x283, 0, data)))
|
||||
|
||||
def test_343_actuation_blocked(self):
|
||||
"""
|
||||
For SecOC cars, longitudinal acceleration must be sent in ACC_CONTROL_2, but all other ACC
|
||||
data remains in ACC_CONTROL. Verify no actuation is sent via ACC_CONTROL.
|
||||
"""
|
||||
for controls_allowed in [True, False]:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
for accel in np.arange(self.MIN_ACCEL - 1, self.MAX_ACCEL + 1, 0.1):
|
||||
should_tx = np.isclose(accel, self.INACTIVE_ACCEL, atol=0.0001)
|
||||
self.assertEqual(should_tx, self._tx(self._accel_msg_343(accel)))
|
||||
self.assertEqual(should_tx, self._tx(self._accel_msg_343(accel, cancel_req=1)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
from iqdbc.car.volkswagen.values import VolkswagenSafetyFlags
|
||||
from iqdbc.car.lateral import ISO_LATERAL_JERK
|
||||
|
||||
MAX_ACCEL = 2.0
|
||||
MIN_ACCEL = -3.5
|
||||
|
||||
MSG_ESC_51 = 0xFC
|
||||
MSG_QFK_01 = 0x13D
|
||||
MSG_Motor_54 = 0x14C
|
||||
MSG_Motor_51 = 0x10B
|
||||
MSG_ACC_18 = 0x14D
|
||||
MSG_MEB_ACC_01 = 0x300
|
||||
MSG_HCA_03 = 0x303
|
||||
MSG_GRA_ACC_01 = 0x12B
|
||||
MSG_LDW_02 = 0x397
|
||||
MSG_MOTOR_14 = 0x3BE
|
||||
MSG_TA_01 = 0x26B
|
||||
MSG_KLR_01 = 0x25D
|
||||
MSG_EA_01 = 0x1A4
|
||||
MSG_EA_02 = 0x1F0
|
||||
MSG_AWV_03 = 0xDB
|
||||
MSG_MEB_DISTANCE_01 = 0x24F
|
||||
MSG_UDS_FUNCTIONAL = 0x700
|
||||
|
||||
|
||||
class TestVolkswagenMebSafetyBase(common.CarSafetyTest):
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_03, MSG_LDW_02, MSG_EA_02),
|
||||
2: (MSG_KLR_01,)}
|
||||
|
||||
CURVATURE_TO_CAN = 149253.7313
|
||||
MAX_CURVATURE = 0.195
|
||||
SEND_RATE = 0.02
|
||||
MAX_POWER = 225
|
||||
POWER_FACTOR = 0.4
|
||||
|
||||
def _speed_msg(self, speed_mps: float):
|
||||
spd_kph = speed_mps * 3.6
|
||||
values = {"HL_Radgeschw": spd_kph, "HR_Radgeschw": spd_kph, "VL_Radgeschw": spd_kph, "VR_Radgeschw": spd_kph}
|
||||
return self.packer.make_can_msg_safety("ESC_51", 0, values)
|
||||
|
||||
def _speed_msg_2(self, speed: float):
|
||||
return None
|
||||
|
||||
def _motor_14_msg(self, brake):
|
||||
values = {"MO_Fahrer_bremst": brake}
|
||||
return self.packer.make_can_msg_safety("Motor_14", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
return self._motor_14_msg(brake)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"Accel_Pedal_Pressure": gas, "TSK_Status": 3}
|
||||
return self.packer.make_can_msg_safety("Motor_51", 0, values)
|
||||
|
||||
def _tsk_status_msg(self, enable, main_switch=True):
|
||||
tsk_status = 3 if enable else (2 if main_switch else 0)
|
||||
values = {"TSK_Status": tsk_status}
|
||||
return self.packer.make_can_msg_safety("Motor_51", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
return self._tsk_status_msg(enable)
|
||||
|
||||
def _curvature_meas_msg(self, curvature):
|
||||
values = {"Curvature": abs(curvature), "Curvature_VZ": curvature > 0}
|
||||
return self.packer.make_can_msg_safety("QFK_01", 0, values)
|
||||
|
||||
def _curvature_cmd_msg(self, curvature, steer_req=True, power=50):
|
||||
values = {
|
||||
"Curvature": abs(curvature),
|
||||
"Curvature_VZ": curvature > 0,
|
||||
"RequestStatus": 4 if steer_req else 0,
|
||||
"Power": power,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("HCA_03", 0, values)
|
||||
|
||||
def _button_msg(self, cancel=0, resume=0, _set=0, bus=2):
|
||||
values = {"GRA_Abbrechen": cancel, "GRA_Tip_Setzen": _set, "GRA_Tip_Wiederaufnahme": resume}
|
||||
return self.packer.make_can_msg_safety("GRA_ACC_01", bus, values)
|
||||
|
||||
def test_curvature_measurements(self):
|
||||
self._rx(self._curvature_meas_msg(0.15))
|
||||
self._rx(self._curvature_meas_msg(-0.1))
|
||||
for _ in range(4):
|
||||
self._rx(self._curvature_meas_msg(0))
|
||||
|
||||
self.assertEqual(int(-0.1 * self.CURVATURE_TO_CAN), self.safety.get_angle_meas_min())
|
||||
self.assertEqual(int(0.15 * self.CURVATURE_TO_CAN), self.safety.get_angle_meas_max())
|
||||
|
||||
self._reset_safety_hooks()
|
||||
self.assertEqual(0, self.safety.get_angle_meas_min())
|
||||
self.assertEqual(0, self.safety.get_angle_meas_max())
|
||||
|
||||
def test_curvature_cmd_limits(self):
|
||||
self._rx(self._speed_msg(0.0))
|
||||
self._rx(self._curvature_meas_msg(0.0))
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
self.safety.set_desired_angle_last(int(self.MAX_CURVATURE * self.CURVATURE_TO_CAN))
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(self.MAX_CURVATURE, True, power=50)))
|
||||
self.safety.set_desired_angle_last(int(self.MAX_CURVATURE * self.CURVATURE_TO_CAN))
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(self.MAX_CURVATURE + 0.05, True, power=50)))
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0.0, False, power=0)))
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0.01, False, power=0)))
|
||||
|
||||
power_over = (self.MAX_POWER + 1) * self.POWER_FACTOR
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0.0, True, power=power_over)))
|
||||
|
||||
def test_curvature_cmd_jerk_limit(self):
|
||||
speed = 10.0
|
||||
for _ in range(common.MAX_SAMPLE_VALS):
|
||||
self._rx(self._speed_msg(speed))
|
||||
self._rx(self._curvature_meas_msg(0.0))
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
max_rate = ISO_LATERAL_JERK / (speed * speed)
|
||||
max_delta = max_rate * self.SEND_RATE
|
||||
prev = 0.0
|
||||
self.safety.set_desired_angle_last(int(prev * self.CURVATURE_TO_CAN))
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(prev + max_delta * 0.9, True, power=50)))
|
||||
self.safety.set_desired_angle_last(int(prev * self.CURVATURE_TO_CAN))
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(prev + max_delta * 3.0, True, power=50)))
|
||||
|
||||
|
||||
class TestVolkswagenMebStockSafety(TestVolkswagenMebSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_03, 0], [MSG_LDW_02, 0], [MSG_GRA_ACC_01, 0], [MSG_GRA_ACC_01, 2],
|
||||
[MSG_EA_01, 0], [MSG_EA_02, 0], [MSG_KLR_01, 0], [MSG_KLR_01, 2]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [MSG_KLR_01],
|
||||
2: [MSG_HCA_03, MSG_LDW_02, MSG_EA_02]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_meb")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMeb, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._button_msg(cancel=1)))
|
||||
self.assertFalse(self._tx(self._button_msg(resume=1)))
|
||||
self.assertFalse(self._tx(self._button_msg(_set=1)))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._button_msg(resume=1)))
|
||||
|
||||
|
||||
class TestVolkswagenMqbEvoStockSafety(TestVolkswagenMebStockSafety):
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_mqbevo")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMqbEvo, VolkswagenSafetyFlags.NO_GAS_OFFSET)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestVolkswagenMebLongSafety(TestVolkswagenMebSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_03, 0], [MSG_LDW_02, 0],
|
||||
[MSG_MEB_ACC_01, 0], [MSG_ACC_18, 0], [MSG_TA_01, 0],
|
||||
[MSG_EA_01, 0], [MSG_EA_02, 0], [MSG_KLR_01, 0], [MSG_KLR_01, 2],
|
||||
[MSG_AWV_03, 0], [MSG_MEB_DISTANCE_01, 0], [MSG_UDS_FUNCTIONAL, 0]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [MSG_KLR_01],
|
||||
2: [MSG_HCA_03, MSG_LDW_02, MSG_EA_02, MSG_MEB_ACC_01, MSG_ACC_18, MSG_TA_01]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_03, MSG_LDW_02, MSG_EA_02, MSG_TA_01, MSG_MEB_ACC_01, MSG_ACC_18),
|
||||
2: (MSG_KLR_01,)}
|
||||
INACTIVE_ACCEL = 3.01
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_meb")
|
||||
self.safety = libsafety_py.libsafety
|
||||
safety_param = VolkswagenSafetyFlags.LONG_CONTROL | VolkswagenSafetyFlags.ALLOW_LONG_ACCEL_WITH_GAS_PRESSED
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMeb, safety_param)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _accel_msg(self, accel):
|
||||
values = {"ACC_Sollbeschleunigung_02": accel}
|
||||
return self.packer.make_can_msg_safety("ACC_18", 0, values)
|
||||
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_enable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_cruise_engaged_prev(self):
|
||||
pass
|
||||
|
||||
def test_set_and_resume_buttons(self):
|
||||
for button in ["set", "resume"]:
|
||||
self.safety.set_controls_allowed(0)
|
||||
self._rx(self._tsk_status_msg(False, main_switch=False))
|
||||
self._rx(self._button_msg(_set=(button == "set"), resume=(button == "resume"), bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self._rx(self._button_msg(_set=(button == "set"), resume=(button == "resume"), bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self._rx(self._button_msg(bus=0))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
def test_cancel_button(self):
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._button_msg(cancel=True, bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_main_switch(self):
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._tsk_status_msg(False, main_switch=False))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_accel_safety_check(self):
|
||||
for controls_allowed in [True, False]:
|
||||
for accel in np.concatenate((np.arange(MIN_ACCEL - 2, MAX_ACCEL + 2, 0.03), [0, self.INACTIVE_ACCEL])):
|
||||
accel = round(accel, 2)
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self.assertTrue(self._tx(self._accel_msg(accel)), (controls_allowed, accel))
|
||||
|
||||
def test_accel_allowed_with_gas_pressed(self):
|
||||
self._rx(self._user_gas_msg(1))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._tx(self._accel_msg(0.5)))
|
||||
|
||||
|
||||
class TestVolkswagenMqbEvoLongSafety(TestVolkswagenMebLongSafety):
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_mqbevo")
|
||||
self.safety = libsafety_py.libsafety
|
||||
safety_param = VolkswagenSafetyFlags.LONG_CONTROL | VolkswagenSafetyFlags.NO_GAS_OFFSET | VolkswagenSafetyFlags.ALLOW_LONG_ACCEL_WITH_GAS_PRESSED
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMqbEvo, safety_param)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import numpy as np
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
from iqdbc.car.volkswagen.values import VolkswagenSafetyFlags
|
||||
|
||||
MAX_ACCEL = 2.0
|
||||
MIN_ACCEL = -2.95 # MLB floor, the shared -3.5 faults the Q5 ACC ECU until an ignition cycle
|
||||
INACTIVE_ACCEL = 3.01 # one increment above the ACC_01 range max
|
||||
INACTIVE_ACCEL_TOLERANCE = 15 # raw counts, matches VOLKSWAGEN_MLB_INACTIVE_ACCEL_TOLERANCE
|
||||
|
||||
MSG_LH_EPS_03 = 0x9F # RX from EPS, for driver steering torque
|
||||
MSG_ACC_01 = 0x109 # TX by OP, ACC acceleration request to the drivetrain coordinator
|
||||
MSG_ESP_03 = 0x103 # RX from ABS, for wheel speeds
|
||||
MSG_MOTOR_03 = 0x105 # RX from ECU, for driver throttle input and driver brake input
|
||||
MSG_ESP_05 = 0x106 # RX from ABS, for brake light state
|
||||
MSG_LS_01 = 0x10B # TX by OP, ACC control buttons for cancel/resume
|
||||
MSG_TSK_02 = 0x10C # RX from ECU, for ACC status from drivetrain coordinator
|
||||
MSG_HCA_01 = 0x126 # TX by OP, Heading Control Assist steering torque
|
||||
MSG_ACC_02 = 0x30C # TX by OP, ACC HUD data to the instrument cluster
|
||||
MSG_LDW_02 = 0x397 # TX by OP, Lane line recognition and text alerts
|
||||
|
||||
|
||||
class TestVolkswagenMlbSafetyBase(common.CarSafetyTest, common.DriverTorqueSteeringSafetyTest):
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01, MSG_LDW_02)}
|
||||
|
||||
MAX_RATE_UP = 9
|
||||
MAX_RATE_DOWN = 10
|
||||
MAX_TORQUE_LOOKUP = [0], [300]
|
||||
MAX_RT_DELTA = 169
|
||||
|
||||
DRIVER_TORQUE_ALLOWANCE = 80
|
||||
DRIVER_TORQUE_FACTOR = 3
|
||||
|
||||
# Wheel speeds _esp_03_msg
|
||||
def _speed_msg(self, speed):
|
||||
values = {"ESP_%s_Radgeschw" % s: speed for s in ["HL", "HR", "VL", "VR"]}
|
||||
return self.packer.make_can_msg_safety("ESP_03", 0, values)
|
||||
|
||||
# Driver brake pressure over threshold
|
||||
def _esp_05_msg(self, brake):
|
||||
values = {"ESP_Fahrer_bremst": brake}
|
||||
return self.packer.make_can_msg_safety("ESP_05", 0, values)
|
||||
|
||||
# Brake pedal switch
|
||||
def _motor_03_msg(self, brake_signal=False, gas_signal=0):
|
||||
values = {
|
||||
"MO_BLS": brake_signal,
|
||||
"MO_Fahrpedalrohwert_01": gas_signal,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("Motor_03", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
return self._motor_03_msg(brake_signal=brake)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
return self._motor_03_msg(gas_signal=gas)
|
||||
|
||||
# ACC engagement status
|
||||
def _tsk_status_msg(self, enable, main_switch=True):
|
||||
values = {"ACC_Status_ACC": 1 if not main_switch else 3 if enable else 2}
|
||||
return self.packer.make_can_msg_safety("ACC_05", 2, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
return self._tsk_status_msg(enable)
|
||||
|
||||
# Driver steering input torque
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"EPS_Lenkmoment": abs(torque), "EPS_VZ_Lenkmoment": torque < 0}
|
||||
return self.packer.make_can_msg_safety("LH_EPS_03", 0, values)
|
||||
|
||||
# openpilot steering output torque
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"HCA_01_LM_Offset": abs(torque),
|
||||
"HCA_01_LM_OffSign": torque < 0,
|
||||
"HCA_01_Sendestatus": steer_req,
|
||||
"HCA_01_Status_HCA": 7 if steer_req else 3}
|
||||
return self.packer.make_can_msg_safety("HCA_01", 0, values)
|
||||
|
||||
# Cruise control buttons
|
||||
def _ls_01_msg(self, cancel=0, resume=0, _set=0, main_switch=1, bus=2):
|
||||
values = {"LS_Abbrechen": cancel, "LS_Tip_Setzen": _set, "LS_Tip_Wiederaufnahme": resume,
|
||||
"LS_Hauptschalter": main_switch}
|
||||
return self.packer.make_can_msg_safety("LS_01", bus, values)
|
||||
|
||||
# Acceleration request to drivetrain coordinator
|
||||
def _acc_01_msg(self, accel):
|
||||
values = {"ACC_Sollbeschleunigung": accel}
|
||||
return self.packer.make_can_msg_safety("ACC_01", 0, values)
|
||||
|
||||
# Verify brake_pressed is true if either the switch or pressure threshold signals are true
|
||||
def test_redundant_brake_signals(self):
|
||||
test_combinations = [(True, True, True), (True, True, False), (True, False, True), (False, False, False)]
|
||||
for brake_pressed, motor_03_signal, esp_05_signal in test_combinations:
|
||||
self._rx(self._motor_03_msg(brake_signal=False))
|
||||
self._rx(self._esp_05_msg(False))
|
||||
self.assertFalse(self.safety.get_brake_pressed_prev())
|
||||
self._rx(self._motor_03_msg(brake_signal=motor_03_signal))
|
||||
self._rx(self._esp_05_msg(esp_05_signal))
|
||||
self.assertEqual(brake_pressed, self.safety.get_brake_pressed_prev(),
|
||||
f"expected {brake_pressed=} with {motor_03_signal=} and {esp_05_signal=}")
|
||||
|
||||
def test_torque_measurements(self):
|
||||
# TODO: make this test work with all cars
|
||||
self._rx(self._torque_driver_msg(50))
|
||||
self._rx(self._torque_driver_msg(-50))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
|
||||
self.assertEqual(-50, self.safety.get_torque_driver_min())
|
||||
self.assertEqual(50, self.safety.get_torque_driver_max())
|
||||
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self.assertEqual(0, self.safety.get_torque_driver_max())
|
||||
self.assertEqual(-50, self.safety.get_torque_driver_min())
|
||||
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self.assertEqual(0, self.safety.get_torque_driver_max())
|
||||
self.assertEqual(0, self.safety.get_torque_driver_min())
|
||||
|
||||
|
||||
class TestVolkswagenMlbStockSafety(TestVolkswagenMlbSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_01, 0], [MSG_LDW_02, 0], [MSG_LS_01, 0], [MSG_LS_01, 2]]
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_01, MSG_LDW_02]}
|
||||
FWD_BUS_LOOKUP = {0: 2, 2: 0}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_mlb")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMlb, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._ls_01_msg(cancel=1)))
|
||||
self.assertFalse(self._tx(self._ls_01_msg(resume=1)))
|
||||
self.assertFalse(self._tx(self._ls_01_msg(_set=1)))
|
||||
# do not block resume if we are engaged already
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._ls_01_msg(resume=1)))
|
||||
|
||||
def test_cancel_button(self):
|
||||
# Disable on rising edge of cancel button
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._ls_01_msg(cancel=True, bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after cancel")
|
||||
|
||||
|
||||
class TestVolkswagenMlbLongSafety(TestVolkswagenMlbSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_01, 0], [MSG_LDW_02, 0], [MSG_ACC_01, 0], [MSG_ACC_02, 0]]
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_01, MSG_LDW_02, MSG_ACC_01, MSG_ACC_02]}
|
||||
FWD_BUS_LOOKUP = {0: 2, 2: 0}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01, MSG_LDW_02, MSG_ACC_01, MSG_ACC_02)}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_mlb")
|
||||
self.safety = libsafety_py.libsafety
|
||||
safety_param = VolkswagenSafetyFlags.LONG_CONTROL | VolkswagenSafetyFlags.ALLOW_LONG_ACCEL_WITH_GAS_PRESSED
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMlb, safety_param)
|
||||
self.safety.init_tests()
|
||||
|
||||
# stock cruise controls are entirely bypassed under openpilot longitudinal control
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_enable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_cruise_engaged_prev(self):
|
||||
pass
|
||||
|
||||
def test_set_and_resume_buttons(self):
|
||||
for button in ["set", "resume"]:
|
||||
# ACC main switch must be on, engage on falling edge
|
||||
self.safety.set_controls_allowed(0)
|
||||
self._rx(self._ls_01_msg(_set=(button == "set"), resume=(button == "resume"), main_switch=0, bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} with main switch off")
|
||||
self._rx(self._ls_01_msg(main_switch=0, bus=0))
|
||||
self._rx(self._ls_01_msg(_set=(button == "set"), resume=(button == "resume"), bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} rising edge")
|
||||
self._rx(self._ls_01_msg(bus=0))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), f"controls not allowed on {button} falling edge")
|
||||
|
||||
def test_main_switch(self):
|
||||
# Disable as soon as the ACC main switch turns off
|
||||
self._rx(self._ls_01_msg(bus=0))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._ls_01_msg(main_switch=0, bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after ACC main switch off")
|
||||
|
||||
@staticmethod
|
||||
def _acc_01_accel_on_wire(accel):
|
||||
# ACC_Sollbeschleunigung: 11 bits, 0.005 m/s^2 per count, offset -7.22, scaled by 1000 in safety
|
||||
raw = round((accel + 7.22) / 0.005) & 0x7FF
|
||||
return raw * 5 - 7220
|
||||
|
||||
def test_accel_safety_check(self):
|
||||
for controls_allowed in [True, False]:
|
||||
for accel in np.concatenate((np.arange(MIN_ACCEL - 2, MAX_ACCEL + 2, 0.03), [0, INACTIVE_ACCEL])):
|
||||
accel = round(accel, 2)
|
||||
on_wire = self._acc_01_accel_on_wire(accel)
|
||||
is_inactive_accel = on_wire == 0 or abs(on_wire - 3010) <= INACTIVE_ACCEL_TOLERANCE
|
||||
send = (controls_allowed and MIN_ACCEL <= accel <= MAX_ACCEL) or is_inactive_accel
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self.assertEqual(send, self._tx(self._acc_01_msg(accel)), (controls_allowed, accel))
|
||||
|
||||
def test_accel_allowed_with_gas_pressed(self):
|
||||
self._rx(self._user_gas_msg(1))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._tx(self._acc_01_msg(0.5)))
|
||||
|
||||
|
||||
class TestVolkswagenMlbNoEcanSafety(TestVolkswagenMlbSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_01, 1], [MSG_LDW_02, 1], [MSG_LS_01, 1]]
|
||||
FWD_BLACKLISTED_ADDRS = {}
|
||||
FWD_BUS_LOOKUP = {0: 2, 2: 0}
|
||||
RELAY_MALFUNCTION_ADDRS = {1: (MSG_HCA_01, MSG_LDW_02)}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_mlb")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMlb, VolkswagenSafetyFlags.MLB_NO_ECAN)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
values = {"ESP_%s_Radgeschw" % s: speed for s in ["HL", "HR", "VL", "VR"]}
|
||||
return self.packer.make_can_msg_safety("ESP_03", 1, values)
|
||||
|
||||
def _esp_05_msg(self, brake):
|
||||
values = {"ESP_Fahrer_bremst": brake}
|
||||
return self.packer.make_can_msg_safety("ESP_05", 1, values)
|
||||
|
||||
def _motor_03_msg(self, brake_signal=False, gas_signal=0):
|
||||
values = {"MO_BLS": brake_signal, "MO_Fahrpedalrohwert_01": gas_signal}
|
||||
return self.packer.make_can_msg_safety("Motor_03", 1, values)
|
||||
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"EPS_Lenkmoment": abs(torque), "EPS_VZ_Lenkmoment": torque < 0}
|
||||
return self.packer.make_can_msg_safety("LH_EPS_03", 1, values)
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"HCA_01_LM_Offset": abs(torque),
|
||||
"HCA_01_LM_OffSign": torque < 0,
|
||||
"HCA_01_Sendestatus": steer_req,
|
||||
"HCA_01_Status_HCA": 7 if steer_req else 3}
|
||||
return self.packer.make_can_msg_safety("HCA_01", 1, values)
|
||||
|
||||
def _ls_01_msg(self, cancel=0, resume=0, _set=0, main_switch=1, bus=1):
|
||||
values = {"LS_Abbrechen": cancel, "LS_Tip_Setzen": _set, "LS_Tip_Wiederaufnahme": resume,
|
||||
"LS_Hauptschalter": main_switch}
|
||||
return self.packer.make_can_msg_safety("LS_01", bus, values)
|
||||
|
||||
def _tsk_status_msg(self, enable, main_switch=True):
|
||||
values = {"TSK_Status": 1 if enable else 0 if main_switch else 3}
|
||||
return self.packer.make_can_msg_safety("TSK_02", 1, values)
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._ls_01_msg(cancel=1)))
|
||||
self.assertFalse(self._tx(self._ls_01_msg(resume=1)))
|
||||
self.assertFalse(self._tx(self._ls_01_msg(_set=1)))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._ls_01_msg(resume=1)))
|
||||
|
||||
def test_cancel_button(self):
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._ls_01_msg(cancel=True))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after cancel")
|
||||
|
||||
def test_ecan_bus_is_ignored(self):
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self.packer.make_can_msg_safety("LS_01", 0, {"LS_Abbrechen": 1, "LS_Hauptschalter": 1}))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), "bus 0 cancel disengaged a no-ECAN car")
|
||||
self._rx(self.packer.make_can_msg_safety("LH_EPS_03", 0, {"EPS_Lenkmoment": 200, "EPS_VZ_Lenkmoment": 0}))
|
||||
self.assertEqual(0, self.safety.get_torque_driver_max(), "bus 0 EPS torque reached the driver sample")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import numpy as np
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
from iqdbc.car.volkswagen.values import VolkswagenSafetyFlags
|
||||
|
||||
MAX_ACCEL = 2.0
|
||||
MIN_ACCEL = -3.5
|
||||
|
||||
MSG_ESP_19 = 0xB2 # RX from ABS, for wheel speeds
|
||||
MSG_LH_EPS_03 = 0x9F # RX from EPS, for driver steering torque
|
||||
MSG_ESP_05 = 0x106 # RX from ABS, for brake light state
|
||||
MSG_TSK_06 = 0x120 # RX from ECU, for ACC status from drivetrain coordinator
|
||||
MSG_MOTOR_20 = 0x121 # RX from ECU, for driver throttle input
|
||||
MSG_ACC_06 = 0x122 # TX by OP, ACC control instructions to the drivetrain coordinator
|
||||
MSG_HCA_01 = 0x126 # TX by OP, Heading Control Assist steering torque
|
||||
MSG_GRA_ACC_01 = 0x12B # TX by OP, ACC control buttons for cancel/resume
|
||||
MSG_ACC_07 = 0x12E # TX by OP, ACC control instructions to the drivetrain coordinator
|
||||
MSG_ACC_02 = 0x30C # TX by OP, ACC HUD data to the instrument cluster
|
||||
MSG_LDW_02 = 0x397 # TX by OP, Lane line recognition and text alerts
|
||||
|
||||
|
||||
MSG_MQB_APD_1 = 0x6A0
|
||||
|
||||
|
||||
class TestVolkswagenMqbSafetyBase(common.CarSafetyTest):
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01, MSG_LDW_02), 2: (MSG_LH_EPS_03,)}
|
||||
|
||||
MAX_RATE_UP = 4
|
||||
MAX_RATE_DOWN = 10
|
||||
MAX_TORQUE_LOOKUP = [0], [300]
|
||||
MAX_RT_DELTA = 75
|
||||
|
||||
DRIVER_TORQUE_ALLOWANCE = 80
|
||||
DRIVER_TORQUE_FACTOR = 3
|
||||
|
||||
# Wheel speeds _esp_19_msg
|
||||
def _speed_msg(self, speed):
|
||||
values = {"ESP_%s_Radgeschw_02" % s: speed for s in ["HL", "HR", "VL", "VR"]}
|
||||
return self.packer.make_can_msg_safety("ESP_19", 0, values)
|
||||
|
||||
# Driver brake pressure over threshold
|
||||
def _esp_05_msg(self, brake):
|
||||
values = {"ESP_Fahrer_bremst": brake}
|
||||
return self.packer.make_can_msg_safety("ESP_05", 0, values)
|
||||
|
||||
# Brake pedal switch
|
||||
def _motor_14_msg(self, brake):
|
||||
values = {"MO_Fahrer_bremst": brake}
|
||||
return self.packer.make_can_msg_safety("Motor_14", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
return self._motor_14_msg(brake)
|
||||
|
||||
# Driver throttle input
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"MO_Fahrpedalrohwert_01": gas}
|
||||
return self.packer.make_can_msg_safety("Motor_20", 0, values)
|
||||
|
||||
# ACC engagement status
|
||||
def _tsk_status_msg(self, enable, main_switch=True):
|
||||
if main_switch:
|
||||
tsk_status = 3 if enable else 2
|
||||
else:
|
||||
tsk_status = 0
|
||||
values = {"TSK_Status": tsk_status}
|
||||
return self.packer.make_can_msg_safety("TSK_06", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
return self._tsk_status_msg(enable)
|
||||
|
||||
# Driver steering input torque
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"EPS_Lenkmoment": abs(torque), "EPS_VZ_Lenkmoment": torque < 0}
|
||||
return self.packer.make_can_msg_safety("LH_EPS_03", 0, values)
|
||||
|
||||
# openpilot steering output torque
|
||||
def _torque_cmd_msg(self, torque, steer_req=1):
|
||||
values = {"HCA_01_LM_Offset": abs(torque), "HCA_01_LM_OffSign": torque < 0, "HCA_01_Sendestatus": steer_req}
|
||||
return self.packer.make_can_msg_safety("HCA_01", 0, values)
|
||||
|
||||
# Cruise control buttons
|
||||
def _gra_acc_01_msg(self, cancel=0, resume=0, _set=0, bus=2):
|
||||
values = {"GRA_Abbrechen": cancel, "GRA_Tip_Setzen": _set, "GRA_Tip_Wiederaufnahme": resume}
|
||||
return self.packer.make_can_msg_safety("GRA_ACC_01", bus, values)
|
||||
|
||||
# Acceleration request to drivetrain coordinator
|
||||
def _acc_06_msg(self, accel):
|
||||
values = {"ACC_Sollbeschleunigung_02": accel}
|
||||
return self.packer.make_can_msg_safety("ACC_06", 0, values)
|
||||
|
||||
# Acceleration request to drivetrain coordinator
|
||||
def _acc_07_msg(self, accel, secondary_accel=3.02):
|
||||
values = {"ACC_Sollbeschleunigung_02": accel, "ACC_Folgebeschl": secondary_accel}
|
||||
return self.packer.make_can_msg_safety("ACC_07", 0, values)
|
||||
|
||||
# Verify brake_pressed is true if either the switch or pressure threshold signals are true
|
||||
def test_redundant_brake_signals(self):
|
||||
test_combinations = [(True, True, True), (True, True, False), (True, False, True), (False, False, False)]
|
||||
for brake_pressed, motor_14_signal, esp_05_signal in test_combinations:
|
||||
self._rx(self._motor_14_msg(False))
|
||||
self._rx(self._esp_05_msg(False))
|
||||
self.assertFalse(self.safety.get_brake_pressed_prev())
|
||||
self._rx(self._motor_14_msg(motor_14_signal))
|
||||
self._rx(self._esp_05_msg(esp_05_signal))
|
||||
self.assertEqual(brake_pressed, self.safety.get_brake_pressed_prev(),
|
||||
f"expected {brake_pressed=} with {motor_14_signal=} and {esp_05_signal=}")
|
||||
|
||||
def test_torque_measurements(self):
|
||||
# TODO: make this test work with all cars
|
||||
self._rx(self._torque_driver_msg(50))
|
||||
self._rx(self._torque_driver_msg(-50))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
|
||||
self.assertEqual(-50, self.safety.get_torque_driver_min())
|
||||
self.assertEqual(50, self.safety.get_torque_driver_max())
|
||||
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self.assertEqual(0, self.safety.get_torque_driver_max())
|
||||
self.assertEqual(-50, self.safety.get_torque_driver_min())
|
||||
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self.assertEqual(0, self.safety.get_torque_driver_max())
|
||||
self.assertEqual(0, self.safety.get_torque_driver_min())
|
||||
|
||||
|
||||
class TestVolkswagenMqbStockSafety(TestVolkswagenMqbSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_01, 0], [MSG_LDW_02, 0], [MSG_LH_EPS_03, 2], [MSG_GRA_ACC_01, 0], [MSG_GRA_ACC_01, 2], [MSG_MQB_APD_1, 1]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [MSG_LH_EPS_03], 2: [MSG_HCA_01, MSG_LDW_02]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_mqb")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagen, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._gra_acc_01_msg(cancel=1)))
|
||||
self.assertFalse(self._tx(self._gra_acc_01_msg(resume=1)))
|
||||
self.assertFalse(self._tx(self._gra_acc_01_msg(_set=1)))
|
||||
# do not block resume if we are engaged already
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._gra_acc_01_msg(resume=1)))
|
||||
|
||||
|
||||
class TestVolkswagenMqbLongSafety(TestVolkswagenMqbSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_01, 0], [MSG_LDW_02, 0], [MSG_LH_EPS_03, 2], [MSG_ACC_02, 0], [MSG_ACC_06, 0], [MSG_ACC_07, 0], [MSG_MQB_APD_1, 1]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [MSG_LH_EPS_03], 2: [MSG_HCA_01, MSG_LDW_02, MSG_ACC_02, MSG_ACC_06, MSG_ACC_07]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01, MSG_LDW_02, MSG_ACC_02, MSG_ACC_06, MSG_ACC_07), 2: (MSG_LH_EPS_03,)}
|
||||
INACTIVE_ACCEL = 3.01
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_mqb")
|
||||
self.safety = libsafety_py.libsafety
|
||||
safety_param = VolkswagenSafetyFlags.LONG_CONTROL | VolkswagenSafetyFlags.ALLOW_LONG_ACCEL_WITH_GAS_PRESSED
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagen, safety_param)
|
||||
self.safety.init_tests()
|
||||
|
||||
# stock cruise controls are entirely bypassed under openpilot longitudinal control
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_enable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_cruise_engaged_prev(self):
|
||||
pass
|
||||
|
||||
def test_set_and_resume_buttons(self):
|
||||
for button in ["set", "resume"]:
|
||||
# ACC main switch must be on, engage on falling edge
|
||||
self.safety.set_controls_allowed(0)
|
||||
self._rx(self._tsk_status_msg(False, main_switch=False))
|
||||
self._rx(self._gra_acc_01_msg(_set=(button == "set"), resume=(button == "resume"), bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} with main switch off")
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self._rx(self._gra_acc_01_msg(_set=(button == "set"), resume=(button == "resume"), bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} rising edge")
|
||||
self._rx(self._gra_acc_01_msg(bus=0))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), f"controls not allowed on {button} falling edge")
|
||||
|
||||
def test_cancel_button(self):
|
||||
# Disable on rising edge of cancel button
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._gra_acc_01_msg(cancel=True, bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after cancel")
|
||||
|
||||
def test_main_switch(self):
|
||||
# Disable as soon as main switch turns off
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._tsk_status_msg(False, main_switch=False))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after ACC main switch off")
|
||||
|
||||
def test_accel_safety_check(self):
|
||||
for controls_allowed in [True, False]:
|
||||
for accel in np.concatenate((np.arange(MIN_ACCEL - 2, MAX_ACCEL + 2, 0.03), [0, self.INACTIVE_ACCEL])):
|
||||
accel = round(accel, 2)
|
||||
is_inactive_accel = accel == self.INACTIVE_ACCEL
|
||||
send = (controls_allowed and MIN_ACCEL <= accel <= MAX_ACCEL) or is_inactive_accel
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self.assertEqual(send, self._tx(self._acc_06_msg(accel)), (controls_allowed, accel))
|
||||
self.assertEqual(send, self._tx(self._acc_07_msg(accel)), (controls_allowed, accel))
|
||||
both_send = (controls_allowed and MIN_ACCEL <= accel <= MAX_ACCEL) or is_inactive_accel
|
||||
self.assertEqual(both_send, self._tx(self._acc_07_msg(accel, secondary_accel=accel)), (controls_allowed, accel))
|
||||
|
||||
def test_accel_allowed_with_gas_pressed(self):
|
||||
self._rx(self._user_gas_msg(1))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._tx(self._acc_06_msg(0.5)))
|
||||
self.assertTrue(self._tx(self._acc_07_msg(0.5)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
from iqdbc.car.volkswagen.values import VolkswagenSafetyFlags
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.safety.tests.libsafety import libsafety_py
|
||||
import iqdbc.safety.tests.common as common
|
||||
from iqdbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
MSG_LENKHILFE_3 = 0x0D0 # RX from EPS, for steering angle and driver steering torque
|
||||
MSG_HCA_1 = 0x0D2 # TX by OP, Heading Control Assist steering torque
|
||||
MSG_BREMSE_1 = 0x1A0 # RX from ABS, for ego speed
|
||||
MSG_MOTOR_2 = 0x288 # RX from ECU, for CC state and brake switch state
|
||||
MSG_ACC_SYSTEM = 0x368 # TX by OP, longitudinal acceleration controls
|
||||
MSG_MOTOR_3 = 0x380 # RX from ECU, for driver throttle input
|
||||
MSG_GRA_NEU = 0x38A # TX by OP, ACC control buttons for cancel/resume
|
||||
MSG_MOTOR_5 = 0x480 # RX from ECU, for ACC main switch state
|
||||
MSG_ACC_GRA_ANZEIGE = 0x56A # TX by OP, ACC HUD
|
||||
MSG_LDW_1 = 0x5BE # TX by OP, Lane line recognition and text alerts
|
||||
MSG_BLINKMODI_02 = 0x0AA # TX by OP, turn signal control
|
||||
MSG_APD_1 = 0x3D6 # TX by OP, CarParams
|
||||
MSG_IQ = 0x6A1 # TX by OP
|
||||
|
||||
|
||||
class TestVolkswagenPqSafetyBase(common.CarSafetyTest):
|
||||
cruise_engaged = False
|
||||
tsk_status = False
|
||||
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_1, MSG_LDW_1)}
|
||||
|
||||
MAX_RATE_UP = 6
|
||||
MAX_RATE_DOWN = 10
|
||||
MAX_TORQUE_LOOKUP = [0], [300]
|
||||
MAX_RT_DELTA = 113
|
||||
|
||||
DRIVER_TORQUE_ALLOWANCE = 80
|
||||
DRIVER_TORQUE_FACTOR = 3
|
||||
|
||||
def _set_prev_torque(self, t):
|
||||
self.safety.set_desired_torque_last(t)
|
||||
self.safety.set_rt_torque_last(t)
|
||||
|
||||
# Ego speed (Bremse_1)
|
||||
def _speed_msg(self, speed):
|
||||
values = {"BR1_Rad_kmh": speed}
|
||||
return self.packer.make_can_msg_safety("Bremse_1", 1, values)
|
||||
|
||||
# Brake light switch (shared message Motor_2)
|
||||
def _user_brake_msg(self, brake):
|
||||
# since this signal is used for engagement status, preserve current state
|
||||
return self._motor_2_msg(brake_pressed=brake, cruise_engaged=self.safety.get_controls_allowed(), tsk_status=self.tsk_status)
|
||||
|
||||
# ACC engaged status (shared message Motor_2)
|
||||
def _pcm_status_msg(self, enable):
|
||||
self.__class__.cruise_engaged = enable
|
||||
return self._motor_2_msg(cruise_engaged=enable, tsk_status=self.tsk_status)
|
||||
|
||||
# Acceleration request to drivetrain coordinator
|
||||
def _accel_msg(self, accel):
|
||||
values = {"ACS_Sollbeschl": accel}
|
||||
return self.packer.make_can_msg_safety("ACC_System", 0, values)
|
||||
|
||||
# Driver steering input torque
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"LH3_LM": abs(torque), "LH3_LMSign": torque < 0}
|
||||
return self.packer.make_can_msg_safety("Lenkhilfe_3", 1, values)
|
||||
|
||||
# openpilot steering output torque
|
||||
def _torque_cmd_msg(self, torque, steer_req=1, hca_status=7):
|
||||
values = {"LM_Offset": abs(torque), "LM_OffSign": torque < 0, "HCA_Status": hca_status if steer_req else 3}
|
||||
return self.packer.make_can_msg_safety("HCA_1", 0, values)
|
||||
|
||||
# ACC engagement and brake light switch status
|
||||
# Called indirectly for compatibility with common.py tests
|
||||
def _motor_2_msg(self, brake_pressed=False, cruise_engaged=False, tsk_status=False):
|
||||
values = {"MO2_BLS": brake_pressed,
|
||||
"MO2_Sta_GRA": cruise_engaged,
|
||||
"MO2_Status_TSK": tsk_status}
|
||||
return self.packer.make_can_msg_safety("Motor_2", 1, values)
|
||||
|
||||
# ACC main switch status
|
||||
def _motor_5_msg(self, main_switch=False):
|
||||
values = {"MO5_GRA_Hauptsch": main_switch}
|
||||
return self.packer.make_can_msg_safety("Motor_5", 1, values)
|
||||
|
||||
# Driver throttle input (Motor_3)
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"MO3_Pedalwert": gas}
|
||||
return self.packer.make_can_msg_safety("Motor_3", 1, values)
|
||||
|
||||
# Cruise control buttons (GRA_Neu)
|
||||
def _button_msg(self, _set=False, resume=False, cancel=False, bus=2):
|
||||
values = {"GRA_Neu_Setzen": _set, "GRA_Recall": resume, "GRA_Abbrechen": cancel}
|
||||
return self.packer.make_can_msg_safety("GRA_Neu", bus, values)
|
||||
|
||||
def test_torque_measurements(self):
|
||||
# TODO: make this test work with all cars
|
||||
self._rx(self._torque_driver_msg(50))
|
||||
self._rx(self._torque_driver_msg(-50))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
|
||||
self.assertEqual(-50, self.safety.get_torque_driver_min())
|
||||
self.assertEqual(50, self.safety.get_torque_driver_max())
|
||||
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self.assertEqual(0, self.safety.get_torque_driver_max())
|
||||
self.assertEqual(-50, self.safety.get_torque_driver_min())
|
||||
|
||||
self._rx(self._torque_driver_msg(0))
|
||||
self.assertEqual(0, self.safety.get_torque_driver_max())
|
||||
self.assertEqual(0, self.safety.get_torque_driver_min())
|
||||
|
||||
|
||||
class TestVolkswagenPqStockSafety(TestVolkswagenPqSafetyBase):
|
||||
# Transmit of GRA_Neu is allowed on bus 0/1/2 to keep compatibility with gateway and camera integration
|
||||
TX_MSGS = [[MSG_HCA_1, 0], [MSG_GRA_NEU, 0], [MSG_GRA_NEU, 1], [MSG_GRA_NEU, 2], [MSG_LDW_1, 0], [MSG_BLINKMODI_02, 0], [MSG_APD_1, 1], [MSG_IQ, 1]]
|
||||
FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_pq")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._button_msg(cancel=True)))
|
||||
self.assertFalse(self._tx(self._button_msg(resume=True)))
|
||||
self.assertFalse(self._tx(self._button_msg(_set=True)))
|
||||
# do not block resume if we are engaged already
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._button_msg(resume=True)))
|
||||
|
||||
|
||||
class TestVolkswagenPqLongSafety(TestVolkswagenPqSafetyBase, common.LongitudinalAccelSafetyTest):
|
||||
tsk_status = True
|
||||
|
||||
TX_MSGS = [[MSG_HCA_1, 0], [MSG_LDW_1, 0], [MSG_ACC_SYSTEM, 0], [MSG_ACC_GRA_ANZEIGE, 0],
|
||||
[MSG_GRA_NEU, 1], [MSG_GRA_NEU, 2], [MSG_BLINKMODI_02, 0], [MSG_MOTOR_2, 2], [MSG_MOTOR_5, 2], [MSG_APD_1, 1], [MSG_IQ, 1]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [MSG_MOTOR_2, MSG_MOTOR_5, MSG_GRA_NEU],
|
||||
2: [MSG_HCA_1, MSG_LDW_1, MSG_ACC_SYSTEM, MSG_ACC_GRA_ANZEIGE]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_1, MSG_LDW_1, MSG_ACC_SYSTEM, MSG_ACC_GRA_ANZEIGE),
|
||||
2: (MSG_MOTOR_2, MSG_GRA_NEU, MSG_MOTOR_5)}
|
||||
INACTIVE_ACCEL = 3.01
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_pq")
|
||||
self.safety = libsafety_py.libsafety
|
||||
safety_param = VolkswagenSafetyFlags.LONG_CONTROL | VolkswagenSafetyFlags.ALLOW_LONG_ACCEL_WITH_GAS_PRESSED
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, safety_param)
|
||||
self.safety.init_tests()
|
||||
|
||||
# stock cruise controls are entirely bypassed under openpilot longitudinal control
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_enable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_cruise_engaged_prev(self):
|
||||
pass
|
||||
|
||||
def test_set_and_resume_buttons(self):
|
||||
for button in ["set", "resume"]:
|
||||
# ACC main switch must be on, engage on falling edge
|
||||
self.safety.set_controls_allowed(0)
|
||||
self._rx(self._motor_5_msg(main_switch=False))
|
||||
self._rx(self._button_msg(_set=(button == "set"), resume=(button == "resume"), bus=1))
|
||||
self._rx(self._button_msg(bus=1))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} with main switch off")
|
||||
self._rx(self._motor_5_msg(main_switch=True))
|
||||
self._rx(self._button_msg(_set=(button == "set"), resume=(button == "resume"), bus=1))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} rising edge")
|
||||
self._rx(self._button_msg(bus=1))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), f"controls not allowed on {button} falling edge")
|
||||
|
||||
def test_cancel_button(self):
|
||||
# Disable on rising edge of cancel button
|
||||
self._rx(self._motor_5_msg(main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._button_msg(cancel=True, bus=1))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after cancel")
|
||||
|
||||
def test_main_switch(self):
|
||||
# Disable as soon as main switch turns off
|
||||
self._rx(self._motor_5_msg(main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._motor_5_msg(main_switch=False))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after ACC main switch off")
|
||||
|
||||
def test_main_switch_tsk_or(self):
|
||||
for main_switch, tsk_status, expected in (
|
||||
(False, False, False),
|
||||
(True, False, True),
|
||||
(False, True, True),
|
||||
(True, True, True),
|
||||
):
|
||||
self._rx(self._motor_5_msg(main_switch=True))
|
||||
self._rx(self._motor_2_msg(tsk_status=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._motor_5_msg(main_switch=main_switch))
|
||||
self._rx(self._motor_2_msg(tsk_status=tsk_status))
|
||||
self.assertEqual(expected, self.safety.get_controls_allowed(),
|
||||
f"main_switch={main_switch} tsk_status={tsk_status} expected={expected}")
|
||||
|
||||
def test_main_switch_flicker_tsk_holds(self):
|
||||
self._rx(self._motor_5_msg(main_switch=True))
|
||||
self._rx(self._motor_2_msg(tsk_status=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._motor_5_msg(main_switch=False))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), "controls dropped on MO5 flicker while TSK ready")
|
||||
self._rx(self._motor_5_msg(main_switch=True))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self._rx(self._motor_5_msg(main_switch=False))
|
||||
self._rx(self._motor_2_msg(tsk_status=False))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after both MO5 and TSK off")
|
||||
|
||||
def test_set_and_resume_buttons_with_tsk_only(self):
|
||||
for button in ("set", "resume"):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self._rx(self._motor_5_msg(main_switch=False))
|
||||
self._rx(self._motor_2_msg(tsk_status=True))
|
||||
self._rx(self._button_msg(_set=(button == "set"), resume=(button == "resume"), bus=1))
|
||||
self._rx(self._button_msg(bus=1))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), f"controls not allowed on {button} falling edge with TSK ready")
|
||||
|
||||
def test_torque_cmd_enable_variants(self):
|
||||
# The EPS rack accepts either 5 or 7 for an enabled status, with different low speed tuning behavior
|
||||
self.safety.set_controls_allowed(1)
|
||||
for enabled_status in (5, 7):
|
||||
self.assertTrue(self._tx(self._torque_cmd_msg(self.MAX_RATE_UP, steer_req=1, hca_status=enabled_status)),
|
||||
f"torque cmd rejected with {enabled_status=}")
|
||||
|
||||
def test_accel_actuation_limits(self):
|
||||
for accel in np.concatenate((np.arange(self.MIN_ACCEL - 1, self.MAX_ACCEL + 1, 0.05), [0, self.INACTIVE_ACCEL])):
|
||||
accel = round(accel, 2)
|
||||
for controls_allowed in [True, False]:
|
||||
for gas_pressed in [True, False]:
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self.safety.set_gas_pressed_prev(gas_pressed)
|
||||
is_inactive = accel == self.INACTIVE_ACCEL
|
||||
should_tx = (controls_allowed and self.MIN_ACCEL <= accel <= self.MAX_ACCEL) or is_inactive
|
||||
self.assertEqual(should_tx, self._tx(self._accel_msg(accel)), (controls_allowed, gas_pressed, accel))
|
||||
|
||||
def test_accel_allowed_with_gas_pressed(self):
|
||||
self._rx(self._user_gas_msg(1))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertTrue(self._tx(self._accel_msg(0.5)))
|
||||
self.assertTrue(self._tx(self._accel_msg(0.0)))
|
||||
self.assertTrue(self._tx(self._accel_msg(-0.5)))
|
||||
|
||||
|
||||
class TestVolkswagenPqLowlineSafety(TestVolkswagenPqSafetyBase):
|
||||
"""Non-ECAN lateral-only PQ cars: bus 0 dead, TX on bus 1 (ptCAN) directly to EPS."""
|
||||
TX_MSGS = [[MSG_HCA_1, 1], [MSG_GRA_NEU, 1], [MSG_GRA_NEU, 2], [MSG_LDW_1, 1], [MSG_BLINKMODI_02, 1], [MSG_APD_1, 1], [MSG_IQ, 1]]
|
||||
FWD_BUS_LOOKUP = {2: 0}
|
||||
FWD_BLACKLISTED_ADDRS = {}
|
||||
RELAY_MALFUNCTION_ADDRS = {1: (MSG_HCA_1, MSG_LDW_1)}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_pq")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, VolkswagenSafetyFlags.PQ_LOWLINE | VolkswagenSafetyFlags.PQ_NO_CAM_BUS)
|
||||
self.safety.init_tests()
|
||||
|
||||
def _torque_cmd_msg(self, torque, steer_req=1, hca_status=7):
|
||||
values = {"LM_Offset": abs(torque), "LM_OffSign": torque < 0, "HCA_Status": hca_status if steer_req else 3}
|
||||
return self.packer.make_can_msg_safety("HCA_1", 1, values)
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._button_msg(cancel=True)))
|
||||
self.assertFalse(self._tx(self._button_msg(resume=True)))
|
||||
self.assertFalse(self._tx(self._button_msg(_set=True)))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._button_msg(resume=True)))
|
||||
|
||||
|
||||
class TestVolkswagenPqNoCamSafety(TestVolkswagenPqStockSafety):
|
||||
FWD_BUS_LOOKUP = {2: 0}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_pq")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, VolkswagenSafetyFlags.PQ_NO_CAM_BUS)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user