The Seal's camera faults (ACC + ADAS + AEB warning lights, 'unable to activate ADAS') because panda neutralizes its intercepted 790 LKAS commands while the camera watches the 508 feedback for execution — seeing its requests ignored, it disables the whole ADAS suite. The Han avoids this by echoing the camera's request back in the 792 feedback; the Seal needs the same on its 508 STEERING_TORQUE_ANGLE: - bydcan.create_steering_torque_angle: mirrors the Han spoof logic on the 508 layout — when camera LKAS is active, echo its 790 torque request into MAIN_TORQUE and its 482 angle request into TARGET_ANGLE with Cruise_Activated=1; prepare -> LKSPrepare=1; passive -> neutral - carcontroller: Seal sends the 508 spoof, Han keeps the 792 spoof - carstate: capture the camera's 482 angle command for the spoof - panda: whitelist 508 TX on bus 2 (absolute-limit check), stock 508 blocked 0->2 via check_relay — one config covers both platforms - firmware rebuilt; spoof decode-verified (camera's exact torque+angle echoed back); 120 safety tests + MISRA clean NOTE: the ADAS fault latches — the car needs a full power cycle (or a few minutes off) after flashing this build for the dash warnings to clear.
142 lines
5.2 KiB
Python
142 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
import unittest
|
|
|
|
from opendbc.car.structs import CarParams
|
|
from opendbc.safety.tests.libsafety import libsafety_py
|
|
import opendbc.safety.tests.common as common
|
|
from opendbc.safety.tests.common import CANPackerSafety
|
|
|
|
MSG_MPC_LKAS_CMD = 790 # TX by OP, lateral actuation towards EPS
|
|
MSG_STEERING_TORQUE = 792 # RX from EPS / TX by OP, torque feedback towards camera (HAN)
|
|
MSG_STEERING_TORQUE_ANGLE = 508 # RX from EPS / TX by OP, torque feedback towards camera (SEAL)
|
|
MSG_ACC_HUD_ADAS = 813 # RX from ADAS, ACC state
|
|
MSG_ACC_CMD = 814 # TX by OP, longitudinal actuation
|
|
MSG_PCM_BUTTONS = 944 # RX from PCM, steering wheel buttons
|
|
|
|
|
|
def byd_checksum(msg):
|
|
addr, dat, bus = msg
|
|
ret = bytearray(dat)
|
|
|
|
if addr in (MSG_MPC_LKAS_CMD, MSG_STEERING_TORQUE, MSG_ACC_CMD):
|
|
byte_key = 0xAF
|
|
sum_first = sum(byte >> 4 for byte in ret) # upper nibbles
|
|
sum_second = sum(byte & 0xF for byte in ret) # lower nibbles
|
|
|
|
remainder = sum_second >> 4
|
|
|
|
sum_first += (byte_key & 0xF)
|
|
sum_second += (byte_key >> 4)
|
|
|
|
inv_first = ((-sum_first + 0x9) & 0xF)
|
|
inv_second = ((-sum_second + 0x9) & 0xF)
|
|
|
|
ret[7] = (((inv_first + (5 - remainder)) << 4) + inv_second) & 0xFF
|
|
|
|
return addr, ret, bus
|
|
|
|
|
|
class TestBydSafetyBase(common.CarSafetyTest, common.MotorTorqueSteeringSafetyTest):
|
|
TX_MSGS = [[MSG_MPC_LKAS_CMD, 0], [MSG_STEERING_TORQUE, 2], [MSG_STEERING_TORQUE_ANGLE, 2]]
|
|
STANDSTILL_THRESHOLD = 0
|
|
RELAY_MALFUNCTION_ADDRS = {0: (MSG_MPC_LKAS_CMD,), 2: (MSG_STEERING_TORQUE, MSG_STEERING_TORQUE_ANGLE)}
|
|
FWD_BLACKLISTED_ADDRS = {0: [MSG_STEERING_TORQUE, MSG_STEERING_TORQUE_ANGLE], 2: [MSG_MPC_LKAS_CMD]}
|
|
|
|
MAX_TORQUE_LOOKUP = ([0], [300])
|
|
MAX_RATE_UP = 6
|
|
MAX_RATE_DOWN = 6
|
|
MAX_RT_DELTA = 112
|
|
MAX_TORQUE_ERROR = 46
|
|
TORQUE_MEAS_TOLERANCE = 1
|
|
|
|
def setUp(self):
|
|
self.packer = CANPackerSafety("byd_common")
|
|
self.safety = libsafety_py.libsafety
|
|
self.safety.set_safety_hooks(CarParams.SafetyModel.byd, 0)
|
|
self.safety.init_tests()
|
|
|
|
def _torque_cmd_msg(self, torque, steer_req=1):
|
|
values = {"LKAS_Output": torque, "LKAS_ACTIVE": steer_req}
|
|
return self.packer.make_can_msg_safety("MPC_LKAS_CMD", 0, values, byd_checksum)
|
|
|
|
def _torque_meas_msg(self, torque):
|
|
values = {"MAIN_TORQUE": torque}
|
|
return self.packer.make_can_msg_safety("STEERING_TORQUE", 0, values)
|
|
|
|
def _torque_driver_msg(self, torque):
|
|
values = {"Steer_Torque_Sensor": torque}
|
|
return self.packer.make_can_msg_safety("STEERING_TORQUE", 0, values)
|
|
|
|
def _speed_msg(self, speed):
|
|
values = {"VEHICLE_SPEED": speed}
|
|
return self.packer.make_can_msg_safety("ESC", 0, values)
|
|
|
|
def _user_brake_msg(self, brake):
|
|
values = {"BRAKE_PEDAL": brake}
|
|
return self.packer.make_can_msg_safety("PEDAL", 0, values)
|
|
|
|
def _user_gas_msg(self, gas):
|
|
values = {"GAS_PEDAL": gas}
|
|
return self.packer.make_can_msg_safety("PEDAL", 0, values)
|
|
|
|
def _pcm_status_msg(self, enable):
|
|
# CRUISE_STATE: 3 == Active, 0 == Off
|
|
values = {"CRUISE_STATE": 3 if enable else 0}
|
|
return self.packer.make_can_msg_safety("ACC_HUD_ADAS", 2, values)
|
|
|
|
def _lkas_button_msg(self, enabled):
|
|
values = {"LKAS_ON_BTN": enabled}
|
|
return self.packer.make_can_msg_safety("PCM_BUTTONS", 0, values)
|
|
|
|
def _acc_state_msg(self, enable):
|
|
# BYD has no separate ACC main switch state in panda; MADS is toggled
|
|
# with the dedicated LKAS steering wheel button instead
|
|
raise NotImplementedError
|
|
|
|
|
|
class TestBydStockSafety(TestBydSafetyBase):
|
|
pass
|
|
|
|
|
|
class TestBydLongitudinalSafety(TestBydSafetyBase):
|
|
TX_MSGS = [[MSG_MPC_LKAS_CMD, 0], [MSG_STEERING_TORQUE, 2], [MSG_STEERING_TORQUE_ANGLE, 2], [MSG_ACC_CMD, 0]]
|
|
RELAY_MALFUNCTION_ADDRS = {0: (MSG_MPC_LKAS_CMD, MSG_ACC_CMD), 2: (MSG_STEERING_TORQUE, MSG_STEERING_TORQUE_ANGLE)}
|
|
FWD_BLACKLISTED_ADDRS = {0: [MSG_STEERING_TORQUE, MSG_STEERING_TORQUE_ANGLE], 2: [MSG_MPC_LKAS_CMD, MSG_ACC_CMD]}
|
|
|
|
MIN_ACCEL = 20 # -4.0 m/s^2, raw = (m/s^2 + 5) / 0.05
|
|
MAX_ACCEL = 140 # 2.0 m/s^2
|
|
INACTIVE_ACCEL = 100 # 0 m/s^2
|
|
|
|
def setUp(self):
|
|
self.packer = CANPackerSafety("byd_common")
|
|
self.safety = libsafety_py.libsafety
|
|
self.safety.set_safety_hooks(CarParams.SafetyModel.byd, 1) # BydSafetyFlags.LONG_CONTROL
|
|
self.safety.init_tests()
|
|
|
|
def _accel_msg(self, accel: float):
|
|
# accel in m/s^2, ACCEL_CMD raw = (m/s^2 + 5) / 0.05
|
|
values = {"ACCEL_CMD": accel}
|
|
return self.packer.make_can_msg_safety("ACC_CMD", 0, values, byd_checksum)
|
|
|
|
def test_accel_actuation_limits(self):
|
|
self.safety.set_controls_allowed(True)
|
|
for accel in [self.MIN_ACCEL - 1, self.MIN_ACCEL, 100, self.MAX_ACCEL, self.MAX_ACCEL + 1]:
|
|
send = self.MIN_ACCEL <= accel <= self.MAX_ACCEL
|
|
self.assertEqual(send, self._tx(self._accel_raw_msg(accel)), f"{accel=}")
|
|
|
|
def _accel_raw_msg(self, raw: int):
|
|
msg = self._accel_msg(0)
|
|
msg[0].data[0] = raw & 0xFF
|
|
return msg
|
|
|
|
def test_accel_when_not_allowed(self):
|
|
# when controls are not allowed, only 0 m/s^2 (inactive) may be sent
|
|
self.safety.set_controls_allowed(False)
|
|
self.assertTrue(self._tx(self._accel_raw_msg(self.INACTIVE_ACCEL)))
|
|
self.assertFalse(self._tx(self._accel_raw_msg(self.INACTIVE_ACCEL + 5)))
|
|
self.assertFalse(self._tx(self._accel_raw_msg(self.INACTIVE_ACCEL - 5)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|