wip
This commit is contained in:
0
panda/tests/safety/__init__.py
Normal file
0
panda/tests/safety/__init__.py
Normal file
1071
panda/tests/safety/common.py
Normal file
1071
panda/tests/safety/common.py
Normal file
File diff suppressed because it is too large
Load Diff
157
panda/tests/safety/hyundai_common.py
Normal file
157
panda/tests/safety/hyundai_common.py
Normal 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())
|
||||
|
||||
34
panda/tests/safety/test.sh
Normal file
34
panda/tests/safety/test.sh
Normal 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
|
||||
70
panda/tests/safety/test_body.py
Normal file
70
panda/tests/safety/test_body.py
Normal 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()
|
||||
125
panda/tests/safety/test_chrysler.py
Normal file
125
panda/tests/safety/test_chrysler.py
Normal 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()
|
||||
73
panda/tests/safety/test_defaults.py
Normal file
73
panda/tests/safety/test_defaults.py
Normal 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()
|
||||
48
panda/tests/safety/test_elm327.py
Normal file
48
panda/tests/safety/test_elm327.py
Normal 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()
|
||||
476
panda/tests/safety/test_ford.py
Normal file
476
panda/tests/safety/test_ford.py
Normal 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()
|
||||
383
panda/tests/safety/test_gm.py
Normal file
383
panda/tests/safety/test_gm.py
Normal 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()
|
||||
618
panda/tests/safety/test_honda.py
Normal file
618
panda/tests/safety/test_honda.py
Normal 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()
|
||||
216
panda/tests/safety/test_hyundai.py
Normal file
216
panda/tests/safety/test_hyundai.py
Normal 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()
|
||||
272
panda/tests/safety/test_hyundai_canfd.py
Normal file
272
panda/tests/safety/test_hyundai_canfd.py
Normal 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()
|
||||
86
panda/tests/safety/test_mazda.py
Normal file
86
panda/tests/safety/test_mazda.py
Normal 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()
|
||||
117
panda/tests/safety/test_nissan.py
Normal file
117
panda/tests/safety/test_nissan.py
Normal 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()
|
||||
228
panda/tests/safety/test_subaru.py
Normal file
228
panda/tests/safety/test_subaru.py
Normal 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()
|
||||
70
panda/tests/safety/test_subaru_preglobal.py
Normal file
70
panda/tests/safety/test_subaru_preglobal.py
Normal 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()
|
||||
185
panda/tests/safety/test_tesla.py
Normal file
185
panda/tests/safety/test_tesla.py
Normal 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()
|
||||
367
panda/tests/safety/test_toyota.py
Normal file
367
panda/tests/safety/test_toyota.py
Normal 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()
|
||||
225
panda/tests/safety/test_volkswagen_mqb.py
Normal file
225
panda/tests/safety/test_volkswagen_mqb.py
Normal 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()
|
||||
199
panda/tests/safety/test_volkswagen_pq.py
Normal file
199
panda/tests/safety/test_volkswagen_pq.py
Normal 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()
|
||||
Reference in New Issue
Block a user