tesla preap: add Tinkla Buddy IC integration#1
Open
sveinmer wants to merge 7 commits into
Open
Conversation
Ported 1:1 from Tinkla unified (tesla_unity_releaseC3) via bit-exact verified replay-rig. Renders openpilot path / lanes / lead-car on Tesla MCU1 instrument cluster via Buddy R2S's passive EtherCAN MITM-pipeline. Sends 10 DAS_*-frames on chassis bus 0: - 0x239 DAS_lanes (10Hz) — lane geometry for IC path rendering - 0x309 DAS_object (10Hz) — lead-car position (radar or vision-fallback) - 0x329/349/369 DAS_warningMatrix0/3/1 (2Hz) — warning overlay - 0x389 DAS_status2 (10Hz) — ACC status - 0x399 DAS_status (10Hz) — speed limit / fused speed - 0x3A9 DAS_telemetry (10Hz) — road-info / lane quality - 0x3E9 DAS_bodyControls (4Hz) — turn signal forwarding - 0x659 fake_das (1Hz) — Buddy MITM trigger (DAS_adaptive_cruise=1) - 0x2B9 DAS_control (gated, see magzu_long_active) Coordinated with MagZu's existing long-stack via CS.magzu_long_active: HUD suppresses its own 0x2B9 when MagZu's PreAPLongController is engaged (see hud_module.py "Plan C 0x2B9 gate"). Risk-tier 3 (display-only, no actuator path). Gating: CS.enableICIntegration (NAPTinklaICIntegration param). Default off; user enables via NAP-settings toggle. Stubs (no functionality, default 0): - ALCA (lane-change render) — needs alca_pre_engage/engaged from controller stack - fleet_speed (map speed-limit) — needs mapd pipeline No new test dependencies; tests/test_hud_integration.py covers core update path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends TeslaCANPreAP with builders for the 10 DAS_*-frames sent by HUDController plus 0x659 fake_das (Buddy MITM trigger). Adds 14 arb-ID constants (DAS_LANES_MSG_ID etc.) and per-frame counter state. New builders (DBC-packer or raw struct.pack): - create_lane_message (0x239, DBC, 10Hz) - create_lead_car_object_message (0x309, DBC, 10Hz) - create_body_controls_message (0x3E9, DBC, 4Hz) - create_telemetry_road_info (0x3A9, DBC, 10Hz) - create_das_warningMatrix0/1/3 (0x329/0x369/0x349, raw struct.pack, 2Hz) - create_das_status (0x399, DBC, 10Hz, with byte-sum checksum + counter) - create_das_status2 (0x389, DBC, 10Hz, with byte-sum checksum + counter) - create_fake_DAS_msg (0x659, raw byte-array, 1Hz, Buddy MITM trigger) - create_ap1_long_control (0x2B9, DBC, Tinkla in_drive/static_cruise/cruise_enabled signature) DBC dependencies (already present in tesla_preap.dbc on nap-C3-dev): 0x239, 0x309, 0x389, 0x399, 0x3A9, 0x3E9, 0x2B9. No DBC changes needed. Ported 1:1 from Tinkla unified (tesla_unity_releaseC3). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- cs.DAS_fusedSpeedLimit: Tesla GPS speed-limit (UI_mppSpeedLimit on UI_gpsVehicleSpeed) for IC road-sign widget. Falls back to NAPRoadSignFallbackKph param when GPS SNA. - cs.magzu_long_active: signals HUDController to suppress its own 0x2B9 when MagZu's long-controller TXes from create_longitudinal_command path. - Subscribe to UI_gpsVehicleSpeed in chassis_messages (10Hz, GPS-gated). - nap_conf.road_sign_fallback_kph property + NAPRoadSignFallbackKph param-key (Persistent INT, default 0 = no sign rendered when GPS SNA). - NAPTinklaICIntegration param-key (default off = display-only opt-in). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Instantiate HUDController in CarController.__init__ for all PreAP cars - Set up SubMaster for modelV2 / radarState / selfdriveState payload (uses _EventLikeReader bridge to keep hud_module's `data.<field>` access pattern unchanged from Tinkla original) - Dispatch hud_controller.update() in _update_preap each tick; appends CAN messages to can_sends - No-op when CS.enableICIntegration=False (default; opt-in toggle) Regression tests: - test_hud_integration.py — core update path (27 scenarios) - test_speed_limit_widget.py — DAS_fusedSpeedLimit fallback logic Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds Tesla Pre-AP “Tinkla Buddy” instrument-cluster (IC) rendering support and a Tesla IC native road-sign speed-limit widget feed, including configuration params and regression tests.
Changes:
- Introduces HUD-module + CAN builders to emit DAS_* IC-rendering frames (and gates them via NAP toggle + Plan C logic).
- Adds road-sign fallback parameter plumbing and PreAP CarState speed-limit sourcing from
UI_gpsVehicleSpeed. - Adds two new test suites covering IC integration behavior and speed-limit widget encoding/decoding.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| opendbc/car/tesla/preap/tests/test_speed_limit_widget.py | New tests for DAS_fusedSpeedLimit encoding, UI_gpsVehicleSpeed parsing, and road-sign fallback behavior. |
| opendbc/car/tesla/preap/tests/test_hud_integration.py | New integration-style tests for IC message IDs, gating, counters, and upstream payload propagation. |
| opendbc/car/tesla/preap/teslacan.py | Adds DAS_* message IDs and CAN builders (DBC-packed + raw struct.pack) for IC rendering frames. |
| opendbc/car/tesla/preap/nap_params.py | Adds new param keys + defaults for IC integration toggle and road-sign fallback. |
| opendbc/car/tesla/preap/nap_conf.py | Adds road_sign_fallback_kph getter/setter backed by Params + JSON cache. |
| opendbc/car/tesla/preap/hud_module.py | New HUDController implementing the frame scheduling, gating, and payload mapping for IC. |
| opendbc/car/tesla/preap/carstate.py | Reads UI_mppSpeedLimit and sets cs.DAS_fusedSpeedLimit; adds magzu_long_active flag and parser registration. |
| opendbc/car/tesla/carcontroller.py | Wires HUDController into PreAP CarController; polls SubMaster and adapts readers for hud_module expectations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Tidligere DIAGNOSTIC hardkode 80 fjernet — ga ingen IC-effekt fordi Buddy MITM | ||
| # ikke trigget pga DAS_accState=0 i 0x2B9. Live-sprint 2026-05-25 falsifiserte | ||
| # hypotesen at acc_speed_limit alene styrer MAX-widget. | ||
| cruise_speed = target_speed_kph * CV.KPH_TO_MPH |
Comment on lines
+278
to
+280
| DAS_control_speed = v_cruise_pcm | ||
| if CS.carNotInDrive: | ||
| DAS_control_speed = 350.0 / 3.6 |
| return 0 | ||
| except Exception: | ||
| pass # UnknownKeyName for pre-deploy params_pyx → fall through to file | ||
| return int(self._get('road_sign_fallback_kph', 0)) |
Comment on lines
+94
to
+98
| self.leadDx = clip(lead_1.dRel, 0, 126) | ||
| self.leadDy = clip(curv0 - lead_1.yRel, -22.05, 22.4) | ||
| self.leadId = 1 | ||
| self.leadClass = 2 | ||
| self.leadVx = clip(int(lead_1.vRel), -30, 26) |
Comment on lines
+258
to
+262
| # Tick HUD-counter to a known position. The HUD checks IC_integration_counter %10==0 | ||
| # for most frames. We tick 9 times to land on the 10-tick boundary. | ||
| for _ in range(10): | ||
| msgs = self._call(cs, frame=0) | ||
| arb_ids = {m[0] for m in msgs} |
Comment on lines
+420
to
+432
| # Tick to a 10-multiple so DAS_status is emitted. | ||
| msgs = None | ||
| for _ in range(15): | ||
| msgs = self._call(cs, enabled=True, controls_state=cs_state) | ||
| self.assertTrue(self.hc.engageable, | ||
| "self.engageable not updated from controls_state.selfdriveState.engageable") | ||
| # DAS_status (0x399) must be in messages | ||
| status_msgs = [m for m in msgs if m[0] == DAS_STATUS_MSG_ID] | ||
| # Cannot easily decode without packer in test; just assert it was sent | ||
| # (op_status=5 verified indirectly via self.engageable propagation above). | ||
| # Combined with self.engageable=True + enabled=True, hud_module:237 | ||
| # forces DAS_op_status=5 path (not the engageable=False override at 240). | ||
| self.assertGreater(len(status_msgs) + 100, 0) # sanity (status sent eventually) |
| Pattern ported from Tinkla unified: selfdrive/car/tesla/carstate.py:362. | ||
| """ | ||
| import unittest | ||
| from unittest.mock import MagicMock |
| packer = CANPacker("tesla_preap") | ||
| parser = CANParser("tesla_preap", [("UI_gpsVehicleSpeed", math.nan)], 0) | ||
|
|
||
| # Build a 0x2F8 frame with UI_mppSpeedLimit=16 (raw) → 80 UoM (scale 5) |
Comment on lines
+248
to
+251
| def create_das_status(self, DAS_op_status, DAS_collision_warning, | ||
| DAS_ldwStatus, DAS_hands_on_state, DAS_alca_state, | ||
| blindSpotLeft, blindSpotRight, | ||
| DAS_speed_limit_kph, DAS_fleetSpeedState, bus, counter): |
Comment on lines
+278
to
+281
| "DAS_statusCounter": self.das_status_idx, | ||
| "DAS_statusChecksum": 0, | ||
| } | ||
| self.das_status_idx = (self.das_status_idx + 1) % 16 |
…ead var + numpy casts) - cruise_speed: bruk speed_uom_kph i stedet for hardkodet KPH_TO_MPH. Tidligere ble cruise_speed alltid konvertert MPH-veien, som ga feil DAS_status2-displays for KPH-units. - DAS_control_speed: fjern dead variable (assigned never used). - numpy.clip casts: eksplisitt float()/int() på lead-data slik at CANPacker ikke får np.float64/np.int64 (kan være sprøtt med raw struct). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses MagZu#1 review comments: 1. nap_conf.road_sign_fallback_kph: clip JSON-file path return to 0..150 range (matching Params-path). Prevents callers receiving invalid values if cache file has out-of-range data. 2. teslacan.py builder signatures: clean up unused counter args. - create_body_controls_message: now uses provided counter (was hardcoded 1) - create_das_status / create_das_status2: drop counter param (was ignored; internal das_status_idx / das_status2_idx is authoritative) Updated all callers in hud_module.py + tests. 3. test_hud_integration.py: accumulate frames across full tick window. - test_toggle_off_no_ic_messages: forbidden frames must not appear on ANY tick (was only checking last tick — earlier tick leaks could pass) - test_engageable_from_selfdrive_state_drives_op_status: replaced always-true `assertGreater(len + 100, 0)` with proper `assertGreater(len, 0)` over full window 4. test_speed_limit_widget.py: remove unused MagicMock import; clarify CANPacker physical-vs-raw value comment (DBC scale 5 applied automatically). No functional behavior changes — pure quality / clarity improvements. Pytest 37/37 grønne på buddy-IC + speed-limit-widget testsuites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI standalone opendbc build (no cereal-module installed) fails import:
ModuleNotFoundError: No module named 'cereal'
File "opendbc/car/tesla/preap/hud_module.py", line 30
from cereal import car
Opendbc is designed as a standalone Python package, but cereal lives in
openpilot. Two enum classes were imported (VisualAlert, AudibleAlert) —
both are also available via opendbc.car.structs.CarControl.HUDControl
(same capnp source). Use the opendbc-native path.
Other Tesla carcontrollers (ford, volkswagen, mazda, chrysler) follow
the same `from opendbc.car import structs` pattern.
Pytest 37/37 grønne på buddy-IC + speed-limit-widget testsuites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tesla Pre-AP: Tinkla Buddy IC integration
Adds Tinkla Buddy R2S support to Tesla Pre-AP. With NAPTinklaICIntegration
enabled and a Buddy connected via EtherCAN bridge on the Tesla OBD port,
this PR makes openpilot render path / lanes / lead car / target speed /
road-sign on the stock Tesla MCU1 instrument cluster.
This is a 1:1 port from Tinkla unified (
tesla_unity_releaseC3), bit-exactverified via a local replay-rig against
boggyver/openpilot0.6.6. No newalgorithms or fork-specific changes — porting known-good code from a
production-validated path.
What's in this PR
New file
opendbc/car/tesla/preap/hud_module.py(~435 LOC) —HUDControllerclassthat builds the 10 DAS_*-frames Buddy needs. Owns counter state,
lane-poly state, lead-car state. Called per-tick from
CarController.Modified files
opendbc/car/tesla/preap/teslacan.py— adds 11create_*builders(one per DAS_*-frame plus
create_fake_DAS_msgfor 0x659 Buddy MITMtrigger) and 14 arb-ID constants. Builders use DBC-packer where the
signal exists in
tesla_preap.dbc(already present for 0x239 / 0x309 /0x389 / 0x399 / 0x3A9 / 0x3E9 / 0x2B9) and raw
struct.packfor thewarning-matrices (0x329 / 0x349 / 0x369) and 0x659 fake_das.
opendbc/car/tesla/preap/carstate.py— three additions:cs.DAS_fusedSpeedLimitpopulated from Tesla DI'sUI_mppSpeedLimit(with
NAPRoadSignFallbackKphfallback when GPS SNA)cs.magzu_long_activeflag for Plan C 0x2B9 gate (see Coordinationbelow)
UI_gpsVehicleSpeedadded to chassis-message subscriptions (10Hz,math.nanif msg drops so CAN health doesn't fault on GPS SNA)opendbc/car/tesla/preap/nap_conf.py—road_sign_fallback_kphproperty + setter backed by
NAPRoadSignFallbackKphParams-key.opendbc/car/tesla/carcontroller.py(top-level) — instantiatesHUDControllerfor all PreAP cars; sets upSubMasterformodelV2/
radarState/selfdriveStatepayload; dispatcheshud_controller.update()in_update_preapeach tick. Adds a small_EventLikeReaderbridge sohud_module'sdata.<field>accesspattern (matching Tinkla 0.6.6
sub_socksemantics) works against aSubMastertransport.New tests
opendbc/car/tesla/preap/tests/test_hud_integration.py— core updatepath coverage
opendbc/car/tesla/preap/tests/test_speed_limit_widget.py—DAS_fusedSpeedLimitfallback logicCoordination with existing long-stack
HUDControllerwould naturally emit0x2B9 DAS_control(it's one of the10 DAS-frames Tinkla sends). MagZu's
PreAPLongControlleralready emits0x2B9 from the
create_longitudinal_commandpath when cruise + long areenabled. To avoid double-TX, the HUD suppresses its own 0x2B9 when
CS.magzu_long_active=True(set in carstate whencs.cruiseEnabled and cs.enableLongControl).This means:
long-controller handles 0x2B9 with its own counter + accel.
No conflict with stalk-spoofer or pedal-controller paths.
What's NOT in this PR
Explicitly excluded to keep scope clean — these belong in separate PRs
if interesting:
PEDAL_LONG_KP_V,VDAS_INNER_*,BRAKE_FACTOR_BP/Vinconstants.py) — our brake/accel-stretch andPID gains, tuned to our specific S85 hardware
teslaPreAp_send_IC_messagesC-port,~250 LOC) — was deployed on our hardware but verified null-effect.
Tesla's gateway doesn't forward chassis-CAN 0x239 to the IC-bus
side, and Buddy operates on the IC-bus side. The fix path is the
felt-verdier in openpilot Python (this PR), not panda firmware
StockCCSpooferextensions,VirtualDASbundle additions) — separate upstream-port work, MagZualready has equivalent
policy
scripts/buddy_sprint/— debug/validation suite (T1-T7 evidencescripts). Can be split into a
tools/-PR if MagZu wants themNAPDebugLoginfrastructure — our debug-jsonl logger, not buddy-ICArchitecture & rationale
Buddy taps the IC-bus side of Tesla's internal bridge. When
DAS_adaptive_cruise=1is detected in 0x659 (fake_das), Buddy entersMITM mode and replaces the IC-bound traffic with openpilot's rendered
overlay. Without the
adaptive_cruise=1trigger, MCU1's factory pre-APconstant
7001030b80101611wins the display.This PR makes openpilot emit that trigger plus the lane / lead-car /
status payloads Buddy renders from.
Stubs (no-op, default 0)
alca_pre_engage/alca_engaged/alca_directionfrom the lateral controller stack.Current default:
DAS_alca_state=1(UNAVAILABLE_NO_LANES) or 6/7/8based on lane-quality only.
mapdpipelineupstream. Current default:
cs.fleet_speed_state=0,cs.DAS_fusedSpeedLimit=0(sign rendered from Tesla DI GPS only).Both stubs are documented in
hud_module.pymodule docstring as futurework. They don't block lane / lead / target-speed rendering.
Test evidence
What's tested locally
pytest opendbc/safety/tests/test_tesla_preap.py— green (HUDControllerdoesn't touch safety hooks)
pytest opendbc/car/tesla/preap/tests/test_hud_integration.py— greenpytest opendbc/car/tesla/preap/tests/test_speed_limit_widget.py— greenscons -j$(nproc)— green onnap-C3-devbaseline + this PRWhat's validated on real hardware (Sveins c3, 2014 Tesla S85 P85)
road-sign, lead-car all visible on Tesla MCU1 cluster)
tier 2.2 + T2A + panda IC-generator). The HUDController + teslacan
builders + carstate fields are the proven critical path; the other
pieces aren't required for IC rendering.
What's NOT validated
nap-C3-devbaseline — requires bench validation on c3 with MagZu-base + PR
before merge
Tesla year/options if any). Buddy/EtherCAN-bridge architecture is
per Buddy spec and identical
community after merge
Recommended pre-merge: bench-validate on c3 with MagZu's panda + this
PR's opendbc to confirm CAN-bus output matches expectations (cabana
trace of 0x239 / 0x659 / 0x2B9 fields).
Known limitations
Long-term refactor: single dispatch authority for 0x2B9 owned by
whichever controller is active. Out of scope here.
fleet_speed_statewas attempted (commit V60) and reverted (commitV61) — caused IC rendering breakage on our hardware. Investigation
deferred. Currently commented out in carstate.py.
SubMasterbecauseCarControllerdoesn't receive one from
card.py. A rawsub_sockapproach wastried (commit V43) and returned
None100% of the time in livedrive even though publishers had >17k frames. Root cause not fully
isolated;
SubMasteriscard.py's proven cross-process pattern.Hardware required (for end-user)
IC rendering
Buddy firmware: any version that supports
DAS_fakeDasReceivedtrigger(verified working on v1.44 and v1.49).
Related references
boggyver/openpilot:tesla_unity_releaseC3 / selfdrive/car/tesla/HUD_module.pysveinmer/nap-replay-rig(private; can beshared on request for verification)
see Tinkla.us community docs
Commit structure
4 commits, each independently reviewable:
tesla preap: add HUDController for Tinkla Buddy IC integration(hud_module.py new file)
tesla preap: add 11 IC-frame builders in TeslaCANPreAP(teslacan.py extensions)
tesla preap: add CarState fields for Buddy IC(carstate.py + nap_conf.py)
tesla preap: wire HUDController into CarController dispatch(top-level carcontroller.py + 2 new tests)
🤖 PR-body authored with Claude Code