This commit is contained in:
Your Name
2024-04-27 03:19:19 -05:00
parent df418b503f
commit 03af7b6107
306 changed files with 108529 additions and 119 deletions

View File

1071
panda/tests/safety/common.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
import unittest
import panda.tests.safety.common as common
from panda.tests.libpanda import libpanda_py
from panda.tests.safety.common import make_msg
class Buttons:
NONE = 0
RESUME = 1
SET = 2
CANCEL = 4
PREV_BUTTON_SAMPLES = 8
ENABLE_BUTTONS = (Buttons.RESUME, Buttons.SET, Buttons.CANCEL)
class HyundaiButtonBase:
# pylint: disable=no-member,abstract-method
BUTTONS_TX_BUS = 0 # tx on this bus, rx on 0
SCC_BUS = 0 # rx on this bus
def test_button_sends(self):
"""
Only RES and CANCEL buttons are allowed
- RES allowed while controls allowed
- CANCEL allowed while cruise is enabled
"""
self.safety.set_controls_allowed(0)
self.assertFalse(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_TX_BUS)))
self.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS)))
self.safety.set_controls_allowed(1)
self.assertTrue(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_TX_BUS)))
self.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS)))
for enabled in (True, False):
self._rx(self._pcm_status_msg(enabled))
self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL, bus=self.BUTTONS_TX_BUS)))
def test_enable_control_allowed_from_cruise(self):
"""
Hyundai non-longitudinal only enables on PCM rising edge and recent button press. Tests PCM enabling with:
- disallowed: No buttons
- disallowed: Buttons that don't enable cruise
- allowed: Buttons that do enable cruise
- allowed: Main button with all above combinations
"""
for main_button in (0, 1):
for btn in range(8):
for _ in range(PREV_BUTTON_SAMPLES): # reset
self._rx(self._button_msg(Buttons.NONE))
self._rx(self._pcm_status_msg(False))
self.assertFalse(self.safety.get_controls_allowed())
self._rx(self._button_msg(btn, main_button=main_button))
self._rx(self._pcm_status_msg(True))
controls_allowed = btn in ENABLE_BUTTONS or main_button
self.assertEqual(controls_allowed, self.safety.get_controls_allowed())
def test_sampling_cruise_buttons(self):
"""
Test that we allow controls on recent button press, but not as button leaves sliding window
"""
self._rx(self._button_msg(Buttons.SET))
for i in range(2 * PREV_BUTTON_SAMPLES):
self._rx(self._pcm_status_msg(False))
self.assertFalse(self.safety.get_controls_allowed())
self._rx(self._pcm_status_msg(True))
controls_allowed = i < PREV_BUTTON_SAMPLES
self.assertEqual(controls_allowed, self.safety.get_controls_allowed())
self._rx(self._button_msg(Buttons.NONE))
class HyundaiLongitudinalBase(common.LongitudinalAccelSafetyTest):
# pylint: disable=no-member,abstract-method
DISABLED_ECU_UDS_MSG: tuple[int, int]
DISABLED_ECU_ACTUATION_MSG: tuple[int, int]
@classmethod
def setUpClass(cls):
if cls.__name__ == "HyundaiLongitudinalBase":
cls.safety = None
raise unittest.SkipTest
# override these tests from PandaCarSafetyTest, hyundai longitudinal uses button enable
def test_disable_control_allowed_from_cruise(self):
pass
def test_enable_control_allowed_from_cruise(self):
pass
def test_sampling_cruise_buttons(self):
pass
def test_cruise_engaged_prev(self):
pass
def test_button_sends(self):
pass
def _pcm_status_msg(self, enable):
raise Exception
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
raise NotImplementedError
def test_set_resume_buttons(self):
"""
SET and RESUME enter controls allowed on their falling edge.
"""
for btn_prev in range(8):
for btn_cur in range(8):
self._rx(self._button_msg(Buttons.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
should_enable = btn_cur != btn_prev and \
btn_cur != Buttons.CANCEL and \
btn_prev in (Buttons.RESUME, Buttons.SET)
self._rx(self._button_msg(btn_cur))
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())
def test_tester_present_allowed(self):
"""
Ensure tester present diagnostic message is allowed to keep ECU knocked out
for longitudinal control.
"""
addr, bus = self.DISABLED_ECU_UDS_MSG
tester_present = libpanda_py.make_CANPacket(addr, bus, b"\x02\x3E\x80\x00\x00\x00\x00\x00")
self.assertTrue(self._tx(tester_present))
not_tester_present = libpanda_py.make_CANPacket(addr, bus, b"\x03\xAA\xAA\x00\x00\x00\x00\x00")
self.assertFalse(self._tx(not_tester_present))
def test_disabled_ecu_alive(self):
"""
If the ECU knockout failed, make sure the relay malfunction is shown
"""
addr, bus = self.DISABLED_ECU_ACTUATION_MSG
self.assertFalse(self.safety.get_relay_malfunction())
self._rx(make_msg(bus, addr, 8))
self.assertTrue(self.safety.get_relay_malfunction())

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
cd $DIR
# reset coverage data and generate gcc note file
rm -f ../libpanda/*.gcda
scons -j$(nproc) -D --coverage
# run safety tests and generate coverage data
HW_TYPES=( 6 9 )
for hw_type in "${HW_TYPES[@]}"; do
echo "Testing HW_TYPE: $hw_type"
HW_TYPE=$hw_type pytest test_*.py
done
# generate and open report
if [ "$1" == "--report" ]; then
geninfo ../libpanda/ -o coverage.info
genhtml coverage.info -o coverage-out
sensible-browser coverage-out/index.html
fi
# test coverage
GCOV_OUTPUT=$(gcov -n ../libpanda/panda.c)
INCOMPLETE_COVERAGE=$(echo "$GCOV_OUTPUT" | paste -s -d' \n' | grep -E "File.*(safety\/safety_.*)|(safety)\.h" | grep -v "100.00%" || true)
if [ -n "$INCOMPLETE_COVERAGE" ]; then
echo "FAILED: Some files have less than 100% coverage:"
echo "$INCOMPLETE_COVERAGE"
exit 1
else
echo "SUCCESS: All checked files have 100% coverage!"
fi

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import unittest
import panda.tests.safety.common as common
from panda import Panda
from panda.tests.libpanda import libpanda_py
from panda.tests.safety.common import CANPackerPanda
class TestBody(common.PandaSafetyTest):
TX_MSGS = [[0x250, 0], [0x251, 0], [0x350, 0], [0x351, 0],
[0x1, 0], [0x1, 1], [0x1, 2], [0x1, 3]]
def setUp(self):
self.packer = CANPackerPanda("comma_body")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_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_panda("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_panda("TORQUE_CMD", 0, values)
def _knee_torque_cmd_msg(self, torque_l, torque_r):
values = {"TORQUE_L": torque_l, "TORQUE_R": torque_r}
return self.packer.make_can_msg_panda("KNEE_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_panda("MAX_MOTOR_RPM_CMD", 0, values)
def test_rx_hook(self):
self.assertFalse(self.safety.get_controls_allowed())
self.assertFalse(self.safety.get_vehicle_moving())
# controls allowed when we get MOTORS_DATA message
self.assertTrue(self._rx(self._torque_cmd_msg(0, 0)))
self.assertTrue(self.safety.get_vehicle_moving()) # always moving
self.assertFalse(self.safety.get_controls_allowed())
self.assertTrue(self._rx(self._motors_data_msg(0, 0)))
self.assertTrue(self.safety.get_vehicle_moving()) # always moving
self.assertTrue(self.safety.get_controls_allowed())
def test_tx_hook(self):
self.assertFalse(self._tx(self._torque_cmd_msg(0, 0)))
self.assertFalse(self._tx(self._knee_torque_cmd_msg(0, 0)))
self.safety.set_controls_allowed(True)
self.assertTrue(self._tx(self._torque_cmd_msg(0, 0)))
self.assertTrue(self._tx(self._knee_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 enters CAN flashing mode for base & knee
for addr in (0x250, 0x350):
self.assertTrue(self._tx(common.make_msg(0, addr, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a')))
self.assertFalse(self._tx(common.make_msg(0, addr, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0'))) # not correct data/len
self.assertFalse(self._tx(common.make_msg(0, addr + 1, dat=b'\xce\xfa\xad\xde\x1e\x0b\xb0\x0a'))) # wrong address
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python3
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
class TestChryslerSafety(common.PandaCarSafetyTest, common.MotorTorqueSteeringSafetyTest):
TX_MSGS = [[0x23B, 0], [0x292, 0], [0x2A6, 0]]
STANDSTILL_THRESHOLD = 0
RELAY_MALFUNCTION_ADDRS = {0: (0x292,)}
FWD_BLACKLISTED_ADDRS = {2: [0x292, 0x2A6]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
MAX_RATE_UP = 3
MAX_RATE_DOWN = 3
MAX_TORQUE = 261
MAX_RT_DELTA = 112
RT_INTERVAL = 250000
MAX_TORQUE_ERROR = 80
LKAS_ACTIVE_VALUE = 1
DAS_BUS = 0
def setUp(self):
self.packer = CANPackerPanda("chrysler_pacifica_2017_hybrid_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_CHRYSLER, 0)
self.safety.init_tests()
def _button_msg(self, cancel=False, resume=False):
values = {"ACC_Cancel": cancel, "ACC_Resume": resume}
return self.packer.make_can_msg_panda("CRUISE_BUTTONS", self.DAS_BUS, values)
def _pcm_status_msg(self, enable):
values = {"ACC_ACTIVE": enable}
return self.packer.make_can_msg_panda("DAS_3", self.DAS_BUS, values)
def _speed_msg(self, speed):
values = {"SPEED_LEFT": speed, "SPEED_RIGHT": speed}
return self.packer.make_can_msg_panda("SPEED_1", 0, values)
def _user_gas_msg(self, gas):
values = {"Accelerator_Position": gas}
return self.packer.make_can_msg_panda("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_panda("ESP_1", 0, values)
def _torque_meas_msg(self, torque):
values = {"EPS_TORQUE_MOTOR": torque}
return self.packer.make_can_msg_panda("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_panda("LKAS_COMMAND", 0, values)
def test_buttons(self):
for controls_allowed in (True, False):
self.safety.set_controls_allowed(controls_allowed)
# resume only while controls allowed
self.assertEqual(controls_allowed, self._tx(self._button_msg(resume=True)))
# can always cancel
self.assertTrue(self._tx(self._button_msg(cancel=True)))
# only one button at a time
self.assertFalse(self._tx(self._button_msg(cancel=True, resume=True)))
self.assertFalse(self._tx(self._button_msg(cancel=False, resume=False)))
class TestChryslerRamDTSafety(TestChryslerSafety):
TX_MSGS = [[0xB1, 2], [0xA6, 0], [0xFA, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0xA6,)}
FWD_BLACKLISTED_ADDRS = {2: [0xA6, 0xFA]}
MAX_RATE_UP = 6
MAX_RATE_DOWN = 6
MAX_TORQUE = 350
DAS_BUS = 2
LKAS_ACTIVE_VALUE = 2
def setUp(self):
self.packer = CANPackerPanda("chrysler_ram_dt_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_CHRYSLER, Panda.FLAG_CHRYSLER_RAM_DT)
self.safety.init_tests()
def _speed_msg(self, speed):
values = {"Vehicle_Speed": speed}
return self.packer.make_can_msg_panda("ESP_8", 0, values)
class TestChryslerRamHDSafety(TestChryslerSafety):
TX_MSGS = [[0x275, 0], [0x276, 0], [0x23A, 2]]
RELAY_MALFUNCTION_ADDRS = {0: (0x276,)}
FWD_BLACKLISTED_ADDRS = {2: [0x275, 0x276]}
MAX_TORQUE = 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 = CANPackerPanda("chrysler_ram_hd_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_CHRYSLER, Panda.FLAG_CHRYSLER_RAM_HD)
self.safety.init_tests()
def _speed_msg(self, speed):
values = {"Vehicle_Speed": speed}
return self.packer.make_can_msg_panda("ESP_8", 0, values)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import unittest
import panda.tests.safety.common as common
from panda import Panda
from panda.tests.libpanda import libpanda_py
class TestDefaultRxHookBase(common.PandaSafetyTest):
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 = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_NOOUTPUT, 0)
self.safety.init_tests()
class TestSilent(TestNoOutput):
"""SILENT uses same hooks as NOOUTPUT"""
def setUp(self):
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_SILENT, 0)
self.safety.init_tests()
class TestAllOutput(TestDefaultRxHookBase):
# Allow all messages
TX_MSGS = [[addr, bus] for addr in common.PandaSafetyTest.SCANNED_ADDRS
for bus in range(4)]
def setUp(self):
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_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 = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_ALLOUTPUT, 1)
self.safety.init_tests()
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
import unittest
import panda.tests.safety.common as common
from panda import DLC_TO_LEN, Panda
from panda.tests.libpanda import libpanda_py
from panda.tests.safety.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)]
def setUp(self):
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_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))))
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,476 @@
#!/usr/bin/env python3
import numpy as np
import random
import unittest
import panda.tests.safety.common as common
from panda import Panda
from panda.tests.libpanda import libpanda_py
from panda.tests.safety.common import CANPackerPanda
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
def checksum(msg):
addr, t, 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, t, ret, bus
class Buttons:
CANCEL = 0
RESUME = 1
TJA_TOGGLE = 2
# Ford safety has four different configurations tested here:
# * CAN with stock longitudinal
# * CAN with openpilot longitudinal
# * CAN FD with stock longitudinal
# * CAN FD with openpilot longitudinal
class TestFordSafetyBase(common.PandaCarSafetyTest):
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]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
# Max allowed delta between car speeds
MAX_SPEED_DELTA = 2.0 # m/s
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.0002, 0.0001, 0.0001] # windup limit
ANGLE_RATE_DOWN = [0.000225, 0.00015, 0.00015] # unwind limit
cnt_speed = 0
cnt_speed_2 = 0
cnt_yaw_rate = 0
packer: CANPackerPanda
safety: libpanda_py.Panda
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestFordSafetyBase":
raise unittest.SkipTest
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_panda("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_panda("BrakeSysFeatures", 0, values, fix_checksum=checksum)
# PCM vehicle speed
def _speed_msg_2(self, speed: float, quality_flag=True):
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_panda("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_panda("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_panda("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_panda("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_panda("EngBrakeData", 0, values)
# LKAS command
def _lkas_command_msg(self, action: int):
values = {
"LkaActvStats_D2_Req": action,
}
return self.packer.make_can_msg_panda("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_panda("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_panda("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_panda("Steering_Data_FD1", bus, values)
def test_rx_hook(self):
# checksum, counter, and quality flag checks
for quality_flag in [True, False]:
for msg in ["speed", "speed_2", "yaw"]:
self.safety.set_controls_allowed(True)
# send multiple times to verify counter checks
for _ in range(10):
if msg == "speed":
to_push = self._speed_msg(0, quality_flag=quality_flag)
elif msg == "speed_2":
to_push = self._speed_msg_2(0, quality_flag=quality_flag)
elif msg == "yaw":
to_push = self._yaw_rate_msg(0, 0, quality_flag=quality_flag)
self.assertEqual(quality_flag, self._rx(to_push))
self.assertEqual(quality_flag, self.safety.get_controls_allowed())
# Mess with checksum to make it fail, checksum is not checked for 2nd speed
to_push[0].data[3] = 0 # Speed checksum & half of yaw signal
should_rx = msg == "speed_2" and quality_flag
self.assertEqual(should_rx, self._rx(to_push))
self.assertEqual(should_rx, self.safety.get_controls_allowed())
def test_rx_hook_speed_mismatch(self):
# Ford relies on speed for driver curvature limiting, so it checks two sources
for speed in np.arange(0, 40, 0.5):
for speed_delta in np.arange(-5, 5, 0.1):
speed_2 = round(max(speed + speed_delta, 0), 1)
# Set controls allowed in between rx since first message can reset it
self._rx(self._speed_msg(speed))
self.safety.set_controls_allowed(True)
self._rx(self._speed_msg_2(speed_2))
within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA
self.assertEqual(self.safety.get_controls_allowed(), within_delta)
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_steer_allowed(self):
path_offsets = np.arange(-5.12, 5.11, 1).round()
path_angles = np.arange(-0.5, 0.5235, 0.1).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):
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)
with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled,
path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate,
curvature=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_limit_up(self):
"""
When the curvature error is exceeded, commanded curvature must start moving towards meas respecting rate limits.
Since panda 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)
small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary
for speed in np.arange(0, 40, 0.5):
limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED
max_delta_up = np.interp(speed, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP)
max_delta_up_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP)
cases = [
(not limit_command, 0),
(not limit_command, max_delta_up_lower - small_curvature),
(True, max_delta_up_lower),
(True, max_delta_up),
(False, max_delta_up + small_curvature),
]
for sign in (-1, 1):
self._reset_curvature_measurement(sign * (self.MAX_CURVATURE_ERROR + 1e-3), speed)
for should_tx, curvature in cases:
self._set_prev_desired_angle(sign * small_curvature)
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (small_curvature + curvature), 0)))
def test_curvature_rate_limit_down(self):
self.safety.set_controls_allowed(True)
small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary
for speed in np.arange(0, 40, 0.5):
limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED
max_delta_down = np.interp(speed, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN)
max_delta_down_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN)
cases = [
(not limit_command, self.MAX_CURVATURE),
(not limit_command, self.MAX_CURVATURE - max_delta_down_lower + small_curvature),
(True, self.MAX_CURVATURE - max_delta_down_lower),
(True, self.MAX_CURVATURE - max_delta_down),
(False, self.MAX_CURVATURE - max_delta_down - small_curvature),
]
for sign in (-1, 1):
self._reset_curvature_measurement(sign * (self.MAX_CURVATURE - self.MAX_CURVATURE_ERROR - 1e-3), speed)
for should_tx, curvature in cases:
self._set_prev_desired_angle(sign * self.MAX_CURVATURE)
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * 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)))
class TestFordStockSafety(TestFordSafetyBase):
STEER_MESSAGE = MSG_LateralMotionControl
TX_MSGS = [
[MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
[MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0],
]
def setUp(self):
self.packer = CANPackerPanda("ford_lincoln_base_pt")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0)
self.safety.init_tests()
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],
]
def setUp(self):
self.packer = CANPackerPanda("ford_lincoln_base_pt")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_CANFD)
self.safety.init_tests()
class TestFordLongitudinalSafetyBase(TestFordSafetyBase):
RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
MSG_LateralMotionControl2, MSG_IPMA_Data)}
FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
MSG_LateralMotionControl2, MSG_IPMA_Data]}
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
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestFordLongitudinalSafetyBase":
raise unittest.SkipTest
# ACC command
def _acc_command_msg(self, gas: float, brake: float, 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
"CmbbDeny_B_Actl": 1 if cmbb_deny else 0, # [0|1] deny AEB actuation
}
return self.packer.make_can_msg_panda("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, 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, 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)))
def test_brake_safety_check(self):
for controls_allowed in (True, False):
self.safety.set_controls_allowed(controls_allowed)
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
self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake)))
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],
]
def setUp(self):
self.packer = CANPackerPanda("ford_lincoln_base_pt")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL)
self.safety.init_tests()
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],
]
def setUp(self):
self.packer = CANPackerPanda("ford_lincoln_base_pt")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL | Panda.FLAG_FORD_CANFD)
self.safety.init_tests()
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,383 @@
#!/usr/bin/env python3
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
class Buttons:
UNPRESS = 1
RES_ACCEL = 2
DECEL_SET = 3
CANCEL = 6
class GmLongitudinalBase(common.PandaCarSafetyTest, common.LongitudinalGasBrakeSafetyTest):
# pylint: disable=no-member,abstract-method
RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB)} # ASCMLKASteeringCmd, ASCMGasRegenCmd
MAX_POSSIBLE_BRAKE = 2 ** 12
MAX_BRAKE = 400
MAX_POSSIBLE_GAS = 2 ** 12
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_panda("EBCMFrictionBrakeCmd", self.BRAKE_BUS, values)
def _send_gas_msg(self, gas):
values = {"GasRegenCmd": gas}
return self.packer.make_can_msg_panda("ASCMGasRegenCmd", 0, values)
# override these tests from PandaCarSafetyTest, 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.PandaCarSafetyTest, 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,)} # ASCMLKASteeringCmd
BUTTONS_BUS = 0 # rx or tx
BRAKE_BUS = 0 # tx only
MAX_RATE_UP = 10
MAX_RATE_DOWN = 15
MAX_TORQUE = 300
MAX_RT_DELTA = 128
RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 65
DRIVER_TORQUE_FACTOR = 4
PCM_CRUISE = True # openpilot is tied to the PCM state if not longitudinal
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestGmSafetyBase":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
self.packer_chassis = CANPackerPanda("gm_global_a_chassis")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_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_panda("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_panda("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_panda("ECMAcceleratorPos", 0, values)
def _user_regen_msg(self, regen):
values = {"RegenPaddle": 2 if regen else 0}
return self.packer.make_can_msg_panda("EBCMRegenPaddle", 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_panda("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_panda("PSCMStatus", 0, values)
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"LKASteeringCmd": torque, "LKASteeringCmdActive": steer_req}
return self.packer.make_can_msg_panda("ASCMLKASteeringCmd", 0, values)
def _button_msg(self, buttons):
values = {"ACCButtons": buttons}
return self.packer.make_can_msg_panda("ASCMSteeringButton", self.BUTTONS_BUS, 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
[0x104c006c, 3], [0x10400060, 3]] # gmlan
FWD_BLACKLISTED_ADDRS: dict[int, list[int]] = {}
FWD_BUS_LOOKUP: dict[int, int] = {}
BRAKE_BUS = 2
MAX_GAS = 3072
MIN_GAS = 1404 # maximum regen
INACTIVE_GAS = 1404
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
self.packer_chassis = CANPackerPanda("gm_global_a_chassis")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_GM, 0)
self.safety.init_tests()
class TestGmCameraSafetyBase(TestGmSafetyBase):
FWD_BUS_LOOKUP = {0: 2, 2: 0}
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestGmCameraSafetyBase":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
def _user_brake_msg(self, brake):
values = {"BrakePressed": brake}
return self.packer.make_can_msg_panda("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 = CANPackerPanda("gm_global_a_powertrain_generated")
self.packer_chassis = CANPackerPanda("gm_global_a_chassis")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_GM, Panda.FLAG_GM_HW_CAM)
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 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
BUTTONS_BUS = 0 # rx only
MAX_GAS = 3400
MIN_GAS = 1514 # maximum regen
INACTIVE_GAS = 1554
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
self.packer_chassis = CANPackerPanda("gm_global_a_chassis")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_GM, Panda.FLAG_GM_HW_CAM | Panda.FLAG_GM_HW_CAM_LONG)
self.safety.init_tests()
class TestGmSdgmSafety(TestGmSafetyBase):
FWD_BUS_LOOKUP = {0: 2, 2: 0}
TX_MSGS = [[0x180, 0], [0x1E1, 0], # pt bus
[0x184, 2]] # obj bus
FWD_BLACKLISTED_ADDRS = {2: [0x180], 0: [0x184]} # block LKAS message and PSCMStatus
BUTTONS_BUS = 0 # tx
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
self.packer_chassis = CANPackerPanda("gm_global_a_chassis")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_GM, Panda.FLAG_GM_HW_SDGM)
self.safety.init_tests()
def _user_brake_msg(self, brake):
values = {"BrakePressed": brake}
return self.packer.make_can_msg_panda("ECMEngineStatus", 0, values)
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)))
##### OPGM TESTS #####
def interceptor_msg(gas, addr):
to_send = common.make_msg(0, addr, 6)
to_send[0].data[0] = (gas & 0xFF00) >> 8
to_send[0].data[1] = gas & 0xFF
to_send[0].data[2] = (gas & 0xFF00) >> 8
to_send[0].data[3] = gas & 0xFF
return to_send
class TestGmInterceptorSafety(common.GasInterceptorSafetyTest, TestGmCameraSafety):
INTERCEPTOR_THRESHOLD = 515
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
self.packer_chassis = CANPackerPanda("gm_global_a_chassis")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(
Panda.SAFETY_GM,
Panda.FLAG_GM_HW_CAM | Panda.FLAG_GM_NO_ACC | Panda.FLAG_GM_PEDAL_LONG | Panda.FLAG_GM_GAS_INTERCEPTOR)
self.safety.init_tests()
def test_pcm_sets_cruise_engaged(self):
for enabled in [True, False]:
self._rx(self._pcm_status_msg(enabled))
self.assertEqual(enabled, self.safety.get_cruise_engaged_prev())
def test_no_pcm_enable(self):
self._rx(self._interceptor_user_gas(0))
self.safety.set_controls_allowed(0)
self.assertFalse(self.safety.get_controls_allowed())
self._rx(self._pcm_status_msg(True))
self.assertFalse(self.safety.get_controls_allowed())
self.assertTrue(self.safety.get_cruise_engaged_prev())
def test_no_response_to_acc_pcm_message(self):
self._rx(self._interceptor_user_gas(0))
def _acc_pcm_msg(enable):
values = {"CruiseState": enable}
return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values)
for enable in [True, False]:
self.safety.set_controls_allowed(enable)
self._rx(_acc_pcm_msg(True))
self.assertEqual(enable, self.safety.get_controls_allowed())
self._rx(_acc_pcm_msg(False))
self.assertEqual(enable, self.safety.get_controls_allowed())
def test_buttons(self):
self._rx(self._interceptor_user_gas(0))
# 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)))
self.safety.set_controls_allowed(1)
for enabled in (True, False):
self._rx(self._pcm_status_msg(enabled))
self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL)))
self.assertTrue(self.safety.get_controls_allowed())
def test_fwd_hook(self):
pass
def test_disable_control_allowed_from_cruise(self):
pass
def test_enable_control_allowed_from_cruise(self):
pass
def _interceptor_gas_cmd(self, gas):
return interceptor_msg(gas, 0x200)
def _interceptor_user_gas(self, gas):
return interceptor_msg(gas, 0x201)
def _pcm_status_msg(self, enable):
values = {"CruiseActive": enable}
return self.packer.make_can_msg_panda("ECMCruiseControl", 0, values)
class TestGmCcLongitudinalSafety(TestGmCameraSafety):
TX_MSGS = [[384, 0], [481, 0], [388, 2]]
FWD_BLACKLISTED_ADDRS = {2: [384], 0: [388]} # block LKAS message and PSCMStatus
BUTTONS_BUS = 0 # tx only
MAX_GAS = 3400
MAX_REGEN = 1514
INACTIVE_REGEN = 1554
MAX_BRAKE = 400
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
self.packer_chassis = CANPackerPanda("gm_global_a_chassis")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_GM, Panda.FLAG_GM_HW_CAM | Panda.FLAG_GM_NO_ACC | Panda.FLAG_GM_CC_LONG)
self.safety.init_tests()
def _pcm_status_msg(self, enable):
values = {"CruiseActive": enable}
return self.packer.make_can_msg_panda("ECMCruiseControl", 0, values)
def test_fwd_hook(self):
pass
def test_buttons(self):
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):
for btn in (Buttons.RES_ACCEL, Buttons.DECEL_SET, Buttons.CANCEL):
self._rx(self._pcm_status_msg(enabled))
self.assertEqual(enabled, self._tx(self._button_msg(btn)))
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,618 @@
#!/usr/bin/env python3
import unittest
import numpy as np
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda, MAX_WRONG_COUNTERS
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_NIDEC = 0
HONDA_BOSCH = 1
# 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
class HondaButtonEnableBase(common.PandaCarSafetyTest):
# pylint: disable=no-member,abstract-method
# 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 in ["btn", "gas", "speed"]:
self.safety.set_controls_allowed(1)
if msg == "btn":
to_push = self._button_msg(Btn.SET)
if msg == "gas":
to_push = self._user_gas_msg(0)
if msg == "speed":
to_push = self._speed_msg(0)
self.assertTrue(self._rx(to_push))
if msg != "btn":
to_push[0].data[4] = 0 # invalidate checksum
to_push[0].data[5] = 0
to_push[0].data[6] = 0
to_push[0].data[7] = 0
self.assertFalse(self._rx(to_push))
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.PandaCarSafetyTest):
# pylint: disable=no-member,abstract-method
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.PandaCarSafetyTest):
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
STANDSTILL_THRESHOLD = 0
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x194)} # STEERING_CONTROL
FWD_BUS_LOOKUP = {0: 2, 2: 0}
cnt_speed = 0
cnt_button = 0
cnt_brake = 0
cnt_powertrain_data = 0
cnt_acc_state = 0
@classmethod
def setUpClass(cls):
if cls.__name__.endswith("Base"):
cls.packer = None
cls.safety = None
raise unittest.SkipTest
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_panda("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_panda("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_panda("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_panda("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_panda("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)))
# ********************* Honda Nidec **********************
class TestHondaNidecSafetyBase(HondaBase):
TX_MSGS = HONDA_N_COMMON_TX_MSGS
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x194, 0x33D, 0x30C]}
PT_BUS = 0
STEER_BUS = 0
BUTTONS_BUS = 0
MAX_GAS = 198
def setUp(self):
self.packer = CANPackerPanda("honda_civic_touring_2016_can_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, 0)
self.safety.init_tests()
def _send_brake_msg(self, brake, aeb_req=0, bus=0):
values = {"COMPUTER_BRAKE": brake, "AEB_REQ_1": aeb_req}
return self.packer.make_can_msg_panda("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_panda("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)))
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(common.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 = CANPackerPanda("honda_civic_touring_2016_can_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, Panda.FLAG_HONDA_GAS_INTERCEPTOR)
self.safety.init_tests()
class TestHondaNidecPcmAltSafety(TestHondaNidecPcmSafety):
"""
Covers the Honda Nidec safety mode with alt SCM messages
"""
def setUp(self):
self.packer = CANPackerPanda("acura_ilx_2016_can_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, Panda.FLAG_HONDA_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_panda("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_panda("SCM_BUTTONS", bus, values)
class TestHondaNidecAltGasInterceptorSafety(common.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 = CANPackerPanda("acura_ilx_2016_can_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, Panda.FLAG_HONDA_NIDEC_ALT | Panda.FLAG_HONDA_GAS_INTERCEPTOR)
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_panda("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_panda("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]}
def setUp(self):
self.packer = CANPackerPanda("honda_accord_2018_can_generated")
self.safety = libpanda_py.libpanda
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_panda("BRAKE_MODULE", self.PT_BUS, values)
def _send_brake_msg(self, brake):
pass
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())
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(Panda.SAFETY_HONDA_BOSCH, Panda.FLAG_HONDA_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)
to_push = self._alt_brake_msg(0)
self.assertTrue(self._rx(to_push))
to_push[0].data[2] = to_push[0].data[2] & 0xF0 # invalidate checksum
self.assertFalse(self._rx(to_push))
self.assertFalse(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(Panda.SAFETY_HONDA_BOSCH, 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 = 2000
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 = {2: [0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB]}
# 0x1DF is to test that radar is disabled
RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x194), 1: (0x1DF,)} # STEERING_CONTROL, ACC_CONTROL
def setUp(self):
super().setUp()
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH, Panda.FLAG_HONDA_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_panda("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 = libpanda_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 = libpanda_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\xAA\xAA\x00\x00\x00\x00\x00")
self.assertFalse(self._tx(not_tester_present))
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 gas / 1000
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, 0xE5, 0x33D, 0x33DA, 0x33DB]}
def setUp(self):
self.packer = CANPackerPanda("honda_civic_ex_2022_can_generated")
self.safety = libpanda_py.libpanda
class TestHondaBoschRadarlessSafety(HondaPcmEnableBase, TestHondaBoschRadarlessSafetyBase):
"""
Covers the Honda Bosch Radarless safety mode with stock longitudinal
"""
def setUp(self):
super().setUp()
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH, Panda.FLAG_HONDA_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(Panda.SAFETY_HONDA_BOSCH, Panda.FLAG_HONDA_RADARLESS | Panda.FLAG_HONDA_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, 0xE5, 0x33D, 0x33DA, 0x33DB, 0x1C8, 0x30C]}
def setUp(self):
super().setUp()
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH, Panda.FLAG_HONDA_RADARLESS | Panda.FLAG_HONDA_BOSCH_LONG)
self.safety.init_tests()
def _accel_msg(self, accel):
values = {
"ACCEL_COMMAND": accel,
}
return self.packer.make_can_msg_panda("ACC_CONTROL", self.PT_BUS, values)
# Longitudinal doesn't need to send buttons
def test_spam_cancel_safety_check(self):
pass
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python3
import random
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase
# 4 bit checkusm used in some hyundai messages
# lives outside the can packer because we never send this msg
def checksum(msg):
addr, t, dat, bus = msg
chksum = 0
if addr == 0x386:
for i, b in enumerate(dat):
for j in range(8):
# exclude checksum and counter bits
if (i != 1 or j < 6) and (i != 3 or j < 6) and (i != 5 or j < 6) and (i != 7 or j < 6):
bit = (b >> j) & 1
else:
bit = 0
chksum += bit
chksum = (chksum ^ 9) & 0xF
ret = bytearray(dat)
ret[5] |= (chksum & 0x3) << 6
ret[7] |= (chksum & 0xc) << 4
else:
for i, b in enumerate(dat):
if addr in [0x260, 0x421] and i == 7:
b &= 0x0F if addr == 0x421 else 0xF0
elif addr == 0x394 and i == 6:
b &= 0xF0
elif addr == 0x394 and i == 7:
continue
chksum += sum(divmod(b, 16))
chksum = (16 - chksum) % 16
ret = bytearray(dat)
ret[6 if addr == 0x394 else 7] |= chksum << (4 if addr == 0x421 else 0)
return addr, t, ret, bus
class TestHyundaiSafety(HyundaiButtonBase, common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
TX_MSGS = [[0x340, 0], [0x4F1, 0], [0x485, 0]]
STANDSTILL_THRESHOLD = 12 # 0.375 kph
RELAY_MALFUNCTION_ADDRS = {0: (0x340,)} # LKAS11
FWD_BLACKLISTED_ADDRS = {2: [0x340, 0x485]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
MAX_RATE_UP = 3
MAX_RATE_DOWN = 7
MAX_TORQUE = 384
MAX_RT_DELTA = 112
RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 50
DRIVER_TORQUE_FACTOR = 2
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 89
MAX_INVALID_STEERING_FRAMES = 2
MIN_VALID_STEERING_RT_INTERVAL = 810000 # a ~10% buffer, can send steer up to 110Hz
cnt_gas = 0
cnt_speed = 0
cnt_brake = 0
cnt_cruise = 0
cnt_button = 0
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, 0)
self.safety.init_tests()
def _button_msg(self, buttons, main_button=0, bus=0):
values = {"CF_Clu_CruiseSwState": buttons, "CF_Clu_CruiseSwMain": main_button, "CF_Clu_AliveCnt1": self.cnt_button}
self.__class__.cnt_button += 1
return self.packer.make_can_msg_panda("CLU11", bus, values)
def _user_gas_msg(self, gas):
values = {"CF_Ems_AclAct": gas, "AliveCounter": self.cnt_gas % 4}
self.__class__.cnt_gas += 1
return self.packer.make_can_msg_panda("EMS16", 0, values, fix_checksum=checksum)
def _user_brake_msg(self, brake):
values = {"DriverOverride": 2 if brake else random.choice((0, 1, 3)),
"AliveCounterTCS": self.cnt_brake % 8}
self.__class__.cnt_brake += 1
return self.packer.make_can_msg_panda("TCS13", 0, values, fix_checksum=checksum)
def _speed_msg(self, speed):
# panda safety doesn't scale, so undo the scaling
values = {"WHL_SPD_%s" % s: speed * 0.03125 for s in ["FL", "FR", "RL", "RR"]}
values["WHL_SPD_AliveCounter_LSB"] = (self.cnt_speed % 16) & 0x3
values["WHL_SPD_AliveCounter_MSB"] = (self.cnt_speed % 16) >> 2
self.__class__.cnt_speed += 1
return self.packer.make_can_msg_panda("WHL_SPD11", 0, values, fix_checksum=checksum)
def _pcm_status_msg(self, enable):
values = {"ACCMode": enable, "CR_VSM_Alive": self.cnt_cruise % 16}
self.__class__.cnt_cruise += 1
return self.packer.make_can_msg_panda("SCC12", self.SCC_BUS, values, fix_checksum=checksum)
def _torque_driver_msg(self, torque):
values = {"CR_Mdps_StrColTq": torque}
return self.packer.make_can_msg_panda("MDPS12", 0, values)
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"CR_Lkas_StrToqReq": torque, "CF_Lkas_ActToi": steer_req}
return self.packer.make_can_msg_panda("LKAS11", 0, values)
class TestHyundaiSafetyAltLimits(TestHyundaiSafety):
MAX_RATE_UP = 2
MAX_RATE_DOWN = 3
MAX_TORQUE = 270
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_ALT_LIMITS)
self.safety.init_tests()
class TestHyundaiSafetyCameraSCC(TestHyundaiSafety):
BUTTONS_TX_BUS = 2 # tx on 2, rx on 0
SCC_BUS = 2 # rx on 2
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_CAMERA_SCC)
self.safety.init_tests()
class TestHyundaiLegacySafety(TestHyundaiSafety):
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_LEGACY, 0)
self.safety.init_tests()
class TestHyundaiLegacySafetyEV(TestHyundaiSafety):
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_LEGACY, 1)
self.safety.init_tests()
def _user_gas_msg(self, gas):
values = {"Accel_Pedal_Pos": gas}
return self.packer.make_can_msg_panda("E_EMS11", 0, values, fix_checksum=checksum)
class TestHyundaiLegacySafetyHEV(TestHyundaiSafety):
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_LEGACY, 2)
self.safety.init_tests()
def _user_gas_msg(self, gas):
values = {"CR_Vcu_AccPedDep_Pos": gas}
return self.packer.make_can_msg_panda("E_EMS11", 0, values, fix_checksum=checksum)
class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety):
TX_MSGS = [[0x340, 0], [0x4F1, 0], [0x485, 0], [0x420, 0], [0x421, 0], [0x50A, 0], [0x389, 0], [0x4A2, 0], [0x38D, 0], [0x483, 0], [0x7D0, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0x340, 0x421)} # LKAS11, SCC12
DISABLED_ECU_UDS_MSG = (0x7D0, 0)
DISABLED_ECU_ACTUATION_MSG = (0x421, 0)
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_LONG)
self.safety.init_tests()
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
values = {
"aReqRaw": accel,
"aReqValue": accel,
"AEB_CmdAct": int(aeb_req),
"CR_VSM_DecCmd": aeb_decel,
}
return self.packer.make_can_msg_panda("SCC12", self.SCC_BUS, values)
def _fca11_msg(self, idx=0, vsm_aeb_req=False, fca_aeb_req=False, aeb_decel=0):
values = {
"CR_FCA_Alive": idx % 0xF,
"FCA_Status": 2,
"CR_VSM_DecCmd": aeb_decel,
"CF_VSM_DecCmdAct": int(vsm_aeb_req),
"FCA_CmdAct": int(fca_aeb_req),
}
return self.packer.make_can_msg_panda("FCA11", 0, values)
def test_no_aeb_fca11(self):
self.assertTrue(self._tx(self._fca11_msg()))
self.assertFalse(self._tx(self._fca11_msg(vsm_aeb_req=True)))
self.assertFalse(self._tx(self._fca11_msg(fca_aeb_req=True)))
self.assertFalse(self._tx(self._fca11_msg(aeb_decel=1.0)))
def test_no_aeb_scc12(self):
self.assertTrue(self._tx(self._accel_msg(0)))
self.assertFalse(self._tx(self._accel_msg(0, aeb_req=True)))
self.assertFalse(self._tx(self._accel_msg(0, aeb_decel=1.0)))
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,272 @@
#!/usr/bin/env python3
from parameterized import parameterized_class
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase
class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0]]
STANDSTILL_THRESHOLD = 12 # 0.375 kph
FWD_BLACKLISTED_ADDRS = {2: [0x50, 0x2a4]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
MAX_RATE_UP = 2
MAX_RATE_DOWN = 3
MAX_TORQUE = 270
MAX_RT_DELTA = 112
RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 250
DRIVER_TORQUE_FACTOR = 2
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 89
MAX_INVALID_STEERING_FRAMES = 2
MIN_VALID_STEERING_RT_INTERVAL = 810000 # a ~10% buffer, can send steer up to 110Hz
PT_BUS = 0
SCC_BUS = 2
STEER_BUS = 0
STEER_MSG = ""
GAS_MSG = ("", "")
BUTTONS_TX_BUS = 1
@classmethod
def setUpClass(cls):
super().setUpClass()
if cls.__name__ == "TestHyundaiCanfdBase":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
def _torque_driver_msg(self, torque):
values = {"STEERING_COL_TORQUE": torque}
return self.packer.make_can_msg_panda("MDPS", self.PT_BUS, values)
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"TORQUE_REQUEST": torque, "STEER_REQ": steer_req}
return self.packer.make_can_msg_panda(self.STEER_MSG, self.STEER_BUS, values)
def _speed_msg(self, speed):
values = {f"WHEEL_SPEED_{i}": speed * 0.03125 for i in range(1, 5)}
return self.packer.make_can_msg_panda("WHEEL_SPEEDS", self.PT_BUS, values)
def _user_brake_msg(self, brake):
values = {"DriverBraking": brake}
return self.packer.make_can_msg_panda("TCS", self.PT_BUS, values)
def _user_gas_msg(self, gas):
values = {self.GAS_MSG[1]: gas}
return self.packer.make_can_msg_panda(self.GAS_MSG[0], self.PT_BUS, values)
def _pcm_status_msg(self, enable):
values = {"ACCMode": 1 if enable else 0}
return self.packer.make_can_msg_panda("SCC_CONTROL", self.SCC_BUS, values)
def _button_msg(self, buttons, main_button=0, bus=None):
if bus is None:
bus = self.PT_BUS
values = {
"CRUISE_BUTTONS": buttons,
"ADAPTIVE_CRUISE_MAIN_BTN": main_button,
}
return self.packer.make_can_msg_panda("CRUISE_BUTTONS", bus, values)
class TestHyundaiCanfdHDA1Base(TestHyundaiCanfdBase):
TX_MSGS = [[0x12A, 0], [0x1A0, 1], [0x1CF, 0], [0x1E0, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0x12A,)} # LFA
FWD_BLACKLISTED_ADDRS = {2: [0x12A, 0x1E0]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
STEER_MSG = "LFA"
BUTTONS_TX_BUS = 2
SAFETY_PARAM: int
@classmethod
def setUpClass(cls):
super().setUpClass()
if cls.__name__ in ("TestHyundaiCanfdHDA1", "TestHyundaiCanfdHDA1AltButtons"):
cls.packer = None
cls.safety = None
raise unittest.SkipTest
def setUp(self):
self.packer = CANPackerPanda("hyundai_canfd")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, self.SAFETY_PARAM)
self.safety.init_tests()
@parameterized_class([
# Radar SCC, test with long flag to ensure flag is not respected until it is supported
{"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG},
{"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_LONG},
{"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_LONG},
# Camera SCC
{"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_CAMERA_SCC},
{"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC},
{"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC},
])
class TestHyundaiCanfdHDA1(TestHyundaiCanfdHDA1Base):
pass
@parameterized_class([
# Radar SCC, test with long flag to ensure flag is not respected until it is supported
{"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG},
{"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_LONG},
{"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_LONG},
# Camera SCC
{"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_CAMERA_SCC},
{"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC},
{"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC},
])
class TestHyundaiCanfdHDA1AltButtons(TestHyundaiCanfdHDA1Base):
SAFETY_PARAM: int
def setUp(self):
self.packer = CANPackerPanda("hyundai_canfd")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS | self.SAFETY_PARAM)
self.safety.init_tests()
def _button_msg(self, buttons, main_button=0, bus=1):
values = {
"CRUISE_BUTTONS": buttons,
"ADAPTIVE_CRUISE_MAIN_BTN": main_button,
}
return self.packer.make_can_msg_panda("CRUISE_BUTTONS_ALT", self.PT_BUS, values)
def test_button_sends(self):
"""
No button send allowed with alt buttons.
"""
for enabled in (True, False):
for btn in range(8):
self.safety.set_controls_allowed(enabled)
self.assertFalse(self._tx(self._button_msg(btn)))
class TestHyundaiCanfdHDA2EV(TestHyundaiCanfdBase):
TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0x50,)} # LKAS
FWD_BLACKLISTED_ADDRS = {2: [0x50, 0x2a4]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
PT_BUS = 1
SCC_BUS = 1
STEER_MSG = "LKAS"
GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL")
def setUp(self):
self.packer = CANPackerPanda("hyundai_canfd")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_EV_GAS)
self.safety.init_tests()
# TODO: Handle ICE and HEV configurations once we see cars that use the new messages
class TestHyundaiCanfdHDA2EVAltSteering(TestHyundaiCanfdBase):
TX_MSGS = [[0x110, 0], [0x1CF, 1], [0x362, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0x110,)} # LKAS_ALT
FWD_BLACKLISTED_ADDRS = {2: [0x110, 0x362]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
PT_BUS = 1
SCC_BUS = 1
STEER_MSG = "LKAS_ALT"
GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL")
def setUp(self):
self.packer = CANPackerPanda("hyundai_canfd")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_EV_GAS |
Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING)
self.safety.init_tests()
class TestHyundaiCanfdHDA2LongEV(HyundaiLongitudinalBase, TestHyundaiCanfdHDA2EV):
TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0], [0x51, 0], [0x730, 1], [0x12a, 1], [0x160, 1],
[0x1e0, 1], [0x1a0, 1], [0x1ea, 1], [0x200, 1], [0x345, 1], [0x1da, 1]]
RELAY_MALFUNCTION_ADDRS = {0: (0x50,), 1: (0x1a0,)} # LKAS, SCC_CONTROL
DISABLED_ECU_UDS_MSG = (0x730, 1)
DISABLED_ECU_ACTUATION_MSG = (0x1a0, 1)
STEER_MSG = "LFA"
GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL")
STEER_BUS = 1
def setUp(self):
self.packer = CANPackerPanda("hyundai_canfd")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_EV_GAS)
self.safety.init_tests()
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
values = {
"aReqRaw": accel,
"aReqValue": accel,
}
return self.packer.make_can_msg_panda("SCC_CONTROL", 1, values)
# Tests HDA1 longitudinal for ICE, hybrid, EV
@parameterized_class([
# Camera SCC is the only supported configuration for HDA1 longitudinal, TODO: allow radar SCC
{"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG},
{"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_EV_GAS},
{"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_HYBRID_GAS},
])
class TestHyundaiCanfdHDA1Long(HyundaiLongitudinalBase, TestHyundaiCanfdHDA1Base):
FWD_BLACKLISTED_ADDRS = {2: [0x12a, 0x1e0, 0x1a0]}
RELAY_MALFUNCTION_ADDRS = {0: (0x12A, 0x1a0)} # LFA, SCC_CONTROL
DISABLED_ECU_UDS_MSG = (0x730, 1)
DISABLED_ECU_ACTUATION_MSG = (0x1a0, 0)
STEER_MSG = "LFA"
STEER_BUS = 0
SCC_BUS = 2
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestHyundaiCanfdHDA1Long":
cls.safety = None
raise unittest.SkipTest
def setUp(self):
self.packer = CANPackerPanda("hyundai_canfd")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CAMERA_SCC | self.SAFETY_PARAM)
self.safety.init_tests()
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
values = {
"aReqRaw": accel,
"aReqValue": accel,
}
return self.packer.make_can_msg_panda("SCC_CONTROL", 0, values)
# no knockout
def test_tester_present_allowed(self):
pass
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
class TestMazdaSafety(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest):
TX_MSGS = [[0x243, 0], [0x09d, 0], [0x440, 0]]
STANDSTILL_THRESHOLD = .1
RELAY_MALFUNCTION_ADDRS = {0: (0x243,)}
FWD_BLACKLISTED_ADDRS = {2: [0x243, 0x440]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
MAX_RATE_UP = 10
MAX_RATE_DOWN = 25
MAX_TORQUE = 800
MAX_RT_DELTA = 300
RT_INTERVAL = 250000
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 = CANPackerPanda("mazda_2017")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_MAZDA, 0)
self.safety.init_tests()
def _torque_meas_msg(self, torque):
values = {"STEER_TORQUE_MOTOR": torque}
return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values)
def _torque_driver_msg(self, torque):
values = {"STEER_TORQUE_SENSOR": torque}
return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values)
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"LKAS_REQUEST": torque}
return self.packer.make_can_msg_panda("CAM_LKAS", 0, values)
def _speed_msg(self, speed):
values = {"SPEED": speed}
return self.packer.make_can_msg_panda("ENGINE_DATA", 0, values)
def _user_brake_msg(self, brake):
values = {"BRAKE_ON": brake}
return self.packer.make_can_msg_panda("PEDALS", 0, values)
def _user_gas_msg(self, gas):
values = {"PEDAL_GAS": gas}
return self.packer.make_can_msg_panda("ENGINE_DATA", 0, values)
def _pcm_status_msg(self, enable):
values = {"CRZ_ACTIVE": enable}
return self.packer.make_can_msg_panda("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_panda("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,117 @@
#!/usr/bin/env python3
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
class TestNissanSafety(common.PandaCarSafetyTest, common.AngleSteeringSafetyTest):
TX_MSGS = [[0x169, 0], [0x2b1, 0], [0x4cc, 0], [0x20b, 2], [0x280, 2]]
STANDSTILL_THRESHOLD = 0
GAS_PRESSED_THRESHOLD = 3
RELAY_MALFUNCTION_ADDRS = {0: (0x169,)}
FWD_BLACKLISTED_ADDRS = {0: [0x280], 2: [0x169, 0x2b1, 0x4cc]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
EPS_BUS = 0
CRUISE_BUS = 2
# Angle control limits
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 = CANPackerPanda("nissan_x_trail_2017_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_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_panda("LKAS", 0, values)
def _angle_meas_msg(self, angle: float):
values = {"STEER_ANGLE": angle}
return self.packer.make_can_msg_panda("STEER_ANGLE_SENSOR", self.EPS_BUS, values)
def _pcm_status_msg(self, enable):
values = {"CRUISE_ENABLED": enable}
return self.packer.make_can_msg_panda("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_panda("WHEEL_SPEEDS_REAR", self.EPS_BUS, values)
def _user_brake_msg(self, brake):
values = {"USER_BRAKE_PRESSED": brake}
return self.packer.make_can_msg_panda("DOORS_LIGHTS", self.EPS_BUS, values)
def _user_gas_msg(self, gas):
values = {"GAS_PEDAL": gas}
return self.packer.make_can_msg_panda("GAS_PEDAL", self.EPS_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_panda("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
def setUp(self):
self.packer = CANPackerPanda("nissan_x_trail_2017_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, Panda.FLAG_NISSAN_ALT_EPS_BUS)
self.safety.init_tests()
class TestNissanLeafSafety(TestNissanSafety):
def setUp(self):
self.packer = CANPackerPanda("nissan_leaf_2018_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, 0)
self.safety.init_tests()
def _user_brake_msg(self, brake):
values = {"USER_BRAKE_PRESSED": brake}
return self.packer.make_can_msg_panda("CRUISE_THROTTLE", 0, values)
def _user_gas_msg(self, gas):
values = {"GAS_PEDAL": gas}
return self.packer.make_can_msg_panda("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,228 @@
#!/usr/bin/env python3
import enum
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
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 = 0x121
ES_STATIC_1 = 0x22a
ES_STATIC_2 = 0x325
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.PandaCarSafetyTest):
FLAGS = 0
STANDSTILL_THRESHOLD = 0 # kph
RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS,)}
FWD_BUS_LOOKUP = {SUBARU_MAIN_BUS: SUBARU_CAM_BUS, SUBARU_CAM_BUS: SUBARU_MAIN_BUS}
FWD_BLACKLISTED_ADDRS = fwd_blacklisted_addr()
MAX_RT_DELTA = 940
RT_INTERVAL = 250000
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 = CANPackerPanda("subaru_global_2017_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_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_panda("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_panda("Wheel_Speeds", self.ALT_MAIN_BUS, values)
def _angle_meas_msg(self, angle):
values = {"Steering_Angle": angle}
return self.packer.make_can_msg_panda("Steering_Torque", 0, values)
def _user_brake_msg(self, brake):
values = {"Brake": brake}
return self.packer.make_can_msg_panda("Brake_Status", self.ALT_MAIN_BUS, values)
def _user_gas_msg(self, gas):
values = {"Throttle_Pedal": gas}
return self.packer.make_can_msg_panda("Throttle", 0, values)
def _pcm_status_msg(self, enable):
values = {"Cruise_Activated": enable}
return self.packer.make_can_msg_panda("CruiseControl", self.ALT_MAIN_BUS, values)
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_panda("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 = 2400
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_panda("ES_Brake", self.ALT_MAIN_BUS, values)
def _send_gas_msg(self, gas):
values = {"Cruise_Throttle": gas}
return self.packer.make_can_msg_panda("ES_Distance", self.ALT_MAIN_BUS, values)
def _send_rpm_msg(self, rpm):
values = {"Cruise_RPM": rpm}
return self.packer.make_can_msg_panda("ES_Status", self.ALT_MAIN_BUS, values)
class TestSubaruTorqueSafetyBase(TestSubaruSafetyBase, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
MAX_RATE_UP = 50
MAX_RATE_DOWN = 70
MAX_TORQUE = 2047
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 7
MAX_INVALID_STEERING_FRAMES = 1
MIN_VALID_STEERING_RT_INTERVAL = 144000
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"LKAS_Output": torque, "LKAS_Request": steer_req}
return self.packer.make_can_msg_panda("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 = 40
MAX_RATE_DOWN = 40
MAX_TORQUE = 1000
class TestSubaruGen2TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase):
FLAGS = Panda.FLAG_SUBARU_GEN2
TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS)
class TestSubaruGen1LongitudinalSafety(TestSubaruLongitudinalSafetyBase, TestSubaruTorqueSafetyBase):
FLAGS = Panda.FLAG_SUBARU_LONG
TX_MSGS = lkas_tx_msgs(SUBARU_MAIN_BUS) + long_tx_msgs(SUBARU_MAIN_BUS)
class TestSubaruGen2LongitudinalSafety(TestSubaruLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase):
FLAGS = Panda.FLAG_SUBARU_LONG | Panda.FLAG_SUBARU_GEN2
TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS) + long_tx_msgs(SUBARU_ALT_BUS) + gen2_long_additional_tx_msgs()
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 libpanda_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,70 @@
#!/usr/bin/env python3
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
class TestSubaruPreglobalSafety(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest):
FLAGS = 0
DBC = "subaru_outback_2015_generated"
TX_MSGS = [[0x161, 0], [0x164, 0]]
STANDSTILL_THRESHOLD = 0 # kph
RELAY_MALFUNCTION_ADDRS = {0: (0x164,)}
FWD_BLACKLISTED_ADDRS = {2: [0x161, 0x164]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
MAX_RATE_UP = 50
MAX_RATE_DOWN = 70
MAX_TORQUE = 2047
MAX_RT_DELTA = 940
RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 75
DRIVER_TORQUE_FACTOR = 10
def setUp(self):
self.packer = CANPackerPanda(self.DBC)
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_SUBARU_PREGLOBAL, 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_panda("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_panda("Wheel_Speeds", 0, values)
def _user_brake_msg(self, brake):
values = {"Brake_Pedal": brake}
return self.packer.make_can_msg_panda("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_panda("ES_LKAS", 0, values)
def _user_gas_msg(self, gas):
values = {"Throttle_Pedal": gas}
return self.packer.make_can_msg_panda("Throttle", 0, values)
def _pcm_status_msg(self, enable):
values = {"Cruise_Activated": enable}
return self.packer.make_can_msg_panda("CruiseControl", 0, values)
class TestSubaruPreglobalReversedDriverTorqueSafety(TestSubaruPreglobalSafety):
FLAGS = Panda.FLAG_SUBARU_PREGLOBAL_REVERSED_DRIVER_TORQUE
DBC = "subaru_outback_2019_generated"
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,185 @@
#!/usr/bin/env python3
import unittest
import numpy as np
from panda import Panda
import panda.tests.safety.common as common
from panda.tests.libpanda import libpanda_py
from panda.tests.safety.common import CANPackerPanda
MAX_ACCEL = 2.0
MIN_ACCEL = -3.5
class CONTROL_LEVER_STATE:
DN_1ST = 32
UP_1ST = 16
DN_2ND = 8
UP_2ND = 4
RWD = 2
FWD = 1
IDLE = 0
class TestTeslaSafety(common.PandaCarSafetyTest):
STANDSTILL_THRESHOLD = 0
GAS_PRESSED_THRESHOLD = 3
FWD_BUS_LOOKUP = {0: 2, 2: 0}
def setUp(self):
self.packer = None
raise unittest.SkipTest
def _speed_msg(self, speed):
values = {"DI_vehicleSpeed": speed / 0.447}
return self.packer.make_can_msg_panda("DI_torque2", 0, values)
def _user_brake_msg(self, brake):
values = {"driverBrakeStatus": 2 if brake else 1}
return self.packer.make_can_msg_panda("BrakeMessage", 0, values)
def _user_gas_msg(self, gas):
values = {"DI_pedalPos": gas}
return self.packer.make_can_msg_panda("DI_torque1", 0, values)
def _control_lever_cmd(self, command):
values = {"SpdCtrlLvr_Stat": command}
return self.packer.make_can_msg_panda("STW_ACTN_RQ", 0, values)
def _pcm_status_msg(self, enable):
values = {"DI_cruiseState": 2 if enable else 0}
return self.packer.make_can_msg_panda("DI_state", 0, values)
def _long_control_msg(self, set_speed, acc_val=0, jerk_limits=(0, 0), accel_limits=(0, 0), aeb_event=0, bus=0):
values = {
"DAS_setSpeed": set_speed,
"DAS_accState": acc_val,
"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_panda("DAS_control", bus, values)
class TestTeslaSteeringSafety(TestTeslaSafety, common.AngleSteeringSafetyTest):
TX_MSGS = [[0x488, 0], [0x45, 0], [0x45, 2]]
RELAY_MALFUNCTION_ADDRS = {0: (0x488,)}
FWD_BLACKLISTED_ADDRS = {2: [0x488]}
# Angle control limits
DEG_TO_CAN = 10
ANGLE_RATE_BP = [0., 5., 15.]
ANGLE_RATE_UP = [10., 1.6, .3] # windup limit
ANGLE_RATE_DOWN = [10., 7.0, .8] # unwind limit
def setUp(self):
self.packer = CANPackerPanda("tesla_can")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TESLA, 0)
self.safety.init_tests()
def _angle_cmd_msg(self, angle: float, enabled: bool):
values = {"DAS_steeringAngleRequest": angle, "DAS_steeringControlType": 1 if enabled else 0}
return self.packer.make_can_msg_panda("DAS_steeringControl", 0, values)
def _angle_meas_msg(self, angle: float):
values = {"EPAS_internalSAS": angle}
return self.packer.make_can_msg_panda("EPAS_sysStatus", 0, values)
def test_acc_buttons(self):
"""
FWD (cancel) always allowed.
"""
btns = [
(CONTROL_LEVER_STATE.FWD, True),
(CONTROL_LEVER_STATE.RWD, False),
(CONTROL_LEVER_STATE.UP_1ST, False),
(CONTROL_LEVER_STATE.UP_2ND, False),
(CONTROL_LEVER_STATE.DN_1ST, False),
(CONTROL_LEVER_STATE.DN_2ND, False),
(CONTROL_LEVER_STATE.IDLE, False),
]
for btn, should_tx in btns:
for controls_allowed in (True, False):
self.safety.set_controls_allowed(controls_allowed)
tx = self._tx(self._control_lever_cmd(btn))
self.assertEqual(tx, should_tx)
class TestTeslaRavenSteeringSafety(TestTeslaSteeringSafety):
def setUp(self):
self.packer = CANPackerPanda("tesla_can")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TESLA, Panda.FLAG_TESLA_RAVEN)
self.safety.init_tests()
def _angle_meas_msg(self, angle: float):
values = {"EPAS_internalSAS": angle}
return self.packer.make_can_msg_panda("EPAS3P_sysStatus", 2, values)
class TestTeslaLongitudinalSafety(TestTeslaSafety):
def setUp(self):
raise unittest.SkipTest
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.assertEqual(True, 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.assertEqual(False, self._tx(no_aeb_msg))
def test_acc_accel_limits(self):
for controls_allowed in [True, False]:
self.safety.set_controls_allowed(controls_allowed)
for min_accel in np.arange(MIN_ACCEL - 1, MAX_ACCEL + 1, 0.1):
for max_accel in np.arange(MIN_ACCEL - 1, MAX_ACCEL + 1, 0.1):
# floats might not hit exact boundary conditions without rounding
min_accel = round(min_accel, 2)
max_accel = round(max_accel, 2)
if controls_allowed:
send = (MIN_ACCEL <= min_accel <= MAX_ACCEL) and (MIN_ACCEL <= max_accel <= MAX_ACCEL)
else:
send = np.all(np.isclose([min_accel, max_accel], 0, atol=0.0001))
self.assertEqual(send, self._tx(self._long_control_msg(10, acc_val=4, accel_limits=[min_accel, max_accel])))
class TestTeslaChassisLongitudinalSafety(TestTeslaLongitudinalSafety):
TX_MSGS = [[0x488, 0], [0x45, 0], [0x45, 2], [0x2B9, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0x488,)}
FWD_BLACKLISTED_ADDRS = {2: [0x2B9, 0x488]}
def setUp(self):
self.packer = CANPackerPanda("tesla_can")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TESLA, Panda.FLAG_TESLA_LONG_CONTROL)
self.safety.init_tests()
class TestTeslaPTLongitudinalSafety(TestTeslaLongitudinalSafety):
TX_MSGS = [[0x2BF, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0x2BF,)}
FWD_BLACKLISTED_ADDRS = {2: [0x2BF]}
def setUp(self):
self.packer = CANPackerPanda("tesla_powertrain")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TESLA, Panda.FLAG_TESLA_LONG_CONTROL | Panda.FLAG_TESLA_POWERTRAIN)
self.safety.init_tests()
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,367 @@
#!/usr/bin/env python3
import numpy as np
import random
import unittest
import itertools
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
TOYOTA_COMMON_TX_MSGS = [[0x2E4, 0], [0x191, 0], [0x412, 0], [0x343, 0], [0x1D2, 0]] # LKAS + LTA + ACC & PCM cancel cmds
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]]
class TestToyotaSafetyBase(common.PandaCarSafetyTest, common.LongitudinalAccelSafetyTest):
TX_MSGS = TOYOTA_COMMON_TX_MSGS + TOYOTA_COMMON_LONG_TX_MSGS
STANDSTILL_THRESHOLD = 0 # kph
RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x343)}
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191, 0x343]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
EPS_SCALE = 73
packer: CANPackerPanda
safety: libpanda_py.Panda
@classmethod
def setUpClass(cls):
if cls.__name__.endswith("Base"):
cls.packer = None
cls.safety = None
raise unittest.SkipTest
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_panda("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_panda("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_panda("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_panda("STEERING_LTA", 0, values)
def _accel_msg(self, accel, cancel_req=0):
values = {"ACCEL_CMD": accel, "CANCEL_REQ": cancel_req}
return self.packer.make_can_msg_panda("ACC_CONTROL", 0, values)
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_panda("WHEEL_SPEEDS", 0, values)
def _user_brake_msg(self, brake):
values = {"BRAKE_PRESSED": brake}
return self.packer.make_can_msg_panda("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_panda("PCM_CRUISE", 0, values)
def _pcm_status_msg(self, enable):
values = {"CRUISE_ACTIVE": enable}
return self.packer.make_can_msg_panda("PCM_CRUISE", 0, values)
def test_diagnostics(self, stock_longitudinal: bool = False):
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 = libpanda_py.make_CANPacket(0x750, 0, msg)
self.assertEqual(should_tx 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 = libpanda_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)))
def test_rx_hook(self):
# checksum checks
for msg in ["trq", "pcm"]:
self.safety.set_controls_allowed(1)
if msg == "trq":
to_push = self._torque_meas_msg(0)
if msg == "pcm":
to_push = self._pcm_status_msg(True)
self.assertTrue(self._rx(to_push))
to_push[0].data[4] = 0
to_push[0].data[5] = 0
to_push[0].data[6] = 0
to_push[0].data[7] = 0
self.assertFalse(self._rx(to_push))
self.assertFalse(self.safety.get_controls_allowed())
class TestToyotaSafetyGasInterceptorBase(common.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.set_safety_hooks(Panda.SAFETY_TOYOTA, self.safety.get_current_safety_param() |
Panda.FLAG_TOYOTA_GAS_INTERCEPTOR)
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.set_safety_hooks(Panda.SAFETY_TOYOTA, self.safety.get_current_safety_param() |
Panda.FLAG_TOYOTA_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_disengage_on_gas_interceptor,
self.test_gas_interceptor_safety_check):
with self.subTest(test=test.__name__):
with self.assertRaises(AssertionError):
test()
class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
MAX_RATE_UP = 15
MAX_RATE_DOWN = 25
MAX_TORQUE = 1500
MAX_RT_DELTA = 450
RT_INTERVAL = 250000
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 = 18
MAX_INVALID_STEERING_FRAMES = 1
MIN_VALID_STEERING_RT_INTERVAL = 170000 # a ~10% buffer, can send steer up to 110Hz
def setUp(self):
self.packer = CANPackerPanda("toyota_nodsu_pt_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE)
self.safety.init_tests()
class TestToyotaSafetyTorqueGasInterceptor(TestToyotaSafetyGasInterceptorBase, TestToyotaSafetyTorque):
pass
class TestToyotaSafetyAngle(TestToyotaSafetyBase, common.AngleSteeringSafetyTest):
# Angle control limits
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 = CANPackerPanda("toyota_nodsu_pt_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_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_steering_angle_measurements(self, max_angle=None):
# Measurement test tests max angle + 0.5 which will fail
super().test_steering_angle_measurements(max_angle=self.MAX_LTA_ANGLE - 0.5)
def test_angle_cmd_when_enabled(self, max_angle=None):
super().test_angle_cmd_when_enabled(max_angle=self.MAX_LTA_ANGLE)
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.MAX_LTA_ANGLE * 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(min(angle, self.MAX_LTA_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
class TestToyotaAltBrakeSafety(TestToyotaSafetyTorque):
def setUp(self):
self.packer = CANPackerPanda("toyota_new_mc_pt_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_ALT_BRAKE)
self.safety.init_tests()
def _user_brake_msg(self, brake):
values = {"BRAKE_PRESSED": brake}
return self.packer.make_can_msg_panda("BRAKE_MODULE", 0, values)
# No LTA message in the DBC
def test_lta_steer_cmd(self):
pass
class TestToyotaAltBrakeSafetyGasInterceptor(TestToyotaSafetyGasInterceptorBase, TestToyotaAltBrakeSafety):
pass
class TestToyotaStockLongitudinalBase(TestToyotaSafetyBase):
TX_MSGS = TOYOTA_COMMON_TX_MSGS
# Base addresses minus ACC_CONTROL (0x343)
RELAY_MALFUNCTION_ADDRS = {0: (0x2E4,)}
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191]}
def test_diagnostics(self, stock_longitudinal: bool = True):
super().test_diagnostics(stock_longitudinal=stock_longitudinal)
def test_block_aeb(self, stock_longitudinal: bool = True):
super().test_block_aeb(stock_longitudinal=stock_longitudinal)
def test_accel_actuation_limits(self, stock_longitudinal=True):
super().test_accel_actuation_limits(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(accel)))
should_tx = np.isclose(accel, 0, atol=0.0001)
self.assertEqual(should_tx, self._tx(self._accel_msg(accel, cancel_req=1)))
class TestToyotaStockLongitudinalTorque(TestToyotaStockLongitudinalBase, TestToyotaSafetyTorque):
def setUp(self):
self.packer = CANPackerPanda("toyota_nodsu_pt_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL)
self.safety.init_tests()
class TestToyotaStockLongitudinalAngle(TestToyotaStockLongitudinalBase, TestToyotaSafetyAngle):
def setUp(self):
self.packer = CANPackerPanda("toyota_nodsu_pt_generated")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, self.EPS_SCALE | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL | Panda.FLAG_TOYOTA_LTA)
self.safety.init_tests()
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,225 @@
#!/usr/bin/env python3
import unittest
import numpy as np
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
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
class TestVolkswagenMqbSafety(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest):
STANDSTILL_THRESHOLD = 0
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01,)}
MAX_RATE_UP = 4
MAX_RATE_DOWN = 10
MAX_TORQUE = 300
MAX_RT_DELTA = 75
RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 80
DRIVER_TORQUE_FACTOR = 3
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestVolkswagenMqbSafety":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
# 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_panda("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_panda("ESP_05", 0, values)
# Brake pedal switch
def _motor_14_msg(self, brake):
values = {"MO_Fahrer_bremst": brake}
return self.packer.make_can_msg_panda("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_panda("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_panda("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_panda("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_panda("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_panda("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_panda("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_panda("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(TestVolkswagenMqbSafety):
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]]
FWD_BLACKLISTED_ADDRS = {0: [MSG_LH_EPS_03], 2: [MSG_HCA_01, MSG_LDW_02]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
def setUp(self):
self.packer = CANPackerPanda("vw_mqb_2010")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_MQB, 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(TestVolkswagenMqbSafety):
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]]
FWD_BLACKLISTED_ADDRS = {0: [MSG_LH_EPS_03], 2: [MSG_HCA_01, MSG_LDW_02, MSG_ACC_02, MSG_ACC_06, MSG_ACC_07]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
INACTIVE_ACCEL = 3.01
def setUp(self):
self.packer = CANPackerPanda("vw_mqb_2010")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_MQB, Panda.FLAG_VOLKSWAGEN_LONG_CONTROL)
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]:
# enforce we don't skip over 0 or inactive accel
for accel in np.concatenate((np.arange(MIN_ACCEL - 2, MAX_ACCEL + 2, 0.03), [0, self.INACTIVE_ACCEL])):
accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding
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)
# primary accel request used by ECU
self.assertEqual(send, self._tx(self._acc_06_msg(accel)), (controls_allowed, accel))
# additional accel request used by ABS/ESP
self.assertEqual(send, self._tx(self._acc_07_msg(accel)), (controls_allowed, accel))
# ensure the optional secondary accel field remains inactive for now
self.assertEqual(is_inactive_accel, self._tx(self._acc_07_msg(accel, secondary_accel=accel)), (controls_allowed, accel))
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python3
import unittest
from panda import Panda
from panda.tests.libpanda import libpanda_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
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
class TestVolkswagenPqSafety(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest):
cruise_engaged = False
STANDSTILL_THRESHOLD = 0
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_1,)}
MAX_RATE_UP = 6
MAX_RATE_DOWN = 10
MAX_TORQUE = 300
MAX_RT_DELTA = 113
RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 80
DRIVER_TORQUE_FACTOR = 3
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestVolkswagenPqSafety":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
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 = {"Geschwindigkeit_neu__Bremse_1_": speed}
return self.packer.make_can_msg_panda("Bremse_1", 0, 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())
# 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)
# Acceleration request to drivetrain coordinator
def _accel_msg(self, accel):
values = {"ACS_Sollbeschl": accel}
return self.packer.make_can_msg_panda("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_panda("Lenkhilfe_3", 0, values)
# openpilot steering output torque
def _torque_cmd_msg(self, torque, steer_req=1, hca_status=5):
values = {"LM_Offset": abs(torque), "LM_OffSign": torque < 0, "HCA_Status": hca_status if steer_req else 3}
return self.packer.make_can_msg_panda("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):
values = {"Bremslichtschalter": brake_pressed,
"GRA_Status": cruise_engaged}
return self.packer.make_can_msg_panda("Motor_2", 0, values)
# ACC main switch status
def _motor_5_msg(self, main_switch=False):
values = {"GRA_Hauptschalter": main_switch}
return self.packer.make_can_msg_panda("Motor_5", 0, values)
# Driver throttle input (Motor_3)
def _user_gas_msg(self, gas):
values = {"Fahrpedal_Rohsignal": gas}
return self.packer.make_can_msg_panda("Motor_3", 0, 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_panda("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(TestVolkswagenPqSafety):
# Transmit of GRA_Neu is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration
TX_MSGS = [[MSG_HCA_1, 0], [MSG_GRA_NEU, 0], [MSG_GRA_NEU, 2], [MSG_LDW_1, 0]]
FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
def setUp(self):
self.packer = CANPackerPanda("vw_golf_mk4")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_PQ, 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(TestVolkswagenPqSafety, common.LongitudinalAccelSafetyTest):
TX_MSGS = [[MSG_HCA_1, 0], [MSG_LDW_1, 0], [MSG_ACC_SYSTEM, 0], [MSG_ACC_GRA_ANZEIGE, 0]]
FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1, MSG_ACC_SYSTEM, MSG_ACC_GRA_ANZEIGE]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
INACTIVE_ACCEL = 3.01
def setUp(self):
self.packer = CANPackerPanda("vw_golf_mk4")
self.safety = libpanda_py.libpanda
self.safety.set_safety_hooks(Panda.SAFETY_VOLKSWAGEN_PQ, Panda.FLAG_VOLKSWAGEN_LONG_CONTROL)
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=0))
self._rx(self._button_msg(bus=0))
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=0))
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} rising edge")
self._rx(self._button_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._motor_5_msg(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(), "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_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=}")
if __name__ == "__main__":
unittest.main()