panda/tests/safety/test_honda.py

414 lines
14 KiB
Python
Executable File

#!/usr/bin/env python3
import unittest
from typing import Optional
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 CANPackerPanda, make_msg, MAX_WRONG_COUNTERS, UNSAFE_MODE
class Btn:
CANCEL = 2
SET = 3
RESUME = 4
HONDA_N_HW = 0
HONDA_BG_HW = 1
HONDA_BH_HW = 2
class TestHondaSafety(common.PandaSafetyTest):
MAX_BRAKE: float = 255
PT_BUS: Optional[int] = None # must be set when inherited
STEER_BUS: Optional[int] = None # must be set when inherited
cnt_speed = 0
cnt_gas = 0
cnt_button = 0
cnt_brake = 0
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestHondaSafety":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
# override these inherited tests. honda doesn't use pcm 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 _pcm_status_msg(self, enable):
pass
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 _button_msg(self, buttons):
values = {"CRUISE_BUTTONS": buttons, "COUNTER": self.cnt_button % 4}
self.__class__.cnt_button += 1
return self.packer.make_can_msg_panda("SCM_BUTTONS", self.PT_BUS, values)
def _brake_msg(self, brake):
values = {"BRAKE_PRESSED": brake, "COUNTER": self.cnt_gas % 4}
self.__class__.cnt_gas += 1
return self.packer.make_can_msg_panda("POWERTRAIN_DATA", self.PT_BUS, values)
def _gas_msg(self, gas):
values = {"PEDAL_GAS": gas, "COUNTER": self.cnt_gas % 4}
self.__class__.cnt_gas += 1
return self.packer.make_can_msg_panda("POWERTRAIN_DATA", self.PT_BUS, values)
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_resume_button(self):
self.safety.set_controls_allowed(0)
self._rx(self._button_msg(Btn.RESUME))
self.assertTrue(self.safety.get_controls_allowed())
def test_set_button(self):
self.safety.set_controls_allowed(0)
self._rx(self._button_msg(Btn.SET))
self.assertTrue(self.safety.get_controls_allowed())
def test_cancel_button(self):
self.safety.set_controls_allowed(1)
self._rx(self._button_msg(Btn.CANCEL))
self.assertFalse(self.safety.get_controls_allowed())
def test_disengage_on_brake(self):
self.safety.set_controls_allowed(1)
self._rx(self._brake_msg(1))
self.assertFalse(self.safety.get_controls_allowed())
def test_steer_safety_check(self):
self.safety.set_controls_allowed(0)
self.assertTrue(self._tx(self._send_steer_msg(0x0000)))
self.assertFalse(self._tx(self._send_steer_msg(0x1000)))
def test_rx_hook(self):
# TODO: move this test to common
# checksum checks
for msg in ["btn", "gas", "speed"]:
self.safety.set_controls_allowed(1)
# TODO: add this coverage back by re-running all tests with the acura dbc
# to_push = self._button_msg(Btn.SET, 0x1A6) # only in Honda_NIDEC
if msg == "btn":
to_push = self._button_msg(Btn.SET)
if msg == "gas":
to_push = self._gas_msg(0)
if msg == "speed":
to_push = self._speed_msg(0)
self.assertTrue(self._rx(to_push))
if msg != "btn":
to_push[0].RDHR = 0 # invalidate checksum
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_gas += 1
self.__class__.cnt_button += 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._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._gas_msg(0)))
self.assertFalse(self.safety.get_controls_allowed())
# restore counters for future tests with a couple of good messages
for i in range(2):
self.safety.set_controls_allowed(1)
self._rx(self._button_msg(Btn.SET))
self._rx(self._speed_msg(0))
self._rx(self._gas_msg(0))
self._rx(self._button_msg(Btn.SET))
self.assertTrue(self.safety.get_controls_allowed())
def test_tx_hook_on_pedal_pressed(self):
for mode in [UNSAFE_MODE.DEFAULT, UNSAFE_MODE.DISABLE_DISENGAGE_ON_GAS]:
for pedal in ['brake', 'gas']:
self.safety.set_unsafe_mode(mode)
allow_ctrl = False
if pedal == 'brake':
# brake_pressed_prev and vehicle_moving
self._rx(self._speed_msg(100))
self._rx(self._brake_msg(1))
elif pedal == 'gas':
# gas_pressed_prev
self._rx(self._gas_msg(1))
allow_ctrl = mode == UNSAFE_MODE.DISABLE_DISENGAGE_ON_GAS
self.safety.set_controls_allowed(1)
hw = self.safety.get_honda_hw()
if hw == HONDA_N_HW:
self.safety.set_honda_fwd_brake(False)
self.assertEqual(allow_ctrl, self._tx(self._send_brake_msg(self.MAX_BRAKE)))
self.assertEqual(allow_ctrl, self._tx(self._send_steer_msg(0x1000)))
# reset status
self.safety.set_controls_allowed(0)
self.safety.set_unsafe_mode(UNSAFE_MODE.DEFAULT)
if hw == HONDA_N_HW:
self._tx(self._send_brake_msg(0))
self._tx(self._send_steer_msg(0))
if pedal == 'brake':
self._rx(self._speed_msg(0))
self._rx(self._brake_msg(0))
elif pedal == 'gas':
self._rx(self._gas_msg(0))
class TestHondaNidecSafety(TestHondaSafety, common.InterceptorSafetyTest):
TX_MSGS = [[0xE4, 0], [0x194, 0], [0x1FA, 0], [0x200, 0], [0x30C, 0], [0x33D, 0]]
STANDSTILL_THRESHOLD = 0
RELAY_MALFUNCTION_ADDR = 0xE4
RELAY_MALFUNCTION_BUS = 0
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x194, 0x33D, 0x30C]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
PT_BUS = 0
STEER_BUS = 0
INTERCEPTOR_THRESHOLD = 344
def setUp(self):
self.packer = CANPackerPanda("honda_civic_touring_2016_can_generated")
self.safety = libpandasafety_py.libpandasafety
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_NIDEC, 0)
self.safety.init_tests_honda()
# Honda gas gains are the different
def _interceptor_msg(self, gas, addr):
to_send = make_msg(0, addr, 6)
gas2 = gas * 2
to_send[0].RDLR = ((gas & 0xff) << 8) | ((gas & 0xff00) >> 8) | \
((gas2 & 0xff) << 24) | ((gas2 & 0xff00) << 8)
return to_send
def _send_brake_msg(self, brake):
values = {"COMPUTER_BRAKE": brake}
return self.packer.make_can_msg_panda("BRAKE_COMMAND", 0, values)
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()
# TODO: test latching until AEB event is over?
# 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_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)))
self.safety.set_honda_fwd_brake(False)
def test_tx_hook_on_interceptor_pressed(self):
for mode in [UNSAFE_MODE.DEFAULT, UNSAFE_MODE.DISABLE_DISENGAGE_ON_GAS]:
self.safety.set_unsafe_mode(mode)
# gas_interceptor_prev > INTERCEPTOR_THRESHOLD
self._rx(self._interceptor_msg(self.INTERCEPTOR_THRESHOLD + 1, 0x201))
self._rx(self._interceptor_msg(self.INTERCEPTOR_THRESHOLD + 1, 0x201))
allow_ctrl = mode == UNSAFE_MODE.DISABLE_DISENGAGE_ON_GAS
self.safety.set_controls_allowed(1)
self.safety.set_honda_fwd_brake(False)
self.assertEqual(allow_ctrl, self._tx(self._send_brake_msg(self.MAX_BRAKE)))
self.assertEqual(allow_ctrl, self._tx(self._interceptor_msg(self.INTERCEPTOR_THRESHOLD, 0x200)))
self.assertEqual(allow_ctrl, self._tx(self._send_steer_msg(0x1000)))
# reset status
self.safety.set_controls_allowed(0)
self.safety.set_unsafe_mode(UNSAFE_MODE.DEFAULT)
self._tx(self._send_brake_msg(0))
self._tx(self._send_steer_msg(0))
self._tx(self._interceptor_msg(0, 0x200))
self.safety.set_gas_interceptor_detected(False)
class TestHondaBoschSafety(TestHondaSafety):
STANDSTILL_THRESHOLD = 0
RELAY_MALFUNCTION_ADDR = 0xE4
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestHondaBoschSafety":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
def setUp(self):
self.packer = CANPackerPanda("honda_accord_s2t_2018_can_generated")
self.safety = libpandasafety_py.libpandasafety
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
# TODO: add back in once alternative brake checksum/counter validation is added
# 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].RDLR = to_push[0].RDLR & 0xFFF0FFFF # invalidate checksum
# self.assertFalse(self._rx(to_push))
# self.assertFalse(self.safety.get_controls_allowed())
def test_alt_disengage_on_brake(self):
self.safety.set_honda_alt_brake_msg(1)
self.safety.set_controls_allowed(1)
self._rx(self._alt_brake_msg(1))
self.assertFalse(self.safety.get_controls_allowed())
self.safety.set_honda_alt_brake_msg(0)
self.safety.set_controls_allowed(1)
self._rx(self._alt_brake_msg(1))
self.assertTrue(self.safety.get_controls_allowed())
class TestHondaBoschHarnessSafety(TestHondaBoschSafety):
TX_MSGS = [[0xE4, 0], [0xE5, 0], [0x296, 1], [0x33D, 0]] # Bosch Harness
RELAY_MALFUNCTION_BUS = 0
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0xE5, 0x33D]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
PT_BUS = 1
STEER_BUS = 0
def setUp(self):
super().setUp()
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH_HARNESS, 0)
self.safety.init_tests_honda()
def test_spam_cancel_safety_check(self):
self.safety.set_controls_allowed(0)
self.assertTrue(self._tx(self._button_msg(Btn.CANCEL)))
self.assertFalse(self._tx(self._button_msg(Btn.RESUME)))
self.assertFalse(self._tx(self._button_msg(Btn.SET)))
# do not block resume if we are engaged already
self.safety.set_controls_allowed(1)
self.assertTrue(self._tx(self._button_msg(Btn.RESUME)))
class TestHondaBoschGiraffeSafety(TestHondaBoschHarnessSafety):
TX_MSGS = [[0xE4, 2], [0xE5, 2], [0x296, 0], [0x33D, 2]] # Bosch Giraffe
RELAY_MALFUNCTION_BUS = 2
FWD_BLACKLISTED_ADDRS = {1: [0xE4, 0xE5, 0x33D]}
FWD_BUS_LOOKUP = {1: 2, 2: 1}
PT_BUS = 0
STEER_BUS = 2
def setUp(self):
super().setUp()
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH_GIRAFFE, 0)
self.safety.init_tests_honda()
class TestHondaBoschLongSafety(TestHondaBoschSafety):
NO_GAS = -30000
MAX_GAS = 2000
MAX_BRAKE = -3.5
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestHondaBoschLongSafety":
cls.packer = None
cls.safety = None
raise unittest.SkipTest
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)
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 = gas <= self.MAX_GAS if controls_allowed else gas == self.NO_GAS
self.assertEqual(send, self.safety.safety_tx_hook(self._send_gas_brake_msg(gas, accel)), gas)
def test_brake_safety_check(self):
for controls_allowed in [True, False]:
for accel in np.arange(0, self.MAX_BRAKE - 1, -0.1):
self.safety.set_controls_allowed(controls_allowed)
send = self.MAX_BRAKE <= accel <= 0 if controls_allowed else accel == 0
self.assertEqual(send, self._tx(self._send_gas_brake_msg(self.NO_GAS, accel)), (controls_allowed, accel))
class TestHondaBoschLongHarnessSafety(TestHondaBoschLongSafety):
TX_MSGS = [[0xE4, 1], [0x1DF, 1], [0x1EF, 1], [0x1FA, 1], [0x30C, 1], [0x33D, 1], [0x39F, 1], [0x18DAB0F1, 1]] # Bosch Harness w/ gas and brakes
RELAY_MALFUNCTION_BUS = 0
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0xE5, 0x33D]}
FWD_BUS_LOOKUP = {0: 2, 2: 0}
PT_BUS = 1
STEER_BUS = 1
def setUp(self):
super().setUp()
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH_HARNESS, 2)
self.safety.init_tests_honda()
class TestHondaBoschLongGiraffeSafety(TestHondaBoschLongSafety):
TX_MSGS = [[0xE4, 0], [0x1DF, 0], [0x1EF, 0], [0x1FA, 0], [0x30C, 0], [0x33D, 0], [0x39F, 0], [0x18DAB0F1, 0]] # Bosch Giraffe w/ gas and brakes
RELAY_MALFUNCTION_BUS = 2
FWD_BLACKLISTED_ADDRS = {1: [0xE4, 0xE5, 0x33D]}
FWD_BUS_LOOKUP = {1: 2, 2: 1}
PT_BUS = 0
STEER_BUS = 0
def setUp(self):
super().setUp()
self.safety.set_safety_hooks(Panda.SAFETY_HONDA_BOSCH_GIRAFFE, 2)
self.safety.init_tests_honda()
if __name__ == "__main__":
unittest.main()