IQ.Pilot Prebuilt Release @ 7e87bc7

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

View File

@@ -0,0 +1,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

File diff suppressed because it is too large Load Diff

View File

@@ -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)))

View File

@@ -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")

View File

@@ -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

View File

@@ -0,0 +1,5 @@
*.pdf
*.txt
.output.log
new_table
cppcheck/

View 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

View 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

View File

@@ -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?

View 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

View File

@@ -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"

View 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/*

View File

@@ -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"

View File

@@ -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)

View 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

View 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()

View 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()

View File

@@ -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()

View 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()

View 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()

View 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()

View 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()

View 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}))

View File

@@ -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}))

View File

@@ -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)

View 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()

View 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()

View 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()

View 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()

View 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()

View File

@@ -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()

View 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()

View File

@@ -0,0 +1,522 @@
#!/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.DEFAULT},
{"SAFETY_PARAM_IQ": ToyotaSafetyFlagsIQ.UNSUPPORTED_DSU},
]
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 = 0
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):
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_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_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_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_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_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_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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()