Skip to content

mazda: KE support — platform, foreign-radar vision-only, car picker fix - #10

Merged
zephleggett merged 13 commits into
zoompilot:developfrom
mzdnick:mazda-ke-car-picker
Sep 8, 2026
Merged

mazda: KE support — platform, foreign-radar vision-only, car picker fix#10
zephleggett merged 13 commits into
zoompilot:developfrom
mzdnick:mazda-ke-car-picker

Conversation

@mzdnick

@mzdnick mzdnick commented Sep 7, 2026

Copy link
Copy Markdown

Summary

One support case drives this PR: a 2016 KE body (2012-16 CX-5, incl. the 2016.5 refresh) with a swapped 2022 EPS and its stock G46L radar, held on the CX-5 2022 platform by a carried-forward bundle. The G46L answers the firmware query but never puts the 0x361-0x366 tracks on bus 0, so radard starved behind a parser that never went valid and the device raised commIssue through whole drives.

  • KE platform — the 2012-16 CX-5 chassis. Claims no radar bus (the stock G46L sends no parseable tracks on bus 0; the lead comes from vision) and lists no EPS firmware on purpose: these bodies commonly run 2022 EPS swaps, and swap firmware belongs to MAZDA_CX5_2022 and STEER_TO_ZERO_EPS_FW, not the chassis. Includes the corrected steer ratio (the pre-2022 racks run the 2022's 18.1), torque seeds, and tests.
  • Foreign-radar vision-only, G46L dialect, VIN checkradarUnavailable when the platform's DBC claims no radar bus, or the detected radar is the G46L: the one radar known never to publish 0x361-0x366 on bus 0. Any other radar keeps the platform's word, so an unlisted newer revision of a working radar (e.g. a K131-67XK2-G on a 2022 CX-5) loses nothing — per review, this inverts the first draft's unlisted-implies-track-less rule. The G46L's own dialect is what the teardown replays. platform_from_vin in mazda/values.py (shared with match_fw_to_car_fuzzy) cross-checks the selected bundle from _get_params_sp; a mismatch logs platformBundleVinMismatch. Alpha-long availability: steer-to-zero EPS AND (a claimed radar bus OR the detected G46L).
  • Car picker fix — the picker's car_list.json is generated in docs mode with an empty firmware list, so every firmware-gated Mazda resolved dashcamOnly and the generator skipped it: on develop today the CX-5 2017-21, CX-9 2016-20, Mazda 3 and Mazda 6 are hidden from the picker. The fix gates the verdict with if not docs: and regenerates the JSON; on-device behavior is unchanged.

Reviewer notes

Rebased across the danger-unstable promotion (676fa14b). Its flag split — EPS_HW (measured envelope) / STEER_TO_ZERO_EPS (firmware scale + latch) / LEGACY_FW_EPS (floor) — is kept, with G46L_RADAR added alongside; the controller ends with eps_2022, steer_to_zero and g46l. The promotion's set-based EPS detection with the empty-query fallback, and its unconditional steerActuatorDelay = 0.14, win over the branch's older versions. interface.py is the union of the two; both new test classes are kept. During development a dialect registry briefly generalized the G46L replay; its uniqueness test caught a latent bug the rebase had introduced (LEGACY_FW_EPS and G46L_RADAR both on bit 4) before the registry itself was reverted per review — bit 8 and a one-line unshared-bit assertion are what remain, and mazdaRadarVerdict logs at debug level.

Follow-ups

This PR is independent and mergeable on its own. The zoompilot-side companion (the docs for the foreign-radar rule and the G46L dialect, the param-change logging, and the submodule bump) follows in zoompilot/zoompilot once this merges — it pins the merged submodule SHA, so it cannot open earlier.

Testing

Mazda suite + test_car_list at the tip: 430 passed, 5 failed (the safety file separately: 268 passed, all capture tests included) — all the known local panda-version artifact, byte-identical to pristine develop. CI runs the real suite. Road-verified on the swapped car (dongle d057d702, this code at the fork's e9b94b2): radarUnavailable true, 1081 radarTracks messages where the broken build had zero, radarState valid from 108.6 s, commIssue gone except the two-second boot warmup. After the fix shipped in the fork, the picker on the support user's device listed Mazda CX-5 2012-16.

Alpha-long teardown, road-verified on the same car (separate drive, build e9b94b2, 61 s with 11 s of standstill and resume): the param stuck across the cycle; the G46L was silenced by a single UDS session request at 13.4 s; tester-present held it silent at 2 Hz through route end (47 s, no gap near the 5 s S3 timeout); the synthetic replay ran at exact rates (0x499 at 10 Hz, CRZ frames at 50 Hz) in the G46L's own capture bytes on both buses, with the camera never seeing a frame gap through the handover. radarState valid from vision throughout, commIssue only in the boot-warmup window, and no camera or SCBS faults. The hand-back path (disable mid-drive) is the one remaining piece with unit tests only.

AI Usage

Disclaimer: GLM-5.3 by Z.ai was used to help develop, debug, and document this submission. All changes were reviewed and validated by a human.

mzdnick and others added 7 commits September 7, 2026 14:07
The KE chassis (2012-16 CX-5, incl. the 2016.5 mid-year refresh) with its
stock G46L radar. The platform claims no radar bus: the G46L does not put
the 0x361-0x366 tracks on bus 0, so the lead comes from vision and the
radar DBC stays unset. EPS swaps are common on these bodies (the 2022
CX-5 EPS is what unlocks steer-to-zero), so the FW_VERSIONS entry carries
no EPS firmware on purpose: swap firmware belongs to MAZDA_CX5_2022 and
STEER_TO_ZERO_EPS_FW, not the chassis.
VIN years C-G name the platform, the 2016.5 firmware set exact-matches on the
body ECUs alone, and dealer-updated ECUs still name by VIN through the donor
EPS. The platform runs vision-only under a swap and never offers alpha long;
docs build without firmware.
The pre-2022 CX-5 racks run the 2022's 18.1 ratio (same hardware), and the
KE platform mass is 3433 lb from the platform-table research.
The CX-9 2021 entry is the pattern: seeds learned under a donor EPS apply only
when the interface detects it, and requires_steer_to_zero withholds them on the
stock EPS's flat 800-count scale. The KE table copies the CX-5 2022 values
unchanged, since the donor EPS is the EPS those bins were learned under and no
KE has its own fit yet; seed_version mirrors the source table.
The pre-2022 CX-5 racks now run the 2022's 18.1 steerRatio, so the ratio
assertion no longer separates the platforms; mass and tireStiffnessFactor do.
… check

A talking radar whose firmware no platform lists sends no tracks we can
parse. Behind a platform bundle that claims a radar bus (an EPS-swapped
older body carrying a forward bundle), radard starved on radarTracks and
selfdrived raised commIssue for the whole drive.

- radarUnavailable: platform claim OR foreign radar firmware; a silent
  radar keeps the claim
- alpha availability decoupled from radarUnavailable: offered where the
  platform radar speaks the 2022 family dialect or the detected radar is
  the G46L whose replay exists
- port the G46L teardown dialect from mazda-alpha-long-ke: flag,
  firmware set, static-only radar frames, safety capture
- warn when a fixed platform bundle contradicts the VIN's platform
  (platform_from_vin over platform VIN metadata)
Docs mode runs with empty car_fw, so every Mazda outside the factory
steer-to-zero pair resolved dashcamOnly, and the car-list generator
skips dashcamOnly platforms: CX-5 KE, CX-5 2017-21, CX-9 2016-20 (and
Mazda 3/6) were unselectable. dashcamOnly is a measured-hardware call,
so docs mode now keeps the default and the on-device EPS check keeps
the gate; regenerate car_list.json (380 -> 385 platforms).
@mzdnick
mzdnick marked this pull request as draft September 7, 2026 18:46
The G46L facts were told at six production sites. The fw-set definition
now carries the identity story and the null-strip matching, the detection
and availability sites keep only their local why, the controller note
drops, and the mazdacan constant's comment absorbs the lead-on-CRZ_CTRL
clause so the branch note can go. The VIN swap rationale lives in
platform_from_vin's docstring alone. No behavior change.
The G46L replay was a special case threaded through four files: a bare
fw set, a flag bit, a hardcoded capture, and a g46l= boolean plumbed
through the controller. REPLAY_RADAR_DIALECTS now registers each
replayable radar in one place (name, firmware, flag, sends_tracks),
mazdacan's capture table keys off the dialect object, and the interface
resolves the detected radar through the registry. Adding a dialect is
data plus captures; a consistency test pins that every dialect claims an
unclaimed single bit, stays off the track set, and has its capture
registered.

That test forced a flag fix: rebasing onto the danger-unstable promotion
had left LEGACY_FW_EPS and G46L_RADAR both on bit 4. Latent (the dialect
bit is only read behind the steer-to-zero precondition), but every
legacy-firmware car read as carrying the G46L dialect. G46L_RADAR moves
to bit 8, and mazdaRadarVerdict names the resolved dialect instead of a
boolean.
sends_tracks was decorative; the replay-shape test makes it the promise
it names: every static-only dialect replays exactly its registered
capture, whatever the lead. The consistency test also pins that dialect
firmware sets stay pairwise disjoint — two dialects listing the same
string would make resolution silently pick the first. And the safety
capture test now iterates RADAR_STATIC_CAPTURES through mazda_rx_hook,
so a dialect registered without its mazda.h mirror goes red by name.
One stale clause fixed: the registry recipe still told the reader to
mirror captures in the safety tests, which the registry-driven test now
does by itself. The G46L identity facts consolidate into the registry
entry (the no-counter, no-checksum nature of its frame moves there),
and the per-site comments shrink to their local why. Test names carry
what test names can.
@mzdnick

mzdnick commented Sep 7, 2026

Copy link
Copy Markdown
Author

The three red checks are inherited from develop, not introduced by this PR — evidence:

  1. safety + the unittest stage of both test.sh jobs (all 17 + 4 errors are the same ModuleNotFoundError: No module named 'pytest'): the safety tests added in 676fa14b import opendbc.car.mazda.tests.conftest, whose module level does import pytest; the safety/unittest environments don't install it.
  2. The two ty type errors (assertGreater overload, recover_from_rejection assignment): both live in 676fa14b's test code; the line numbers only shifted here because this PR's tests sit above them.

git log -S over this PR's range touches none of that; our additions to the safety file import only mazdacan and pass in CI. 676fa14b was pushed directly to develop and ran only update-uv-lock, so the PR matrix never ran for it — this PR is the first full-CI run since and surfaced it.

AI Usage

Disclaimer: GLM-5.3 by Z.ai was used to help develop, debug, and document this submission. All changes were reviewed and validated by a human.

@mzdnick
mzdnick marked this pull request as ready for review September 7, 2026 20:35

@zephleggett zephleggett left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed writeup and road data. I ran the branch from a clean build:

  • Mazda + SP car/safety tests: 728 passed, 0 failed. The 5 failures you saw appear local.
  • Remaining opendbc/car/tests: 508 passed
  • MISRA: clean
  • No new dash alerts on a listed 2022 CX-5.
  • The steer ratio change looks good; 18.1 should remain for all CX-5s.
  • The mazda.h change only widens the 0x499 whitelist.

Requesting changes on one regression and two simplifications.

1. Unknown radar firmware should not disable radar

The current foreign_radar logic treats any firmware missing from TRACK_RADAR_FW as track-less.

MAZDA_CX5_2022 only lists K131-67XK2-F, so a valid 2022-25 CX-5 with a newer radar revision can fuzzy-match or be manually selected and silently become vision-only.

I reproduced this with K131-67XK2-G:

radarUnavailable = True

That is especially risky because radar firmware is excluded from fuzzy matching.

The classification is also inconsistent: CX-9 2016-20 radar firmwares are in TRACK_RADAR_FW, even though that platform is documented as not publishing 0x361-0x366.

Please invert this: only special-case radars known not to provide the normal tracks.

ret.radarUnavailable = Bus.radar not in DBC[candidate] or g46l

Then remove TRACK_RADAR_FW and update the unknown-radar test accordingly.

This also avoids relying on firmware discovered by another brand's query; the scan currently does not filter fw.brand.

2. The radar registry is too much abstraction for one case

RadarDialect, REPLAY_RADAR_DIALECTS, RADAR_STATIC_CAPTURES, sends_tracks, and the registry tests add a lot of structure for one special radar.

sends_tracks also does not currently affect production behavior.

I would go back to the simpler shape:

G46L_RADAR_FW = {...}
MazdaFlags.G46L_RADAR
create_radar_frames(..., g46l)

If a second radar dialect appears later, that is the right time to introduce a registry.

3. Keep VIN matching inside Mazda

platform_from_vin in opendbc/car/values.py adds generic code for Mazda-specific behavior and duplicates the loop already in match_fw_to_car_fuzzy.

Please factor the shared VIN matching into mazda/values.py and reuse it from:

  • match_fw_to_car_fuzzy
  • Mazda _get_params_sp for the selected-car/VIN mismatch warning

CP.carVin and CP.fingerprintSource are already available there, so car_helpers.py does not need to change.

This also fixes the current check missing the FINGERPRINT env path.

Minor

  • Drop or downgrade carlog.info("mazdaRadarVerdict"); it fires during tests/docs.
  • The if not docs: dashcam gate looks good.
  • car_list.json only adds the expected Mazda entries.

Looks good

The rest looks sound:

  • KE follows the existing CX-9 2016-20 PT-only pattern.
  • RadarInterface correctly honors radarUnavailable.
  • Exact matching with a donor EPS works.
  • A 2022 CX-5 cannot exact-match as KE.
  • Flag bit 8 is unused.
  • The KE torque table matches CX-5 2022 and is correctly gated by requires_steer_to_zero.

@mzdnick

mzdnick commented Sep 7, 2026

Copy link
Copy Markdown
Author

Thanks for the review and the clean-build verification — 728/0 confirms my five failures were local.

  1. Agreed, inverting: radarUnavailable = Bus.radar not in DBC[candidate] or g46l; TRACK_RADAR_FW deleted; unknown radars keep the stock parse path. The verdict line drops to debug — an unknown radar that publishes nothing on a claiming platform stays diagnosable via CarParams.carFw, and the known-track-less set (the G46L today) is where such a radar gets added.
  2. Registry reverted to G46L_RADAR_FW + flag + the g46l= kwarg; a registry returns when a second dialect exists. Kept from the experiment: bit 8, and a one-line assertion that no other single-bit flag shares it (it caught the LEGACY_FW_EPS/bit-4 collision).
  3. Agreed: platform_from_vin moves into mazda/values.py, shared by match_fw_to_car_fuzzy and the mismatch warning, which now lives in _get_params_sp off carVin/fingerprintSource (covering the FINGERPRINT env path). The generic helper and the car_helpers.py hook are deleted.

mazdaRadarVerdict is debug-level now. Pushed as 5426622.

AI Usage

Disclaimer: GLM-5.3 by Z.ai was used to help develop, debug, and document this submission. All changes were reviewed and validated by a human.

… in mazda

Per review: an unlisted newer revision of a working radar must not
silently lose its tracks, so radarUnavailable now keys on the platform's
bus claim or the G46L alone, and TRACK_RADAR_FW is gone. The registry
reverts to the simple G46L_RADAR_FW set, flag and g46l kwarg (bit 8
kept; a one-line test pins that no other single-bit flag shares it —
that assertion caught the LEGACY_FW_EPS/bit-4 collision). VIN matching
consolidates into mazda/values.py: platform_from_vin serves
match_fw_to_car_fuzzy and the mismatch warning, which moves to
_get_params_sp off carVin and fingerprintSource, covering the
FINGERPRINT env path; the generic helper and the car_helpers hook are
deleted. mazdaRadarVerdict downgraded to debug.
@mzdnick
mzdnick requested a review from zephleggett September 7, 2026 21:21
Both files are byte-identical to develop again; they leave the PR diff.
@zephleggett
zephleggett merged commit 88c29ce into zoompilot:develop Sep 8, 2026
10 of 13 checks passed
zephleggett added a commit to zoompilot/zoompilot that referenced this pull request Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants