Files
sunnypilot-byd/opendbc_repo/opendbc/safety/tests/test_byd.py
T
Quintine c8903e592c byd: add BYD car port (HAN EV 23 / Seal Performance 2025) with MADS support
Car port adapted from HotIce0/openpilot develop_byd_comma4 to sunnypilot's
opendbc API (CP_SP signatures, (ret, ret_sp) carstate returns):

- opendbc/car/byd/: interface, carstate, carcontroller, bydcan, values,
  fingerprints (BYD_SEAL_PERFORMANCE_25 with full 91-msg fingerprint)
- opendbc/dbc/byd_common.dbc
- Brand registration: car/values.py, car/fingerprints.py (+MIGRATION),
  car.capnp SafetyModel byd @35, torque_data/params.toml
- safety/modes/byd.h: SAFETY_BYD 35U with real TX checks (steer torque
  cmd checks, longitudinal accel checks), MADS LKAS button from
  PCM_BUTTONS (944), pcm_cruise_check, ESC vehicle_moving. MISRA clean.
- safety/tests/test_byd.py: 120 tests (stock + longitudinal variants)
- opendbc/sunnypilot/car/byd/mads.py: MadsCarState tracking the
  dedicated LKAS steering wheel button
- sunnypilot/mads/mads.py: allow_always for byd (dedicated LKAS button)
- panda: restore SConstruct/SConscript build system, rebuild
  panda_h7.bin.signed (TIZI) with SAFETY_BYD + MADS lateral gating

Known limitation: BYD_HAN_EV_23 fingerprint is a subset of the Seal's,
so HAN requires FINGERPRINT=BYD_HAN_EV_23 env var (live fingerprint
cannot distinguish it; Seal fingerprints natively).
2026-07-25 15:09:00 +10:00

141 lines
5.0 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
MSG_ACC_HUD_ADAS = 813 # RX from camera, 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]]
STANDSTILL_THRESHOLD = 0
RELAY_MALFUNCTION_ADDRS = {0: (MSG_MPC_LKAS_CMD,), 2: (MSG_STEERING_TORQUE,)}
FWD_BLACKLISTED_ADDRS = {0: [MSG_STEERING_TORQUE], 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_ACC_CMD, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (MSG_MPC_LKAS_CMD, MSG_ACC_CMD), 2: (MSG_STEERING_TORQUE,)}
FWD_BLACKLISTED_ADDRS = {0: [MSG_STEERING_TORQUE], 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()