diff --git a/Dockerfile b/Dockerfile index 36848cbc..5525adef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,7 @@ RUN pip3 install --break-system-packages --no-cache-dir $PYTHONPATH/panda/[dev] # TODO: this should be a "pip install" or not even in this repo at all RUN git config --global --add safe.directory $PYTHONPATH/panda -ENV OPENDBC_REF="950e7b34efa64d2ad41df3300652661fbae06f57" +ENV OPENDBC_REF="87a51e38b53d91075419f01b4cd2e625ee7d4516" RUN cd /tmp/ && \ git clone --depth 1 https://github.com/commaai/opendbc opendbc_repo && \ cd opendbc_repo && git fetch origin $OPENDBC_REF && git checkout FETCH_HEAD && rm -rf .git/ && \ diff --git a/board/safety/safety_tesla.h b/board/safety/safety_tesla.h index 7ea50dcf..29c35360 100644 --- a/board/safety/safety_tesla.h +++ b/board/safety/safety_tesla.h @@ -3,70 +3,64 @@ #include "safety_declarations.h" static bool tesla_longitudinal = false; -static bool tesla_powertrain = false; // Are we the second panda intercepting the powertrain bus? -static bool tesla_raven = false; - static bool tesla_stock_aeb = false; static void tesla_rx_hook(const CANPacket_t *to_push) { int bus = GET_BUS(to_push); int addr = GET_ADDR(to_push); - if (!tesla_powertrain) { - if((!tesla_raven && (addr == 0x370) && (bus == 0)) || (tesla_raven && (addr == 0x131) && (bus == 2))) { - // Steering angle: (0.1 * val) - 819.2 in deg. + if (bus == 0) { + // Steering angle: (0.1 * val) - 819.2 in deg. + if (addr == 0x370) { // Store it 1/10 deg to match steering request int angle_meas_new = (((GET_BYTE(to_push, 4) & 0x3FU) << 8) | GET_BYTE(to_push, 5)) - 8192U; update_sample(&angle_meas, angle_meas_new); } - } - if(bus == 0) { - if(addr == (tesla_powertrain ? 0x116 : 0x118)) { - // Vehicle speed: ((0.05 * val) - 25) * MPH_TO_MPS - float speed = (((((GET_BYTE(to_push, 3) & 0x0FU) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; - vehicle_moving = ABS(speed) > 0.1; + // Vehicle speed + if (addr == 0x257) { + // Vehicle speed: ((val * 0.08) - 40) / MS_TO_KPH + float speed = ((((GET_BYTE(to_push, 2) << 4) | (GET_BYTE(to_push, 1) >> 4)) * 0.08) - 40) / 3.6; UPDATE_VEHICLE_SPEED(speed); } - if(addr == (tesla_powertrain ? 0x106 : 0x108)) { - // Gas pressed - gas_pressed = (GET_BYTE(to_push, 6) != 0U); + // Gas pressed + if (addr == 0x118) { + gas_pressed = (GET_BYTE(to_push, 4) != 0U); } - if(addr == (tesla_powertrain ? 0x1f8 : 0x20a)) { - // Brake pressed - brake_pressed = (((GET_BYTE(to_push, 0) & 0x0CU) >> 2) != 1U); + // Brake pressed + if (addr == 0x39d) { + brake_pressed = (GET_BYTE(to_push, 2) & 0x03U) == 2U; } - if(addr == (tesla_powertrain ? 0x256 : 0x368)) { - // Cruise state - int cruise_state = (GET_BYTE(to_push, 1) >> 4); + // Cruise state + if (addr == 0x286) { + int cruise_state = (GET_BYTE(to_push, 1) >> 4) & 0x07U; bool cruise_engaged = (cruise_state == 2) || // ENABLED (cruise_state == 3) || // STANDSTILL (cruise_state == 4) || // OVERRIDE (cruise_state == 6) || // PRE_FAULT (cruise_state == 7); // PRE_CANCEL + + vehicle_moving = cruise_state != 3; // STANDSTILL pcm_cruise_check(cruise_engaged); } } if (bus == 2) { - int das_control_addr = (tesla_powertrain ? 0x2bf : 0x2b9); - if (tesla_longitudinal && (addr == das_control_addr)) { + if (tesla_longitudinal && (addr == 0x2b9)) { // "AEB_ACTIVE" - tesla_stock_aeb = ((GET_BYTE(to_push, 2) & 0x03U) == 1U); + tesla_stock_aeb = (GET_BYTE(to_push, 2) & 0x03U) == 1U; } } - if (tesla_powertrain) { - // 0x2bf: DAS_control should not be received on bus 0 - generic_rx_checks((addr == 0x2bf) && (bus == 0)); - } else { - // 0x488: DAS_steeringControl should not be received on bus 0 - generic_rx_checks((addr == 0x488) && (bus == 0)); - } + generic_rx_checks((addr == 0x488) && (bus == 0)); // DAS_steeringControl + generic_rx_checks((addr == 0x27d) && (bus == 0)); // APS_eacMonitor + if (tesla_longitudinal) { + generic_rx_checks((addr == 0x2b9) && (bus == 0)); + } } @@ -84,8 +78,8 @@ static bool tesla_tx_hook(const CANPacket_t *to_send) { }; const LongitudinalLimits TESLA_LONG_LIMITS = { - .max_accel = 425, // 2. m/s^2 - .min_accel = 287, // -3.52 m/s^2 // TODO: limit to -3.48 + .max_accel = 425, // 2 m/s^2 + .min_accel = 288, // -3.48 m/s^2 .inactive_accel = 375, // 0. m/s^2 }; @@ -93,10 +87,10 @@ static bool tesla_tx_hook(const CANPacket_t *to_send) { int addr = GET_ADDR(to_send); bool violation = false; - if(!tesla_powertrain && (addr == 0x488)) { - // Steering control: (0.1 * val) - 1638.35 in deg. + // Steering control: (0.1 * val) - 1638.35 in deg. + if (addr == 0x488) { // We use 1/10 deg as a unit here - int raw_angle_can = (((GET_BYTE(to_send, 0) & 0x7FU) << 8) | GET_BYTE(to_send, 1)); + int raw_angle_can = ((GET_BYTE(to_send, 0) & 0x7FU) << 8) | GET_BYTE(to_send, 1); int desired_angle = raw_angle_can - 16384; int steer_control_type = GET_BYTE(to_send, 2) >> 6; bool steer_control_enabled = (steer_control_type != 0) && // NONE @@ -107,35 +101,43 @@ static bool tesla_tx_hook(const CANPacket_t *to_send) { } } - if (!tesla_powertrain && (addr == 0x45)) { - // No button other than cancel can be sent by us - int control_lever_status = (GET_BYTE(to_send, 0) & 0x3FU); - if (control_lever_status != 1) { + // DAS_control: longitudinal control message + if (addr == 0x2b9) { + // No AEB events may be sent by openpilot + int aeb_event = GET_BYTE(to_send, 2) & 0x03U; + if (aeb_event != 0) { violation = true; } - } - if(addr == (tesla_powertrain ? 0x2bf : 0x2b9)) { - // DAS_control: longitudinal control message + int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1FU) << 4) | (GET_BYTE(to_send, 5) >> 4); + int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0FU) << 5) | (GET_BYTE(to_send, 4) >> 3); + int acc_state = GET_BYTE(to_send, 1) >> 4; + if (tesla_longitudinal) { - // No AEB events may be sent by openpilot - int aeb_event = GET_BYTE(to_send, 2) & 0x03U; - if (aeb_event != 0) { - violation = true; - } - // Don't send messages when the stock AEB system is active if (tesla_stock_aeb) { violation = true; } + // Prevent both acceleration from being negative, as this could cause the car to reverse after coming to standstill + if ((raw_accel_max < TESLA_LONG_LIMITS.inactive_accel) && (raw_accel_min < TESLA_LONG_LIMITS.inactive_accel)) { + violation = true; + } + // Don't allow any acceleration limits above the safety limits - int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1FU) << 4) | (GET_BYTE(to_send, 5) >> 4); - int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0FU) << 5) | (GET_BYTE(to_send, 4) >> 3); violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LONG_LIMITS); violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LONG_LIMITS); } else { - violation = true; + // does allowing cancel here disrupt stock AEB? TODO: find out and add safety or remove comment + // Can only send cancel longitudinal messages when not controlling longitudinal + if (acc_state != 13) { // ACC_CANCEL_GENERIC_SILENT + violation = true; + } + + // No actuation is allowed when not controlling longitudinal + if ((raw_accel_max != TESLA_LONG_LIMITS.inactive_accel) || (raw_accel_min != TESLA_LONG_LIMITS.inactive_accel)) { + violation = true; + } } } @@ -149,25 +151,24 @@ static bool tesla_tx_hook(const CANPacket_t *to_send) { static int tesla_fwd_hook(int bus_num, int addr) { int bus_fwd = -1; - if(bus_num == 0) { - // Chassis/PT to autopilot + if (bus_num == 0) { + // Party to autopilot bus_fwd = 2; } - if(bus_num == 2) { - // Autopilot to chassis/PT - int das_control_addr = (tesla_powertrain ? 0x2bf : 0x2b9); - + if (bus_num == 2) { bool block_msg = false; - if (!tesla_powertrain && (addr == 0x488)) { + // DAS_steeringControl, APS_eacMonitor + if ((addr == 0x488) || (addr == 0x27d)) { block_msg = true; } - if (tesla_longitudinal && (addr == das_control_addr) && !tesla_stock_aeb) { + // DAS_control + if (tesla_longitudinal && (addr == 0x2b9) && !tesla_stock_aeb) { block_msg = true; } - if(!block_msg) { + if (!block_msg) { bus_fwd = 0; } } @@ -176,64 +177,32 @@ static int tesla_fwd_hook(int bus_num, int addr) { } static safety_config tesla_init(uint16_t param) { - const int TESLA_FLAG_POWERTRAIN = 1; - const int TESLA_FLAG_LONGITUDINAL_CONTROL = 2; - const int TESLA_FLAG_RAVEN = 4; - static const CanMsg TESLA_TX_MSGS[] = { + static const CanMsg TESLA_M3_Y_TX_MSGS[] = { {0x488, 0, 4}, // DAS_steeringControl - {0x45, 0, 8}, // STW_ACTN_RQ - {0x45, 2, 8}, // STW_ACTN_RQ {0x2b9, 0, 8}, // DAS_control + {0x27D, 0, 3}, // APS_eacMonitor }; - static const CanMsg TESLA_PT_TX_MSGS[] = { - {0x2bf, 0, 8}, // DAS_control - }; - - tesla_powertrain = GET_FLAG(param, TESLA_FLAG_POWERTRAIN); + UNUSED(param); +#ifdef ALLOW_DEBUG + const int TESLA_FLAG_LONGITUDINAL_CONTROL = 1; tesla_longitudinal = GET_FLAG(param, TESLA_FLAG_LONGITUDINAL_CONTROL); - tesla_raven = GET_FLAG(param, TESLA_FLAG_RAVEN); +#endif tesla_stock_aeb = false; - safety_config ret; - if (tesla_powertrain) { - static RxCheck tesla_pt_rx_checks[] = { - {.msg = {{0x106, 0, 8, .frequency = 100U}, { 0 }, { 0 }}}, // DI_torque1 - {.msg = {{0x116, 0, 6, .frequency = 100U}, { 0 }, { 0 }}}, // DI_torque2 - {.msg = {{0x1f8, 0, 8, .frequency = 50U}, { 0 }, { 0 }}}, // BrakeMessage - {.msg = {{0x2bf, 2, 8, .frequency = 25U}, { 0 }, { 0 }}}, // DAS_control - {.msg = {{0x256, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, // DI_state - }; + static RxCheck tesla_model3_y_rx_checks[] = { + {.msg = {{0x2b9, 2, 8, .frequency = 25U}, { 0 }, { 0 }}}, // DAS_control + {.msg = {{0x257, 0, 8, .frequency = 50U}, { 0 }, { 0 }}}, // DI_speed (speed in kph) + {.msg = {{0x370, 0, 8, .frequency = 100U}, { 0 }, { 0 }}}, // EPAS3S_internalSAS (steering angle) + {.msg = {{0x118, 0, 8, .frequency = 100U}, { 0 }, { 0 }}}, // DI_systemStatus (gas pedal) + {.msg = {{0x39d, 0, 5, .frequency = 25U}, { 0 }, { 0 }}}, // IBST_status (brakes) + {.msg = {{0x286, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, // DI_state (acc state) + {.msg = {{0x311, 0, 7, .frequency = 10U}, { 0 }, { 0 }}}, // UI_warning (blinkers, buckle switch & doors) + }; - ret = BUILD_SAFETY_CFG(tesla_pt_rx_checks, TESLA_PT_TX_MSGS); - } else if (tesla_raven) { - static RxCheck tesla_raven_rx_checks[] = { - {.msg = {{0x2b9, 2, 8, .frequency = 25U}, { 0 }, { 0 }}}, // DAS_control - {.msg = {{0x131, 2, 8, .frequency = 100U}, { 0 }, { 0 }}}, // EPAS3P_sysStatus - {.msg = {{0x108, 0, 8, .frequency = 100U}, { 0 }, { 0 }}}, // DI_torque1 - {.msg = {{0x118, 0, 6, .frequency = 100U}, { 0 }, { 0 }}}, // DI_torque2 - {.msg = {{0x20a, 0, 8, .frequency = 50U}, { 0 }, { 0 }}}, // BrakeMessage - {.msg = {{0x368, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, // DI_state - {.msg = {{0x318, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, // GTW_carState - }; - - ret = BUILD_SAFETY_CFG(tesla_raven_rx_checks, TESLA_TX_MSGS); - } else { - static RxCheck tesla_rx_checks[] = { - {.msg = {{0x2b9, 2, 8, .frequency = 25U}, { 0 }, { 0 }}}, // DAS_control - {.msg = {{0x370, 0, 8, .frequency = 25U}, { 0 }, { 0 }}}, // EPAS_sysStatus - {.msg = {{0x108, 0, 8, .frequency = 100U}, { 0 }, { 0 }}}, // DI_torque1 - {.msg = {{0x118, 0, 6, .frequency = 100U}, { 0 }, { 0 }}}, // DI_torque2 - {.msg = {{0x20a, 0, 8, .frequency = 50U}, { 0 }, { 0 }}}, // BrakeMessage - {.msg = {{0x368, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, // DI_state - {.msg = {{0x318, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, // GTW_carState - }; - - ret = BUILD_SAFETY_CFG(tesla_rx_checks, TESLA_TX_MSGS); - } - return ret; + return BUILD_SAFETY_CFG(tesla_model3_y_rx_checks, TESLA_M3_Y_TX_MSGS); } const safety_hooks tesla_hooks = { diff --git a/tests/safety/test_tesla.py b/tests/safety/test_tesla.py index 8af82c87..aeab5b9a 100755 --- a/tests/safety/test_tesla.py +++ b/tests/safety/test_tesla.py @@ -1,51 +1,68 @@ #!/usr/bin/env python3 import unittest -import numpy as np from opendbc.car.tesla.values import TeslaSafetyFlags from opendbc.safety import Safety -import panda.tests.safety.common as common from panda.tests.libsafety import libsafety_py +import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda -MAX_ACCEL = 2.0 -MIN_ACCEL = -3.5 +MSG_DAS_steeringControl = 0x488 +MSG_APS_eacMonitor = 0x27d +MSG_DAS_Control = 0x2b9 -class CONTROL_LEVER_STATE: - DN_1ST = 32 - UP_1ST = 16 - DN_2ND = 8 - UP_2ND = 4 - RWD = 2 - FWD = 1 - IDLE = 0 +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]] - -class TestTeslaSafety(common.PandaCarSafetyTest): - STANDSTILL_THRESHOLD = 0 + STANDSTILL_THRESHOLD = 0.1 GAS_PRESSED_THRESHOLD = 3 FWD_BUS_LOOKUP = {0: 2, 2: 0} - def setUp(self): - self.packer = None - raise unittest.SkipTest + # Angle control limits + DEG_TO_CAN = 10 - def _speed_msg(self, speed): - values = {"DI_vehicleSpeed": speed / 0.447} - return self.packer.make_can_msg_panda("DI_torque2", 0, values) + 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 = {"driverBrakeStatus": 2 if brake else 1} - return self.packer.make_can_msg_panda("BrakeMessage", 0, values) + 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_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) + 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} @@ -63,68 +80,49 @@ class TestTeslaSafety(common.PandaCarSafetyTest): } 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))) -class TestTeslaSteeringSafety(TestTeslaSafety, common.AngleSteeringSafetyTest): - TX_MSGS = [[0x488, 0], [0x45, 0], [0x45, 2]] - RELAY_MALFUNCTION_ADDRS = {0: (0x488,)} - FWD_BLACKLISTED_ADDRS = {2: [0x488]} + 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) - # 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 +class TestTeslaStockSafety(TestTeslaSafetyBase): def setUp(self): - self.packer = CANPackerPanda("tesla_can") + 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 _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 test_accel_actuation_limits(self, stock_longitudinal=True): + super().test_accel_actuation_limits(stock_longitudinal) - def _angle_meas_msg(self, angle: float): - values = {"EPAS_internalSAS": angle} - return self.packer.make_can_msg_panda("EPAS_sysStatus", 0, values) + 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_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) + 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 TestTeslaRavenSteeringSafety(TestTeslaSteeringSafety): +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_can") + self.packer = CANPackerPanda("tesla_model3_party") self.safety = libsafety_py.libsafety - self.safety.set_safety_hooks(Safety.SAFETY_TESLA, TeslaSafetyFlags.FLAG_TESLA_RAVEN) + self.safety.set_safety_hooks(Safety.SAFETY_TESLA, TeslaSafetyFlags.FLAG_TESLA_LONG_CONTROL) 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) @@ -137,50 +135,34 @@ class TestTeslaLongitudinalSafety(TestTeslaSafety): # 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)) + 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.assertEqual(False, self._tx(no_aeb_msg)) + self.assertFalse(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]))) + 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)))) -class TestTeslaChassisLongitudinalSafety(TestTeslaLongitudinalSafety): - TX_MSGS = [[0x488, 0], [0x45, 0], [0x45, 2], [0x2B9, 0]] - RELAY_MALFUNCTION_ADDRS = {0: (0x488,)} - FWD_BLACKLISTED_ADDRS = {2: [0x2B9, 0x488]} + # 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)))) - def setUp(self): - self.packer = CANPackerPanda("tesla_can") - self.safety = libsafety_py.libsafety - self.safety.set_safety_hooks(Safety.SAFETY_TESLA, TeslaSafetyFlags.FLAG_TESLA_LONG_CONTROL) - self.safety.init_tests() + # 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)))) - -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 = libsafety_py.libsafety - self.safety.set_safety_hooks(Safety.SAFETY_TESLA, TeslaSafetyFlags.FLAG_TESLA_LONG_CONTROL | TeslaSafetyFlags.FLAG_TESLA_POWERTRAIN) - self.safety.init_tests() + # 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__":