Files
panda-meb/tests/safety/test_tesla.py
Lukas ebdc376ade Safety for Tesla Model 3 / Model Y (#2036)
* wip model3

* master

* tesla model 3 / y

* prevent tesla to reverse

* remove can 1 from safety

* use DI_vehicleSpeed

* - add APS_eacMonitor to TX
- use DI_state as a standstill signal

* block eacMonitor

* fix tesla safety tests

* fix tesla safety tests

* add generic_rx_check for eacMonitor

* fix tests

* consistent ordering of common user brake test setup

* Tesla: Panda safety update (#2075)

* - match "vehicle_moving" with opendbc
- allow to cancel

* remove comment

* update _vehicle_moving_msg

* remove redundant condition

* whoops

* update ref

* spacing!

* long behind ALLOW_DEBUG

* consistent styling

* ?

* misra and clean up

* divide instead

* double (())

* more stylistic

* this is more clear

* always check aeb

* this test catches it

* it should test angle steering in both modes?

* we weren't testing long at all, and ALSO PYTEST SILENTLY SKIPS CLASSES WITH MISSING ABSTRACT METHODS WTF

* finalize safety tests

* update opendbc to master

* ltl

* revert this

* rm conftest

* loop this

---------

Co-authored-by: Greg Hogan <gregjhogan@gmail.com>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
2025-02-18 12:42:38 -08:00

170 lines
6.8 KiB
Python
Executable File

#!/usr/bin/env python3
import unittest
from opendbc.car.tesla.values import TeslaSafetyFlags
from opendbc.safety import Safety
from panda.tests.libsafety import libsafety_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
MSG_DAS_steeringControl = 0x488
MSG_APS_eacMonitor = 0x27d
MSG_DAS_Control = 0x2b9
class TestTeslaSafetyBase(common.PandaCarSafetyTest, common.AngleSteeringSafetyTest, common.LongitudinalAccelSafetyTest):
RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_APS_eacMonitor)}
FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_APS_eacMonitor]}
TX_MSGS = [[MSG_DAS_steeringControl, 0], [MSG_APS_eacMonitor, 0], [MSG_DAS_Control, 0]]
STANDSTILL_THRESHOLD = 0.1
GAS_PRESSED_THRESHOLD = 3
FWD_BUS_LOOKUP = {0: 2, 2: 0}
# 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
# Long control limits
MAX_ACCEL = 2.0
MIN_ACCEL = -3.48
INACTIVE_ACCEL = 0.0
packer: CANPackerPanda
@classmethod
def setUpClass(cls):
if cls.__name__ == "TestTeslaSafetyBase":
raise unittest.SkipTest
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 = {"EPAS3S_internalSAS": angle}
return self.packer.make_can_msg_panda("EPAS3S_sysStatus", 0, values)
def _user_brake_msg(self, brake):
values = {"IBST_driverBrakeApply": 2 if brake else 1}
return self.packer.make_can_msg_panda("IBST_status", 0, values)
def _speed_msg(self, speed):
values = {"DI_vehicleSpeed": speed * 3.6}
return self.packer.make_can_msg_panda("DI_speed", 0, values)
def _vehicle_moving_msg(self, speed: float):
values = {"DI_cruiseState": 3 if speed <= self.STANDSTILL_THRESHOLD else 2}
return self.packer.make_can_msg_panda("DI_state", 0, values)
def _user_gas_msg(self, gas):
values = {"DI_accelPedalPos": gas}
return self.packer.make_can_msg_panda("DI_systemStatus", 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)
def _accel_msg(self, accel: float):
# For common.LongitudinalAccelSafetyTest
return self._long_control_msg(10, accel_limits=(accel, max(accel, 0)))
def test_vehicle_speed_measurements(self):
# OVERRIDDEN: 79.1667 is the max speed in m/s
self._common_measurement_test(self._speed_msg, 0, 285 / 3.6, common.VEHICLE_SPEED_FACTOR,
self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max)
class TestTeslaStockSafety(TestTeslaSafetyBase):
def setUp(self):
self.packer = CANPackerPanda("tesla_model3_party")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(Safety.SAFETY_TESLA, 0)
self.safety.init_tests()
def test_accel_actuation_limits(self, stock_longitudinal=True):
super().test_accel_actuation_limits(stock_longitudinal)
def test_cancel(self):
for accval in range(16):
self.safety.set_controls_allowed(True)
should_tx = accval == 13 # ACC_CANCEL_GENERIC_SILENT
self.assertFalse(self._tx(self._long_control_msg(0, acc_val=accval, accel_limits=(self.MIN_ACCEL, self.MAX_ACCEL))))
self.assertEqual(should_tx, self._tx(self._long_control_msg(0, acc_val=accval)))
def test_no_aeb(self):
for aeb_event in range(4):
self.assertEqual(self._tx(self._long_control_msg(10, acc_val=13, aeb_event=aeb_event)), aeb_event == 0)
class TestTeslaLongitudinalSafety(TestTeslaSafetyBase):
RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_APS_eacMonitor, MSG_DAS_Control)}
FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_APS_eacMonitor, MSG_DAS_Control]}
def setUp(self):
self.packer = CANPackerPanda("tesla_model3_party")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(Safety.SAFETY_TESLA, TeslaSafetyFlags.FLAG_TESLA_LONG_CONTROL)
self.safety.init_tests()
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.assertTrue(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.assertFalse(self._tx(no_aeb_msg))
def test_prevent_reverse(self):
# Note: Tesla can reverse while at a standstill if both accel_min and accel_max are negative.
self.safety.set_controls_allowed(True)
# accel_min and accel_max are positive
self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(1.1, 0.8))))
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(1.1, 0.8))))
# accel_min and accel_max are both zero
self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(0, 0))))
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0, 0))))
# accel_min and accel_max have opposing signs
self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(-0.8, 1.3))))
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0.8, -1.3))))
self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0, -1.3))))
# accel_min and accel_max are negative
self.assertFalse(self._tx(self._long_control_msg(set_speed=10, accel_limits=(-1.1, -0.6))))
self.assertFalse(self._tx(self._long_control_msg(set_speed=0, accel_limits=(-0.6, -1.1))))
self.assertFalse(self._tx(self._long_control_msg(set_speed=0, accel_limits=(-0.1, -0.1))))
if __name__ == "__main__":
unittest.main()