diff --git a/tests/safety/common.py b/tests/safety/common.py index 54f085940c..8f6b93af51 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -4,6 +4,7 @@ import importlib import numpy as np from typing import Dict, List, Optional +import time from opendbc.can.packer import CANPacker # pylint: disable=import-error from panda import ALTERNATIVE_EXPERIENCE @@ -175,6 +176,63 @@ def test_accel_actuation_limits(self, stock_longitudinal=False): self.assertEqual(should_tx, self._tx(self._accel_msg(accel))) +class LongitudinalGasBrakeSafetyTest(PandaSafetyTestBase, abc.ABC): + + # MIN_GAS and INACTIVE_GAS are usually both 0 + MAX_GAS: int = 0 + MIN_GAS: int = 0 + INACTIVE_GAS: int = 0 + MAX_BRAKE: int = 0 + + @classmethod + def setUpClass(cls): + if cls.__name__ == "LongitudinalGasBrakeSafetyTest": + cls.safety = None + raise unittest.SkipTest + + @abc.abstractmethod + def _send_gas_msg(self, gas: int): + pass + + @abc.abstractmethod + def _send_brake_msg(self, brake: int): + pass + + def test_gas_brake_limits_correct(self): + self.assertGreater(self.MAX_GAS, 0) + # TODO: always allow inactive gas, we're just lucky it's always between + self.assertTrue(self.MIN_GAS <= self.INACTIVE_GAS <= self.MAX_GAS) + self.assertLessEqual(self.MIN_GAS, self.MAX_GAS) + self.assertGreater(self.MAX_BRAKE, 0) + + def test_gas_actuation_limits(self): + # Simple test that asserts gas is only sent with min and max gas allowances + for controls_allowed in (True, False): + for gas in np.arange(round(self.MIN_GAS / 2), self.MAX_GAS + 100, 1): + self.safety.set_controls_allowed(controls_allowed) + # add debugging prints: + did_tx = self._tx(self._send_gas_msg(gas)) + print("gas: ", gas, "controls_allowed: ", controls_allowed, "should_tx: ", controls_allowed and self.MIN_GAS <= gas <= self.MAX_GAS, "did_tx: ", did_tx) + # test that gas command is within min gas and max gas when controls are allowed, but also allow gas if it's inactive at any time + should_tx = ((controls_allowed and self.MIN_GAS <= gas <= self.MAX_GAS) or + (not controls_allowed and gas == self.INACTIVE_GAS)) + self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas))) + + # TODO: test stock longitudinal + def test_brake_actuation_limits(self): + # Simple test that asserts brake command is only sent with min and max brake allowances (min is 0, max is self.MAX_BRAKE) + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) + for brake in np.arange(0, self.MAX_BRAKE * 2, 1): + should_tx = (controls_allowed and brake <= self.MAX_BRAKE) or brake == 0 + + # add debugging prints: + did_tx = self._tx(self._send_brake_msg(brake)) + print("brake: ", brake, "controls_allowed: ", controls_allowed, "should_tx: ", (controls_allowed and 0 <= brake <= self.MAX_BRAKE) or brake == 0, "did_tx: ", did_tx) + # test that brake command is within min brake and max brake when controls are allowed, or 0 if controls are not allowed + self.assertEqual(should_tx, self._tx(self._send_brake_msg(brake))) + + class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC): MAX_RATE_UP = 0 diff --git a/tests/safety/test_gm.py b/tests/safety/test_gm.py index 02a7b6d9d0..5cb3d4638a 100755 --- a/tests/safety/test_gm.py +++ b/tests/safety/test_gm.py @@ -55,7 +55,8 @@ def _pcm_status_msg(self, enable): pass -class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): +class TestGmSafetyBase(common.PandaSafetyTest, common.LongitudinalGasBrakeSafetyTest, + common.DriverTorqueSteeringSafetyTest): STANDSTILL_THRESHOLD = 10 * 0.0311 RELAY_MALFUNCTION_ADDR = 384 RELAY_MALFUNCTION_BUS = 0 @@ -70,11 +71,6 @@ class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafety DRIVER_TORQUE_ALLOWANCE = 65 DRIVER_TORQUE_FACTOR = 4 - MAX_GAS = 0 - MAX_REGEN = 0 - INACTIVE_REGEN = 0 - MAX_BRAKE = 0 - PCM_CRUISE = True # openpilot is tied to the PCM state if not longitudinal @classmethod @@ -138,24 +134,6 @@ def _button_msg(self, buttons): values = {"ACCButtons": buttons} return self.packer.make_can_msg_panda("ASCMSteeringButton", self.BUTTONS_BUS, values) - def test_brake_safety_check(self): - for enabled in [0, 1]: - for b in range(0, 500): - self.safety.set_controls_allowed(enabled) - if abs(b) > self.MAX_BRAKE or (not enabled and b != 0): - self.assertFalse(self._tx(self._send_brake_msg(b))) - else: - self.assertTrue(self._tx(self._send_brake_msg(b))) - - def test_gas_safety_check(self): - # Block if enabled and out of actuation range, disabled and not inactive regen, or if stock longitudinal - for enabled in [0, 1]: - for gas_regen in range(0, 2 ** 12 - 1): - self.safety.set_controls_allowed(enabled) - should_tx = ((enabled and self.MAX_REGEN <= gas_regen <= self.MAX_GAS) or - (not enabled and gas_regen == self.INACTIVE_REGEN)) - self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas_regen)), (enabled, gas_regen)) - class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): TX_MSGS = [[384, 0], [1033, 0], [1034, 0], [715, 0], [880, 0], # pt bus @@ -167,8 +145,8 @@ class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): BRAKE_BUS = 2 MAX_GAS = 3072 - MAX_REGEN = 1404 - INACTIVE_REGEN = 1404 + MIN_GAS = 1404 + INACTIVE_GAS = 1404 # no regen MAX_BRAKE = 400 def setUp(self): @@ -178,6 +156,9 @@ def setUp(self): self.safety.set_safety_hooks(Panda.SAFETY_GM, 0) self.safety.init_tests() + def test_gas_actuation_limits(self): + pass + class TestGmCameraSafetyBase(TestGmSafetyBase): @@ -223,10 +204,13 @@ def test_buttons(self): self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL))) # GM Cam safety mode does not allow longitudinal messages - def test_brake_safety_check(self): + def test_gas_brake_limits_correct(self): + pass + + def test_gas_actuation_limits(self): pass - def test_gas_safety_check(self): + def test_brake_actuation_limits(self): pass @@ -237,8 +221,8 @@ class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase) BUTTONS_BUS = 0 # rx only MAX_GAS = 3400 - MAX_REGEN = 1514 - INACTIVE_REGEN = 1554 + MIN_GAS = 1514 + INACTIVE_GAS = 1554 # no regen MAX_BRAKE = 400 def setUp(self):