From 9874e73350198edc66c61ae53769cd15ae88596b Mon Sep 17 00:00:00 2001 From: Adeeb <8762862+quillford@users.noreply.github.com> Date: Wed, 29 Apr 2020 20:37:12 -0700 Subject: [PATCH] Abstract steering safety tests for Toyota and Chrysler (#520) * start abstracting torque steering tests * remove duplicate * chrysler * remove that * revert that for now * fix toyota * unused --- tests/safety/common.py | 135 ++++++++++++++++++++++++++++++++++ tests/safety/test_chrysler.py | 121 ++++-------------------------- tests/safety/test_toyota.py | 112 +++------------------------- 3 files changed, 158 insertions(+), 210 deletions(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index 44050ed..8d81f11 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -109,6 +109,141 @@ class InterceptorSafetyTest(PandaSafetyTestBase): self.assertEqual(send, self._tx(self._interceptor_msg(gas, 0x200))) +class TorqueSteeringSafetyTest(PandaSafetyTestBase): + + MAX_RATE_UP = 0 + MAX_RATE_DOWN = 0 + MAX_TORQUE = 0 + MAX_RT_DELTA = 0 + RT_INTERVAL = 0 + MAX_TORQUE_ERROR = 0 + TORQUE_MEAS_TOLERANCE = 0 + + @classmethod + def setUpClass(cls): + if cls.__name__ == "TorqueSteeringSafetyTest": + cls.safety = None + raise unittest.SkipTest + + @abc.abstractmethod + def _torque_meas_msg(self, torque): + pass + + @abc.abstractmethod + def _torque_msg(self, torque): + pass + + def _set_prev_torque(self, t): + self.safety.set_desired_torque_last(t) + self.safety.set_rt_torque_last(t) + self.safety.set_torque_meas(t, t) + + def test_steer_safety_check(self): + for enabled in [0, 1]: + for t in range(-self.MAX_TORQUE*2, self.MAX_TORQUE*2): + self.safety.set_controls_allowed(enabled) + self._set_prev_torque(t) + if abs(t) > self.MAX_TORQUE or (not enabled and abs(t) > 0): + self.assertFalse(self._tx(self._torque_msg(t))) + else: + self.assertTrue(self._tx(self._torque_msg(t))) + + def test_torque_absolute_limits(self): + for controls_allowed in [True, False]: + for torque in np.arange(-self.MAX_TORQUE - 1000, self.MAX_TORQUE + 1000, self.MAX_RATE_UP): + self.safety.set_controls_allowed(controls_allowed) + self.safety.set_rt_torque_last(torque) + self.safety.set_torque_meas(torque, torque) + self.safety.set_desired_torque_last(torque - self.MAX_RATE_UP) + + if controls_allowed: + send = (-self.MAX_TORQUE <= torque <= self.MAX_TORQUE) + else: + send = torque == 0 + + self.assertEqual(send, self._tx(self._torque_msg(torque))) + + def test_non_realtime_limit_up(self): + self.safety.set_controls_allowed(True) + + self._set_prev_torque(0) + self.assertTrue(self._tx(self._torque_msg(self.MAX_RATE_UP))) + + self._set_prev_torque(0) + self.assertFalse(self._tx(self._torque_msg(self.MAX_RATE_UP + 1))) + + def test_non_realtime_limit_down(self): + self.safety.set_controls_allowed(True) + + torque_meas = self.MAX_TORQUE - self.MAX_TORQUE_ERROR - 50 + + self.safety.set_rt_torque_last(self.MAX_TORQUE) + self.safety.set_torque_meas(torque_meas, torque_meas) + self.safety.set_desired_torque_last(self.MAX_TORQUE) + self.assertTrue(self._tx(self._torque_msg(self.MAX_TORQUE - self.MAX_RATE_DOWN))) + + self.safety.set_rt_torque_last(self.MAX_TORQUE) + self.safety.set_torque_meas(torque_meas, torque_meas) + self.safety.set_desired_torque_last(self.MAX_TORQUE) + self.assertFalse(self._tx(self._torque_msg(self.MAX_TORQUE - self.MAX_RATE_DOWN + 1))) + + def test_exceed_torque_sensor(self): + self.safety.set_controls_allowed(True) + + for sign in [-1, 1]: + self._set_prev_torque(0) + for t in np.arange(0, self.MAX_TORQUE_ERROR + 2, 2): # step needs to be smaller than MAX_TORQUE_ERROR + t *= sign + self.assertTrue(self._tx(self._torque_msg(t))) + + self.assertFalse(self._tx(self._torque_msg(sign * (self.MAX_TORQUE_ERROR + 2)))) + + def test_realtime_limit_up(self): + self.safety.set_controls_allowed(True) + + for sign in [-1, 1]: + self.safety.init_tests() + self._set_prev_torque(0) + for t in np.arange(0, self.MAX_RT_DELTA+1, 1): + t *= sign + self.safety.set_torque_meas(t, t) + self.assertTrue(self._tx(self._torque_msg(t))) + self.assertFalse(self._tx(self._torque_msg(sign * (self.MAX_RT_DELTA + 1)))) + + self._set_prev_torque(0) + for t in np.arange(0, self.MAX_RT_DELTA+1, 1): + t *= sign + self.safety.set_torque_meas(t, t) + self.assertTrue(self._tx(self._torque_msg(t))) + + # Increase timer to update rt_torque_last + self.safety.set_timer(self.RT_INTERVAL + 1) + self.assertTrue(self._tx(self._torque_msg(sign * self.MAX_RT_DELTA))) + self.assertTrue(self._tx(self._torque_msg(sign * (self.MAX_RT_DELTA + 1)))) + + def test_torque_measurements(self): + trq = 50 + for t in [trq, -trq, 0, 0, 0, 0]: + self._rx(self._torque_meas_msg(t)) + + max_range = range(trq, trq + self.TORQUE_MEAS_TOLERANCE + 1) + min_range = range(-(trq+self.TORQUE_MEAS_TOLERANCE), -trq + 1) + self.assertTrue(self.safety.get_torque_meas_min() in min_range) + self.assertTrue(self.safety.get_torque_meas_max() in max_range) + + max_range = range(0, self.TORQUE_MEAS_TOLERANCE+1) + min_range = range(-(trq+self.TORQUE_MEAS_TOLERANCE), -trq + 1) + self._rx(self._torque_meas_msg(0)) + self.assertTrue(self.safety.get_torque_meas_min() in min_range) + self.assertTrue(self.safety.get_torque_meas_max() in max_range) + + max_range = range(0, self.TORQUE_MEAS_TOLERANCE+1) + min_range = range(-self.TORQUE_MEAS_TOLERANCE, 0 + 1) + self._rx(self._torque_meas_msg(0)) + self.assertTrue(self.safety.get_torque_meas_min() in min_range) + self.assertTrue(self.safety.get_torque_meas_max() in max_range) + + class PandaSafetyTest(PandaSafetyTestBase): TX_MSGS = None STANDSTILL_THRESHOLD = None diff --git a/tests/safety/test_chrysler.py b/tests/safety/test_chrysler.py index 6a259ef..7584156 100755 --- a/tests/safety/test_chrysler.py +++ b/tests/safety/test_chrysler.py @@ -1,20 +1,10 @@ #!/usr/bin/env python3 import unittest -import numpy as np from panda import Panda from panda.tests.safety import libpandasafety_py import panda.tests.safety.common as common from panda.tests.safety.common import make_msg -MAX_RATE_UP = 3 -MAX_RATE_DOWN = 3 -MAX_STEER = 261 - -MAX_RT_DELTA = 112 -RT_INTERVAL = 250000 - -MAX_TORQUE_ERROR = 80 - def chrysler_checksum(msg, len_msg): checksum = 0xFF @@ -41,12 +31,7 @@ def chrysler_checksum(msg, len_msg): shift = shift >> 1 return ~checksum & 0xFF -class TestChryslerSafety(common.PandaSafetyTest): - cnt_torque_meas = 0 - cnt_gas = 0 - cnt_cruise = 0 - cnt_brake = 0 - +class TestChryslerSafety(common.PandaSafetyTest, common.TorqueSteeringSafetyTest): TX_MSGS = [[571, 0], [658, 0], [678, 0]] STANDSTILL_THRESHOLD = 0 RELAY_MALFUNCTION_ADDR = 0x292 @@ -54,6 +39,18 @@ class TestChryslerSafety(common.PandaSafetyTest): FWD_BLACKLISTED_ADDRS = {2: [658, 678]} 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 + + cnt_torque_meas = 0 + cnt_gas = 0 + cnt_cruise = 0 + cnt_brake = 0 + def setUp(self): self.safety = libpandasafety_py.libpandasafety self.safety.set_safety_hooks(Panda.SAFETY_CHRYSLER, 0) @@ -94,11 +91,6 @@ class TestChryslerSafety(common.PandaSafetyTest): self.__class__.cnt_brake += 1 return to_send - def _set_prev_torque(self, t): - self.safety.set_desired_torque_last(t) - self.safety.set_rt_torque_last(t) - self.safety.set_torque_meas(t, t) - def _torque_meas_msg(self, torque): to_send = make_msg(0, 544) to_send[0].RDHR = ((torque + 1024) >> 8) + (((torque + 1024) & 0xff) << 8) @@ -112,17 +104,6 @@ class TestChryslerSafety(common.PandaSafetyTest): to_send[0].RDLR = ((torque + 1024) >> 8) + (((torque + 1024) & 0xff) << 8) return to_send - def test_steer_safety_check(self): - for enabled in [0, 1]: - for t in range(-MAX_STEER*2, MAX_STEER*2): - self.safety.set_controls_allowed(enabled) - self._set_prev_torque(t) - if abs(t) > MAX_STEER or (not enabled and abs(t) > 0): - self.assertFalse(self._tx(self._torque_msg(t))) - else: - self.assertTrue(self._tx(self._torque_msg(t))) - - # TODO: why does chrysler check if moving? def test_disengage_on_gas(self): self.safety.set_controls_allowed(1) self._rx(self._speed_msg(2.2)) @@ -133,82 +114,6 @@ class TestChryslerSafety(common.PandaSafetyTest): self._rx(self._gas_msg(1)) self.assertFalse(self.safety.get_controls_allowed()) - def test_non_realtime_limit_up(self): - self.safety.set_controls_allowed(True) - - self._set_prev_torque(0) - self.assertTrue(self._tx(self._torque_msg(MAX_RATE_UP))) - - self._set_prev_torque(0) - self.assertFalse(self._tx(self._torque_msg(MAX_RATE_UP + 1))) - - def test_non_realtime_limit_down(self): - self.safety.set_controls_allowed(True) - - self.safety.set_rt_torque_last(MAX_STEER) - torque_meas = MAX_STEER - MAX_TORQUE_ERROR - 20 - self.safety.set_torque_meas(torque_meas, torque_meas) - self.safety.set_desired_torque_last(MAX_STEER) - self.assertTrue(self._tx(self._torque_msg(MAX_STEER - MAX_RATE_DOWN))) - - self.safety.set_rt_torque_last(MAX_STEER) - self.safety.set_torque_meas(torque_meas, torque_meas) - self.safety.set_desired_torque_last(MAX_STEER) - self.assertFalse(self._tx(self._torque_msg(MAX_STEER - MAX_RATE_DOWN + 1))) - - def test_exceed_torque_sensor(self): - self.safety.set_controls_allowed(True) - - for sign in [-1, 1]: - self._set_prev_torque(0) - for t in np.arange(0, MAX_TORQUE_ERROR + 2, 2): # step needs to be smaller than MAX_TORQUE_ERROR - t *= sign - self.assertTrue(self._tx(self._torque_msg(t))) - - self.assertFalse(self._tx(self._torque_msg(sign * (MAX_TORQUE_ERROR + 2)))) - - def test_realtime_limit_up(self): - self.safety.set_controls_allowed(True) - - for sign in [-1, 1]: - self.safety.init_tests_chrysler() - self._set_prev_torque(0) - for t in np.arange(0, MAX_RT_DELTA+1, 1): - t *= sign - self.safety.set_torque_meas(t, t) - self.assertTrue(self._tx(self._torque_msg(t))) - self.assertFalse(self._tx(self._torque_msg(sign * (MAX_RT_DELTA + 1)))) - - self._set_prev_torque(0) - for t in np.arange(0, MAX_RT_DELTA+1, 1): - t *= sign - self.safety.set_torque_meas(t, t) - self.assertTrue(self._tx(self._torque_msg(t))) - - # Increase timer to update rt_torque_last - self.safety.set_timer(RT_INTERVAL + 1) - self.assertTrue(self._tx(self._torque_msg(sign * MAX_RT_DELTA))) - self.assertTrue(self._tx(self._torque_msg(sign * (MAX_RT_DELTA + 1)))) - - def test_torque_measurements(self): - self._rx(self._torque_meas_msg(50)) - self._rx(self._torque_meas_msg(-50)) - self._rx(self._torque_meas_msg(0)) - self._rx(self._torque_meas_msg(0)) - self._rx(self._torque_meas_msg(0)) - self._rx(self._torque_meas_msg(0)) - - self.assertEqual(-50, self.safety.get_torque_meas_min()) - self.assertEqual(50, self.safety.get_torque_meas_max()) - - self._rx(self._torque_meas_msg(0)) - self.assertEqual(0, self.safety.get_torque_meas_max()) - self.assertEqual(-50, self.safety.get_torque_meas_min()) - - self._rx(self._torque_meas_msg(0)) - self.assertEqual(0, self.safety.get_torque_meas_max()) - self.assertEqual(0, self.safety.get_torque_meas_min()) - def test_cancel_button(self): CANCEL = 1 for b in range(0, 0x1ff): diff --git a/tests/safety/test_toyota.py b/tests/safety/test_toyota.py index dee80d7..b0247ff 100755 --- a/tests/safety/test_toyota.py +++ b/tests/safety/test_toyota.py @@ -6,22 +6,14 @@ from panda.tests.safety import libpandasafety_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda, make_msg, UNSAFE_MODE -MAX_RATE_UP = 10 -MAX_RATE_DOWN = 25 -MAX_TORQUE = 1500 - MAX_ACCEL = 1.5 MIN_ACCEL = -3.0 ISO_MAX_ACCEL = 2.0 ISO_MIN_ACCEL = -3.5 -MAX_RT_DELTA = 375 -RT_INTERVAL = 250000 - -MAX_TORQUE_ERROR = 350 - -class TestToyotaSafety(common.PandaSafetyTest, common.InterceptorSafetyTest): +class TestToyotaSafety(common.PandaSafetyTest, common.InterceptorSafetyTest, \ + common.TorqueSteeringSafetyTest): 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 @@ -34,6 +26,14 @@ class TestToyotaSafety(common.PandaSafetyTest, common.InterceptorSafetyTest): FWD_BUS_LOOKUP = {0: 2, 2: 0} INTERCEPTOR_THRESHOLD = 845 + MAX_RATE_UP = 10 + MAX_RATE_DOWN = 25 + MAX_TORQUE = 1500 + MAX_RT_DELTA = 375 + RT_INTERVAL = 250000 + MAX_TORQUE_ERROR = 350 + TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conversative for rounding + @classmethod def setUp(self): self.packer = CANPackerPanda("toyota_prius_2017_pt_generated") @@ -41,11 +41,6 @@ class TestToyotaSafety(common.PandaSafetyTest, common.InterceptorSafetyTest): self.safety.set_safety_hooks(Panda.SAFETY_TOYOTA, 66) self.safety.init_tests() - def _set_prev_torque(self, t): - self.safety.set_desired_torque_last(t) - self.safety.set_rt_torque_last(t) - self.safety.set_torque_meas(t, t) - def _torque_meas_msg(self, torque): values = {"STEER_TORQUE_EPS": torque} return self.packer.make_can_msg_panda("STEER_TORQUE_SENSOR", 0, values) @@ -98,93 +93,6 @@ class TestToyotaSafety(common.PandaSafetyTest, common.InterceptorSafetyTest): should_tx = np.isclose(accel, 0, atol=0.0001) self.assertEqual(should_tx, self._tx(self._accel_msg(accel))) - def test_torque_absolute_limits(self): - for controls_allowed in [True, False]: - for torque in np.arange(-MAX_TORQUE - 1000, MAX_TORQUE + 1000, MAX_RATE_UP): - self.safety.set_controls_allowed(controls_allowed) - self.safety.set_rt_torque_last(torque) - self.safety.set_torque_meas(torque, torque) - self.safety.set_desired_torque_last(torque - MAX_RATE_UP) - - if controls_allowed: - send = (-MAX_TORQUE <= torque <= MAX_TORQUE) - else: - send = torque == 0 - - self.assertEqual(send, self._tx(self._torque_msg(torque))) - - def test_non_realtime_limit_up(self): - self.safety.set_controls_allowed(True) - - self._set_prev_torque(0) - self.assertTrue(self._tx(self._torque_msg(MAX_RATE_UP))) - - self._set_prev_torque(0) - self.assertFalse(self._tx(self._torque_msg(MAX_RATE_UP + 1))) - - def test_non_realtime_limit_down(self): - self.safety.set_controls_allowed(True) - - self.safety.set_rt_torque_last(1000) - self.safety.set_torque_meas(500, 500) - self.safety.set_desired_torque_last(1000) - self.assertTrue(self._tx(self._torque_msg(1000 - MAX_RATE_DOWN))) - - self.safety.set_rt_torque_last(1000) - self.safety.set_torque_meas(500, 500) - self.safety.set_desired_torque_last(1000) - self.assertFalse(self._tx(self._torque_msg(1000 - MAX_RATE_DOWN + 1))) - - def test_exceed_torque_sensor(self): - self.safety.set_controls_allowed(True) - - for sign in [-1, 1]: - self._set_prev_torque(0) - for t in np.arange(0, MAX_TORQUE_ERROR + 10, 10): - t *= sign - self.assertTrue(self._tx(self._torque_msg(t))) - - self.assertFalse(self._tx(self._torque_msg(sign * (MAX_TORQUE_ERROR + 10)))) - - def test_realtime_limit_up(self): - self.safety.set_controls_allowed(True) - - for sign in [-1, 1]: - self.safety.init_tests() - self._set_prev_torque(0) - for t in np.arange(0, 380, 10): - t *= sign - self.safety.set_torque_meas(t, t) - self.assertTrue(self._tx(self._torque_msg(t))) - self.assertFalse(self._tx(self._torque_msg(sign * 380))) - - self._set_prev_torque(0) - for t in np.arange(0, 370, 10): - t *= sign - self.safety.set_torque_meas(t, t) - self.assertTrue(self._tx(self._torque_msg(t))) - - # Increase timer to update rt_torque_last - self.safety.set_timer(RT_INTERVAL + 1) - self.assertTrue(self._tx(self._torque_msg(sign * 370))) - self.assertTrue(self._tx(self._torque_msg(sign * 380))) - - def test_torque_measurements(self): - for trq in [50, -50, 0, 0, 0, 0]: - self._rx(self._torque_meas_msg(trq)) - - # toyota safety adds one to be conservative on rounding - self.assertEqual(-51, self.safety.get_torque_meas_min()) - self.assertEqual(51, self.safety.get_torque_meas_max()) - - self._rx(self._torque_meas_msg(0)) - self.assertEqual(1, self.safety.get_torque_meas_max()) - self.assertEqual(-51, self.safety.get_torque_meas_min()) - - self._rx(self._torque_meas_msg(0)) - self.assertEqual(1, self.safety.get_torque_meas_max()) - self.assertEqual(-1, self.safety.get_torque_meas_min()) - def test_rx_hook(self): # checksum checks for msg in ["trq", "pcm"]: