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).
141 lines
5.0 KiB
Python
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()
|