Files
sunnypilot-byd/opendbc_repo/opendbc/safety/tests/test_byd.py
T
Quintine 02f1972e99 byd: spoof 508 steering feedback to camera on SEAL — fixes ADAS lockout
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.
2026-07-25 19:37:06 +10:00

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()