Files
sunnypilot-byd/opendbc_repo/opendbc/car/byd/bydcan.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

203 lines
7.6 KiB
Python

from opendbc.car.byd.values import CANBUS
def byd_checksum(data: bytearray) -> int:
byte_key = 0xAF
sum_first = sum(byte >> 4 for byte in data) # Extract upper nibble.
sum_second = sum(byte & 0xF for byte in data) # Extract lower nibble.
remainder = sum_second >> 4
sum_first += (byte_key & 0xF) # Low nibble of byte_key.
sum_second += (byte_key >> 4) # High nibble of byte_key.
# Inline inverse computation for each sum:
# inv = (-sum + 0x9) & 0xF
inv_first = ((-sum_first + 0x9) & 0xF)
inv_second = ((-sum_second + 0x9) & 0xF)
return (((inv_first + (5 - remainder)) << 4) + inv_second) & 0xFF
def byd_checksum_short(data: bytearray) -> int:
# checksum for CHECKSUM_S 4byte
pass
class BydCAN:
LKAS_MODE_ACTIVE2 = 3
LKAS_MODE_PASSIVE = 1
def __init__(self, packer):
self.packer = packer
self.mpc_lkas_counter = 0
self.eps_steering_torque_counter = 0
self.mpc_acc_cmd_counter = 0
def update_mpc_lkas_counter(self, val):
self.mpc_lkas_counter = val
def _generate_mpc_lkas_new_counter(self):
counter = self.mpc_lkas_counter
self.mpc_lkas_counter = int(self.mpc_lkas_counter + 1) & 0xF
return counter
def update_eps_steering_torque_counter(self, val):
self.eps_steering_torque_counter = val
def _generate_eps_steering_torque_new_counter(self):
counter = self.eps_steering_torque_counter
self.eps_steering_torque_counter = int(
self.eps_steering_torque_counter + 1) & 0xF
return counter
def update_acc_cmd_counter(self, val):
self.mpc_acc_cmd_counter = val
def _generate_acc_cmd_new_counter(self):
counter = self.mpc_acc_cmd_counter
self.mpc_acc_cmd_counter = int(self.mpc_acc_cmd_counter + 1) & 0xF
return counter
# MPC -> Panda -> EPS
def create_steering_control_torque(self, mpc_lkas_cmd_msg, torque,
request_prepare, active, mode,
eps_active):
lkas_output = torque if active and eps_active else 0
lkas_prepare = request_prepare
lkas_active = active
lkas_mode = mode
left_lane = mpc_lkas_cmd_msg["LeftLane"]
right_lane = mpc_lkas_cmd_msg["RightLane"]
if active or request_prepare:
left_lane = 1
right_lane = 1
values = {
"SETME_0x1": mpc_lkas_cmd_msg["SETME_0x1"],
"LeftLane": left_lane,
"Config": mpc_lkas_cmd_msg["Config"],
"SETME2_0x1": mpc_lkas_cmd_msg["SETME2_0x1"],
"Keep_Hands_On_Wheel": 0,
"MPCErr": mpc_lkas_cmd_msg["MPCErr"],
"SETME3_0x1": mpc_lkas_cmd_msg["SETME3_0x1"],
"LKAS_Output": lkas_output,
"LKASPrepare": lkas_prepare,
"LKAS_ACTIVE": lkas_active,
"TSRStatus": mpc_lkas_cmd_msg["TSRStatus"],
"SETME4_0x1": mpc_lkas_cmd_msg["SETME4_0x1"],
"RightLane": right_lane,
"LKAS_Mode": lkas_mode,
"SETME_0x0": mpc_lkas_cmd_msg["SETME_0x0"],
"TSRResult": mpc_lkas_cmd_msg["TSRResult"],
"Unknow1": mpc_lkas_cmd_msg["Unknow1"],
"COUNTER": self._generate_mpc_lkas_new_counter(),
}
data = self.packer.make_can_msg("MPC_LKAS_CMD", CANBUS.main_bus, values)[1]
values["CHECKSUM"] = byd_checksum(data)
return self.packer.make_can_msg("MPC_LKAS_CMD", CANBUS.main_bus, values)
def create_steering_torque(self, eps_steering_torque_msg, mpc_lkas_output,
mpc_lkas_request_prepare, mpc_lkas_active):
lks_prepared = eps_steering_torque_msg["LKSPrepare"]
cruise_activated = eps_steering_torque_msg["Cruise_Activated"]
main_torque = eps_steering_torque_msg["MAIN_TORQUE"]
if mpc_lkas_active:
lks_prepared = 0
cruise_activated = 1
main_torque = mpc_lkas_output
elif mpc_lkas_request_prepare:
lks_prepared = 1
cruise_activated = 0
main_torque = 0
else:
lks_prepared = 0
cruise_activated = 0
main_torque = 0
values = {
"LKSPrepare": lks_prepared,
"Cruise_Activated": cruise_activated,
"TORQUE_FAILED": eps_steering_torque_msg["TORQUE_FAILED"],
"SET_ME_1_1": eps_steering_torque_msg["SET_ME_1_1"],
"Steer_Warning": eps_steering_torque_msg["Steer_Warning"],
"Steer_Error_1": eps_steering_torque_msg["Steer_Error_1"],
"Steer_Error_2": eps_steering_torque_msg["Steer_Error_2"],
"SET_ME_1": eps_steering_torque_msg["SET_ME_1"],
"MAIN_TORQUE": main_torque,
"SET_ME_1_2": eps_steering_torque_msg["SET_ME_1_2"],
"Keep_Hands_On_Wheel": 0,
"SET_ME_3": eps_steering_torque_msg["SET_ME_3"],
"Steer_Torque_Sensor": eps_steering_torque_msg["Steer_Torque_Sensor"],
"SET_ME_XF_1": eps_steering_torque_msg["SET_ME_XF_1"],
"SET_ME_XFFF": eps_steering_torque_msg["SET_ME_XFFF"],
"COUNTER": self._generate_eps_steering_torque_new_counter(),
}
data = self.packer.make_can_msg("STEERING_TORQUE", CANBUS.cam_bus, values)[1]
values["CHECKSUM"] = byd_checksum(data)
return self.packer.make_can_msg("STEERING_TORQUE", CANBUS.cam_bus, values)
# SEAL: spoofed 508 STEERING_TORQUE_ANGLE feedback towards the camera.
# Mirrors the camera's own request state back to it so the stock ADAS does
# not fault while panda intercepts its LKAS commands (same trick as the
# HAN's 792 spoofing, but on the Seal's 508 message layout).
def create_steering_torque_angle(self, eps_steering_torque_msg, mpc_lkas_output,
mpc_lkas_request_prepare, mpc_lkas_active,
mpc_lkas_angle_output):
lks_prepared = eps_steering_torque_msg["LKSPrepare"]
cruise_activated = eps_steering_torque_msg["Cruise_Activated"]
main_torque = eps_steering_torque_msg["MAIN_TORQUE"]
target_angle = eps_steering_torque_msg["TARGET_ANGLE"]
if mpc_lkas_active:
lks_prepared = 0
cruise_activated = 1
main_torque = mpc_lkas_output
target_angle = mpc_lkas_angle_output
elif mpc_lkas_request_prepare:
lks_prepared = 1
cruise_activated = 0
main_torque = 0
else:
lks_prepared = 0
cruise_activated = 0
main_torque = 0
values = {
"LKSPrepare": lks_prepared,
"Cruise_Activated": cruise_activated,
"TORQUE_FAILED": eps_steering_torque_msg["TORQUE_FAILED"],
"Steer_Torque_Sensor": eps_steering_torque_msg["Steer_Torque_Sensor"],
"TARGET_ANGLE": target_angle,
"MAIN_TORQUE": main_torque,
"Keep_Hands_On_Wheel": 0,
"TORQUE_TEMP_FAILED": eps_steering_torque_msg["TORQUE_TEMP_FAILED"],
"COUNTER": self._generate_eps_steering_torque_new_counter(),
}
data = self.packer.make_can_msg("STEERING_TORQUE_ANGLE", CANBUS.cam_bus, values)[1]
values["CHECKSUM"] = byd_checksum(data)
return self.packer.make_can_msg("STEERING_TORQUE_ANGLE", CANBUS.cam_bus, values)
def create_acc_cmd(self, acc_cmd_msg, accel):
values = {
"ACCEL_CMD": accel,
"ComfortBandUpper": acc_cmd_msg["ComfortBandUpper"],
"ComfortBandLower": acc_cmd_msg["ComfortBandLower"],
"JerkUpperLimit": 1,
"SET_ME_1": acc_cmd_msg["SET_ME_1"],
"JerkLowerLimit": -1,
"STANDSTILL_RESUME": 0,
"STANDSTILL_STATE": 0,
"BRAKE_BEHAVIOR": acc_cmd_msg["BRAKE_BEHAVIOR"],
"ACC_REQ_NOT_STANDSTILL": acc_cmd_msg["ACC_REQ_NOT_STANDSTILL"],
"ACC_CONTROLLABLE_AND_ON": acc_cmd_msg["ACC_CONTROLLABLE_AND_ON"],
"ACC_OVERRIDE_OR_STANDSTILL": acc_cmd_msg["ACC_OVERRIDE_OR_STANDSTILL"],
"ESP_BEHAVIOR": acc_cmd_msg["ESP_BEHAVIOR"],
"COUNTER": self._generate_acc_cmd_new_counter(),
"SET_ME_XF": acc_cmd_msg["SET_ME_XF"],
}
data = self.packer.make_can_msg("ACC_CMD", CANBUS.main_bus, values)[1]
values["CHECKSUM"] = byd_checksum(data)
return self.packer.make_can_msg("ACC_CMD", CANBUS.main_bus, values)