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.
This commit is contained in:
Quintine committed 2026-07-25 19:37:06 +10:00
1 parent 63e9aa6cc1
commit 02f1972e99
23 files changed
+81 -21

No files matched your search

+41
View File
@@ -138,6 +138,47 @@ class BydCAN:
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,
+12 -6
View File
@@ -30,10 +30,10 @@ class CarController(CarControllerBase):
self.apply_accel_last = 0
# EPS steering feedback forwarding (792 STEERING_TORQUE) only applies to
# platforms whose EPS reports on 792 (HAN). The SEAL EPS reports on
# 508 STEERING_TORQUE_ANGLE which is forwarded stock for now.
self.send_steering_feedback = CP.carFingerprint != CAR.BYD_SEAL_PERFORMANCE_25
# EPS steering feedback is spoofed towards the camera on both platforms so
# the stock ADAS does not fault while its LKAS commands are intercepted:
# HAN uses 792 STEERING_TORQUE, SEAL uses 508 STEERING_TORQUE_ANGLE.
self.seal_platform = CP.carFingerprint == CAR.BYD_SEAL_PERFORMANCE_25
def update(self, CC, CC_SP, CS, now_nanos):
# car control running in 100Hz
@@ -80,12 +80,18 @@ class CarController(CarControllerBase):
lkas_request_prepare, lkas_active,
lkas_mode, CS.eps_activated)
can_sends.append(pack)
if self.send_steering_feedback:
if self.seal_platform:
pack = self.can.create_steering_torque_angle(CS.eps_steering_torque_msg,
CS.mpc_lkas_output,
CS.mpc_lkas_request_prepare,
CS.mpc_lkas_active,
CS.mpc_lkas_angle_output)
else:
pack = self.can.create_steering_torque(CS.eps_steering_torque_msg,
CS.mpc_lkas_output,
CS.mpc_lkas_request_prepare,
CS.mpc_lkas_active)
can_sends.append(pack)
can_sends.append(pack)
self.apply_torque_last = apply_torque
+3
View File
@@ -35,6 +35,7 @@ class CarState(CarStateBase, MadsCarState):
self.mpc_lkas_output = 0
self.mpc_lkas_active = False
self.mpc_lkas_request_prepare = False
self.mpc_lkas_angle_output = 0.0
def update(self, can_parsers) -> tuple[structs.CarState, structs.CarStateSP]:
cp = can_parsers[Bus.pt]
@@ -106,6 +107,8 @@ class CarState(CarStateBase, MadsCarState):
self.mpc_lkas_output = cp_cam.vl["MPC_LKAS_CMD"]["LKAS_Output"]
self.mpc_lkas_active = cp_cam.vl["MPC_LKAS_CMD"]["LKAS_ACTIVE"] != 0
self.mpc_lkas_request_prepare = cp_cam.vl["MPC_LKAS_CMD"]["LKASPrepare"] != 0
# camera's angle-based LKAS request (SEAL), used to spoof the 508 TARGET_ANGLE echo
self.mpc_lkas_angle_output = cp_cam.vl["MPC_LKAS_CMD_ANGLE"]["LKAS_Output"]
# for generate ACC_CMD
self.acc_cmd_msg = copy.copy(cp_adas.vl["ACC_CMD"])
return ret, ret_sp
+14 -5
View File
@@ -42,15 +42,22 @@ static bool byd_tx_hook(const CANPacket_t *msg) {
}
}
// STEERING_TORQUE: feedback message towards the camera, torque echoes the
// camera's own request. Only sanity-check the absolute limit so stock LKAS
// requests don't fault when openpilot is inactive.
// STEERING_TORQUE (792, HAN) / STEERING_TORQUE_ANGLE (508, SEAL):
// feedback message towards the camera, torque echoes the camera's own
// request. Only sanity-check the absolute limit so stock LKAS requests
// don't fault when openpilot is inactive.
if (msg->addr == 792U) {
int main_torque = BYD_GET_MAIN_TORQUE(msg);
if (safety_max_limit_check(main_torque, BYD_STEERING_LIMITS.max_torque, -BYD_STEERING_LIMITS.max_torque)) {
tx = false;
}
}
if (msg->addr == 508U) {
int main_torque = BYD_GET_MAIN_TORQUE_ANGLE(msg);
if (safety_max_limit_check(main_torque, BYD_STEERING_LIMITS.max_torque, -BYD_STEERING_LIMITS.max_torque)) {
tx = false;
}
}
// ACC_CMD: longitudinal actuation
if (msg->addr == 814U) {
@@ -115,12 +122,14 @@ static void byd_rx_hook(const CANPacket_t *msg) {
static safety_config byd_init(uint16_t param) {
static const CanMsg BYD_TX_MSGS[] = {
{790, 0, 8, .check_relay = true}, // MPC_LKAS_CMD
{792, 2, 8, .check_relay = true}, // STEERING_TORQUE
{792, 2, 8, .check_relay = true}, // STEERING_TORQUE (HAN)
{508, 2, 8, .check_relay = true}, // STEERING_TORQUE_ANGLE (SEAL)
};
static const CanMsg BYD_TX_LONG_MSGS[] = {
{790, 0, 8, .check_relay = true}, // MPC_LKAS_CMD
{792, 2, 8, .check_relay = true}, // STEERING_TORQUE
{792, 2, 8, .check_relay = true}, // STEERING_TORQUE (HAN)
{508, 2, 8, .check_relay = true}, // STEERING_TORQUE_ANGLE (SEAL)
{814, 0, 8, .check_relay = true}, // ACC_CMD
};
@@ -7,8 +7,9 @@ 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_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
@@ -36,10 +37,10 @@ def byd_checksum(msg):
class TestBydSafetyBase(common.CarSafetyTest, common.MotorTorqueSteeringSafetyTest):
TX_MSGS = [[MSG_MPC_LKAS_CMD, 0], [MSG_STEERING_TORQUE, 2]]
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,)}
FWD_BLACKLISTED_ADDRS = {0: [MSG_STEERING_TORQUE], 2: [MSG_MPC_LKAS_CMD]}
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
@@ -98,9 +99,9 @@ class TestBydStockSafety(TestBydSafetyBase):
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]}
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