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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-5684dd9d-DEBUG";
const uint8_t gitversion[19] = "DEV-63e9aa6c-DEBUG";
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
DEV-5684dd9d-DEBUG
DEV-63e9aa6c-DEBUG
Binary file not shown.