Skip to content

BYD ATTO3: new brand port (lateral) - #3677

Open
qzwf wants to merge 2 commits into
commaai:masterfrom
qzwf:byd-atto3-upstream
Open

BYD ATTO3: new brand port (lateral)#3677
qzwf wants to merge 2 commits into
commaai:masterfrom
qzwf:byd-atto3-upstream

Conversation

@qzwf

@qzwf qzwf commented Aug 11, 2026

Copy link
Copy Markdown

Adds BYD as a brand with the ATTO3 (2022-24) as its first platform, at community support level. Submission for the BYD port bounty, #2017. Harness work and connector pinout are in #2065.

Lateral is done and driven daily on a comma 3X. Longitudinal stays with the stock ACC.

What the car does

The EPS takes an absolute steering wheel angle target in STEERING_MODULE_ADAS (0x1E2), so this is an angle-control port, not a torque one. Two properties of the car shaped the design and are worth calling out, because both were found the hard way:

The camera's steering frame cannot be synthesised. Everything in 0x1E2 other than the angle is held at the constant the camera itself transmits while steering (bytes 0-2 = 2b 55 eb, at every speed). Treating those bytes as a sequence and walking them made the car drop its entire ADAS mid-drive — LKAS, ACC and the cluster indicators all went away until a power cycle. The controller now copies the camera's own latched frame when it has seen the camera steer, and falls back to the captured constant otherwise (a camera that never gets a lock never emits one).

The EPS must never see two sources. openpilot transmits 0x1E2 and LKAS_HUD_ADAS (0x316) only while it is actually steering. The safety mode blocks the camera's copies per-frame while that is happening and hands both back ~150 ms after openpilot goes quiet, so the stock LKAS resumes on disengage and there is never a window with neither source driving the EPS. Static blocking is disabled for exactly this reason; check_relay is kept so stuck-relay detection still works.

The angle command is anchored to the measured angle and clamped to a fixed ±12° window around it. Without that anchor a limiter that tracks only its own previous output can ratchet away from the wheel, saturate at the clamp, and get every frame rejected by panda — which is what happened before this was in place.

Safety

SAFETY_BYD uses the standard angle checks: speed-dependent rate limits (matching CarControllerParams.ANGLE_LIMITS), inactive-angle tracking, and driver override on DRIVER_EPS_TORQUE, the column torque sensor. MAIN_TORQUE from STEERING_TORQUE is EPS motor output, not driver input, and is deliberately not used for override.

ACC_CMD is deliberately absent from the TX allowlist, so panda blocks longitudinal outright.

  • opendbc/safety/tests/test_byd.py: 34 tests, including coverage for the frame-template behaviour above
  • MISRA: clean
  • ruff / ty / codespell / cpplint: clean
  • Full car + safety suites pass

Signal notes

A few signals on this platform are not what their DBC names suggest, all verified against a controlled pedal capture and ~3.6 h of driving:

  • DRIVE_STATE.BRAKE_PRESSED is dead — byte 4 is a constant 0x0C
  • PEDAL_PRESSED_ACTIVE_LOW is the brake-light switch; 86% of its on-road assertions happened while the camera's ACC was commanding decel, not the driver. brakePressed uses PEDAL.BRAKE_PEDAL instead, the only signal that tracks the driver's foot.
  • ACC_HUD_ADAS.ACC_ON1/ON2 are main-switch/standby state, not engagement; they stay set while the driver brakes and drives manually. Engagement comes from ACC_CMD (CMD_REQ_ACTIVE_LOW).
  • WHEELSPEED_BR's high byte is a status byte, not speed; wheel speed sums the three trustworthy wheels.
  • STEER_REQ_ACTIVE_LOW is not the inverse of STEER_REQ — the camera holds it at 0.

Known gaps

  • No openpilot longitudinal. pcmCruise = True, stock ACC only. The ACC_CMD scaffolding in carcontroller.py is gated behind openpilotLongitudinalControl and unreachable today; it needs ACCEL_CMD scaling calibrated on the car before it can be enabled. Happy to strip it if you'd rather not carry it.
  • No FW fingerprint. The car matches on its CAN message set. Firmware versions collected so far were not reproducible across cars, so they are left out rather than shipped unverified; FW_QUERY_CONFIG still queries bus 0 so versions keep being collected.
  • No test route in routes.py yet. b8b2d1a1df1b3aad|000000a0--7c51639c22 is driven and public, but only qlogs have been uploaded so far and test_models needs rlogs. BYD_ATTO3 is in non_tested_cars until those finish uploading, at which point it's a one-line change.
  • Harness is CarHarness.custom pending a name. The camera is a Veoneer MVS4; the car-side connector is TE 1-1355211-2 (mating 1-1670459-2). Full pinout in BYD Atto 3 Harness #2065.

Bus layout

Bus Contents
0 car-side chassis CAN (read car ECU signals)
1 camera's private CAN (image/radar, CAN-FD)
2 camera-side chassis CAN (camera's ADAS messages)

Port by QZWF.

@github-actions

Copy link
Copy Markdown
Contributor

Car behavior report

Replays driving segments through this PR and compares the behavior to master.
Please review any changes carefully to ensure they are expected.

✅ No changes detected

@github-actions github-actions Bot added DBC signals car related to opendbc/car/ car safety vehicle-specific safety code labels Aug 11, 2026
Adds BYD as a brand with the ATTO3 (2022-24) as its first platform, at
community support level. Lateral only; longitudinal stays with the stock ACC.

The EPS takes an absolute steering wheel angle target in STEERING_MODULE_ADAS
(0x1E2), so this is an angle-control port. Two things about the car shaped the
design:

- The camera does not tolerate having its steering frame synthesised from
  scratch. Everything in 0x1E2 other than the angle is held at the constant the
  camera itself transmits while steering; varying those fields made the car drop
  its entire ADAS mid-drive.
- The EPS must never see two sources. openpilot transmits 0x1E2 and 0x316 only
  while it is actually steering, and the safety mode blocks the camera's copies
  per-frame while that is happening, handing both back ~150 ms after openpilot
  goes quiet. There is no window with neither source driving the EPS.

The command is anchored to the measured angle and clamped to a fixed window
around it, so the limiter cannot ratchet away from the wheel and saturate.

Safety mode SAFETY_BYD uses the standard angle checks: speed-dependent rate
limits, inactive-angle tracking, and driver override on the column torque
sensor. ACC_CMD is deliberately absent from the TX allowlist.
@gadhadar
gadhadar force-pushed the byd-atto3-upstream branch from 4b6233d to 05652c2 Compare August 13, 2026 09:05
The ATTO3 now has an uploaded route, so it moves out of non_tested_cars.

Two things the route surfaced:

- ANGLE_DEG_TO_CAN needed a byd entry (10, from the 0.1 deg DBC factor)
  for test_panda_safety_carstate to check the measured angle.

- test_panda_safety_tx_cases asserts every car transmits >50 messages, but
  all of its cases are inactive. This port sends nothing unless it is
  actively steering: the safety mode blocks the camera's steering command
  per-frame only while openpilot is transmitting, so transmitting while
  inactive would lock the camera out of the EPS permanently. The message
  count check is skipped for that case and an active case is added instead,
  so the transmitting path stays covered.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

car safety vehicle-specific safety code car related to opendbc/car/ DBC signals

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants