Compare commits

...

674 Commits

Author SHA1 Message Date
discountchubbs cf96d4edbd Sync: commaai/openpilot:master -> sunnypilot/sunnypilot:master
# Conflicts:
#	.gitmodules
#	.lfsconfig
#	opendbc_repo
#	openpilot/selfdrive/modeld/SConscript
#	openpilot/selfdrive/modeld/modeld.py
#	openpilot/selfdrive/ui/mici/layouts/settings/toggles.py
#	openpilot/system/sentry.py
#	panda
#	scripts/lint/lint.sh
#	tinygrad_repo
#	tools/release/build_stripped.sh
#	tools/release/check-submodules.sh
#	uv.lock
2026-09-16 22:41:24 -07:00
Harald Schäfer 6080cc6168 Use a precompiled eGPU driving model (#38930)
* Ship precompiled eGPU model and camera warps

Compile f78ed37d-afad-4dbc-8050-40ea885eedde/12864 through xx/ml_tools/openpilot_compile using the pinned tinygrad version.

* Precompile the existing master driving model

Use the unchanged master ONNX (SHA-256 6fee5937923c74848df4a63f6239eb6331c6274dd4bdb7a5d6ec0388a8b543d5) instead of updating the trained model.

* Compile camera warps on device

* Remove obsolete ONNX chunking and big model build check

* Chunk model artifacts only during release packaging

* Require model and camera warps for Chestnut readiness

* Recompile precompiled CPU helpers for the runtime host

* Ship the eGPU model with an ARM submission helper

* Exempt model pickles from the build product size limit
2026-09-16 08:13:45 -07:00
Harald Schäfer 81ae1a2e2d Use tinygrad generic ONNX compiler artifacts (#38926)
* Use tinygrad generic ONNX compiler artifacts

* Keep compiler temporary model buffers off device tmpfs

* Keep recurrent model state updates on the GPU

* Use compiled output buffers and swap recurrent state inputs

* Note when ONNX chunk reassembly can be removed

* Bump tinygrad to rebased compilers and AMD queue fix

* Keep recurrent buffers fixed and use faster USB argument updates

* Bump tinygrad for faster model startup
2026-09-15 18:01:25 -07:00
Daniel Koepping f15544b482 rm pyserial from updater bundle (#38925)
rm pyserial dependency from updater package
2026-09-15 13:20:49 -07:00
Harald Schäfer b751b04cda Use tinygrad ONNX and warp compilers for driving and DM (#38922)
* modeld: use tinygrad ONNX and warp compilers for driving and DM

* Bump tinygrad with current openpilot model compile tests

* modeld: prioritize tinygrad compiler imports in build environment

* Use compiler branch based on openpilot's pinned tinygrad

* modeld: invoke tinygrad compilers without firmware patch
2026-09-15 11:21:47 -07:00
Harald Schäfer fa9f56ed76 Compile warp and onnx separately (#38864)
* Extract shared model warp routines

* Move DM warp compilation into compile_warp

* Configure shared warp compilation through SCons flags

* Move the driving warp wrapper into compile_warp

* Build driving warps separately from the model

* Compile model inputs directly from ONNX

* modeld: simplify split input setup

* modeld: extract input packing setup

* modeld: simplify packed views and share firmware setup
2026-09-14 23:07:26 -07:00
Harald Schäfer 64f9b47b6e modeld: move history queues into ONNX (#38916)
* modeld: move history queues into ONNX

* Move ONNX history export into xx exporter

* Rename ONNX image input to new_img

* Combine ONNX model history state

* Revert model history README addition

* Keep float32 input packing from master

* Rename model input specs to input shapes
2026-09-14 20:44:33 -07:00
Harald Schäfer 7b469af438 ci: run docs builds on Namespace (#38921) 2026-09-14 20:21:16 -07:00
Harald Schäfer 2c88d1ed64 Revert "bump msgq (#38836)" (#38920)
This reverts commit 1e0914051f.
2026-09-14 20:20:53 -07:00
stef ffef0e6d3b ui: custom alert icons (#38917)
* custom alerts and alert pill better

* add comment back

* fix
2026-09-14 20:03:25 -07:00
stef e6aa405e08 ui: fix touch validity on alerts menu (#38915)
fix touch validity on alerts menu
2026-09-14 17:14:38 -07:00
Matt Purnell 2689917757 locationd: fix critical_services spelling (#38912) 2026-09-14 10:43:43 -07:00
Amy Jeanes a5f44653d7 mici: add a refresh models button to the models panel (#2018)
models: add a refresh models button to mici, gate and show progress on both panels

Adds the refresh-models tile to the mici models panel (it was missing there),
factoring the sync-key trigger + in-progress check into refresh_model_list()
and refresh_in_progress() shared with the big UI.

On both UIs the refresh button is now gated on offroad + not-downloading +
not-refreshing (the manager runs offroad-only and its per-tick manifest fetch
sits above a blocking download loop, so a refresh queued in either state would
stick), and shows progress while the manager refetches: mici shows "fetching..."
on the tile, the big UI flips its button from REFRESH to "FETCHING..." to match
its FETCHING.../SELECT/CLEAR label style (as in the OSM panel), replacing the
old fire-and-forget popup.


Claude-Session: https://claude.ai/code/session_01EGMnVnSk5inGTrd7kuDVG9

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2026-09-14 08:33:48 -07:00
Trey Moen 5f50bda7ba cabana: improve signal panel layout (#38910)
* cabana: improve signal panel layout

* cabana: match row actions to selection colors

* cabana: preserve master remove action

* cabana: match remove icon to selected row
2026-09-13 17:06:52 -07:00
Trey Moen 5b34f151de cabana: balance signal action spacing (#38911) 2026-09-13 17:01:04 -07:00
Trey Moen 64e5d1e5e3 cabana: preserve selected menu color on hover (#38909)
* cabana: unify menu highlight colors

* cabana: preserve selected menu color on hover
2026-09-13 16:54:34 -07:00
Trey Moen d3106c2b4b cabana: unify video and live stream titles (#38908)
* cabana: unify video and live stream titles

* cabana: put stream before dbc in window title

* cabana: show fingerprint in route details
2026-09-13 16:43:53 -07:00
Trey Moen 885792de3f cabana: remove message table outer border (#38907) 2026-09-13 16:36:16 -07:00
Trey Moen 5319474ee5 cabana: disable tool window collapse (#38906) 2026-09-13 16:31:19 -07:00
Trey Moen 164a3a1e30 cabana: stop chart tooltips over menus (#38905)
* cabana: keep chart tooltips below menus

* cabana: exclude popup menus from plot hover
2026-09-13 16:27:40 -07:00
Trey Moen c715ef4762 cabana: flatten signal panel containers (#38904)
* cabana: flatten signal panel containers

* cabana: keep messages and logs switch border
2026-09-13 16:15:25 -07:00
Trey Moen 2ae3598b51 cabana: prevent signal panel collapse (#38903)
* cabana: keep signal scrolling inside the list

* cabana: reserve space for the signal panel
2026-09-13 15:56:21 -07:00
Trey Moen 4e60afe70d cabana: show persistent fps counter (#38898)
* cabana: show persistent UI FPS

* cabana: render whole fps in monospace

* cabana: keep fps aligned beside centered progress

* cabana: move fps to top right

* Revert "cabana: move fps to top right"

This reverts commit 51a5334aec79807f8102b9a6f1f902f22814d5c7.
2026-09-13 15:48:35 -07:00
Trey Moen 5c365d3398 cabana: remove inner video container (#38901) 2026-09-13 15:46:29 -07:00
Trey Moen e6f9b3c6c4 cabana: preserve menu bar border on highlight (#38902)
* cabana: preserve menu bar border on highlight

* cabana: remove border drawing comment
2026-09-13 15:46:14 -07:00
Trey Moen 94f71fce00 cabana: add browser sign-in for remote routes (#38894)
* cabana: add browser sign-in for remote routes

* cabana: use shared dialog styling for browser sign-in

* cabana: support spacious centered action buttons

* cabana: vertically center sign-in states

* cabana: keep sign-in layout stable between states

* cabana: center sign-in error text

* cabana: avoid hint flash when retrying sign-in
2026-09-13 15:23:22 -07:00
Trey Moen b6918cb31b cabana: fix download bar layout (#38900)
* cabana: fix download bar layout

* cabana: reduce download label font size
2026-09-13 15:22:13 -07:00
Trey Moen 11093e743e cabana: improve dark slider track contrast (#38899) 2026-09-13 14:44:13 -07:00
Trey Moen ebb202f29d tools: share cancellable browser sign-in (#38893)
* tools: share cancellable browser sign-in

* tools: invoke auth JSON mode directly from Cabana

* tools: require an explicit Python module

* tools: order module constants alphabetically

* tools: use shared unauthorized handling for devices
2026-09-13 14:35:46 -07:00
Trey Moen 9c054e285d replay: retry failed segment loads (#38897)
* replay: retry failed segment loads

* replay: rename test helper for spellcheck

* replay: include portable temporary directory declaration

* replay: name attempt limit and shorten test delays

* replay: drop timing-dependent retry tests
2026-09-13 14:19:59 -07:00
Trey Moen c5cf29cf5e cabana: contain tooltips and capture plot drags (#38896)
* cabana: contain chart tooltips within columns

* cabana: refresh tooltip bounds after chart resize

* cabana: capture plot drags to prevent window movement
2026-09-13 13:53:35 -07:00
Amy Jeanes 63a2a3868e models: don't freeze the ui on an unset LagdToggleDelay (#2026)
models: don't block the ui on an unset LagdToggleDelay

Params.get's second positional is `block`, not a fallback value, so
get("LagdToggleDelay", "0.2") passes block=True and does a blocking read.
When the param is unset this spins the ui thread until it appears, freezing
the models panel (the description is rebuilt every frame). Read it the same
way livedelay/lagd_toggle.py does.


Claude-Session: https://claude.ai/code/session_01EGMnVnSk5inGTrd7kuDVG9

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2026-09-13 13:31:13 -07:00
Matt Purnell 5484f7f4a7 modeld: set the valid flag on modelDataV2SP (#2017)
* modeld: set the valid flag on modelDataV2SP

modelDataV2SP was published with new_message's default valid=False, so
the message was permanently invalid. Nothing acts on that today because
selfdrived lists it under ignore_valid, but it shows up as invalid in
every commIssue dump and hides any real problem behind a false one.

Copy modelV2's flag, the same way fill_model_msg already does for
drivingModelData.

Assisted-by: Claude
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* modeld_v2: set the valid flag on modelDataV2SP

The sunnypilot model runner publishes the same message and had the same
gap. Copy modelV2's flag here too, so both daemons agree.

Assisted-by: Claude
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2026-09-13 12:49:59 -07:00
dzid26 347238b307 camera offset: use real horizon for the shear center (#2016) 2026-09-13 12:38:20 -07:00
Amy Jeanes c57f9a7f4e workflows: let forks run their own model builds (#2009)
build-single-tinygrad-model and build-all-tinygrad-models gain a docs_repo
input (default sunnypilot/sunnypilot-models) so a fork can run either against
its own gh-pages catalog, next to the existing hf_repo input for the dataset.

build-all's setup job now checks out the repo and branch it was dispatched
from instead of sunnypilot/sunnypilot's default branch. That keeps the
manifest's tinygrad_ref tied to the code that compiled the models, and lets a
fork run the full rebuild against its own dataset and docs repo.


Claude-Session: https://claude.ai/code/session_01EGMnVnSk5inGTrd7kuDVG9

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2026-09-13 10:31:27 -07:00
Amy Jeanes 3c24eeea25 ci: remove stale disabled workflows (#1994)
Both are disabled in the Actions tab and have not run in months:

- Release Drafter (release-drafter.yml) and its config
  .github/release-drafter.yml: last run 2025-12-18
- Debug Discourse Posting (test-discourse.yaml.yml): one-off debug
  workflow from #1435, last run 2025-10-28

The post-to-discourse composite action is kept; the prebuilt workflow
still uses it. docs, stale and jenkins scan are also disabled here but
are inherited from commaai/openpilot and left in place to avoid
modify/delete conflicts on every upstream sync.


Claude-Session: https://claude.ai/code/session_01WAnUCRcA7Hp68qxyL11Wvm

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2026-09-13 10:16:44 -07:00
Trey Moen 45a7747ca3 cabana: fix detached panel menus (#38890)
* cabana: keep dropdowns above detached panels and within the screen

* cabana: shorten dropdown comments
2026-09-13 09:46:43 -07:00
Harald Schäfer 064a51fe59 model replay: use a mici segment (#38878)
* model replay: use a mici segment

* process replay: wait for vision listener startup
2026-09-13 09:38:23 -07:00
Trey Moen e5a6a8b73e cabana: make the Signals pane a native dock panel, fix pad (#38889)
* cabana: align center tabs and keep signal panel docked

* cabana: prevent panels from docking into the tabless center

* cabana: use native docking for the Signals panel

* cabana: migrate dock tabs when loading the layout
2026-09-13 09:19:48 -07:00
Amy Jeanes 7430f245c7 mici: add a clear cache button to the models panel (#2008)
models: add a clear cache button to mici, gate and show progress on both panels

Adds the clear-cache tile to the mici models panel (trash slide-to-confirm),
factoring the cache-size math into model_cache_size_mb() shared with the big UI.

On both UIs the clear button is now gated on offroad + not-downloading +
not-clearing (the manager runs offroad-only, so a clear queued onroad would
never be serviced and would stick), and shows progress while the manager works:
mici shows "clearing..." on the tile, the big UI flips its button to
"CLEARING..." to match its FETCHING.../SELECT/CLEAR label style (as in the OSM
panel).


Claude-Session: https://claude.ai/code/session_01EGMnVnSk5inGTrd7kuDVG9

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-13 08:12:56 -07:00
Daniel Koepping d38264c42c log chestnut telemetry in hardwared (#38866)
log chestnut hardware state independently of modeld
2026-09-12 23:57:49 -07:00
Trey Moen 0d4a4ab910 cabana: disable collapse for dockable panels (#38885) 2026-09-12 23:48:57 -07:00
Trey Moen 44914a7cf4 cabana: remove custom focus-loss handling (#38884)
* cabana: fix focus handling across docked and floating windows

* cabana: remove custom focus-loss handling
2026-09-12 22:45:32 -07:00
Trey Moen 4c0799eb8e cabana: make Charts a native dockable panel (#38883)
* cabana: make charts a native dockable panel

* cabana: remove redundant charts docking button
2026-09-12 21:40:31 -07:00
Trey Moen 1aa97338c4 Cabana: standardize floating dropdown menus (#38874)
* Cabana: standardize floating dropdown menus

* Cabana: use uniform items in menus and combo lists

* Cabana: remove dropdown test and explanatory comments
2026-09-12 20:56:08 -07:00
Trey Moen 20064d9681 cabana: show placeholder for unresolved timestamps (#38870) 2026-09-12 20:46:51 -07:00
Trey Moen dc338c2831 cabana: standardize button spacing (#38873)
* cabana: standardize button spacing and control alignment

* cabana: center shared step controls and inset chart content

* cabana: use uniform per-chart content insets
2026-09-12 20:37:01 -07:00
Daniel Koepping 298e51010c print jenkins crash log (#38880) 2026-09-12 19:48:31 -07:00
Trey Moen 7e84a61668 cabana: use ImGui decorations for floating panes (#38871)
* cabana: redraw floating panes during native resizing

* cabana: keep rendering while native resize is held

* cabana: use ImGui decorations to avoid native resize loops

* cabana: draw docking previews only on the target viewport
2026-09-12 17:45:14 -07:00
Trey Moen f147094cb7 cabana: restore Qt palettes and contrast (#38875)
* cabana: restore Qt dark palette and contrast

* cabana: restore Qt standard light palette and selection text

* cabana: drive component colors from shared style values

* cabana: remove historical palette comments
2026-09-12 17:44:43 -07:00
Adeeb Shihadeh f235784042 Revert tinygrad update (#38879) 2026-09-12 17:26:45 -07:00
Harsh Singh 39146cb240 test_runner: collect parameterized_class tests instead of skipping them with their base (#38820) 2026-09-12 14:30:51 -07:00
commaci-public 19f0f69d85 [bot] Update Python packages (#38698)
* Update Python packages

* Fix pandad exports

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-09-12 14:28:19 -07:00
Adeeb Shihadeh 64b4dbf115 add shellcheck-like static analysis (#38877) 2026-09-12 14:14:39 -07:00
Kacper Rączy ad5c0b946d tools: optimize route parsing (#38714) 2026-09-12 14:13:08 -07:00
David 5cad99bf7a fix(tools): allow spaces in workspace path for op.sh (#38839) 2026-09-12 14:06:20 -07:00
Adeeb Shihadeh f1ba169fb5 remove old profiler setups 2026-09-12 13:45:51 -07:00
Daniel Koepping dd226ac791 update chestnut fw in CI (#38876)
Update Chestnut firmware before CI build
2026-09-12 12:41:14 -07:00
James Vecellio-Grant b67898fac4 models: test tinygrad concurrency (#2006) 2026-09-11 20:24:12 -07:00
Adeeb Shihadeh 0cf294d85f ui: add descriptions for settings (#38867) 2026-09-11 20:13:00 -07:00
Daniel Koepping 6965492e66 clean up after jenkins disconnect (#38865)
* fix SSH build cleanup

* simplify SSH cleanup
2026-09-11 17:25:33 -07:00
Bhavya Gada 73720bfd08 Get MetaDrive simulator working on macOS (#38830) 2026-09-11 15:29:13 -07:00
Harald Schäfer 6107da1cd6 Cinque v2 (#38850)
1a421175-db71-4e3d-9d62-e2166421b02b/12864
2026-09-10 21:24:09 -07:00
Daniel Koepping 6c410eee37 validate compiled model (#38855)
* validate serialized models after writing
2026-09-10 21:06:22 -07:00
Daniel Koepping fc4af50824 enable alpha long on nightly-chestnut (#38854)
panda debug build
2026-09-10 20:57:57 -07:00
Daniel Koepping f87e170bee reject incomplete model buffers (#38852) 2026-09-10 18:17:40 -07:00
Daniel Koepping 2a70b40cdc rebuild models when helpers change (#38853)
rebuild models when serialization helpers change
2026-09-10 18:14:23 -07:00
Daniel Koepping e6f401b5cf build nightly-chestnut (#38849)
build nightly-chestnut
2026-09-10 17:38:04 -07:00
Daniel Koepping e9711bc51d longer SSH keepalive for chestnut model load (#38851)
ci: allow longer SSH keepalive gaps
2026-09-10 17:26:33 -07:00
Harald Schäfer 9b899fdaa2 Reapply Git LFS hosting on Hugging Face (#38841) 2026-09-10 16:47:40 -07:00
Daniel Koepping 511c1f1e4e chestnut updater: close files explicitly (#38846)
chestnut: close USB sysfs files after reading
2026-09-10 14:28:12 -07:00
Daniel Koepping 443967ddad enable caching in chestnut CI (#38845)
enable caching
2026-09-10 14:17:21 -07:00
Daniel Koepping 633117b2a9 test_onroad in chestnut CI (#38844)
test chestnut with live camera frames
2026-09-10 14:02:18 -07:00
Daniel Koepping f174e39d7a add powertest for mici (#38843) 2026-09-10 13:19:44 -07:00
Zeph 68d829c7c9 selfdrived: no localizer alerts from capnp defaults (#38840)
selfdrived: don't alert on a message that was never received

posenetInvalid, locationdTemporaryError and paramsdTemporaryError are raised
off the capnp defaults of deviceMotion and vehicleParameters before either
message has ever arrived. When modeld is down, locationd and paramsd never
publish, the defaults read as a nan posenet speed, and that NoEntryAlert can
hide the process not running alert that names modeld.

Same guard as the big model alert in #38500.
2026-09-10 12:14:54 -07:00
Daniel Koepping 0cd3f0f20b remove old manifest before rebuild (#38842)
invalidate manifest before rebuilt
2026-09-10 11:18:54 -07:00
Trey Moen b97a2d8201 eSIM: require internet to delete a profile (#38838)
* Guard eSIM profile deletion with internet connectivity check

* Simplify eSIM delete connectivity guard
2026-09-09 20:21:52 -07:00
Harald Schäfer d9ecb0b678 Revert "Move Git LFS hosting to Hugging Face" (#38837) 2026-09-09 18:14:47 -07:00
Harald Schäfer ff2f78c3e8 Move Git LFS hosting to Hugging Face (#38824)
* Move LFS asset transfers to xet-core

* Limit migration to Git LFS hosting on Hugging Face

---------

Co-authored-by: Harald Schäfer <6804392+haraschax@users.noreply.github.com>
2026-09-09 14:31:06 -07:00
Adeeb Shihadeh 1e0914051f bump msgq (#38836) 2026-09-09 09:57:22 -07:00
Amy Jeanes 20bddff857 Use LF line endings
Split from https://github.com/commaai/openpilot/pull/38810.
2026-09-09 09:40:15 -07:00
Adeeb Shihadeh 14617070ab lil more comment 2026-09-09 09:33:57 -07:00
Trey Moen 802a231bd2 mici: simplify eSIM connecting status (#38833) 2026-09-08 23:34:37 -07:00
Trey Moen 179718dcb0 esim: mici profile download UI (#38813)
* esim: MICI profile download UI

* Show temporary feedback for non-LPA QR codes

* Scan eSIM QR codes every 250 ms

* Shrink eSIM error text and log displayed errors

* Activate downloaded eSIM through the profile tap flow

* Let the eSIM connectivity error wrap naturally

* Release add-profile button feedback immediately

* Check internet before opening the eSIM QR scanner

* Use UI network state and standard connectivity dialog for eSIM

* Drop LPA prime settings change from eSIM download UI

* Scope eSIM UI constants to their owning classes

* Remove unused ot codespell exception

* esim: simplify profile download operation and UI state

* esim: share activation code validation with QR scanner

* esim: match QR scanner video to cabin preview

* esim: show errors in a scrollable text dialog

* esim: require nonblank profile nicknames
2026-09-08 23:16:17 -07:00
Trey Moen d70df67366 mici: process eSIM notifications after profile deletion (#38832) 2026-09-08 21:39:24 -07:00
Trey Moen 66b4dbe2a1 qrcode: add QR decoder (#38814)
* qrcode: add QR decoder

Decodes a QR code from a grayscale image or a module matrix: adaptive
binarization, finder pattern search by run ratios, perspective sampling
with alignment pattern refinement, format info, unmasking, block
de-interleaving, Reed-Solomon correction, and numeric, alphanumeric,
byte (with ECI), and Kanji segments.

Fixtures are packed matrices from python-qrcode 8.2 covering every
version and level, plus Segno 1.6.6 matrices for the ECI cases.

* qrcode: tune decoder for the device

Measured on a comma mici with real cabin frames downsampled to 672x380,
the size the eSIM screen scans at. Per frame: 30 ms -> 8 ms with no
code in view, 42 ms -> 13 ms with a code.

- binarize: fixed two-radius fill from one integral image instead of an
  iterative outward fill (a dark cabin is almost all flat tiles), tile
  stats on a contiguous layout, flat tiles decided whole so sensor noise
  cannot become speckle, uint8 threshold compare
- finder search: scan every 4th row, build column runs only at
  candidate columns
- alignment search starts at a 2-module radius
- Reed-Solomon syndromes through table lookups, placement order cached

* qrcode: simplify decoder

Fold the function-module mask into _data_coords and the format
coordinates into _read_format, evaluate masks directly on the data
coordinates, compact the ECI, numeric, and alphanumeric parsing, and
flatten the retry loop in decode. No behavior change.
2026-09-09 04:23:55 +00:00
Trey Moen ef769c173d qrcode: generalize EC, format, and GF tables (#38828)
Replace the level-L-only Reed-Solomon parameters, the single hardcoded
format word, and the bitwise GF(256) multiply with the full EC table,
the 32 format words, and log/antilog tables. Block splitting and
interleaving move into _block_lengths and _interleaved. This is the
structure a decoder needs to read any version, level, and mask; the
encoder's module matrices are unchanged and test_encoder pins them.
2026-09-08 19:15:39 -07:00
Trey Moen a4a24d5d87 qrcode: fix alignment pattern positions for version 32 (#38827)
The closed-form step between alignment patterns rounds the wrong way
for version 32 (28 instead of 26). Use the form that is right for every
version. The encoder only emits versions 1-20, so its output is
unchanged.
2026-09-08 19:05:18 -07:00
Trey Moen 1fb3edb7dd qrcode: test alignment pattern positions (#38829) 2026-09-08 18:53:33 -07:00
Daniel Koepping dd4615850c replay chestnut in CI (#38826)
run model replay on chestnut in CI
2026-09-08 18:05:03 -07:00
Daniel Koepping f23e65768c ui: resize status icons (#38749)
* ui: resize status icons

* Update orange GPU icon to 108px height

* Trigger CI after LFS upload

* ci
2026-09-08 17:06:12 -07:00
Adeeb Shihadeh 96be8c7eb5 check dependency footprint (#38825) 2026-09-08 16:46:19 -07:00
Daniel Koepping 23d0474b95 compile big model in jenkins CI (#38822)
compile chestnut in ci
2026-09-08 15:37:12 -07:00
ZwX1616 60d8ae2d4f modeld/SConscript: fix chunk_targets estimate (#38821) 2026-09-08 15:27:11 -07:00
Trey Moen 7bfe6ba4d0 pandad: allow disabling driver camera IR offroad (#38812) 2026-09-08 15:21:03 -07:00
Pramish 9946c521f7 modeld: build DM warp and model JITs for all camera configs (#38767)
* modeld: build DM warp and model JITs for all camera configs

In #38684, camera_configs on comma_arm64 was restricted to only the host device
running SCons (selecting either _os_fisheye or _ar_ox_fisheye).

However, prebuilt release and nightly bundles are built on TICI/tizi runners
in Jenkins and distributed to both Comma 3/3X (TICI) and Comma 4 (MICI) devices.
Because the build machine was TICI, dm_warp_1344x760_tinygrad.pkl was omitted
from the packaged bundle, causing dmonitoringmodeld to fail with FileNotFoundError
on Comma 4 and leaving driver-monitoring calibration stuck at 0%.

Restore CAMERA_CONFIGS so both camera resolutions (1928x1208 and 1344x760)
are built for all platforms.

Fixes #38762

* simplify

---------

Co-authored-by: ZwX1616 <zwx1616@gmail.com>
2026-09-08 14:16:21 -07:00
Trey Moen 25d9d41c90 esim: comma four profile management UI (#37844)
* esim: MICI eSIM profile management UI

* esim: align rename button position regardless of delete button

* esim: skip profile UI when SIM is not an eUICC

Probe is_euicc() on the first profile poll and cache it; non-eUICC SIMs
avoid list_profiles/process_notifications (which hang on a plain SIM) and
the eSIM button shows ICCID/MCC-MNC metadata instead of opening the
profile management screen.

* esim: satisfy ruff E731 in action_pressed helper

* esim: get modem info from modem.py state instead of shelling out

* esim: simplify switch lifecycle, drop active flag and settle window

* esim: only show 'switching...' on the target profile

* esim: read cell strength directly from HARDWARE

* esim: hide checkmark and dim cell icon during switch; keep rename available

* esim: disable active profile button (rename still clickable as overlay)

* esim: stop rename/delete buttons and labels from flashing during operations

* esim: show 'comma prime' on network button for comma profile

* esim: move display_name and is_comma onto Profile dataclass

* esim: anchor rename button to rightmost slot to prevent shift

* esim: include iccid prefix check in Profile.is_comma

* esim: drop process_notifications from cellular manager

* esim: disable network button when SIM isn't an eUICC

* esim: rename ESim* classes to Esim*

* esim: sort imports

* esim: default to 'loading...' instead of 'no active profile'

* esim: rename PROFILE_POLL_INTERVAL to PROFILE_POLL_INTERVAL_S

* esim: slim EsimNetworkButton

* esim: use DEFAULT_TEXT_COLOR; load delete dialog texture in __init__

* esim: revert cell-icon index trick, name each NetworkStrength explicitly

* esim: tighten delete/rename spacing so 'switch' label fits on one line

* esim: shrink delete/rename buttons by 25%

* esim: keep rename button at full size, only delete shrinks

* Revert "disable modem.py for now"

This reverts commit 1eeba86ec1.

* Revert "modem.py is disabled"

This reverts commit d238a1ccc4.

* lpa: inline comma iccid prefix

* ui/cellular_manager: clear switching state when LPA returns

* ui/cellular_manager: lock callback queue, drop poll log noise

* esim: sort NetworkStrength/NetworkType imports

* esim: drop switching_iccid concept

* esim: show 'switching...' on the clicked profile button

* esim: optimistic switch, skip post-switch list_profiles to avoid flicker

* esim: only style profile button from local op flags, not global busy

* esim: remove deleting/switching state, collapse profile button branches

* esim: show GSM settings on full prime when non-comma profile is active

* esim: expose CellularManager.active_profile, dedup callers

* esim: apply comma prime defaults on switch (roaming, metered, no APN)

* esim: restore re_sort on show_event to avoid first-frame jank

* lpa: apply comma prime defaults inside TiciLPA.switch_profile

* lpa: lazy-import Params to break circular import

* lpa: treat +CME ERROR 13 (SIM failure) as non-eUICC in is_euicc

* esim: show 'obtaining IP...' on non-eUICC path while connecting

* Revert "lpa: treat +CME ERROR 13 (SIM failure) as non-eUICC in is_euicc"

This reverts commit 475ca448ea914ff8f9892b4cbf4525d961d7618b.

* esim: poll profiles every 5s

* lpa: tighten Profile.is_comma to require both Webbing provider and comma BIN

* lpa: fall back to '<unnamed>' instead of iccid prefix in display_name

* esim: re-probe is_euicc each poll for runtime SIM swaps

* esim_ui: rename EsimUIMici to EsimUI

* esim: simplify cellular manager and profile UI

- optimistic profile switch at click time so the active button no longer bounces back
- unify LPA worker threads, refresh_profiles resets the poll timer
- deterministic profile ordering on show
- reuse wifi ForgetButton for delete, drop DeleteButton
- construct CellularManager inside NetworkLayoutMici
- drop comma prime param defaults from LPA.switch_profile (moved to a separate PR)

* esim: drop eUICC state change log

* ui: gate cellular settings on prime subscription

* ui: disable eSIM management for full prime

* ui: keep eSIM management disabled for full prime

* ui: unify eSIM profile action buttons

* ui: tighten eSIM profile action spacing

* ui: place rename before delete in eSIM actions

* esim: restrict individual profiles and process switch notifications

* esim: defer poll after operations and confirm eUICC loss before clearing profiles

* ui: allow cellular settings for unregistered and unpaired devices

* ui: disable all profile switching for full prime

* ui: simplify cellular access to not full prime

* ui: make profile poll interval a CellularManager constant

* ui: read modem state on the profile poll cadence

* ui: scope eSIM profile colors to their class

* ui: inline modem state read in cellular polling

* ui: rely on hardware modem state fallback
2026-09-07 21:55:28 -07:00
Trey Moen 8f2d66d0e5 cabana: round only the outer video frame (#38808)
* cabana: remove video frame corner rounding

* cabana: round the outer video frame including letterboxing
2026-09-07 21:26:53 -07:00
Trey Moen 3eafb658bf mici: remove duplicate pairing button from main settings (#38809) 2026-09-07 17:48:33 -07:00
Trey Moen 10b9e73859 cabana: fill event bar to camera edges (#38807) 2026-09-07 17:06:01 -07:00
Trey Moen bb86bee688 cabana: clean up UI wording and formatting (#38803) 2026-09-07 14:56:19 -07:00
Trey Moen 3e54af9db8 cabana: playback controls for live streaming (#38802)
* Hide Cabana playback controls during live streaming

* cabana: retain pause and go-live controls for live streams
2026-09-07 14:47:20 -07:00
Trey Moen 20ae4ac44d cabana: standardize settings step buttons (#38801)
* cabana: align and brighten settings step buttons

* cabana: use standard settings icon button color
2026-09-07 14:08:32 -07:00
Trey Moen 7c1f224443 cabana: stack the undo/redo zoom buttons in the chart menu (#38800) 2026-09-07 13:03:12 -07:00
Trey Moen 2b4d050e9c cabana: fix video startup and desktop interactions (#38798)
* cabana: fix video startup and desktop interactions

* cabana: remove util comments
2026-09-07 12:43:41 -07:00
Trey Moen 1bb019521e cabana: fix chart ranges and control layout (#38797) 2026-09-07 12:42:41 -07:00
Trey Moen 624d5b9995 cabana: fix stream and replay lifetimes (#38796) 2026-09-07 12:33:14 -07:00
Trey Moen 0f9ec7158b cabana: validate message IDs and include recorded CAN messages (#38799) 2026-09-07 12:32:22 -07:00
Trey Moen 9c02eebb72 cabana: fix clipped control outlines and filter sizing (#38795)
* cabana: fix clipped control outlines and filter sizing

* cabana: remove added comments

* cabana: document control helper API usage
2026-09-07 12:05:26 -07:00
Trey Moen cb70ba89aa cabana: smooth startup layout (#38794)
* cabana: smooth startup layout

* cabana: remove camera tab startup comment

* cabana: initialize dock geometry on the first frame

* cabana: remove route loading placeholder
2026-09-07 11:50:13 -07:00
Trey Moen 65c411433b cabana: reduce downloader startup and dense chart rendering overhead (#38793)
* cabana: reduce downloader startup and dense chart rendering overhead

* cabana: remove downloader spawn tests
2026-09-07 01:22:23 -07:00
Adeeb Shihadeh 6bbdf8ad18 remove sentry (#38790) 2026-09-06 21:20:39 -07:00
Trey Moen 9d1f0d4171 cabana: restyle (#38789)
* cabana: connect palette theme, rounded cards, floating page switch

Move the jotpluggler port's connect palette into a shared theme module
(ui/theme.h/.cc) and apply it to all of cabana: Palette struct for dark
and light, applyTheme fills ImGui and ImPlot styles, readableCurveColor
and sectionTitle for reuse by the analysis workspace. Drop the old
DarkTheme constants and per-theme color branches in widgets.

Docked panels are borderless with content in rounded bordered cards;
custom-drawn rects use the style radii. The detail widget's bottom tab
bar is replaced by a floating Messages/Logs pill.

* cabana: scrollbar and menu contrast, page switch below the page

The scrollbar grab uses the border color on the window-colored track.
Selection colors are stronger so an open menu reads as blue in both
themes and the hovered item is visible in light mode. The Messages/Logs
pill sits in a strip below the page instead of covering its last rows.

* cabana: even spacing around the page switch, steady top menu highlight

* cabana: inner spacing before the clear button of a clearable input

* cabana: center the page switch below the page, space the signal row buttons

* cabana: logs table cells padded and bordered like the messages table

* cabana: recess the signals card in the window color

* cabana: one button vocabulary

Three button shapes, all one frame height with the style padding and
rounding: ImGui::Button for framed text, iconButton for a framed square
icon, toolButton for a flat square icon (or flat text). Adjacent buttons
are ItemInnerSpacing apart, groups ItemSpacing apart. Tool bars drop
their private padding and 1 px spacing, the signal row, chart header,
detail header, logs export and signal selector buttons all use the
shared sizes, and the page switch is built from the same style values.

* cabana: center the glyph ink in square icon buttons

* cabana: framed tool buttons and plain icon glyphs

toolButton is now iconButton with an optional text, so every button in
the app is framed. Boxed glyphs (plus-square, x-square, dash-square,
arrow-*-square, window-stack) are replaced by their plain variants
(plus-lg, trash, arrows-collapse, box-arrow-*, window-plus), the light
x/plus/dash by their -lg forms, the chart menu by three-dots-vertical
and the toolbar overflow by chevron-double-right.

* cabana: draw square button glyphs at 80% so full bleed icons keep a margin

* cabana: signal values in the mono font

* cabana: center square button icons on their atlas ink, snapped to framebuffer pixels

* cabana: draw square button glyph quads directly, AddText truncates the position

* cabana: right align the messages and signals tool bars to the table edge, full size collapse button

* cabana: detail header and tab scroll buttons flush with the right edge

* cabana: header and footer strips, gap above the panels, video splitter as a separator

The menu bar and the status bar are strips in the surface color with a
separator line towards the panels, and the dockspace sits one item
spacing below the menu bar, matching the spacing above the status bar.
The video/charts splitter draws the same 1 px separator line as the
dock splitters, centered in a gap as tall as the side padding, and a
double click snaps the video back to its natural height.

* cabana: top menus flush with the menu bar, 2 px video splitter without double click

* cabana: dropdowns continue the menu bar, splitter in the border color, row button gap

Top level menus draw no top border and square top corners, and the menu
bar leaves its separator out under the open dropdown. The video splitter
uses the border color like the dock splitters. The signal row buttons
keep one inner spacing before the scrollbar.

* cabana: dropdowns share the bar's 1 px border line, messages toolbar spacing matches the video card

* cabana: tool bar groups, framed dropdown buttons, chart content flush with the tool bar

Tool bar items are ItemSpacing apart and the items of a group (a button
pair, a separator and its neighbor) ItemInnerSpacing apart, instead of
a private 1 px spacing. The dropdown buttons are framed like the other
buttons. The video time is a plain mono readout that toggles on click.
Chart cards have no horizontal margin so their header and plot line up
with the tool bar above.

* cabana: chart list spacing: gap above the scroll area, charts clear of the scrollbar, even legend gaps

* cabana: loop and speed buttons grouped like the playback buttons

* cabana: survive narrow panels and small windows

Stress tested by dragging the column and video splitters to their
limits and shrinking the window to 640x480. Fixes: the messages tool
bar and the message detail header overflow into the >> menu instead of
clipping (the view button and the name stay); the binary view keeps a
minimum cell width and scrolls sideways; the Messages/Logs switch drops
to icons when the strip is too narrow; the camera keeps its last frame
across reconnects, so a video restored from a collapsed splitter while
paused is no longer black.

* cabana: range slider spans the charts >> menu, remove all and float as menu entries

* cabana: the charts card keeps its tool bar row or collapses, like the video at the other end

* cabana: uniform >> menus: dropdowns become submenus, the time readout an action entry

* cabana: video/charts splitter gap matches the column splitters

* cabana: drop unused lambda capture in chart type menu

Claude-Session: https://claude.ai/code/session_01XTFaJpf235LeHBmyFRgeJ1

* cabana: the menu bar and the status bar stay in full screen

* cabana: simplify theme helpers and toolbar actions

* cabana: preserve panel width for overflow edit dialog

* cabana: keep message editor usable in narrow panels

* cabana: group heatmap modes in a dropdown

* cabana: trim redundant restyle comments

* cabana: fix review findings from the restyle

- set the caret and other unset colors so the light theme has no white caret
- tolerate float error in the toolbar fit check to stop spurious overflow
- restore the full screen chrome hiding
- clear stale camera frames when the server changes, keep them across a collapse
- cache icon glyph ink bounds instead of scanning the atlas every frame
- drop dead plot_bg and ImPlot globals, add a badge palette role
- add toolbarMenu and use toolbarAction for the remove message item
- derive the signal view button size seed from the font and style

* cabana: remove remaining redundant comments
2026-09-06 19:46:02 -07:00
James Vecellio-Grant 6135084c94 modeld_v2: realize frames on npy -> amd (#1993) 2026-09-05 20:17:14 -07:00
YassineYousfi c51e3e5a71 cinque terre model 🇮🇹 (#38771)
a4c5f1d1-1f5d-4807-9593-54afa27099d5/12864
2026-09-05 19:03:12 -07:00
Trey Moen 3bc6701f34 cabana: fill video pane and reset video height (#38786) 2026-09-05 17:02:05 -07:00
Trey Moen cb0da74b41 cabana: center speed dropdown label, add divider after loop button (#38785) 2026-09-05 15:34:00 -07:00
Trey Moen a4f7c50d2a cabana: fix sparkline edges and stroke rendering (#38784)
cabana: preserve sparkline edges and improve stroke rendering
2026-09-05 14:54:17 -07:00
Trey Moen 444be0a649 cabana: pace frames instead of relying on vsync (#38783)
On Wayland a vsynced glfwSwapBuffers blocks until the compositor sends a
frame callback, which it stops doing while the window is on another
workspace or otherwise off-screen. The main thread never gets back to
glfwPollEvents, so Hyprland reports cabana as not responding.

Run with swap interval 0 and pace the loop to the monitor refresh rate.
2026-09-05 14:49:37 -07:00
Trey Moen a989bc0b50 cabana: misc UI tweaks (#38782) 2026-09-05 14:13:15 -07:00
Trey Moen f9dacd0d6b cabana: build binary directly, drop wrapper script (#38781) 2026-09-05 13:45:12 -07:00
James Vecellio-Grant 047ae41c0d modeld_v2: one dev warp and enqueue (#1990) 2026-09-04 21:15:00 -07:00
Trey Moen 0ec3a082c7 op: support all the linuxes! (#38776) 2026-09-04 21:10:04 -07:00
Trey Moen c8603fb3ce op: point git-lfs config at the vendored binary (#38775)
git-lfs is vendored in the venv, but `git lfs install` writes filters and hooks that invoke a bare `git-lfs`, which is only on PATH inside the venv. With filter.lfs.required set, any checkout from a plain shell fails.

git runs filters and hooks from the worktree root, so a relative path into the venv resolves from anywhere.
2026-09-04 21:09:40 -07:00
Nayan 7eb457f6c4 chaos (#1991)
burn it all
2026-09-05 10:50:12 +08:00
Trey Moen 69ad376fef Revert "op: support all the linuxes!" (#38774)
Revert "op: support all the linuxes! (#38773)"

This reverts commit 33790c855c.
2026-09-04 19:09:45 -07:00
Trey Moen 33790c855c op: support all the linuxes! (#38773) 2026-09-04 18:30:45 -07:00
James Vecellio-Grant 302f3ad892 ci: compile dm warp (#1989) 2026-09-04 16:52:09 -07:00
Adeeb Shihadeh 675ff56981 tools: add op docs command (#38770) 2026-09-04 15:20:43 -07:00
YassineYousfi e3def37695 revert 38742 (#38760) 2026-09-03 09:45:51 -07:00
Jason Wen 132b31f4cf ci: poll GH API in prepare model jobs (#1987) 2026-09-03 10:10:21 -04:00
James Vecellio-Grant 752c07f9e4 ci: Replace hf oath with token (#1986)
replace oauth with token
2026-09-03 08:22:43 -04:00
Trey Moen 3a13b67b6f bye bye Qt! (#38753)
* cabana: remove Qt implementation
2026-09-02 20:44:11 -07:00
Trey Moen 7bade9a67a cabana: halve dock panel min width (#38759) 2026-09-02 20:28:53 -07:00
Trey Moen c163743e32 cabana: better colors (#38758) 2026-09-02 20:20:23 -07:00
Jason Wen e87dbbaba7 models: sanitize default model name for HF (#1984) 2026-09-02 14:53:31 -04:00
Jason Wen 15efdb392f Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1983)
* ui: remove raygui usage (#38708)

* ui: remove raygui usage

* match previous gui_text_box line spacing

* Revert "match previous gui_text_box line spacing"

This reverts commit ffd2fe31725c6d50bffaebc621c1e170d0926c66.

* Reapply "match previous gui_text_box line spacing"

This reverts commit d41404f09607e225f43868f7747f22dc0bb2cf16.

* log chestnut supply fault (#38711)

* log chestnut INA supply fault

* ci

* bump raylib (#38712)

* cabana: replace custom non-view Qt signals w/ plain observer (#38713)

* cabana: move RoutesDialog out of streams/ (#38716)

* cabana: string helpers in utils return std::string (#38720)

* cabana: use std::string in RoutesDialog API results (#38717)

* cabana: move stream open widgets into streamselector (#38715)

* cabana: remove Qt from livestream (#38722)

* cabana: split SettingsDialog out of settings (#38719)

cabana: split SettingsDialog out of settings.{h,cc}

* cabana: split comma API route fetching out of RoutesDialog (#38721)

* cabana: de-QT streams (#38718)

* ui: fix install update button overflow (#38696)

* cabana: split utils/util into Qt-free util and qtutil (#38723)

* ui: guard branch switcher before internet connected (#38692)

* ui: check for update on target branch switch (#38693)

* ui: sync gpu loading to offroad (#38727)

ui: sync gpu loading state

* add chestnut offroad alerts (#38706)

* system: add chestnut offroad alerts

* system: refine chestnut offroad alerts

* system: refine chestnut power alerts

* system: confirm chestnut power recovery from PCIe

* system: detect missing chestnut power from INA voltage

* common: fix OpenpilotPrefix cleanup on macOS (#38728)

The destructor built its cleanup commands as "rm %s -rf", with the flags
after the operand. GNU rm permutes arguments so this works on device and
in CI, but BSD rm on macOS stops option parsing at the first operand and
treats "-rf" as a second filename:

  $ mkdir -p /tmp/rmtest/sub && rm /tmp/rmtest -rf
  rm: /tmp/rmtest: is a directory
  rm: -rf: No such file or directory
  exit=1

So nothing is removed, and each of the four calls prints two errors plus
"system command failed (256)" from check_system. Every run of a tool that
owns an OpenpilotPrefix (replay, cabana) leaks its params dir, its
comma_home and its /tmp/msgq_ dir; 33 of each had accumulated on my
machine.

Pass the flags first.

* replay: capture downloader's stderr so download progress is reported again (#38734)

* bump panda (new health packet) (#38736)

pandad: support compact health packet

* BMRLNAP (#38681)

* ui: clarify branch switcher error message (#38732)

* ui(mici): name updater signal constants (#38731)

* mici: name updater signal constants

* drop SIGNAL_ prefix

* self contained

---------

Co-authored-by: Shane Smiskol <shane@smiskol.com>

* modem.py: accept hex chars in ICCID (#38735)

E.118 specifies decimal digits, but many real SIMs carry hex characters
in EF_ICCID (e.g. China Mobile's 898600B5... range, some MVNO/IoT SIMs).
AT+QCCID returns them verbatim, and the strict isdigit() check blanked
the ICCID, leaving the modem daemon stuck in INITIALIZING forever and
cellular dead. ModemManager parses ICCID as hex for the same reason.

Verified on a comma four with a China Mobile SIM (EG916Q-GL): previously
stuck retrying 'identity read incomplete', now dials and passes traffic.

* TGC (#38739)

* 23e6a04e-e6e5-462b-a0bb-e4088275ee43/12864 tgc

* here

* monitor chestnut USB in hardwared (#38741)

hardwared: monitor chestnut USB independently

* modeld: wait for stable chestnut (#38742)

modeld: wait for stable chestnut

* Revert "monitor chestnut USB in hardwared (#38741)" (#38744)

This reverts commit 7d5596d5c3.

* amd warp (#38684)

* modeld: fuse warp and policy TinyJit

* bump tg

* fix?

* this simple trick...

* debug 1

* bump tg

* pack all

* wips

* fix

* BIG_INTO_SMALL remove

* slower

* ui: show usb connection (#38745)

* ui: show USB status

* ui: resize USB icon

* ui: classify USB device once

* ui: debounce USB disconnect

* cereal: log big model in drivingModelData (#38747)

* ui: show one GPU status (#38748)

ui: show one GPU status icon

* AGNOS 19.7 (#38750)

---------

Co-authored-by: Trey Moen <50057480+greatgitsby@users.noreply.github.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Robbe Derks <robbe.derks@gmail.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: XiaoXX <xiaoxx97@outlook.com>
Co-authored-by: YassineYousfi <yyousfi1@binghamton.edu>
2026-09-02 13:57:07 -04:00
Jason Wen f5bb855477 Merge commit '6249f4d5b0e63c05f08bce12ca3afebda9f764a3' into sync-20260902
# Conflicts:
#	openpilot/selfdrive/modeld/SConscript
#	openpilot/selfdrive/modeld/modeld.py
#	openpilot/selfdrive/pandad/pandad.cc
#	openpilot/selfdrive/selfdrived/alerts_offroad.json
#	openpilot/selfdrive/ui/layouts/onboarding.py
#	openpilot/selfdrive/ui/mici/layouts/home.py
#	openpilot/system/hardware/hardwared.py
#	panda
#	tinygrad_repo
2026-09-02 13:47:27 -04:00
Trey Moen 06d76bdb24 cabana: imgui port (#38725)
* cabana: imgui frontend base infra (ui/)

* cabana: line-for-line imgui ports of the Qt widgets and dialogs into ui/

* cabana ui: fixes from the side by side parity test

* cabana ui: deqt.md status after the line-for-line ports

* cabana ui: fixes from the line-for-line review

* cabana ui: parity fixes from the end to end matrix, event driven frame pacing

* cabana ui: preserve F1 help formatting

* cabana ui: parity fixes from the end to end matrix, event driven frame pacing

* cabana ui: deqt.md status

* cabana: fix macOS build of imgui frontend

- link Security.framework (libusb's darwin backend needs
  SecTaskCreateFromSelf/SecTaskCopyValueForEntitlement)
- drop unused loop counter in SignalView::updateChartState
- avoid -Wshadow on Sparkline::size and SignalView::highlight

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: parity round 3 fixes, thread pool and one malloc arena for flat memory

* cabana ui: toolbar overflow budget, menu button carets, help overlay colors, signal editor validation

* cabana ui: deqt.md status

* cabana: let vsync pace the frame loop

The loop threw away frames with glfwWaitEventsTimeout() to hit a 30fps
(10fps idle) target while glfwSwapBuffers() was already blocking on
vsync, so two clocks beat against each other. Input events also reset
frameInterval() to 0 for a few frames, so a click swung the cadence from
~33ms to ~13ms and back -- the camera view redraws on the UI's schedule,
so that showed up as a visible hitch on every message selection.

Poll and draw every iteration and let glfwSwapInterval(1) do the pacing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana: fix macOS build of thread pool / malloc arena changes

- malloc.h and mallopt(M_ARENA_MAX) are glibc only; guard on __GLIBC__.
  macOS has a single allocator zone, so there is nothing to cap.
- capturing structured bindings in a lambda is a C++20 extension and
  clang rejects it under -Werror; bind the event range to plain locals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: chart axis label precision, signal list click on empty space clears the selection

* cabana ui: codespell

* cabana ui: taller signal rows, system theme detection, full dark palette

- signal rows are 1.5x a frame tall with the sparkline sized to the row,
  so the graphs are readable. The expanded sub-rows keep the Qt height.
- "Automatic" resolved to the light theme regardless of the system: Qt
  gets this from QStyle::standardPalette(), which follows the macOS
  appearance, so read AppleInterfaceStyle and match it. Other platforms
  stay light, like standardPalette() does there.
- widgets that tested settings.theme == DARK_THEME themselves went light
  under AUTO_THEME while the style went dark; they now ask isDarkTheme()
  for the theme applyTheme() actually resolved.
- the dark branch only set 10 ImGuiCol_ entries, so buttons, scrollbars,
  tabs, title bars, separators, tables and grips still came from imgui's
  stock dark theme. Derive the whole palette from DarkTheme instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: drop the dead y axis alignment chain and chart cache stub

ImPlot::BeginAlignedPlots aligns the chart y axes, so the hand-rolled
alignment protocol ported from Qt no longer feeds anything: align_to was
write-only. Remove the whole chain (y_label_width and its measuring loop
in draw(), axisYLabelWidthChanged, align_timer, alignCharts), which
reduces updatePlotArea(int, bool) to updatePlotArea().

resetChartCache() has been an empty stub since the port (imgui redraws
every frame); drop it and its four call sites. Also replace the empty
move_icon_rect branch in mousePressEvent with an early return.

No behavior change. updateAxisY's "|| y_label_width == 0" guard only
fired after a unit change, and y_precision/y_tick_count depend solely on
min_y/max_y/tick_count, so the recompute it triggered was a no-op.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: stop the main window from scrolling

The host window that holds the dockspace uses zero WindowPadding but the
default ItemSpacing, so the dockspace and the status bar below it summed
to ItemSpacing.y (5px) more than the viewport, leaving the whole UI
scrollable by a few pixels.

Reserve the spacing along with the status bar height, and mark the host
window NoScrollbar/NoScrollWithMouse: it is pinned to the viewport work
area and should never scroll, as QMainWindow does not. Full screen is
unaffected, it draws no status bar and reserves no height.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: inset the status bar so its ends are not clipped

A borderless BeginChild gets WindowPadding forced to zero, so the status
bar text sat flush against both edges and the first glyph of "For Help,
Press F1" was cut off. Inset both ends by WindowPadding.x, which lines
the text up with the content of the docked panels above it.

Right align the cached minutes / FPS label to the captured child width
rather than calling GetContentRegionAvail() again after the message
text, which only returned the full width because the text had already
wrapped the cursor to a new line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: drop the leftover imgui object census log

The block printed an imgui/implot object count to stderr every 10s,
which was instrumentation for tracking down a leak, not something the
port needs. It was the only user of implot_internal.h in mainwin.cc, so
drop that include with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: keep the selected row highlighted while hovered

Selectable() picks its background as HeaderActive when held, else
HeaderHovered when hovered, else Header. The hovered test comes before
the selected one, so a selected row under the cursor takes the hover
color. Both tables push a transparent HeaderHovered to suppress the hover
highlight that QTreeView/QTableView do not have, which also blanked the
selection: clicking a row walked press (HeaderActive, visible), release
(HeaderHovered, transparent, looks unselected), then mouse off the row
(Header, visible), so the selection appeared to flicker until the cursor
moved away.

Only make HeaderHovered transparent for rows that are not selected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: draw the binary view hex column in bold and in the text color

The Qt delegate never sets a pen for the hex column, so it inherited the
black QPainter default and the two halves of a row did not match. Use
paletteText(is_message_active), the same color the bit columns use, so
both halves read as one row and dim together when the message goes
inactive.

JetBrains Mono ships no bold variant and imgui has no embolden option, so
give drawStaticText a bold flag that redraws the glyphs a fraction of a
pixel to the right. That keeps the monospace advance, which switching to
the proportional bold face would not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: highlight the whole row in the speed menu

MenuItem spans from the cursor to the right edge, so the Indent() that
made room for the radio bullet also pushed the highlight in by one
FontSize and left the bullet column outside it. QMenu highlights the
whole item, indicator included.

Draw each row as one full width Selectable with the bullet and the label
inside it. Every row declares the same width, so the popup is as wide as
the widest entry and the highlights reach both edges; a label-less
Selectable sized zero would declare no layout width and collapse the
auto-sizing popup. Selectable auto-closes its parent popup like MenuItem
does, so clicking still works in the dropdown and in the Speed submenu of
the toolbar overflow menu, which shares this function.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: fix help menu overlay, Ctrl+F11, time label clipping, toolbar overflow and tool table parity

* cabana ui: click to edit signal cells, light theme selection, logs cell selection and filter fixes

* cabana ui: chart x label margin, zoom context menu items, video splitter collapse, signal editor close

* cabana ui: signal editor lifetime and close, logs cell hover, tool table headers

* cabana ui: branch indicator clicks, editor swallows shortcut and button clicks, selection colors

* cabana ui: float dock windows into real OS windows

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: toolbar menu arrow spacing, only open the editor on editable cells

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* op: add cabana2 to run the imgui cabana

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana: cabana2 launcher script builds _cabana_ui before running it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: only float the dock panels into their own OS windows

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: open the video pane at the camera's natural aspect ratio

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana: default fps to 30

* cabana ui: fusion palette, shared fusion slider, chart grid colors

* cabana ui: smaller checkbox indicator, closer to the Fusion size

* cabana ui: video toolbar height and item centering, separator extent and menu arrow like Fusion

* cabana ui: message and signal row heights match Qt

* cabana ui: timeline slider height, groove and stacking match the Qt video layout

* cabana ui: fix crash opening a live stream, the video pane default height read the missing camera widget

* cabana: drop the FPS setting, the streams update the UI at a fixed 30 fps

* cabana ui: video and charts pane run edge to edge with a uniform toolbar margin like the Qt frames

* cabana ui: binary view M/L marker size and timeline event fills match Qt

* cabana ui: signal view keeps its sparklines while filtering, number label size matches Qt

* cabana ui: no modal dim fade, QDialog does not animate in

* cabana ui: collapse all is an auto-raise tool button with the Qt icon size

* cabana ui: dialogs and tool windows open as real OS windows like QDialog

* cabana ui: hold back a focus loss while a mouse button is down so tearing a panel off does not abort the drag and create its window twice

* cabana ui: speed menu rows pad the label on the right like QMenu

* cabana ui: raise the dark theme contrast, the Darcula grays all sat on top of the background

the chart y axis guides are opaque and 2 px in dark mode, and text, outlines,
separators, table borders, scrollbar and slider grabs move away from the window
and base grays. light theme is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: simplify comments, drop the Qt references and the ones that only restate the code

* cabana ui: keep the sparkline antialiased, thin peaks sparkled as the window slid

Qt dropped antialiasing above 500 points because it rasterized into a pixmap. drawing
straight into the frame, an aliased 1 px segment between two columns rounds into one of
them and the rounding flips as the window advances, so the peaks of the dense signals
popped between columns. updateState runs at STREAM_UPDATE_FPS, which at a 30s range moves
the curve well under a pixel per update, so the flip is all that is visible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: anchor the sparkline to the time window, it jittered sideways as samples expired

x was measured from the first sample still inside the window, which changes whenever the
oldest one falls out, so every update slid the whole curve sideways by the sample spacing.
measuring from the start of the window pins the right edge and lets the samples flow left
with the clock. the density heuristic wanted the data span, which is no longer the x of the
last point, and the flat line started at x 0 instead of at its first sample.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: scroll the sparkline at the frame rate, it stepped at the message rate

the window ended at the last message of the id and updateState only runs when one arrives,
so a slow message held the sparkline still for several frames and then jumped it. the window
now ends at the playback clock, and draw() slides the rendered polyline by the time that has
passed since it was built, clipped to the cell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: keep the signal value on the right of the column before there is a sparkline

it was drawn left aligned from the start of the column, so the value sat next to the name
until the first samples arrived and then jumped across the row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: read a little past the sparkline window so the oldest points slide out of it

a sample used to disappear the moment it aged out, which on a fixed rate signal is a point
popping off the left edge. the query reaches back a bit further and the clip rect hides it
instead. the lead in must not move the scale, so it is left out of the min and max.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: use the macOS fullscreen space, taking over the monitor rendered at the old size

glfwSetWindowMonitor switched the display mode and returned a drawable at that mode, so the
ui was rendered at the windowed size and stretched over the screen. NSWindow toggleFullScreen
keeps the backing scale. the green button goes through the same space, so the state is read
back from the style mask rather than tracked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: let the signal value column shrink again, it held the widest value ever seen

max_value_width only ever grew, so one long value left the sparklines ending
well short of the text for the rest of the message. Track the current widest
value instead, growing on demand and giving room back once it is a couple of
characters too wide, which keeps the width from jittering per sample.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: make the signal rows a quarter taller so the sparklines read better

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: lift the binary view heatmap alphas in dark mode, the tints washed out

The alphas were tuned against a white background; over the dark base the same
values left the cells barely colored. The floor comes up and the ramp gets a
gamma in dark mode only, which keeps the flip counts in the same order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: saturate the sparkline color in light mode, the pastel lines vanished

Signal colors are low saturation and high value, which is aimed at dark fills;
a 1 px line of one of them on white is hard to see. Draw the sparkline with
twice the saturation and less value when the theme is light.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: give the docked panels a minimum width, the dividers ran over content

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* replay: capture the downloader's stderr so the progress lines reach the handler

file_downloader.py writes PROGRESS:<cur>:<total> to stderr while it streams a
file, but runPython() only piped stdout and left stderr attached to the parent,
so the progress handler only ever fired on failure and cabana downloaded a
segment with no progress bar. Pipe stderr as well, turn the PROGRESS lines into
handler calls, and pass everything else through to the parent's stderr.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* replay: read the downloader's stderr on a thread, the two fd select was overkill

getline() handles the line splitting, partial lines and EOF, so the manual
splitter, the second drain loop and the multiplexing bookkeeping go away and
the stdout loop goes back to what it was.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: one toolButton helper in imgui_util.h

* cabana ui: one toImU32/toImVec4/paletteBrightText in imgui_util.h

* cabana ui: string helpers live in utils/strings.h

* cabana ui: share the fusion slider handle from imgui_util.h

* cabana ui: style.cc declarations move to imgui_util.h

* cabana ui: validatedInput and the qt validators move to imgui_util.h

* cabana ui: viewSelectable helper for the item view rows

* cabana ui: drop the comboBox pass-through

* cabana ui: tabbar moves to its own file, ready for the charts port

* cabana ui: find signal searches on the main thread again, deferred one frame like QTimer::singleShot(0)

* cabana ui: find signal computes its button and label state from the model instead of caching it

* cabana ui: share the queued popup owner protocol between the message box and the file dialog

* cabana ui: one beginDialog helper for the three centered modals, with the reopen recovery

* cabana ui: tool dialogs share the title, begin/end and escape handling, and own their connections

* cabana ui: comboBox helper replaces the hand rolled zero separated item buffers

* cabana ui: drop the unused dialog isOpen accessors

* cabana ui: one validatedText helper for the ip, double and int line edits

* cabana ui: route info reads the route segments instead of caching a row copy

* cabana ui: settings drops the phantom fps label, the constant spin flags and the raw log path buffer

* cabana ui: find similar bits drops the no-op find button disable and the address round trip

* cabana ui: message box warning overload without the empty detailed text

* cabana ui: help overlay only collects rects while it is up, skips torn off panels, and the fingerprint compare is ordered

* cabana ui: drop the glfw callbacks that only forward to the imgui backend, which already installs them

* cabana ui: elided label renders with RenderTextEllipsis instead of a paint time cache

* cabana ui: one save loop, inline cached minutes label, and drop the dead help parser branches

* cabana ui: signal cell bands are built from the two corner notches instead of a region sweep

* cabana ui: binary view items go through itemAt and updateItem assigns unconditionally

* cabana ui: message bytes read the cell padding inline and colored bytes keep the qt default pen

* cabana ui: detail widget drops createToolBar, the welcome widget flag and the unused whatsThis

* cabana ui: messages widget owns its view, header and model directly, fixed size filter editors, from_chars range parsing

* cabana ui: colored hex bytes keep the row pen, the qt delegate does not reset it per cell

* cabana ui: closeEditor drops the queued commit, its item is about to be deleted

* cabana ui: rowsInserted carries the insert position so the selected row follows its message

* cabana ui: the log filter and the message name editors use the shared validators

* cabana ui: the log header sizing and painting are free functions, no HeaderView object

* cabana ui: the visible row range is the change detector, no mirrored scroll position

* cabana ui: the line editor takes the focus flag as an argument and lets InputText revert on escape

* cabana ui: signal view drops the unreachable column parameters, unused item values and repeated sparkline tests

* cabana ui: rows outside the tree viewport are measured but not painted

* cabana ui: one toolbar item table, playback state read in draw()

* cabana ui: draw the stored thumbnail scaled by AddImage, no cpu prescaled copy

* cabana ui: one radioMenuItem helper for the speed and series type menus

* cabana ui: drop dead camera members, hoist the jpeg colour space branch, decode qlog thumbnails on the thread pool

* cabana ui: stop the vipc thread when the splitter collapses the video pane

* cabana ui: the toolbar strings and enabled state are computed in drawToolBar

* cabana ui: chartswidget uses the shared tabbar widget

* cabana ui: the chart header layout is recomputed in resizeEvent, no updateTitle

* cabana ui: updateLayout has no force flag

* cabana ui: one condition hides the value tip

* cabana ui: signal selector uses the shared dialog buttons and row indexes

* cabana ui: the chart tile geometry is one Layout struct saved and restored by drawGhost

* cabana ui: drop the empty theme branch, the unused size hint and the charts container indirections

* cabana ui: the chart holds its tip label by value

* cabana ui: one toolbar item list for both toolbar groups

* cabana ui: the sparkline polyline is emitted through the draw list path

* cabana ui: the tab bar latches a programmatic selection and defers the close request past EndTabBar

* cabana ui: validatedText refuses an invalid edit inside the imgui buffer and the ip field keeps its char filter

* cabana ui: drop the ambiguous three argument warning overload

* cabana ui: combobox reports a real change, menu rows span the popup, one popup protocol for the dialogs

* cabana ui: the startup stream is owned by the window until the first frame opens it

* cabana ui: the slider toolbar index has an out of range sentinel

* cabana ui: the find signal search runs on the frame after the pending state is painted

* cabana ui: the log clears its selection when the rows are dropped and cells are identified by their message

* cabana ui: the open editor survives a scroll, collapse all commits it and escape in the size editor does not

* cabana ui: the header display order is filled with iota

* cabana ui: qlog thumbnails decode on their own threads, the hover thumbnail is mipmapped and the time tooltip appears on the first toggle

* cabana ui: a collapsed video dock stops the vipc thread

* cabana ui: one TOOLBAR_ITEM_SPACING, the signal view used the QCommonStyle 4 instead of Fusion's 1

* cabana ui: sparkline includes imgui_util.h for isDarkTheme

* cabana ui: imgui_util.h becomes util.h and util.cc

* cabana: delete deqt.md

* cabana ui: panel min width, matching video panel padding, centered tabs

The dock panels stop shrinking at the width where the signal view tool bar
squishes instead of at a hardcoded 440. The video/charts panel keeps the
standard window padding so it lines up with the other panels, and the
Msg/Logs tabs are centered in their bar. The cached minutes label is gone
from the status bar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: persist imgui state in cabana.json and migrate the Qt frontend's layout blobs

* cabana ui: 2px sparkline stroke

a 1px antialiased line is all fringe and no solid core, so the curve
faded and shimmered as the window scrolled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana: drop the Automatic color theme

it only did anything on macOS: the Qt frontend fell through to
standardPalette() for both Automatic and Light, so the two were
identical everywhere else.

LIGHT_THEME and DARK_THEME keep their values so a persisted dark theme
still reads as dark; the old Automatic 0 falls back to light on load.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: window color between the binary and signal views, fixed split

the binary view sits at its size hint and no longer resizes, and the gap
below it takes the window color instead of leaving a white seam.

also border the video and chart panels like the signal view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: bigger tab bar scroll buttons

imgui's built in scroll arrows are fixed at FontSize - 2 wide with the
glyph drawn in a FontSize box, so they are small and sit off center.
draw chevron buttons at the frame height instead, spaced and disabled at
the ends of the scroll range like the rest of the tool buttons.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* cabana ui: 1.5px sparkline stroke

* cabana ui: stable sparkline scrolling, one sample per column, aliased spikes

Scroll the polyline by whole physical pixels with each sample's subpixel
phase fixed to its timestamp, thin to one sample per pixel column, and
draw near-vertical segments aliased so spikes stay crisp while smooth
curves keep antialiasing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: fix spelling

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: size the value column for the widest value a signal can produce

Sizing it to the values in the last message moved the sparklines every
time a value changed length.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: center the tab bar scroll chevrons and give the buttons a frame

The icon font glyph sits off center in its padded advance, so the
chevron is drawn in the button rect. The buttons take the theme's
button background and border like the tool buttons next to them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: darker grid lines between table cells

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: the gap between the video and the charts matches the side padding

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: plus-square icon for the new chart button, like the remove all button

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: video toolbar buttons as tall as the charts toolbar buttons

The buttons carried 10 px of vertical padding, so the hover rect was
much taller than the glyph and the row looked off center.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: the time display in the mono font at the ui font size

With proportional digits the time changed width as it ticked and the
items after it moved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: closing a floated side panel docks it back, no hide tab bar button, the charts container never scrolls

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: the message tabs use the TabBar widget, chevron scroll buttons for both tab bars

The chevron scroll buttons move into a scrollable tab bar widget that
both use. The wheel scrolls the tabs, both tab bars get Close Other
Tabs, and the Msg tab is labeled Messages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: deliver a focus loss right away on macOS, holding it back swallowed the first click in a popup

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: draw the remote routes dialog at the modal's level, opened inside the tab's child window it never appeared

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* cabana ui: --no-cache turns off the local route file cache, same as replay

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* cabana: parse merged segments on replay's merge thread, the parse on the main thread dropped frames on every merge while downloading

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* cabana ui: Esc leaves full screen on all platforms

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* cabana ui: an app bundle on macOS so the menu bar and the dock show Cabana

A bare binary shows its file name there. The build wraps _cabana_ui in
a minimal Cabana.app with an Info.plist and the launcher runs that.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Revert "cabana ui: an app bundle on macOS so the menu bar and the dock show Cabana"

This reverts commit 1a45540125847ca46fcd4d7ac0522918e315057d.

* cabana ui: the app menu on macOS says Cabana

A bare binary gets an info dictionary with its file name in it, which
glfw reads for the app menu. The name is set there before glfw brings
up cocoa.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: macapp moves into ui/util

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: setMacAppName lives in ui/util.cc

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: apply the code review

Bugs: an empty decode batch dereferenced vals.front(), the chart x comparator
took a float, reserve used capacity(), the bit text went black while resizing
a signal, byte cells under-sized non-multiple-of-8 payloads, the CAN speed
lookup read the data speed table, the export tooltip never showed while
disabled, and the thumbnail parser and the signal search joined ad-hoc
threads on the render thread. Both now run off the pool; the search fans out
over it from its own thread.

Shared helpers in ui/util: the tool bar with its overflow menu, the menu
button, modal dialog setup, Escape inside dialogButtons, drawText,
drawElidedText, color markers, alignRight, clearableInput,
disabledItemTooltip, inputTextMultiline, tableHeadersRow with the right
click, withAlpha, boldFont, the Cocoa full screen glue, the messages panel
id. utils gets hexByte, guarded and nonEmptyDBCFiles; threadpool gets
parallelFor.

The help overlay leaves mainwin.cc for its own file, the stream teardown is
one releaseStream(), the charts own their views through unique_ptr, the
find-similar-bits message list follows the source bus, the clipboard copies
every open DBC, and the Qt vestiges (event names, model API, protected
sections, restating comments, stale TODOs, mixed member naming) are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: collapse the model/view/delegate split

The Qt-era model, view and delegate classes were ported one to one, but
imgui draws a table in one function. MessageView, MessageViewHeader and
MessageListModel's string API fold into MessagesWidget over a plain
MessageList; HistoryLogModel folds into LogsWidget; BinaryViewModel and
BinaryItemDelegate fold into BinaryView; SignalItemDelegate folds into
SignalView, and SignalModel keeps only the item tree with three predicates
instead of the flags bitmask; FindSignalModel becomes SignalSearch. The
byte cell painter is a set of free functions shared by the two tables.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cabana ui: load the route behind the window

The route file listing is an HTTPS round trip to the comma API and took
the whole first second of startup before the window existed. The replay
branch of main() now hands run() a loader that a worker executes after
the first frame; the window maps in ~0.1 s instead of ~1 s. On a failed
load the app stays open in the No Stream state with the error box
instead of exiting before a window ever appeared.

* cabana ui: fix the live stream video controls layout

* cabana ui: only copy dbc to clipboard with a single file

* cabana ui: report route loading failures instead of hanging

* cabana: launch the imgui frontend by default, --legacy for Qt

---------

Co-authored-by: Trey Moen <trey@Treys-MacBook-Pro.local>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 08:06:41 -07:00
YassineYousfi 50fb67f718 bump tg + TC_MIN_GLOBALS (#38751)
bump op + TC min globals
2026-09-01 23:12:28 -07:00
Jason Wen 47db84ebfb models: add big model ONNX hash tracking (#1982) 2026-09-02 01:28:43 -04:00
Jason Wen 68be777395 bump tg 2026-09-01 22:14:43 -04:00
github-actions[bot] ab389498a8 [bot] Update Python packages (#1950)
* Update Python packages

* bump tg

* bump

* ci: route build_model runner by target_hardware instead of hardcoding chestnut

* hack, remove before merge

* Revert build-model runner hack and uv.lock update

* why were they hard coded

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-09-01 22:12:54 -04:00
Daniel Koepping 6249f4d5b0 AGNOS 19.7 (#38750) 2026-09-01 18:32:59 -07:00
Daniel Koepping 8b88f7dd6e ui: show one GPU status (#38748)
ui: show one GPU status icon
2026-09-01 18:32:39 -07:00
Harald Schäfer 79658800ce cereal: log big model in drivingModelData (#38747) 2026-09-01 17:15:13 -07:00
Daniel Koepping 36561258fa ui: show usb connection (#38745)
* ui: show USB status

* ui: resize USB icon

* ui: classify USB device once

* ui: debounce USB disconnect
2026-09-01 15:45:57 -07:00
YassineYousfi cb85ac1f0e amd warp (#38684)
* modeld: fuse warp and policy TinyJit

* bump tg

* fix?

* this simple trick...

* debug 1

* bump tg

* pack all

* wips

* fix

* BIG_INTO_SMALL remove

* slower
2026-09-01 13:59:55 -07:00
Daniel Koepping c9f1602040 Revert "monitor chestnut USB in hardwared (#38741)" (#38744)
This reverts commit 7d5596d5c3.
2026-09-01 11:13:30 -07:00
Daniel Koepping 06af2abe67 modeld: wait for stable chestnut (#38742)
modeld: wait for stable chestnut
2026-09-01 07:20:56 -07:00
Daniel Koepping 7d5596d5c3 monitor chestnut USB in hardwared (#38741)
hardwared: monitor chestnut USB independently
2026-09-01 05:59:02 -07:00
YassineYousfi a2e422eee0 TGC (#38739)
* 23e6a04e-e6e5-462b-a0bb-e4088275ee43/12864 tgc

* here
2026-08-31 22:30:28 -07:00
Jason Wen 51987a62d0 ci: route build_model runner by hardware type 2026-09-01 01:16:04 -04:00
XiaoXX e10c0fd960 modem.py: accept hex chars in ICCID (#38735)
E.118 specifies decimal digits, but many real SIMs carry hex characters
in EF_ICCID (e.g. China Mobile's 898600B5... range, some MVNO/IoT SIMs).
AT+QCCID returns them verbatim, and the strict isdigit() check blanked
the ICCID, leaving the modem daemon stuck in INITIALIZING forever and
cellular dead. ModemManager parses ICCID as hex for the same reason.

Verified on a comma four with a China Mobile SIM (EG916Q-GL): previously
stuck retrying 'identity read incomplete', now dials and passes traffic.
2026-08-31 21:35:56 -07:00
James Vecellio-Grant 98ed8111f6 modeld_v2: big to small model fallback (#1974) 2026-09-01 00:15:23 -04:00
Trey Moen da8ce858ec ui(mici): name updater signal constants (#38731)
* mici: name updater signal constants

* drop SIGNAL_ prefix

* self contained

---------

Co-authored-by: Shane Smiskol <shane@smiskol.com>
2026-08-31 15:55:16 -07:00
Trey Moen 9fa7ef3d17 ui: clarify branch switcher error message (#38732) 2026-08-31 15:46:52 -07:00
Harald Schäfer 4adbb85742 BMRLNAP (#38681) 2026-08-31 09:25:32 -07:00
Robbe Derks 70df7f227b bump panda (new health packet) (#38736)
pandad: support compact health packet
2026-08-31 14:01:20 +02:00
royjr de197ba6fa chestnut: alert when big model ready (#1947)
egpu: alert when big model ready

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-30 16:20:16 -04:00
Trey Moen 0e32059484 replay: capture downloader's stderr so download progress is reported again (#38734) 2026-08-30 09:32:36 -07:00
Trey Moen 7cf55c3b7a common: fix OpenpilotPrefix cleanup on macOS (#38728)
The destructor built its cleanup commands as "rm %s -rf", with the flags
after the operand. GNU rm permutes arguments so this works on device and
in CI, but BSD rm on macOS stops option parsing at the first operand and
treats "-rf" as a second filename:

  $ mkdir -p /tmp/rmtest/sub && rm /tmp/rmtest -rf
  rm: /tmp/rmtest: is a directory
  rm: -rf: No such file or directory
  exit=1

So nothing is removed, and each of the four calls prints two errors plus
"system command failed (256)" from check_system. Every run of a tool that
owns an OpenpilotPrefix (replay, cabana) leaks its params dir, its
comma_home and its /tmp/msgq_ dir; 33 of each had accumulated on my
machine.

Pass the flags first.
2026-08-28 22:11:52 -07:00
Daniel Koepping 682b6a20df add chestnut offroad alerts (#38706)
* system: add chestnut offroad alerts

* system: refine chestnut offroad alerts

* system: refine chestnut power alerts

* system: confirm chestnut power recovery from PCIe

* system: detect missing chestnut power from INA voltage
2026-08-28 15:46:56 -07:00
Daniel Koepping a67cdf9a51 ui: sync gpu loading to offroad (#38727)
ui: sync gpu loading state
2026-08-28 15:08:18 -07:00
Trey Moen e571e21d14 ui: check for update on target branch switch (#38693) 2026-08-28 12:07:15 -07:00
Trey Moen 839d3f5004 ui: guard branch switcher before internet connected (#38692) 2026-08-28 12:06:33 -07:00
Trey Moen 5645370f84 cabana: split utils/util into Qt-free util and qtutil (#38723) 2026-08-28 11:37:09 -07:00
Trey Moen 633d17cd12 ui: fix install update button overflow (#38696) 2026-08-28 11:30:13 -07:00
Trey Moen 5419f57b3a cabana: de-QT streams (#38718) 2026-08-28 10:18:10 -07:00
Jason Wen 1dd5a7c91d Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1973) 2026-08-28 12:58:26 -04:00
Trey Moen 46f612224c cabana: split comma API route fetching out of RoutesDialog (#38721) 2026-08-28 09:57:47 -07:00
Trey Moen 6e0f4f4630 cabana: split SettingsDialog out of settings (#38719)
cabana: split SettingsDialog out of settings.{h,cc}
2026-08-28 09:55:28 -07:00
Trey Moen 0f9c753e6e cabana: remove Qt from livestream (#38722) 2026-08-28 09:46:45 -07:00
nayan acb784d207 Merge commit '4a13639cfd122ccb9113a4d6ce225dcbd8e61914' into sync-20260827
# Conflicts:
#	openpilot/selfdrive/modeld/SConscript
#	openpilot/selfdrive/modeld/modeld.py
#	openpilot/selfdrive/ui/mici/layouts/home.py
#	openpilot/selfdrive/ui/ui_state.py
#	tinygrad_repo
2026-08-28 12:39:53 -04:00
Trey Moen 131e473f37 cabana: move stream open widgets into streamselector (#38715) 2026-08-28 09:36:26 -07:00
Trey Moen 30f358eb59 cabana: use std::string in RoutesDialog API results (#38717) 2026-08-28 07:25:34 -07:00
Trey Moen 9b9e3ea604 cabana: string helpers in utils return std::string (#38720) 2026-08-28 07:25:11 -07:00
Trey Moen 7cc48b5bc9 cabana: move RoutesDialog out of streams/ (#38716) 2026-08-27 22:00:24 -07:00
Trey Moen cbf750de20 cabana: replace custom non-view Qt signals w/ plain observer (#38713) 2026-08-27 18:54:06 -07:00
Trey Moen 318257fa3b bump raylib (#38712) 2026-08-27 11:38:53 -07:00
Daniel Koepping 4cdc16031f log chestnut supply fault (#38711)
* log chestnut INA supply fault

* ci
2026-08-27 11:21:56 -07:00
Trey Moen 31ea1850f7 ui: remove raygui usage (#38708)
* ui: remove raygui usage

* match previous gui_text_box line spacing

* Revert "match previous gui_text_box line spacing"

This reverts commit ffd2fe31725c6d50bffaebc621c1e170d0926c66.

* Reapply "match previous gui_text_box line spacing"

This reverts commit d41404f09607e225f43868f7747f22dc0bb2cf16.
2026-08-27 10:54:52 -07:00
Nayan 4075befc5e osm: support map deletion via sunnylink (#1971)
delete delete
2026-08-27 11:23:26 -04:00
Jason Wen 9f43d2477d [MICI] ui: move and restyle the sunnylink pill in settings (#1972) 2026-08-27 03:52:57 -04:00
Nayan 2d6cc4c065 models: Model Selector upgrades (#1953)
* uh, i did not commit anything all this time

* slideee to the left, cha cha

* lint lint

* ui: unify model source predicate and per-source bundle lookup in model_info

* [TIZI/TICI] ui: disable the other-model row onroad like the active row

* [TIZI/TICI] ui: drop docstring that restates the function name

* [TIZI/TICI] ui: keep Favorites as the first model folder in the picker

* ui: record why model names read the params slots and not modelManagerSP

* ui: show the default model's name on the picker Default entries

* models: bind a download to its ref so cancel and reselect work everywhere

* models: resume partial chunked downloads and verify silently

* models: publish a verifying status so cached checks read as verification, not a stuck download

* [TIZI/TICI] ui: move download status onto each model's own row

* [TIZI/TICI] ui: show the row status description while it has text

* [TIZI/TICI] ui: restore the Model Status bar row

* models: a cancel interrupts verification immediately and keeps on-disk chunks

* models: a selection made mid-download queues instead of cancelling the transfer

* [TIZI/TICI] ui: Model Status shows both slots idle and the queued pick while busy

* [TIZI/TICI] ui: label the Model Status slots small and big and scroll long names

* models: start a queued download in the same tick and label empty slots (Default)

* ui: scroll Model Status names at the corrected speed

* [TIZI/TICI] ui: Model Status shows the big model failing over to small

* [TIZI/TICI] ui: stable model rows and a runner-matched failover note on Model Status

* [TIZI/TICI] ui: model rows show full names and the failover note reopens with the page

* ui: name the actually driving model runner-matched and bring mici to state parity

* fix ugly

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2026-08-27 02:03:53 -04:00
Daniel Koepping 4a13639cfd reduce chestnut states (#38705)
ui: unify chestnut status presentation
2026-08-26 19:12:49 -07:00
YassineYousfi fa75fdd852 chestnut stats: overlap with gpu work (#38704)
* chestnut stats: overlap with gpu work

* ci

---------

Co-authored-by: elkoled <elkoled@gmail.com>
2026-08-26 17:57:55 -07:00
Daniel Koepping 5cfdb2f4da rename usbgpu to chestnut (#38703)
chestnut: rename eGPU interfaces
2026-08-26 15:42:59 -07:00
Daniel Koepping 63548ce10d bump tinygrad (#38702) 2026-08-26 15:20:04 -07:00
Daniel Koepping 980fb79c1a update orange GPU icon (#38701)
mici: update failed eGPU icon
2026-08-26 12:21:14 -07:00
Harald Schäfer d40df6f829 modeld: fall back on invalid big model outputs (#38700) 2026-08-26 12:06:53 -07:00
Nayan da28afca91 models: dual-slot backend (qcom/usbgpu) with ref-based downloads (#1966)
* models: dual-slot backend (qcom/usbgpu) with ref-based downloads

* models: restore get_active_source and the usbgpu-to-qcom fallback

* models: fix per-slot validation and cap mismatched-source refetches

* ui/models: select models by ref and seed the usbgpu slot on migration

* models: drop defensive attribute guards on capnp bundles

* models: remove vestigial fetcher state and dead fallbacks

* models: resolve the active bundle from the active source slot only

* models: pass the usbgpu kwarg through the modeld test stubs

* models: resolve the displayed model from the active slot in ui_state

* models: correct the validation memo type hint

* models: drop docstrings that restate the function name

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-26 02:34:02 -04:00
Jason Wen 15f201caed ui: use full big model failure detection for sidebar and home eGPU icons (#1969) 2026-08-25 21:07:44 -04:00
Jason Wen 1d4558c067 [TIZI/TICI] sidebar: show eGPU icon when chestnut is present (#1968)
* [tizi/tici] sidebar: show eGPU icon when chestnut is present

* matchy match

* fix
2026-08-25 20:47:52 -04:00
Jason Wen 78a766eb61 ui: fix scrolling label speed at non-60fps refresh rates (#1967)
* ui: fix scrolling label speed at non-60fps refresh rates

* send it

* nope

* more
2026-08-25 20:38:02 -04:00
Jason Wen b742b96c44 [MICI] ui: four-state eGPU icon for non-default big models (#1945)
* ui: four-state eGPU icon for non-default big models

* oops

* try this out

* align
2026-08-25 12:04:39 -04:00
Jason Wen 25c25047b8 models: persist model selection per catalog across chestnut state changes (#1960) 2026-08-25 01:12:12 -04:00
Jason Wen cefe5737b9 models: fix current model not updating on chestnut status (#1959)
* models: preserve user model selection across reboots and power cycles

* no

* again

* idk

* over
2026-08-25 00:41:31 -04:00
Jason Wen 760c19d3f9 ui/models: handle missing files during cache size calculation (#1958) 2026-08-24 23:31:52 -04:00
James Vecellio-Grant 45814e3313 modeld_v2: spatial features (#1934)
* modeld_v2: spatial features

* Update fetcher.py

* dont reshape non 4 dim arrays

* realize for non compiled

* Update compile_modeld.py

* god dammit it was realize()

* it was fucking frozen tinygrad. just need to recompile

* bump

* ci: add is_big flag to metadata.json to support backward compat

* Update model_generator.py

* Update sunnypilot-build-model.yaml

* Update helpers.py

* Revert "Update helpers.py"

This reverts commit 3a955ca11a.

* Reapply "Update helpers.py"

This reverts commit ca9c6e1933.

* models: use less strict chestnut detection state

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-24 22:52:29 -04:00
Jason Wen 2ba91d2be5 ci: add tinygrad ref check to prepare_chestnut and even faster prebuilt stages (#1957)
* ci: faster prebuilt stages

* tg check chestnut

* zoomer!
2026-08-24 22:42:45 -04:00
Jason Wen 19f83b274f ci: identical environment for publish_chestnut prebuilt 2026-08-24 22:06:20 -04:00
Jason Wen d14d0b1dd0 ci: parallelize models chunk downloads and split branch publishing (#1955)
* ci: parallelize model chunk downloads and better publish

* ci: download all model chunks in parallel with xargs -P8

* split split

* ew

* must require
2026-08-24 21:48:53 -04:00
Jason Wen 6cc5f3aad8 ci: fix DM model build, separate HF defaults paths, nuke build races (#1956)
* ci: fix DM model build, separate HF defaults paths, nuke build races

* more split!

* name

* ci: download driving and DM model chunks into chestnut prebuilt output
2026-08-24 20:02:48 -04:00
Jason Wen 8e16c9babb ci: offload small model compilation (#1952)
* ci: compile default big model with stock modeld

* Revert "Revert big RL model (#38627)"

This reverts commit 516ec1e682.

* ci: compile default small model with stock modeld compiler

* ci: offload small model compilation

* Reapply "Revert big RL model (#38627)"

This reverts commit d06cfabb62.

* Reapply "Revert big RL model (#38627)"

This reverts commit d06cfabb62.
2026-08-24 16:24:19 -04:00
Jason Wen 2bcfed5c71 ci: compile default models with stock modeld (#1954)
* ci: compile default big model with stock modeld

* Revert "Revert big RL model (#38627)"

This reverts commit 516ec1e682.

* ci: compile default small model with stock modeld compiler

* Reapply "Revert big RL model (#38627)"

This reverts commit d06cfabb62.
2026-08-24 15:37:46 -04:00
Jason Wen 66cf334067 ci: unify default model build into single workflow (#1951)
* ci: unify default model build into single workflow

* ci: consolidate upload jobs and add tinygrad ref validation
2026-08-24 12:35:37 -04:00
Jason Wen 94ed0608e6 models: use less strict chestnut detection state (#1948) 2026-08-24 01:40:31 -04:00
Jason Wen 0fbca979df models: show big model list when Chestnut present (#1943) 2026-08-23 20:19:57 -04:00
Jason Wen dcddb2a0bd models: revert icon override from this branch scope 2026-08-23 19:53:46 -04:00
Jason Wen 699eaf7957 include them! 2026-08-23 19:16:15 -04:00
Jason Wen c246e6318a Merge branch 'master' into models-good-detect 2026-08-23 16:56:44 -04:00
Jason Wen 718db8c62e Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1944) 2026-08-23 16:56:00 -04:00
Jason Wen c2214d4c32 Merge commit '084747c75d2cbd23af65ab7a9e770bbd7b98bac9' into sync-20260823
# Conflicts:
#	openpilot/common/params_keys.h
2026-08-23 15:36:54 -04:00
Jason Wen 0de7fbf33d new 2026-08-23 15:06:53 -04:00
Jason Wen 211f990f6b models: fix sunnylink default model display and false big model re-downloading (#1941)
* big needs small

* no download

* actually

* send it
2026-08-23 04:04:46 -04:00
Jason Wen 97468e4fa4 [TIZI/TICI] ui: remove calibration reset dialog on model change (#1942) 2026-08-23 03:48:52 -04:00
Jason Wen 6c6fba9a14 ci: fix flaky LLK test (#1940) 2026-08-23 02:57:02 -04:00
Jason Wen 34621cf816 ci: refactor big model chunk handling (#1939) 2026-08-23 02:48:10 -04:00
Jason Wen 086530b7c6 [TIZI/TICI] ui: fix path width during gas and steering override (#1938) 2026-08-22 21:47:38 -04:00
Jason Wen 4f46433e2b alerts: add branch metadata to chestnut offroad warning (#1936)
* alerts: add branch metadata to chestnut offroad warning

* all branches
2026-08-22 10:20:06 -04:00
Jason Wen 5a8567e3e7 ci: chestnut prebuilt branches (#1935)
* ci: chestnut prebuilt branches

* fix

* nope

* big

* try again

* diff

* malformed

* auth

* more
2026-08-22 03:41:48 -04:00
Jason Wen 07558166c8 ci: only check default model on dispatch 2026-08-22 00:32:35 -04:00
Jason Wen ca9338812e ci: prep for chestnut prebuilts 2026-08-22 00:16:10 -04:00
granolaFPV 4667241fe7 [TIZI/TICI] ui: dynamic path width color (#1926)
* Fix UI path color and thickness based on lateral steering state (Issue #1441)

* Fix UI path color and thickness based on lateral control engagement (Issue #1441)

* Fix UI path width and color based on MADS lateral engagement (Issue #1441)

* Fix UI path width and color based on MADS lateral engagement (Issue #1441)

* move to ModelRendererSP

* match torque bar

* same behavior across the board

* simplify

---------

Co-authored-by: Brennan Browne <brennanbrowne@google.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-21 20:51:43 -04:00
Shane Smiskol 084747c75d Fix button label widths (#38680)
* Revert "ui: fix text and icon overlap on button (#38628)"

This reverts commit d9c4120f89.

* simple

* can do this

* fix eliding

* Revert "fix eliding"

This reverts commit b271a350182ad87f9942d7363383ee8ec72d0e36.

* clean up

* clean up
2026-08-21 15:41:12 -07:00
Marceline Milligan a49c260927 ui: show default big model name when eGPU present/active (#1930)
* Name the big default model in the device UI

Build on the default big-model metadata from #1929 and resolve the displayed model from cached capability and modeld runtime state. Keep model selection behavior unchanged.

Assisted-by: GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove get_default_model_label

* simplify

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: nayan <nayan8teen@gmail.com>
2026-08-21 13:26:52 -04:00
Jason Wen 5ad2bfdb75 ci: deprecate GitHub runners (#1933) 2026-08-21 00:35:38 -04:00
Jason Wen b742557d62 sunnylink: add model resolver (#1931)
* models: add get_default_model resolver for sunnylink

* models: move get_default_model to default_model.py
2026-08-20 21:56:57 -04:00
Nayan 5ecd05aedf models: add big model to default model resolution (#1929)
* device

* sunnylink

* lint

* lfs?

* Revert "lfs?"

This reverts commit bcdaec6b4c.

* update path

* Scope the default big model down to the sunnylink schema

* Drop the mock-only default model test

* Move the default model resolver out to separate PR

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-20 21:31:32 -04:00
Jason Wen 5ae100aa1d models fetcher: bump big model to v21 2026-08-20 19:19:59 -04:00
Jason Wen be76a88b80 ci override LFS fetch exclude for real ONNX file retrieval (#1928)
ci: override lfs.fetchexclude so the model fetch pulls real ONNX files instead of pointers
2026-08-20 19:12:59 -04:00
James Vecellio-Grant 049d225d5a ci: Dedicated Model Runner (#1922)
* ci: Dedicated Model Runner

* recurse

* not needed

* fix wrapper

* whoops

* bypass

* modeld_v2: restore chestnut link check before big model build

* modeld_v2: stage onnx to disk instead of shared memory

* ci: clear unchunked onnx temps before model build

* ci: stream the pkl hash instead of loading it into memory

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-20 16:18:17 -04:00
github-actions[bot] c783f2225a [bot] Update Python packages (#1925)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-20 14:44:15 -04:00
Robin Dittrich 53e13a7bc0 LagdToggle: fix inverted get_lat_delay branches (#1906)
* helpers.py get_lat_delay fix

* fix trailing whitespace

* lint

---------

Co-authored-by: Nayan <nayan8teen@gmail.com>
2026-08-19 21:36:37 -04:00
stef 555f48c5d2 params: remove livestream param on ignition (#38679)
* remove livestream param on ignition

* simplify process config
2026-08-19 14:21:59 -07:00
stef dcf9d25bf3 webrtcd: more descriptive errors (#38677)
more descriptive errors
2026-08-19 14:02:03 -07:00
stef a8d1a280c6 webrtcd/athenad: we don't have to fail on no car params (#38678)
we don't have to fail on no car params
2026-08-19 13:49:19 -07:00
stef 5b36799eec webrtc: fix message handler race (#38675)
open message handler early
2026-08-18 21:56:33 -07:00
Kumar ba29a38507 Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1921) 2026-08-18 21:16:47 -07:00
stef 20fdc3d824 webrtcd: cloud logging (#38674)
* logging

* remove test

* get rid of redudant try except

* fix logger context
2026-08-18 21:13:57 -07:00
Jason Wen ed35a82129 Merge commit '8edce0da4492307df211c710af6c4ece1c4a218e' into sync-20260818 2026-08-18 22:10:51 -04:00
Shane Smiskol 7bd6cad821 Re-open agnos updater UI if crash (#38672)
loop if crash
2026-08-18 19:06:48 -07:00
Shane Smiskol 8edce0da44 Clean up big model detection w/ helpers (#38671)
use helpers
2026-08-18 18:40:54 -07:00
stef 3c90b66b65 webrtcd: bind to localhost (#38664)
check content type and bind to localhost
2026-08-18 18:40:11 -07:00
Adeeb Shihadeh 08c83149b0 Pin SCons to 4.10.1 (#38669) 2026-08-18 18:37:47 -07:00
Shane Smiskol 03711a13b0 Revert "chestnut: don't compile if big model is LFS pointer" (#38670)
Revert "chestnut: don't compile if big model is LFS pointer (#38655)"

This reverts commit b8e14d85fb.
2026-08-18 18:31:07 -07:00
Shane Smiskol 9f1709a7e1 Revert "lfs: exclude big driving model in master clones (#38626)"
This reverts commit b7657f6553.
2026-08-18 18:29:33 -07:00
Jason Wen 2b576c5fce ci: tmp disable ui_report 2026-08-18 19:46:56 -04:00
Jason Wen 20ba774eaa [TIZI/TICI] ui: fix missing model download status and rework status row (#1920)
* [TIZI/TICI] ui: fix missing model download status and rework the status row

* fix lint
2026-08-18 19:45:40 -04:00
James Vecellio-Grant 59833c500a models: bump json version (#1919) 2026-08-18 15:23:47 -07:00
Harald Schäfer 3d09a47a47 cruise planner: fix decel jerk from cruise (#38653)
* cruise planner: fix decel jerk from cruise

* dead variable
2026-08-18 14:15:20 -07:00
Jimmy 2f4744d39b modeld_v2: fix features_buffer alignment for supercombo models (#1918)
Co-authored-by: Quantizr (Jimmy) <jimmyfang@ucla.edu>
2026-08-18 13:47:52 -07:00
Jason Wen 6dd3457f4f ci: refactor prebuilt workflow (#1916)
* ci: fix prebuilt file copy for null-separated release_files.py output

* ci: drop prebuilt symlinks the launch script recreates and gate the release on no submodules

* ci: use the device-local scons cache and prune dead prebuilt config

* ci: keep the scons cache in the runner workspace instead of the device's
2026-08-18 02:36:48 -04:00
Shane Smiskol b7657f6553 lfs: exclude big driving model in master clones (#38626)
* exclude big

* lfs

* Revert "lfs"

This reverts commit b646d5fb50d2a5be1e6e73275f2ee302687e670f.
2026-08-17 21:54:29 -07:00
Shane Smiskol b8e14d85fb chestnut: don't compile if big model is LFS pointer (#38655)
* use compiled helper for hardwared alert, source doesn't matter. scons skips compile if it's empty/lfs pointer

* log it

* rmnl

* compile failed

* out of scope

* rmnl
2026-08-17 21:47:14 -07:00
Jason Wen 0b1ed0d047 Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1913) 2026-08-17 01:43:11 -04:00
Jason Wen 9fb45b315a Merge commit '053d9c446800df38aca42b69bb99197aefbea77b' into sync-20260816
# Conflicts:
#	README.md
#	docs/CARS.md
#	opendbc_repo
#	tinygrad_repo
#	tools/release/build_release.sh
#	tools/release/build_stripped.sh
2026-08-17 00:38:50 -04:00
James Vecellio-Grant 94a32493e3 modeld_v2: chestnut support (#1894)
* modeld_v2: Support eGpu

* bump tg

* egpu

* no pkls please

* god no onnx either

* fix test

* done in build model now

* lint

* rip

* egpu ready build all split

* manual seed , reuse memory buffers across runs

* dont download big when we dont have big lol

* whoops

* no i and x

* cd

* who put those there. ??

* reduce flakiness by using artifact-name from build-model to regex, speed up pub b y checking the name before trying to clone and publish again

* try hf as a trusted publisher :)

* mf its a dataaset. i knew that

* fucking validation wants raw to fetch and full to push. grr

* smh

* dude i am missing so much

* pkl name

* move build all to hf

* tests: migrate sunnypilot tests to unittest and remove pytest

* red diff mf

* im scared , this may be a bad idea lol

* fetch latest commit.

* transition to requests

* models: use requests instead of aiohttp

* tici

* fix

* gpu fixes from upstream

* lint

* bump

* needed to say

* old

* how??

* epgu flag reduce

* this made me cry

* support monolith still

* precache warp in legacy

* Move jsons to param for sunnylink

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-16 21:40:16 -04:00
commaci-public 053d9c4468 [bot] Update Python packages (#38650)
* Update Python packages

* revert that for now

* ignore dashcam only

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-08-16 18:38:12 -07:00
Adeeb Shihadeh 85d364d4de loggerd: fix ~0.5s startup logging delay (#38649) 2026-08-16 18:24:01 -07:00
Adeeb Shihadeh dfbe0ee7c1 jenkins: set big cache dir (#38648) 2026-08-16 18:23:37 -07:00
Adeeb Shihadeh 03e6c81821 test_onroad: more precise frame ID check (#38647) 2026-08-16 16:10:35 -07:00
Adeeb Shihadeh 047be14df9 release: speedup builds (#38644) 2026-08-16 15:25:19 -07:00
stef 351701689f bump teleop (#38645) 2026-08-16 14:22:04 -07:00
Adeeb Shihadeh ec86732af8 ui: fix false positive openpilot unavailable on startup (#38643) 2026-08-16 13:11:50 -07:00
Adeeb Shihadeh 0f40ca1d88 pigeond: continuously try to get AGPS (#38642) 2026-08-16 12:50:16 -07:00
Jason Wen 91d0f3309c DEC: restore gate on longitudinal E2E output (#1911)
dec: restore Dynamic Experimental Control gate on longitudinal e2e output
2026-08-15 19:56:19 -04:00
Jason Wen 73fc740831 [TIZI/TICI] ui: fix developer UI crash on renamed field (#1910)
ui: fix developer UI crash on renamed lateralTorqueParameters valid field
2026-08-15 19:34:50 -04:00
Adeeb Shihadeh 97542f838f check the pkl too 2026-08-15 16:17:21 -07:00
stef dcbd66ad81 ui: big model failed alert (#38629)
* big model failed + supply voltage check

* remove ltssm and supply voltage stuff for seperate PR
2026-08-15 16:05:02 -07:00
Adeeb Shihadeh 391132465d release: chestnut build scripts (#38638)
* release: chestnut build scripts

* simplify

* only max

* no new script

* release: exclude local virtualenv
2026-08-15 16:01:42 -07:00
Adeeb Shihadeh a996f8ef90 chestnut gets its own table 2026-08-15 15:59:20 -07:00
Adeeb Shihadeh 6ad3532113 Document chestnut branches 2026-08-15 15:54:19 -07:00
Adeeb Shihadeh 48b7f171a7 release: speed up pushes by 9x (#38640) 2026-08-15 15:33:43 -07:00
Adeeb Shihadeh 5e3d17c72c chestnut: fix flashing on old FW (#38639) 2026-08-15 15:31:19 -07:00
Adeeb Shihadeh fb555fdefd big model doesn't need big build times (#38637)
* big model doesn't need big build times

* revert htat
2026-08-15 14:28:45 -07:00
Adeeb Shihadeh 28560d6cf1 show an offroad alert to switch to a chestnut branch (#38636)
* show an offroad alert to switch to a chestnut branch

* add to keys
2026-08-15 13:18:02 -07:00
Adeeb Shihadeh 748c725e3e soundd: more robust test (#38635) 2026-08-15 12:39:26 -07:00
Adeeb Shihadeh 3447ec1789 selfdrived: excessive actuation check is not for notCars (#38634) 2026-08-15 12:28:38 -07:00
Adeeb Shihadeh 76b69af59a remove offroad OS update alert (#38633) 2026-08-15 12:08:38 -07:00
Adeeb Shihadeh d9c4120f89 ui: fix text and icon overlap on button (#38628) 2026-08-15 12:03:34 -07:00
stef df4566ef2f ui: small software ui fixes (#38630)
add icon and scroller for branch name
2026-08-14 21:01:22 -07:00
Nayan d48cfa730b ui: dismiss screensaver on wake instead of on awake transition (#1907)
* fix wake-up behavior

* ui: dismiss screensaver on wake instead of on awake transition

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-14 17:21:34 -04:00
Toby Penner 516ec1e682 Revert big RL model (#38627) 2026-08-14 13:45:22 -07:00
Jason Wen 6e69da4037 Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1908) 2026-08-14 15:47:04 -04:00
Jason Wen 6405f15d5a models: use requests instead of aiohttp 2026-08-14 15:35:14 -04:00
Jason Wen ac4ab9a9b8 tests: migrate sunnypilot tests to unittest and remove pytest 2026-08-14 15:35:14 -04:00
Jason Wen 7461f70fdb Merge commit 'b7c333cf3fee117779515c9ebfd7b2beb164fa81' into sync-20260813
# Conflicts:
#	README.md
#	SConstruct
#	conftest.py
#	docs/CARS.md
#	msgq_repo
#	opendbc_repo
#	openpilot/common/params_keys.h
#	openpilot/common/params_pyx.pyx
#	openpilot/common/tests/test_swaglog.cc
#	openpilot/selfdrive/car/card.py
#	openpilot/selfdrive/car/tests/test_car_interfaces.py
#	openpilot/selfdrive/car/tests/test_cruise_speed.py
#	openpilot/selfdrive/car/tests/test_models.py
#	openpilot/selfdrive/controls/controlsd.py
#	openpilot/selfdrive/controls/lib/latcontrol_torque.py
#	openpilot/selfdrive/controls/lib/longitudinal_planner.py
#	openpilot/selfdrive/controls/plannerd.py
#	openpilot/selfdrive/controls/radard.py
#	openpilot/selfdrive/controls/tests/test_longcontrol.py
#	openpilot/selfdrive/locationd/torqued.py
#	openpilot/selfdrive/modeld/modeld.py
#	openpilot/selfdrive/monitoring/dmonitoringd.py
#	openpilot/selfdrive/monitoring/test_monitoring.py
#	openpilot/selfdrive/selfdrived/selfdrived.py
#	openpilot/selfdrive/selfdrived/tests/test_alertmanager.py
#	openpilot/selfdrive/test/longitudinal_maneuvers/plant.py
#	openpilot/selfdrive/test/process_replay/migration.py
#	openpilot/selfdrive/test/process_replay/process_replay.py
#	openpilot/selfdrive/ui/feedback/feedbackd.py
#	openpilot/selfdrive/ui/layouts/settings/device.py
#	openpilot/selfdrive/ui/layouts/settings/toggles.py
#	openpilot/selfdrive/ui/mici/layouts/onboarding.py
#	openpilot/selfdrive/ui/onroad/augmented_road_view.py
#	openpilot/selfdrive/ui/tests/test_soundd.py
#	openpilot/selfdrive/ui/translations/app.pot
#	openpilot/selfdrive/ui/translations/app_de.po
#	openpilot/selfdrive/ui/translations/app_en.po
#	openpilot/selfdrive/ui/translations/app_es.po
#	openpilot/selfdrive/ui/translations/app_fr.po
#	openpilot/selfdrive/ui/translations/app_ja.po
#	openpilot/selfdrive/ui/translations/app_ko.po
#	openpilot/selfdrive/ui/translations/app_pt-BR.po
#	openpilot/selfdrive/ui/translations/app_th.po
#	openpilot/selfdrive/ui/translations/app_tr.po
#	openpilot/selfdrive/ui/translations/app_uk.po
#	openpilot/selfdrive/ui/translations/app_zh-CHS.po
#	openpilot/selfdrive/ui/translations/app_zh-CHT.po
#	openpilot/system/athena/athenad.py
#	openpilot/system/hardware/hardwared.py
#	openpilot/system/loggerd/deleter.py
#	openpilot/system/manager/process_config.py
#	openpilot/system/ui/lib/application.py
#	panda
#	pyproject.toml
#	tinygrad_repo
#	uv.lock
2026-08-14 15:35:10 -04:00
Adeeb Shihadeh c988e78893 jenkins fixups (#38532) 2026-08-13 22:46:13 -07:00
Shane Smiskol bdc8e4b02c deprecate long kp (#38614)
* deprecate long kp

* bump
2026-08-13 22:25:35 -07:00
Adeeb Shihadeh b7c333cf3f pigeond: add comma AGPS source (#38616) 2026-08-12 20:31:41 -07:00
Adeeb Shihadeh fe47e752f6 log USB3 lane (#38617) 2026-08-12 20:15:10 -07:00
Daniel Koepping 10502adf95 AGNOS 19.6 (#38611)
* bump version

* add production
2026-08-11 23:27:16 -07:00
Adeeb Shihadeh 7a76a50c4f Add release-chestnut branch 2026-08-11 21:48:27 -07:00
Daniel Koepping 927e822b5a fix tap to factory reset (#38613)
hardware: lazily import optional SIM support
2026-08-11 21:25:45 -07:00
Daniel Koepping f1746f2e2d fix spinner crash (#38612)
ui: keep spinner independent of build artifacts
2026-08-11 20:42:08 -07:00
Adeeb Shihadeh 473ab17876 add cupra to release notes 2026-08-11 18:15:10 -07:00
Adeeb Shihadeh ebc77e4c90 Revert "camerad: improve IFE robustness to lags (#38548)"
This reverts commit 122a8ca00a.
2026-08-11 18:13:02 -07:00
Daniel Koepping ad5afe222d log chestnut ASM at 10Hz (#38609)
* modeld: only read the smu while the big model runs

* chestnutState at 10Hz

* smu polling
2026-08-11 18:09:25 -07:00
Shane Smiskol 32b518d51d set long maneuvers to actuation limits 2026-08-11 17:48:13 -07:00
Kumar 2d859a8ca6 Controls: Road Edge Lane Change Controller (#1085)
* relc

* so picky

* clean up

* switch to distance based

* same calls

* all you brother i'm just here to red diff

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-11 10:29:14 -04:00
Adeeb Shihadeh 062cb672e8 camerad: force BPS clock to max (#38539)
* camerad: account for BPS clock overhead

* force to max
2026-08-11 00:39:51 -07:00
Adeeb Shihadeh 122a8ca00a camerad: improve IFE robustness to lags (#38548)
camerad: keep request IDs monotonic
2026-08-10 22:36:54 -07:00
Daniel Koepping b1cdf387b5 sync after a build (#38606)
* build: sync after a successful build

* sync on failed builds too
2026-08-10 21:23:59 -07:00
Shane Smiskol b5b156825d ui: fix inverted DM head tracking on RHD (#38607)
* ui: fix inverted DM head tracking on RHD

b29d0a17af switched the mici driver state renderer from computing pose
from driverStateV2 itself to consuming driverMonitoringState.visionPolicyState.pose.
That published yaw is mirrored for RHD (policy.py:269) so the asymmetric yaw
offsets/thresholds can be tuned in a single frame, so the UI's unconditional
sign flip left RHD head tracking backwards. LHD was unaffected, which is why
the refactor looked like a no-op.

Undo the mirror on the UI side.

* Apply suggestions from code review

* fold the sign into the RHD multiplier
2026-08-10 20:02:52 -07:00
Vraj Parikh eecff73850 Sleep shadow mode (#38578)
* dmvision c703670b-6a4a-4743-8f79-69d5486cadf7 (a62f9948 with new sleep head)
* dmvision c703670b-6a4a-4743-8f79-69d5486cadf7 (a62f9948 with new sleep head and same batchnorms)
2026-08-10 10:21:16 -07:00
Adeeb Shihadeh 6d5d1f6917 Rename "live" cereal services (#38601) 2026-08-10 09:30:22 -07:00
commaci-public 434fbad5fc [bot] Update Python packages (#38599)
* Update Python packages

* Fix package update CI

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-08-10 08:36:12 -07:00
Adeeb Shihadeh 65c007a9a2 tools: setup.sh isn't for AGNOS (#38588)
* tools: setup.sh isn't for AGNOS

* Update setup.sh
2026-08-10 08:16:57 -07:00
Nayan 2a16b0fbba ui: screensaver (#1551)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* Option Control

* Need this

* introducing ui_state_sp for py

* param to control stock vs sp ui

* better

* add ui_update callback

* better padding

* this

* listitem -> listitemsp

* Revert "add ui_update callback"

This reverts commit 4da32cc009.

* add show_description method

* remove padding from line separator.
like, WHY? 😩😩

* simplify

* I. SAID. SIMPLIFY.

* AAARGGGGGG.....

* init

* option control value fix

* add all controls

* hide all controls

* lint

* scroller -> scroller_tici

* scroller -> scroller_tici

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* optimizations

* Removed hide for now

* why? because!

* oops

* better

* add param, timeout

* add toggle

* not doing this

* we doing this now @devtekve? FINE!!

* it wasn't me

* really @devtekve? REALLY??

* refresh controls

* ugh

* changes

* fix

* inline everything again

* handle default

* use ui_state param

* better

* fix

* lint

* better

* solve for lint

* default on pls

* screen saver hue distance and increase minimum color shift

* debounce screen saver color changes

* fix dismiss flow

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
2026-08-10 04:27:30 -04:00
Adeeb Shihadeh ea4dbc8936 Remove unused code (#38598) 2026-08-09 21:09:50 -07:00
Jason Wen c6595fd99b offroad alerts: replace comma references with sunnypilot community and sunnylink (#1904) 2026-08-09 23:12:51 -04:00
github-actions[bot] 617fcd4084 [bot] Update Python packages (#1866)
* Update Python packages

* no

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-09 20:22:41 -04:00
Daniel Koepping 855c99bf92 speedup chestnut probe (#38596)
use link-up in scons
2026-08-09 16:26:55 -07:00
Jason Wen 91a53aa161 Controls: Lateral Jerk Torque Controller (#693)
* init

* more init

* keep it alive

* fixes

* more fixes

* more fix

* new submodule for nn data

* bump submodule

* update path to submodule

* spacing???

* update submodule path

* update submodule path

* bump

* dump

* bump

* introduce params

* Add Neural Network Lateral Control toggle to developer panel

This introduces a new toggle for enabling Neural Network Lateral Control (NNLC), providing detailed descriptions of its functionality and compatibility. It includes UI integration, car compatibility checks, and feedback links for unsupported vehicles.

* decouple even more

* static

* codespell

* remove debug

* in structs

* fix import

* convert to capnp

* fixes

* debug

* only initialize if NNLC is enabled or allow to enable

* oops

* fix initialization

* only allow engage if nnlc is off

* fix toggle param

* fix tests

* lint

* fix more test

* capnp test

* try this out

* validate if it's not None

* make it 33 to match

* align

* share the same friction input calculation

* return stock values if not enabled

* unused

* split base and child

* space

* rename

* NeuralNetworkFeedForwardModel

* less

* just use file name

* try this

* more explicit

* rename

* move it

* child class for additional controllers

* rename

* time to split out custom lateral acceleration

* move around

* space

* fix

* TODO-SP

* TODO-SP

* update regardless, it's an extension now

* update name and expose toggle

* ui: sunnypilot Panel -> Steering Panel

* Update selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h

* merge

* move to steering panel

* no need for this

* live params in a thread

* no live for now

* new structs

* more ui

* more flexible

* more ui

* no longer needed

* another ui

* cereal changes

* bump opendbc

* simplify checks

* all in one place

* just Enhanced Lat Accel only

* no submodule for this

* Enhanced Lateral Acceleration: fix bugs, restore NNLC, add UI/schema

Fix 4 bugs in latcontrol_torque_lat_accel.py: missing CI param, wrong
method name (torque_from_lateral_accel → torque_from_lateral_accel_in_torque_space),
self.enabled collision with NNLC, missing output torque recomputation.

Restore NNLC + ExtOverride inheritance chain with Enhanced slotted between
ExtBase and NNLC. Add mutual exclusion constraints, Python UI toggle,
sunnylink schema entries, and controller init/update tests.

* fix lint: remove unused default params list, add ty ignore, fix test fixture

* use the og name

* rename

* need gates

* TODOs

* wrong

* gate them all on init

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
2026-08-09 15:56:33 -04:00
Jason Wen fc4699a747 controls: abstract update_output_torque to ExtBase (#1903) 2026-08-09 15:19:35 -04:00
Jason Wen 9e32dee281 ci: fix process replay with missing sunnypilot service ignores (#1902) 2026-08-09 03:47:59 -04:00
Jason Wen f8d50e062d updated: add fallback mechanism for agnos.json path resolution (#38593)
* updated: add fallback mechanism for agnos.json path resolution

* nvm fallback seems overkill, red diff

* add symlink for tici/agnos.json

---------

Co-authored-by: stefpi <19478336+stefpi@users.noreply.github.com>
2026-08-08 19:55:32 -07:00
Jason Wen f531952be3 ci: sunnypilot process replay (#1900)
* ci: sunnypilot process replay

* ours

* temp

* Revert "temp"

This reverts commit d198cbb327.

* fixes, hopefully

* bump
2026-08-08 19:49:06 -04:00
Adeeb Shihadeh fedea9fe8d camera names that make sense (#38585) 2026-08-08 09:44:21 -07:00
Adeeb Shihadeh 9738db0e39 Move camera stream types out of msgq (#38591) 2026-08-08 09:32:47 -07:00
Adeeb Shihadeh df0bd5e6e3 update translations (#38590) 2026-08-08 08:51:58 -07:00
stef cf790746cc encoder/webrtc: resize streams to baseline 3.1 H264 profile (#38587)
* add h264 profile option and different profiles for comma3X and comma4

* update to high profile

* resize to be baseline 3.1

* fix

* gop 15 and clean

* revert gop 15

* fix white space

* simplify teleoprtc

* bump teleop
2026-08-07 23:20:17 -07:00
Jason Wen 6f2a1d573c ci: sunnypilot CI test routes (#1899) 2026-08-08 01:27:33 -04:00
Daniel Koepping 5701fcad05 ui: show gpu icon longer (#38586) 2026-08-07 21:11:34 -07:00
Daniel Koepping 59f01d212a AGNOS 19.5 (#38582)
* bump version

* add staging

* add production

* fix AGNOS manifest symlink
2026-08-07 21:08:33 -07:00
Daniel Koepping e32b1a344d chestnut: update firmware (#38576)
* chestnut: update firmware to b0805491

* update to ed4e39b7
2026-08-07 20:16:13 -07:00
Adeeb Shihadeh d02355b1a5 rename tici hardware platform to comma (#38581)
* rename tici hardware platform to comma

* no /TICI

* lil more

* lil more

* you had a good life larch64

* lil more

* one more
2026-08-07 20:12:34 -07:00
Daniel Koepping b755d32276 fix telemetry hanging big model (#38565)
* modeld: fail fast when the gpu hangs mid run

* modeld: harden chestnut telemetry

* selfdrived: rm the big model lagging trigger
2026-08-07 19:38:13 -07:00
Adeeb Shihadeh cbda3f7994 Rename hardware/tici/ -> hardware/comma/ (#38580) 2026-08-07 18:49:27 -07:00
Fang Li a75cd2d277 Process eSIM notifications after profile operations per SGP.22 spec (#38241)
* Process eSIM notifications after profile operations

* Clarify sleep purpose after operation in execute_and_process_notifications

Added comment to clarify the purpose of the sleep.
2026-08-07 16:56:49 -07:00
Adeeb Shihadeh 8a88608800 rm qrcode (#38575) 2026-08-07 15:41:28 -07:00
ZwX1616 4eb515b32d DM: Zoom Zoom Model (#38554) 2026-08-07 15:39:59 -07:00
Adeeb Shihadeh 612d97cfdb paramsd: don't learn in reverse gear (#38573) 2026-08-07 14:56:18 -07:00
Adeeb Shihadeh edd938105e remove old regen infra (#38570) 2026-08-07 14:11:12 -07:00
PeterPhuTran 5fadc71a3a swaglog: delete oldest logs on rollover, not newest (#38322) 2026-08-07 13:37:57 -07:00
GavinnnK 0819f5c0fd common: clean up failed atomic writes (#38552)
* common: clean up failed atomic writes

* common: remove atomic write regression test

---------

Co-authored-by: GavinnnK <239280171+GavinnnK@users.noreply.github.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-08-07 13:34:11 -07:00
eFini 66b3590b63 Use Noto fonts for Asian languages (#36514)
ui: use Noto fonts for Asian languages

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-08-07 13:30:58 -07:00
Adeeb Shihadeh b77da00697 rm unused NotoColorEmoji.ttf 2026-08-07 13:20:53 -07:00
Adeeb Shihadeh e6939dbede inline modem serial support (#38564) 2026-08-07 13:19:02 -07:00
Adeeb Shihadeh 96ca1f8ed5 ui: load fonts on the fly (#38567) 2026-08-07 13:10:32 -07:00
Adeeb Shihadeh 6f94f217ff remove PR template 2026-08-07 12:51:07 -07:00
Adeeb Shihadeh 03803d0c82 manager: remove restarter (#38566) 2026-08-07 11:56:48 -07:00
Jimmy be191eb8ac loggerd: only wait to rotate on cameras that are actually streaming (#38362)
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-08-07 11:48:13 -07:00
Adeeb Shihadeh 4e9e9190a5 params: fix cleanup hang on exit (#38562) 2026-08-07 11:29:47 -07:00
Zayan Khan ff9c703289 Fix doc links broken by the openpilot/ restructure (#38467) 2026-08-07 10:41:07 -07:00
Andi Radulescu 2f4981045b esim: report when no eUICC is available (#38497)
* esim: report when no eUICC is available

* remove newline
2026-08-07 10:39:45 -07:00
Matt Purnell faf966adb2 ui: fix value text eliding when it fits (#38502) 2026-08-07 10:39:22 -07:00
Matt Purnell bd7b419a4e ui: don't spam logs when NetworkManager is not available (#38504) 2026-08-07 10:36:52 -07:00
Adeeb Shihadeh 9bf834e64e op: handle AGNOS updates (#38543)
* op: handle AGNOS updates

* use common

* don't just reboot
2026-08-07 10:32:28 -07:00
Daniel Koepping 4adf52fc6f bump opendbc (#38561) 2026-08-07 10:32:08 -07:00
commaci-public 76e900d3ad [bot] Update Python packages (#38553)
Update Python packages

Co-authored-by: Vehicle Researcher <user@comma.ai>
2026-08-07 09:48:06 -07:00
Daniel Koepping 155d206b2b ui: hide egpu icon on disengage (#38557) 2026-08-07 02:40:53 -07:00
Daniel Koepping dd2214a78c chestnut updater (#38546)
* chestnut: production flasher with ROM bootloader recovery

* hardwared: flash chestnut firmware offroad

* comments

* single FW version

* hardwared: fix chestnut flasher path

* chestnut: update firmware to 1d368808

* hardwared: log the flasher output

* comment

* hardwared: flash in a thread

* match panda flashing style

* fix

* chestnut: defer signals and harden the flasher

* chestnut: detect bootloader mode and retry

* comments

* chestnut: name the config dir for its content

* chestnut: name the ROM bootloader consistently

* chestnut: clearer name for the device lookup

* chestnut: spell out the low level names

* chestnut: claim the interface, not the device
2026-08-07 02:35:17 -07:00
Daniel Koepping d252ab5b42 modeld: drop non-finite usbgpu output (#38556) 2026-08-07 01:41:46 -07:00
Daniel Koepping 2002a64d3b modelV2: add big model bool (#38555)
modelV2: add big
2026-08-07 01:30:48 -07:00
MVL 9e0d89968d Honda Clarity: brake hold fix (#1247)
* Nidec Hybrid brake hold fix

* intent fix

* bump opendbc

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-06 22:18:37 -04:00
Jason Wen fec5a97a9e docs: update policy (#1898) 2026-08-06 21:57:56 -04:00
stef 438035ec17 bump teleoprtc (#38551)
bump
2026-08-06 18:47:31 -07:00
Daniel Koepping 5406aba1a2 more chestnut metrics (#38550)
* more chestnut metrics

* comment
2026-08-06 15:13:44 -07:00
Adeeb Shihadeh 17150b6db0 camerad: better names for request and frame IDs (#38549) 2026-08-06 12:14:31 -07:00
Adeeb Shihadeh e7b1ee3a5b jp: fix segment range parsing 2026-08-05 21:42:11 -07:00
Jason Wen 099143ad9d [mici] ui: fix swipe-down in sunnylink and models panels (#1896)
* [mici] ui: fix swipe-down in sunnylink and models panels

* update mici replay test

* [mici] ui: push model sub-views as separate widgets for correct back navigation
2026-08-05 23:22:36 -04:00
Jason Wen 6909aa95ff ui: use sunnypilot and sunnylink branding fonts (#1895)
* ui: use sunnypilot and sunnylink branding fonts

* split
2026-08-05 22:53:04 -04:00
Nayan 5bdc0c23a9 modeld_v2: Support Deep Models (#1887)
* modeld_v2: safe model validation

* fix string

* numpy

* dumb

* god use full attribute names please

* modeld_v2: refactor compile_modeld

* redundant

* CREAM AND SUGAR

* gpu stuffs

* Update fetcher.py

* Update compile_modeld.py

* Update compile_modeld.py

* Update compile_modeld.py

* Update compile_modeld.py

* summary

* Update compile_modeld.py

* Update compile_modeld.py

* Update compile_modeld.py

* i could lie say

* needed

* i could

* Update compile_modeld.py

* done done done

* bye metadata

* simplify

* deeeeep

* oopsie

* i don't know what i'm doing

* it's a supercombo

* wtf. ghostwriter

* i might be blind

* fuck. i AM blind. or dumb. or both.

* hmmmm

* read/open - what's the difference

* realize. that i don't know shit

* whatever

* fix paths

* fuckit. dynamic everything.

* simplify everything.

* fix macos cabana

* np.random.seed is deprecated, use Generator or default rng instead @nayan

* this is annoying me

* too much fluff

* no

* lint be crazy now, man i've been away a while

* back

* fix pkl loader test

* that comment was wrong, its still per model, just compiled at with the input/output shapes

* fix chunking

* dump and assign to cpu

* prev_feat in cpu to prevent corruption

* add todo-sp

* desire me?

* allow legacy

* lint

* shapey

* bye

---------

Co-authored-by: discountchubbs <alexgrant990@gmail.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-05 12:30:28 -07:00
Daniel Koepping d86686fffd log chestnut metrics (#38527)
* log chestnut state

* chestnut metrics

* more metrics

* fix chestnut metrics

* cache chestnut power limit

* use valid

* move to class
2026-08-05 07:21:58 -07:00
Daniel Koepping a7f32be2fd probe chestnut before compiling big model (#38540)
* probe gpu

* only on uncompiled

* fix USB GPU build probe

* allow cold Chestnut initialization

* retry

* comment

* fix
2026-08-05 03:43:54 -07:00
Shane Smiskol 4db21dd4f8 comma four: experimental mode explainer (#37961)
* exp mode confirm dialog

* full

* base

* clean up

* too diff

* fix?

* rmnl

* only toggle if exp mode is confirmed

---------

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-08-05 02:30:00 -07:00
CzokNorris aa0ac919ba pandad: guarantee SPI slave turnaround time (#38464)
pandad: add SPI turnaround delay

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-08-04 22:41:16 -07:00
xiaoxx970 2e4de38eb8 timed: set the clock from GPS in UTC, not local time (#38523) 2026-08-04 21:00:05 -07:00
Adeeb Shihadeh ed777cf5e2 agnos 19.4 (#38537) 2026-08-04 20:55:08 -07:00
Adeeb Shihadeh f7bd4002d6 add libdatachannel to agnos (#38535) 2026-08-04 15:43:46 -07:00
Adeeb Shihadeh 38ef0d2398 agnos 19.2 (#38531) 2026-08-04 11:37:25 -07:00
Toby Penner b361e952c9 Revert "Rebellious Hope model" (#38530)
Revert "Rebellious Hope model (#38475)"

This reverts commit 93f5aa469a.
2026-08-04 10:58:56 -07:00
Adeeb Shihadeh d4211790da agnos 19.1 (#38528) 2026-08-04 09:09:06 -07:00
Shane Smiskol b614253eeb bump opendbc (#38525)
* bump

* bump
2026-08-03 22:45:10 -07:00
Adeeb Shihadeh 6f88b59fdd update release notes 2026-08-03 19:35:27 -07:00
Adeeb Shihadeh e04313cc91 agnos 19 (#38520) 2026-08-03 19:34:00 -07:00
Daniel Koepping 7ef4304e2e handle transient sysfs read errors (#38521)
handle transient USB sysfs read error
2026-08-03 16:49:01 -07:00
Adeeb Shihadeh afa2b390ae ci: fix race condition on closed branches check 2026-08-03 14:49:27 -07:00
YassineYousfi 04847f3805 chestnut: add new usb vid (#38517) 2026-08-03 12:46:46 -07:00
commaci-public 9002016ac5 [bot] Update Python packages (#38516)
Update Python packages

Co-authored-by: Vehicle Researcher <user@comma.ai>
2026-08-03 09:57:06 -07:00
Adeeb Shihadeh 54a7e8926f update athena version test 2026-08-01 23:25:35 -07:00
Adeeb Shihadeh 20eb0d733a athena: publish git commit date (#38513) 2026-08-01 23:07:50 -07:00
Adeeb Shihadeh f09211fcc5 clips fixups (#38512) 2026-08-01 22:28:30 -07:00
Adeeb Shihadeh 7dbdff832f encoderd: add fast hardware clip transcoding (#38508) 2026-08-01 21:39:24 -07:00
Adeeb Shihadeh 88c8f5a520 clips: add getClipChunk (#38510)
* clips: upload over webrtc

* revert that

* clips: transfer over athena
2026-08-01 21:26:31 -07:00
Amy Jeanes 1a07e47228 Tesla: MADS Screen Activation (#1808)
* Tesla: MADS Screen Button Settings

Adds a Tesla vehicle setting (vehicle bus required) to control how many
fingers activate the MADS screen button, or disable it entirely.

Rebased onto current master:
- Migrated the setting metadata from the deprecated params_metadata.json
  (removed in #1862) to the yaml SDUI system: added TeslaMadsScreenButton
  to settings_ui_src/pages/vehicle.yaml and recompiled settings_ui.json.
  Vehicle-bus gating uses the tesla_has_vehicle_bus capability visibility.
- Bumped opendbc_repo to the latest head of sunnypilot/opendbc#459.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JiSH2KAAueUmDe29xvpuAd

* bump

* Tesla: address MADS Screen Activation review feedback

Default TeslaMadsScreenButton to Off for fresh installs and add a param
migration that seeds existing Tesla installs with 3-Finger, preserving the
previous always-on behaviour. Brand resolves from CarPlatformBundle, falling
back to CarParamsPersistent so auto-fingerprinted Teslas are covered too.

Rename the setting to "MADS Screen Activation", hyphenate the finger-count
labels, and reword the description to use <br> (descriptions render as HTML)
with a note that a higher finger count may reduce accidental activations.
Applied both on-device and in sunnylink.

Also fix test_tesla_with_vehicle_bus_uses_param, which broke once
get_mads_limited_brands started reading TeslaMadsScreenButton from the same
blanket params mock, and add coverage for the screen-button-Off path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5icDp7zZ49gpyC2mpCfF1

* bump opendbc

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-08-01 23:40:52 -04:00
Adeeb Shihadeh 5037016901 loggerd: move qcom decoder into v4l_decoder (#38511) 2026-08-01 20:40:08 -07:00
Jason Wen 50b860c928 Reapply "plannerd: check all services for validity (#38341)"
This reverts commit 0265ae5f76.
2026-08-01 23:27:36 -04:00
YassineYousfi bf74ce5447 Be Right Here Model 🏃 (big) (#38499)
* 8e649fab-fb3d-4407-b99c-70f27df8b282/400

* bump tg

* TC_OPT
2026-08-01 20:27:29 -07:00
Jason Wen 978ec800fe plannerd & selfdriveStateSP: poll modelV2, relay button state via bitmask (#1893)
* selfdrived: continuous button state and release counters in selfdriveStateSP

* tests and more
2026-08-01 23:05:03 -04:00
Adeeb Shihadeh 3643dfbef0 simple clips (#38509) 2026-08-01 19:17:44 -07:00
Shane Smiskol cc83d8615d bump opendbc (#38507)
bump
2026-08-01 06:26:49 -07:00
Shane Smiskol d3058de5c1 ldw: fix blinker cooldown using wrong time step (#38501)
Co-authored-by: Shane Smiskol <trivisionzero@gmail.com>
2026-08-01 06:26:31 -07:00
Daniel Koepping a2ee4dfffc AGNOS 18.8 (#38496)
* bump version

* add staging

* update to production
2026-07-31 11:39:39 -07:00
Adeeb Shihadeh b72f6e9f7d we write it ourselves 2026-07-31 11:39:17 -07:00
Daniel Koepping d4e29ec945 fade out eGPU icon (#38506)
Fade GPU logo on engagement
2026-07-31 11:26:03 -07:00
Daniel Koepping e3ba6492f9 fix big model alert (#38500) 2026-07-30 17:33:25 -07:00
Daniel Koepping caee482fe7 usb logging on topology change (#38495)
speedup usb detection
2026-07-30 08:26:02 -07:00
Daniel Koepping 1c54ff853a change big model alerts (#38490)
* fix big model failed transition

* fix eGPU onroad transitions

* don't alert on eGPU hot connect

* green

* big
2026-07-30 08:20:25 -07:00
Shane Smiskol c9f0d296b1 rename CarEvents (#38493) 2026-07-29 20:08:20 -07:00
Shane Smiskol 6ac27d491b Add carNotReady noEntry (#38485)
* car not ready

* missing

* missing

* bump
2026-07-29 20:02:35 -07:00
Daniel Koepping 47a7524595 use GPU icon onroad (#38492)
* rm permanent alert

* use gpu icon instead of BIG text

* add crossed state

* rm state

* opacity
2026-07-29 17:51:13 -07:00
Daniel Koepping 0088cb7ef0 revert tinygrad bump (#38489) 2026-07-29 13:07:49 -07:00
Daniel Koepping 3ec980477d show big model failed (#38487) 2026-07-29 10:22:32 -07:00
Shane Smiskol e4d152e1a7 bump opendbc (#38486)
bump
2026-07-29 02:51:42 -07:00
Shane Smiskol 4cfdcea1d1 cabana: fix macOS build and --zmq bridge path (#38484)
Two macOS-only cabana fixes:

- signalview.h: rename the SignalView::setModel parameter that shadowed
  the `model` member. Newer clang (Apple clang 21) folds
  "parameter shadows a field" into plain -Wshadow, so with -Werror this
  breaks the macOS build. Older clang (CI's image) only flags it under
  -Wshadow-all, which is why CI stays green. The rename is clean under
  any -Wshadow level and gcc.

- devicestream.cc: the --zmq bridge path had a stale `openpilot/`
  segment (`../../openpilot/cereal/messaging/bridge`), a monorepo-layout
  leftover that resolved to a non-existent triple-`openpilot` path, so
  the bridge failed to exec and live streaming was broken. Drop it to
  `../../cereal/messaging/bridge`.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 20:00:25 -07:00
Daniel Koepping 952d2cab8a Revert "profile usb gpu link stability (#38368)" (#38483) 2026-07-28 18:15:11 -07:00
Daniel Koepping 799551c2d6 rm complete sound (#38481)
* rm complete sound for now

* deprecate bigModelReady
2026-07-28 17:25:36 -07:00
Adeeb Shihadeh 0b7bd2561d unittest speedups (#38472)
* unittest speedups

* fix deleter test helper signature

* document small deleter test files

* test uploader clears locks on startup
2026-07-28 16:52:41 -07:00
Adeeb Shihadeh 83000c571c no slow unittests (#38480)
* rm test_regen

* not slow

* skip bridge

* rm that one too

* just op test

* just test
2026-07-28 16:42:38 -07:00
Daniel Koepping 294635beb5 big model failed alert (#38479) 2026-07-28 16:25:34 -07:00
Daniel Koepping 3040f55382 ui: breathe BIG while loading model (#38478) 2026-07-28 15:52:20 -07:00
Adeeb Shihadeh b4c9403211 gitignore intermediate build products 2026-07-28 10:05:13 -07:00
Adeeb Shihadeh e04bb46b68 Update Python packages (#38477)
* Update Python packages

* fix DeviceLike awake protocol

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
2026-07-28 09:20:33 -07:00
Shane Smiskol e43de73311 bump opendbc (#38476)
bump
2026-07-28 04:18:47 -07:00
stef 02b6cef15c webrtc: cleanup api (#38447)
* add support for multi-track with wideRoad single track as default

* remove stale debug mode

* multi track support

* make cameras list optional

* remove multi camera support from athena, make it only local

* update to teleop master
2026-07-27 20:47:43 -07:00
Toby Penner 93f5aa469a Rebellious Hope model (#38475)
d0c60276-564d-4b97-a59c-f1ab853d8c26/400
2026-07-27 15:35:28 -07:00
Adeeb Shihadeh 74009870ab cleanup banned APIs 2026-07-26 21:28:30 -07:00
Adeeb Shihadeh 1d7b6f0f9f dev is tools 2026-07-26 21:27:34 -07:00
Adeeb Shihadeh 98e7c4f987 replace pytest with lil custom runner (#38471) 2026-07-26 21:26:19 -07:00
stef 4dab10e98b rm pillow package (#38466) 2026-07-26 15:04:56 -07:00
stef df90fc2526 remove PIL (#38320)
* replace PIL in emoji

* replace PIL in test

* fix uv.lock

* fix uv lock

* revert emoji removal from this P R

* revert merge artifact

* remove more merge artifacts

* fix lock

* red diff

* red diff +
2026-07-26 14:57:54 -07:00
stef f29eb5d25c ui: remove emoji support (#38361)
* remove emoji support, replace with png

* upload icons

* fix icon left/right positioning

* simplify rewrite

* simplify firehose

* simplify custom software set up text

* lint

* fix custom software

* move fix to diff pr

---------

Co-authored-by: elkoled <elkoled@gmail.com>
2026-07-26 14:37:19 -07:00
Adeeb Shihadeh 0bf99879d1 docs fixups 2026-07-26 14:34:43 -07:00
Adeeb Shihadeh e1db0f59f1 docs: replace zensical with dumb html template (#38462) 2026-07-26 13:56:32 -07:00
Adeeb Shihadeh 5bcff3f87d bump opendbc: rm jinja (#38463) 2026-07-26 12:18:11 -07:00
Adeeb Shihadeh 9a39ccc66f tools/auth: use any open port (#38458) 2026-07-26 08:52:49 -07:00
Jason Wen 3a05c03079 ci: no more docker (#1886) 2026-07-25 15:34:36 -04:00
Adeeb Shihadeh 27122bbd28 lint: check indentation (#38454)
* lint: check indentation

* cleanup

* happy

* that was too much

* happy
2026-07-25 10:29:37 -07:00
Eitan fd22de1c9a SCC-M: fix operator precedence in quadratic roots (#1816)
* controls/scc: fix operator precedence in map controller quadratic roots

* not async

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-07-25 09:37:18 -04:00
Christopher Haucke ee3583df33 [tizi/tici] ui: Camera offset controls (#1813)
* Camera offset controls

* final

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-07-25 09:15:05 -04:00
Jason Wen 7801bdf0cc Reapply "mapd: ignore in plannerd health check (#1880)"
This reverts commit 9163d1cb74.
2026-07-25 01:36:05 -04:00
Jason Wen 0265ae5f76 Revert "plannerd: check all services for validity (#38341)"
This reverts commit a9ffebe96e.
2026-07-25 01:27:25 -04:00
Jason Wen 9163d1cb74 Revert "mapd: ignore in plannerd health check (#1880)"
This reverts commit 2c334ede44.
2026-07-25 01:27:11 -04:00
Adeeb Shihadeh c37855d113 op: merge setup and install (#38450) 2026-07-24 11:03:01 -07:00
Toby Penner 9225dac433 Revert "Rebel Legion model (#38164)" (#38449)
This reverts commit 2895346746.
2026-07-24 10:21:01 -07:00
Jason Wen a0cc313fdc version: bump to 2026.003.000 2026-07-24 11:36:33 -04:00
Jason Wen 93bb596a2e Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1883) 2026-07-24 11:18:35 -04:00
Jason Wen 41b14fa595 Merge commaai/openpilot fdd1df79 into sync-20260721 2026-07-24 11:07:06 -04:00
stef ee6f374621 bump teleoprtc (#38446)
bump
2026-07-23 20:45:49 -07:00
stef 0fdfaf737f ui(body): flip eye direction (#38445)
flip eye
2026-07-23 20:42:51 -07:00
stef 71e29583cb Revert "bump teleoprtc" (#38444)
Revert "bump teleoprtc (#38443)"

This reverts commit 171f271d11.
2026-07-23 20:39:22 -07:00
stef 171f271d11 bump teleoprtc (#38443)
bump
2026-07-23 19:59:21 -07:00
Adeeb Shihadeh 1a753ad9ae test_models moved to opendbc (#38441)
* remove test_models

* rm that

* update test_models docs

* restore car model test tool

* leave dependencies unchanged
2026-07-23 19:50:08 -07:00
Adeeb Shihadeh 9c312e91e7 release: include nested packages in PYTHONPATH (#38442) 2026-07-23 19:49:31 -07:00
Adeeb Shihadeh 4d4d6803e5 modeld: remove tinygrad probe on build (#38439) 2026-07-23 19:11:23 -07:00
Harald Schäfer 9c21b92c4d should_stop: use shared helper (#38438) 2026-07-23 18:36:31 -07:00
Daniel Koepping ab7284fc8a AGNOS 18.7 (#38416)
* bump version

* update AGNOS 18.7

* update to production
2026-07-23 15:51:34 -07:00
stef 554d8d2114 webrtcd: timeout after 5 minutes (#38437)
* webrtc timeout after 5 mins

* omit timeout for body
2026-07-23 15:11:22 -07:00
Daniel Koepping b392328c35 big model loading permanent alert (#38436)
show big model loading as permanent alert
2026-07-23 15:00:25 -07:00
Adeeb Shihadeh ddf782f08a big release 2026-07-23 13:36:12 -07:00
Adeeb Shihadeh e04fd3c724 rm private reporter comment (#38431) 2026-07-23 10:23:08 -07:00
Adeeb Shihadeh 6b47a5b6b7 tools: decompress in the python downloader (#38430)
* tools: decompress in the python downloader

* and zstd
2026-07-23 10:06:44 -07:00
Daniel Koepping 3601b850c2 new sounds for 3x (#38404)
rm tizi sounds
2026-07-23 09:57:47 -07:00
Adeeb Shihadeh cfa8e5bf03 gitignore test_swaglog 2026-07-23 09:42:36 -07:00
Adeeb Shihadeh e1c69719c6 bump panda (#38429) 2026-07-23 09:35:05 -07:00
Adeeb Shihadeh a8bb6b5aee rm hypothesis (#38428) 2026-07-23 09:17:18 -07:00
Adeeb Shihadeh 87fde8a058 pyproject cleanup 2026-07-23 08:31:54 -07:00
stef 66300306f4 ui(body): hide body layout if no car fingerprint (#38427)
* hide body layout if car params is none

* reposition

* refactor
2026-07-22 21:03:25 -07:00
Harald Schäfer 2895346746 Rebel Legion model (#38164)
* 3e5b7d32-e114-4517-96b4-b3a2448fd939/400

* 9ffd502c-01d0-40f3-93c2-07a4d3e4445c/400

* 1afc2042-8761-4663-ae6a-d593b13defd0/400

* d4b183da-3b8c-4ff7-8927-e3e7216ddb86/400

* 60a46520-f02f-49c5-8eae-e0f8de92af07/400

* ca63f597-de00-478f-970c-302069a59130/400

* 0322768e-5d7e-46e6-9712-d52de312e1da/400

* d25f38c8-8689-42a7-8326-f0c7ce00625f

* f14f0be4-bf1e-4dc3-914d-3d9a23b5a05a/400

* lil smooth

* 03dfde49-54e6-4129-b05f-24b0b0c7e5f0/400

* 6d9d6f8a-5c82-41f6-92aa-4c1a11eb5645/400

* ci: fix model review tinygrad import

* ci: skip unfetched big model in review

---------

Co-authored-by: Toby Penner <toby@comma.ai>
2026-07-22 18:12:13 -07:00
Shane Smiskol 49c3b8fc34 joystick: add override state (#38423)
fix js joverride
2026-07-22 17:24:38 -07:00
Daniel Koepping 0bac3c9039 big model loaded chime (#38420)
play chime when big model is loaded
2026-07-22 16:44:16 -07:00
Daniel Koepping 6335db69bd eGPU UI (#38419) 2026-07-22 16:23:32 -07:00
Shane Smiskol 05f42f752b joystickd: use should_stop (#38421)
* param

* not really needed now
2026-07-22 16:07:43 -07:00
Daniel Koepping 1791057369 add big model fallback (#38303)
* big model fallback

* reorder
2026-07-22 15:13:46 -07:00
Harald Schäfer 736f3b1a00 longitudinal: move cruise speed limit out of the MPC (#38367)
*
2026-07-22 14:53:56 -07:00
Adeeb Shihadeh 2a1c44c904 rm scons cython tool (#38417) 2026-07-22 13:43:23 -07:00
Adeeb Shihadeh 74ac5ef9a0 try ctypes params (#38411)
* try cffi params

* ctypes

* lil more

* rm cython

* lil more

* that was fine

* lil more

* Drop unrelated Params concurrency changes

* Clean up ctypes Params build integration

* just c

* Clarify Params exception translation

* Expand Params C wrappers

* lil more
2026-07-22 13:26:28 -07:00
Daniel Koepping aac9d9ecf4 ui: show eGPU icon when enumerated (#38407)
* ui: react to egpu hotplug instead of waiting for modeld params

* use chestnutPresent
2026-07-22 10:25:58 -07:00
Adeeb Shihadeh 08fd571272 add -pipe to build (#38410) 2026-07-21 22:35:27 -07:00
Adeeb Shihadeh 75d590fb90 replace catch2 tests for 20% faster builds (#38408)
* replace catch2 tests

* lil more

* lil more

* rm dep!
2026-07-21 22:20:09 -07:00
Adeeb Shihadeh 576de9c7ed speed up jotpluggler build (#38406) 2026-07-21 21:28:21 -07:00
Adeeb Shihadeh 86753ea93d better alerts (#38403)
* better alerts

* just rm that

* lil more
2026-07-21 21:01:19 -07:00
Adeeb Shihadeh c9b9fd56ea gc dead events (#38402) 2026-07-21 18:49:47 -07:00
Adeeb Shihadeh 765d6fff9b remove audio feedback (#38401)
* remove audio feedback

* rm feedbackd

* backwards
2026-07-21 18:30:20 -07:00
Daniel Koepping 9e02936717 AGNOS 18.6 (#38393)
* version

* agnos 18.6

* fix
2026-07-21 16:23:27 -07:00
Armand du Parc Locmaria f1977eaa93 bump tg (#38398)
* bump tg

* slightly nicer
2026-07-21 16:08:19 -07:00
Adeeb Shihadeh eb6c9c7f0d Run device tests directly (#38397) 2026-07-21 12:52:48 -07:00
Adeeb Shihadeh 1262e26899 athena: bring back port migration
This reverts commit d9596fa998.
2026-07-21 12:51:45 -07:00
Adeeb Shihadeh 911f07ee87 convert tests to unittest (#38387) 2026-07-21 11:02:34 -07:00
Adeeb Shihadeh ebb5cc12f5 ci: don't pay the big model tax yet (#38371)
* ci: don't pay the big model tax yet

* skip big in release
2026-07-21 10:55:26 -07:00
Adeeb Shihadeh fdd1df79fb longitudinal: remove per-car stopping tunes (#38394)
* remove per-car longitudinal stopping tunes

* bump opendbc

* lil more

* lil more

* lil more

* lil more

* revert modeld for now
2026-07-20 19:52:23 -07:00
Adeeb Shihadeh 031b1ad0a3 longcontrol: remove starting state (#38340) 2026-07-20 16:30:42 -07:00
Daniel Koepping b1f2e638de fix branch cleanup burning GH API requests (#38392)
* ci: stop branch cleanup from burning API rate limit

* simpler
2026-07-20 16:00:20 -07:00
Daniel Koepping c20263d985 speedup chunk reading (#38389)
read chunk directly into caller buffer
2026-07-20 14:46:33 -07:00
Daniel Koepping 5472e69e35 New sounds (#38154)
* new sounds

* soundd: fix sound cutoff

* update sounds

* add max warning_immediate variant

* mici: play sounds button in developer settings

* rename sounds

* add complete sound

* update sounds

* raise soundfloor by 5%

* update sounds

* play unused sounds

* update sounds

* bump opendbc

* Revert "play unused sounds"

This reverts commit 47e538da2200e700804714576baa5f8c0f28cdb3.

* Revert "mici: play sounds button in developer settings"

This reverts commit 149141bb8a21bec7a04dc8f231d0b6b9120cc7c2.

* remove unused sounds

* raise soundfloor by 5%

* opendbc

* add space
2026-07-20 14:31:55 -07:00
Jason Wen 2c334ede44 mapd: ignore in plannerd health check (#1880)
* mapd: ignore in plannerd health check

* more
2026-07-20 15:05:19 -04:00
Daniel Koepping 78909dac73 soundd: add complete sound (#38390) 2026-07-20 10:50:18 -07:00
Daniel Koepping 24a9b6dae5 profile usb gpu link stability (#38368)
* profile usb gpu link before using

* ci
2026-07-20 10:36:09 -07:00
Jason Wen bf1b93f757 Revert "agnos: split launch for c3 and c3x to support custom agnos" (#1879)
Revert "agnos: split launch for c3 and c3x to support custom agnos (#1186)"

This reverts commit 54174d1e
2026-07-20 09:25:49 -04:00
Jason Wen bae7e49626 Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1876) 2026-07-19 23:51:06 -04:00
royjr 560cde61ae sync: fix upstream conflicts
Co-authored-by: royjr <royjr96@gmail.com>
2026-07-19 23:18:43 -04:00
Adeeb Shihadeh d9596fa998 gc old athena and uploader migration code 2026-07-19 14:55:42 -07:00
Adeeb Shihadeh e124d6df9b more dead code gc 2026-07-19 14:52:04 -07:00
Adeeb Shihadeh 3dbf02f803 cereal: gc dead ZMQ branches in tests 2026-07-19 14:45:26 -07:00
Adeeb Shihadeh fef29ad225 start porting tests to unittest style (#38384) 2026-07-19 14:33:26 -07:00
Adeeb Shihadeh 157c7080ce bump opendbc (#38370) 2026-07-19 12:21:04 -07:00
Adeeb Shihadeh caa9e770cc manager: remove preimport stage (#38381) 2026-07-19 12:20:25 -07:00
Adeeb Shihadeh 24893ebadb rm pre-commit-hooks (#38382)
* rm pre-commit-hooks

* rm test for the tests

* lil more
2026-07-19 12:10:48 -07:00
Adeeb Shihadeh 39e12c8bb1 rm cffi, it's a raylib transitive dep 2026-07-19 11:14:40 -07:00
Adeeb Shihadeh f0d93eb32d more ty, part 2 (#38379) 2026-07-19 11:02:00 -07:00
Adeeb Shihadeh 19ecc37de8 more ty (#38378)
* enable no matching overload

* enable call non callable

* enable unsupported-operator

* enable not subscriptable

* refactor pass
2026-07-19 09:45:46 -07:00
Adeeb Shihadeh ecac2d386b these go in tools 2026-07-19 09:02:02 -07:00
Adeeb Shihadeh e475d10adc more ruff (#38377)
* lil more ruff

* no exclusions!

* rm nb exception

* and generated

* not used

* c408

* random

* all

* unittest is fine
2026-07-19 08:51:16 -07:00
Adeeb Shihadeh 7d74c3c99b rm setuptools (#38376)
* rm setuptools

* lock
2026-07-19 08:25:31 -07:00
Adeeb Shihadeh cefcf10ec3 rm jinja2 2026-07-19 08:15:01 -07:00
Adeeb Shihadeh 1c07e00759 rm xattr (#38373)
* rm xattr

* doesn't add new coverage
2026-07-18 13:55:47 -07:00
Adeeb Shihadeh f0841b827a remove av (#38366) 2026-07-18 09:04:41 -07:00
Adeeb Shihadeh 3f49e2d33c jp: add thumbnail source (#38363) 2026-07-18 09:02:08 -07:00
Adeeb Shihadeh 3f93b00120 webrtc: remove av (#38369) 2026-07-18 08:52:02 -07:00
stef c21b0821da fix(ui): label alignment and text with icon (#38365)
fix align bottom and text positioning with icon
2026-07-17 16:23:44 -07:00
Adeeb Shihadeh b9f25f8a43 webrtcd: move to libdatachannel (#38228)
* try libdatachannel

* fix uv lock

* fix uv lock

* remove datachannel abstraction and just use libdatachannel

* clean

* fix livestream bitrate controller

* fix sample

* fix import breaking ci

* clean up and add catches

* bump tele

* move to teleop master

* fix linter

* .

* remove libdatachannel explicit reference from pyproject.toml

* add PyJWT crypto and catch in registration

* spelling

* add crypto back to lock

---------

Co-authored-by: stefpi <19478336+stefpi@users.noreply.github.com>
2026-07-17 15:28:39 -07:00
ZwX1616 c8786d930d DM: reasonable lockout ramp up (#38358) 2026-07-17 15:27:20 -07:00
Adeeb Shihadeh 3a55f31dc5 agnos 18.5 (#38302) 2026-07-17 15:24:34 -07:00
Adeeb Shihadeh a04c045cd7 cabana: de-Qt, part 3 (#38360) 2026-07-17 09:31:16 -07:00
rkdune 9aa5c3d2c6 expose submodule dependencies as an extra 2026-07-16 18:16:08 -07:00
Adeeb Shihadeh 5d23a78c77 cabana: de-Qt, part 2 (#38359) 2026-07-16 14:35:45 -07:00
Adeeb Shihadeh 06a73f538e cabana: de-Qt, part 1 (#38357)
* cabana: de-Qt, part 1

* lil more
2026-07-16 13:45:57 -07:00
Shane Smiskol 60716edc37 Fix thumbnail creation (#38354)
it's not a stream
2026-07-15 22:51:41 -07:00
Jason Wen 14318d2f09 Merge commit '91b067cca0913d0cc9f8b5dab68e3302e278efdc' into sync-20260716
# Conflicts:
#	.github/labeler.yaml
#	.github/workflows/auto_pr_review.yaml
#	.github/workflows/docs.yaml
#	.github/workflows/release.yaml
#	.github/workflows/repo-maintenance.yaml
#	.github/workflows/tests.yaml
#	.github/workflows/ui_preview.yaml
#	README.md
#	SConstruct
#	conftest.py
#	docs/CARS.md
#	msgq_repo
#	opendbc_repo
#	openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py
#	openpilot/common/api.py
#	openpilot/common/hardware/hw.py
#	openpilot/common/version.py
#	openpilot/selfdrive/assets/fonts/Audiowide-Regular.ttf
#	openpilot/selfdrive/assets/sounds/prompt_single_high.wav
#	openpilot/selfdrive/assets/sounds/prompt_single_low.wav
#	openpilot/selfdrive/car/card.py
#	openpilot/selfdrive/car/helpers.py
#	openpilot/selfdrive/car/tests/test_car_interfaces.py
#	openpilot/selfdrive/car/tests/test_cruise_speed.py
#	openpilot/selfdrive/controls/lib/desire_helper.py
#	openpilot/selfdrive/controls/lib/longcontrol.py
#	openpilot/selfdrive/controls/plannerd.py
#	openpilot/selfdrive/controls/radard.py
#	openpilot/selfdrive/controls/tests/test_longcontrol.py
#	openpilot/selfdrive/modeld/compile_warp.py
#	openpilot/selfdrive/modeld/modeld.py
#	openpilot/selfdrive/monitoring/policy.py
#	openpilot/selfdrive/monitoring/test_monitoring.py
#	openpilot/selfdrive/selfdrived/events.py
#	openpilot/selfdrive/selfdrived/selfdrived.py
#	openpilot/selfdrive/ui/layouts/onboarding.py
#	openpilot/selfdrive/ui/mici/layouts/onboarding.py
#	openpilot/selfdrive/ui/soundd.py
#	openpilot/selfdrive/ui/tests/diff/replay.py
#	openpilot/selfdrive/ui/translations/app.pot
#	openpilot/system/athena/athenad.py
#	openpilot/system/athena/manage_athenad.py
#	openpilot/system/hardware/hardwared.py
#	openpilot/system/loggerd/config.py
#	openpilot/system/manager/github_runner.sh
#	openpilot/system/manager/manager.py
#	openpilot/system/updated/updated.py
#	panda
#	pyproject.toml
#	scripts/lint/lint.sh
#	system/manager/process_config.py
#	system/statsd.py
#	tinygrad_repo
#	tools/release/build_release.sh
#	tools/release/build_stripped.sh
#	uv.lock
2026-07-16 00:58:32 -04:00
Adeeb Shihadeh 61608db786 jp: fix linking on macOS (#38353)
* jp: fix linking on macOS

* mv to root
2026-07-15 21:39:47 -07:00
Jason Wen 70f2c3ac79 Sync: commaai/openpilot:mastersunnypilot/sunnypilot:master (#1875) 2026-07-15 23:45:06 -04:00
Jason Wen a43fc920b5 Merge commit 'b871364e1f48af04113f05c0f6604a9c42f57ac7' into sync-20260715-1
# Conflicts:
#	.github/workflows/prebuilt.yaml
#	common/api.py
#	common/params_keys.h
#	opendbc_repo
#	selfdrive/controls/controlsd.py
#	selfdrive/controls/lib/desire_helper.py
#	selfdrive/modeld/modeld.py
#	selfdrive/ui/mici/layouts/settings/device.py
#	selfdrive/ui/soundd.py
#	system/athena/athenad.py
#	system/athena/registration.py
#	system/hardware/hardwared.py
#	system/loggerd/deleter.py
#	system/manager/manager.py
#	system/manager/process_config.py
#	system/sentry.py
#	system/updated/updated.py
#	tinygrad_repo
#	tools/scripts/uiview.py
2026-07-15 23:33:33 -04:00
Adeeb Shihadeh 91b067cca0 jenkins speedups (#38328)
* power draw first

* speedup encoder

* speed up hardware test suites

* refresh IRQ actions after hardware setup

* skip unused big model in device checkout

* move LFS pruning off device checkout path

* wtf is that for?

* reverts

* locality

* rm that
2026-07-15 18:43:48 -07:00
Adeeb Shihadeh 41886a90da rm redundant params tests 2026-07-15 18:40:08 -07:00
Daniel Koepping 680dd7cf3b bump opendbc (#38352) 2026-07-15 16:52:25 -07:00
Adeeb Shihadeh db99c8e5a6 op: add -u to scons command line (#38348) 2026-07-15 15:39:56 -07:00
Jason Wen 48a5bdc8e4 Revert "msgq: point back to comma's" (#1874)
* Revert "msgq: point back to comma's (#1498)"

This reverts commit 04eac60983.

* point
2026-07-15 17:18:14 -04:00
Jason Wen 79b79edd2f MADS: add tests and prevent re-enable at standstill in Pause mode (#1871)
* tests

* lateral mismatch

* no unittest oops
2026-07-15 17:02:34 -04:00
Adeeb Shihadeh 2b1fd89064 annotate radar fields (#38347) 2026-07-15 12:01:46 -07:00
Adeeb Shihadeh ec72ee096d cleanup unused radar fields (#38346)
* cleanup unused radar fields

* bump opendbc

* this is nice for debugging

* bring that back too

* bring these back

* more revert
2026-07-15 11:52:31 -07:00
Harald Schäfer eee13472ce longitudinal: add e2e flag to tuning report (#38345) 2026-07-15 10:31:03 -07:00
Shane Smiskol d275c65f75 fix ui.py 2026-07-14 23:53:52 -07:00
Adeeb Shihadeh a9ffebe96e plannerd: check all services for validity (#38341)
* longitudinal planner: carControl must be valid

* plannerd: check all services for validity
2026-07-14 20:40:15 -07:00
Adeeb Shihadeh 45661ee8c0 tmp disable ui_report 2026-07-14 19:24:16 -07:00
Kacper Rączy c16039e0bc lagd: lower max lag (#38307)
* Max lag 0.6

* Fix tests

* 650ms
2026-07-15 02:17:30 +00:00
Adeeb Shihadeh d1e143ac98 longcontrol: simplify state machine (#38337) 2026-07-14 19:01:22 -07:00
Adeeb Shihadeh 45b53cf66a Rename LeadData.status to LeadData.present (#38339)
* rename LeadData.status to LeadData.present

* rename LeadData.status to LeadData.present

* resolve longitudinal MPC merge conflict
2026-07-14 18:54:07 -07:00
Daniel Koepping cf8371c2c5 add usb link err counter (#38338)
* add usb link err counter

* ci
2026-07-14 17:44:46 -07:00
Adeeb Shihadeh f42dbbb01c longitudinal planner: remove unused model trajectory parsing (#38336) 2026-07-14 16:01:45 -07:00
Adeeb Shihadeh 4fd4ddb438 long_mpc: remove unused solver state and timings (#38335) 2026-07-14 15:55:04 -07:00
Adeeb Shihadeh 503531ab38 replace psutil with simple /proc helper (#38326)
* replace psutil with simple /proc helper

* mv to common
2026-07-11 13:51:22 -07:00
Adeeb Shihadeh a52da0d3df bump msgq, no runtime deps 2026-07-11 13:08:39 -07:00
Adeeb Shihadeh a26254304f Move eigen dependency into rednose (#38321)
* Move eigen dependency into rednose

* Declare complete rednose package dependencies

* Update rednose submodule

* Update rednose submodule

* Update rednose submodule

* Fix rednose dependency integration

* bump rednose
2026-07-11 12:59:49 -07:00
stef c91731b13a athena: remove take snapshot (#38319)
remove snapshot
2026-07-09 19:15:23 -07:00
Adeeb Shihadeh 60924556f6 athena: minimal json-rpc implementation (#38318)
* athena: minimal json-rpc implementation

* lil more
2026-07-09 16:55:49 -07:00
Adeeb Shihadeh ee11c24cf4 ci: test op setup with empty submodules (#38316)
* ci: test op setup with empty submodules

* ci: init submodules via op setup instead of checkout

* ci: keep submodules for build release

* ci: drop explicit submodules: false

* ci: remove checkout comment
2026-07-09 12:26:56 -07:00
Adeeb Shihadeh 6392096de5 op setup: init submodules before uv sync (#38314) 2026-07-09 09:31:34 -07:00
Adeeb Shihadeh 39117a5879 remove submodule symlinks (#38312) 2026-07-08 21:30:17 -07:00
Adeeb Shihadeh 1478d0c476 Fix diff report artifact download (#38313) 2026-07-08 21:25:43 -07:00
Adeeb Shihadeh f283f6703c Revert "try removing submodule symlinks (#38310)"
This reverts commit b827c0f55e.
2026-07-08 21:12:12 -07:00
Adeeb Shihadeh b827c0f55e try removing submodule symlinks (#38310) 2026-07-08 21:11:39 -07:00
Adeeb Shihadeh bf5540c364 rm pyserial (#38311)
* rm pyserial

* lil more

* lil more
2026-07-08 21:06:53 -07:00
Adeeb Shihadeh 3b3f5967ec rm crcmod-plus (#38309)
* rm crcmod-plus

* cleanup
2026-07-08 20:43:12 -07:00
Adeeb Shihadeh 1e49eac4d1 rm libyuv (#38306)
* rm libyuv

* cleanup
2026-07-08 20:27:27 -07:00
Adeeb Shihadeh 9877f6ac0e ffmpeg: use shared libraries (#38308) 2026-07-08 20:20:24 -07:00
Adeeb Shihadeh 1ab1ed7453 Use PyPI comma-deps packages (#38295) 2026-07-08 19:35:39 -07:00
Adeeb Shihadeh cb82722dd5 remove libjpeg (#38305)
* remove libjpeg

* lil smaller

* happy linter
2026-07-08 12:21:22 -07:00
Adeeb Shihadeh 960c988355 Replace xvfb with headless raylib backend (#38298) 2026-07-08 11:53:47 -07:00
Shane Smiskol 05cf8023a9 fix cabana 2026-07-07 16:14:26 -07:00
Shane Smiskol 1b6e127e68 fix juggle.py 2026-07-07 15:40:56 -07:00
Adeeb Shihadeh c02cf706a6 fix cabana launch script 2026-07-07 14:09:32 -07:00
Adeeb Shihadeh 0d93f0e820 declare submodules as dependencies in pyproject (#38300)
* declare submodules as dependencies in pyproject

* spidev

* lock
2026-07-07 10:36:49 -07:00
stef 6de5d2c23a revert checkout@v7 to fix nightly release (#38294)
* revert back to checkout@v4

* add comment
2026-07-05 20:04:06 -07:00
Adeeb Shihadeh 92f3d24c8a Remove lateral MPC library (#38281) 2026-07-03 17:17:51 -07:00
Adeeb Shihadeh d606014ce2 ci: delete closed PR branches automatically (#38280) 2026-07-03 09:45:08 -07:00
commaci-public 2aa934e7c2 [bot] Update Python packages (#38263)
Update Python packages

Co-authored-by: Vehicle Researcher <user@comma.ai>
2026-07-02 19:04:06 -07:00
Adeeb Shihadeh 70a6efb8fc ci: update GitHub Actions to latest versions (#38278)
* ci: update GitHub Actions to latest versions

Bump all workflow actions to their latest releases, including major
updates for checkout (v7), upload-artifact (v7), github-script (v9),
action-download-artifact (v21), and thollander comment action (v3).

* ci: pin third-party actions to commit SHAs

Keep official GitHub actions on version tags, but pin community
actions to immutable commit hashes for supply-chain safety.
2026-07-02 19:00:35 -07:00
Adeeb Shihadeh 07ec389f4c bump ty (#38277)
* bump ty

* cleanup
2026-07-02 18:48:53 -07:00
Adeeb Shihadeh 9d03e737dd test_onroad: fix service timing threshold 2026-07-02 14:48:32 -07:00
YassineYousfi 4532320fb3 desire: enter preLaneChange if blinker before engaged (#38276) 2026-07-01 18:52:11 -07:00
YassineYousfi 4c5bef5840 rm modeld/models README 2026-07-01 18:50:52 -07:00
Jimmy 8e0ba7a91e modeld: use correct intrinsics for extra transform when wide camera disabled (#38239)
use correct intrinsics for extra transform when wide camera disabled
2026-07-01 17:17:25 -07:00
Armand du Parc Locmaria fa0c6876d3 lebowski model (#38268)
* Reapply "usbgpu: never materialize model weights in RAM" (#38271)

This reverts commit 8196b743af.

* release oob buffers as we load them

* don't leak fds

* lint

* bigger big

* don't roundtrip the features to qcom

* Revert "don't roundtrip the features to qcom"

This reverts commit 0d2ca73d7a71bd32c46beda425957339f1dbbcf8.

* tmp bump allowed lag

* faster

* Revert "tmp bump allowed lag"

This reverts commit b407702a0242074eb12abdff14e61789fc05109a.
2026-07-01 01:26:11 -07:00
Armand du Parc Locmaria a2c554805b Reapply "usbgpu: never materialize model weights in RAM" (#38273)
* Reapply "usbgpu: never materialize model weights in RAM" (#38271)

This reverts commit 8196b743af.

* release oob buffers as we load them

* don't leak fds

* lint

* 10s faster loading time for big model
2026-07-01 01:22:48 -07:00
Daniel Koepping e1e9efb965 add USB logging (#38186)
* usb logging

* fix test

* discover by PID

* hardwared, add counters

* edit msg

* add manufacturer

* reorder

* rm portli

* rm this
2026-06-30 16:17:26 -07:00
Armand du Parc Locmaria 8196b743af Revert "usbgpu: never materialize model weights in RAM" (#38271)
Revert "usbgpu: never materialize model weights in RAM (#38265)"

This reverts commit 724971afc3.
2026-06-30 13:13:23 -07:00
Armand du Parc Locmaria 724971afc3 usbgpu: never materialize model weights in RAM (#38265)
* bigger big

* don't load big model in ram

* patch tg to allow out of band buffers

* dump/load oob

* roundtrip pkl to disk

* streaming chunking

* to_disk

* fixup

* linter

* Revert "bigger big"

This reverts commit 7a8147dd7fab88db963d512ab4008de940714025.
2026-06-30 00:35:12 -07:00
Armand du Parc Locmaria 4fc5e308f3 Reapply "modeld: enqueue in policy" (#38267)
* Reapply "modeld: enqueue in policy" (#38266)

This reverts commit 8a3bbcdd4e.

* correct device for policy inputs
2026-06-30 00:31:54 -07:00
Armand du Parc Locmaria 8a3bbcdd4e Revert "modeld: enqueue in policy" (#38266)
Revert "modeld: enqueue in policy (#38264)"

This reverts commit 8c927d0fc2.
2026-06-30 00:15:50 -07:00
Armand du Parc Locmaria 8c927d0fc2 modeld: enqueue in policy (#38264)
* move enqueue to policy

* typo

* correct shape

* correct shape
2026-06-29 13:08:37 -07:00
Armand du Parc Locmaria 97215b17d7 modeld: remove prepare_only (#38252)
remove prepare_only
2026-06-29 11:53:27 -07:00
Jason Wen 31dc4d8e52 ci: fix cereal validation for upstream directory restructure (#1869) 2026-06-28 22:56:17 -04:00
Toby Penner 61b6d52b01 Fix broken tools README link (#38260) 2026-06-28 10:40:05 -07:00
Jason Wen da6313dbe9 Update CHANGELOG.md 2026-06-26 21:33:25 -04:00
ZwX1616 e9e5548edc bump DM NoEntryAlert priority (#38253) 2026-06-26 18:23:01 -07:00
ZwX1616 f9c555a3fe DM: update escalation/lockout specs; softer sounds (#38244) 2026-06-26 14:57:19 -07:00
ZwX1616 069506aa36 sound: use log enum (#38246) 2026-06-26 13:55:30 -07:00
probablyanasian 5629b5a83d bump sensors test limits (#38245)
* bump sensors test limits

* loosen back to 60 c max due to device density

* 10 dps in 3d is 0.3 rad max
2026-06-25 18:06:42 -07:00
Adeeb Shihadeh addca46f68 move some tools back to root (#38235) 2026-06-24 19:21:19 -07:00
Harald Schäfer 7d325d6650 Simplify lane change logic (#38229)
* desire cleanups

* remove the finalize timeout

---------

Co-authored-by: Robbe Derks <robbe.derks@gmail.com>
2026-06-23 11:34:05 -07:00
commaci-public 288aa60bf4 [bot] Update Python packages (#38227)
* Update Python packages

* fix numpy reshape warning

* relock

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-06-22 15:28:11 -07:00
Adeeb Shihadeh cddb6103eb webrtc: replace aiohttp with stdlib http server (#38226)
* webrtc: replace aiohttp with stdlib http server

* lil more

* simplify

* remove aiohttp error response helper

---------

Co-authored-by: stefpi <19478336+stefpi@users.noreply.github.com>
2026-06-22 14:25:19 -07:00
Adeeb Shihadeh 1474d5474d move root include to the end to prevent using stale build products 2026-06-22 09:45:00 -07:00
Adeeb Shihadeh 2e9fa514bb simple pip install test (#38218)
* fails

* lil more

* try that
2026-06-21 19:39:21 -07:00
Adeeb Shihadeh 8a9ca15464 cleanup leftovers from the shuffling (#38224)
* cleanup leftovers

* rm

* one more
2026-06-21 19:17:01 -07:00
Adeeb Shihadeh 5edc0bd89d mv root dirs into nested openpilot (#38219) 2026-06-21 18:49:46 -07:00
Adeeb Shihadeh 20e0f21b58 prefix paths with openpilot (#38223)
* prefix paths with openpilot

* fix setup root detection

* fix split branch codespell skips
2026-06-21 18:28:35 -07:00
Adeeb Shihadeh 37eda06c95 move cereal/ into nested openpilot (#38220) 2026-06-21 17:55:17 -07:00
Adeeb Shihadeh df1663c58d the one true car.capnp (#38221)
* the one true car.capnp

* fix jotpluggler capnp import path

* bump to master
2026-06-21 17:32:19 -07:00
Adeeb Shihadeh bc168bb33a rm opencv-python-headless (#38214)
* rm opencv-python-headless

* and metadrive

* skip sim
2026-06-21 12:49:12 -07:00
Fang Li d79267fa22 modem: fall back when cellular DNS is missing (#38213)
* modem: fall back when cellular DNS is missing

* rm test

* comment

---------

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-06-21 12:37:56 -07:00
Adeeb Shihadeh cea5273f1f move version to common/ (#38210) 2026-06-21 09:43:53 -07:00
Adeeb Shihadeh d8bd6c784e rename androidLog -> operatingSystemLog (#38209)
not android
2026-06-21 09:11:04 -07:00
Adeeb Shihadeh ad5151b38b single IsOffroad param (#38205) 2026-06-20 16:55:48 -07:00
Adeeb Shihadeh fddb9fb31c remove statsd (#38204) 2026-06-20 16:35:28 -07:00
Adeeb Shihadeh b871364e1f remove rest of docker (#38203) 2026-06-20 16:22:37 -07:00
Adeeb Shihadeh f0831e549b rm dead updated tests 2026-06-20 16:20:05 -07:00
Adeeb Shihadeh 7cee534b72 rm dead scripts 2026-06-20 16:16:15 -07:00
Adeeb Shihadeh e0e9f1a4d3 move test_power_draw.py 2026-06-20 16:12:54 -07:00
Adeeb Shihadeh 160942dfde move HAL to common/hardware/ (#38202)
* move HAL to common/hardware/

* lint

* rm shim
2026-06-20 16:09:22 -07:00
Adeeb Shihadeh 52e25ebe4e we don't have this anymore 2026-06-20 16:08:24 -07:00
Adeeb Shihadeh 3b4077d31b system/hardware/ cleanup (#38201)
* imei cleanup

* rm get_modem_version

* cpp cleanup

* that's not in the panda!
2026-06-20 15:51:01 -07:00
Adeeb Shihadeh f0f7b877c3 athena: remove getNetworks (#38200) 2026-06-20 15:27:36 -07:00
Adeeb Shihadeh cb6e422c91 move esim to common/ (#38199)
* move esim to common/

* add pem
2026-06-20 15:16:48 -07:00
Adeeb Shihadeh f48e99b335 updated: remove (never used) casync support (#38198) 2026-06-20 14:19:25 -07:00
Adeeb Shihadeh b2a7084906 move webcam to system/camerad/ (#38196)
* move webcam to system/camerad/

* lil more
2026-06-20 13:54:47 -07:00
Adeeb Shihadeh 62b97fabf7 merge selfdrive/debug/ into tools/scripts/ (#38195)
* merge selfdrive/debug/ into tools/scripts/

* rm unused

* single timings script

* lil more

* profiling is scripts quality
2026-06-20 13:52:16 -07:00
stef f257544f1b tools: goodbye webjoystick (#38194)
remove body web tool
2026-06-19 19:08:21 -07:00
stef 45d8bcd7f3 car livestream (#38193)
* athenad and webrtcd updates

* remove feature stream services from webrtcd split

* stream encoder thread

* reduce diff

* wire webrtc to livestream camera encoder

* request livestream camera switch service

* remove camera list in favour of init camera field

* remove cors

* clean

* remove unused

* remove extra try except

* add back exception trace

* add stream road camera info to stream cameras

* fix

* clean diff

* clean diff

* add testJoystick only on body

* fix camera list

* remove reference to future service

* encode all cameras and swap in video track in webrtc

* clean

* explicitly gate bridge send

* clean leftover

* initial idea

* add a watchdog to kill the process after disconnect or onroad

* start camerad and stream encoderd as well

* add message handler even without bridge

* remove carState when offroad

* transition to onroad works for body, kill for car not working

* turn off stream processes on started car

* cereal messaging sub field allow list, carparamspersistent, ping webrtcd to see when awake

* fix imports

* fix and increase max retries

* reduce max retries, increase timeout

* timeout on wait for connection

* remove cereal sub message and update process config to run on body ignition

* debounce 5s on teardown

* update error messages

* fix tear down crash

* clean

* clean

* fix lint
2026-06-19 18:20:55 -07:00
stef 0038d84e13 webrtc: do not replace connection on prewarm (#38190)
* refactor

* remove accidental change

* clean error handler

* clean structure

* only is other stream is active and enabled

* better structure

* remove enabled None default

* update comment
2026-06-17 14:53:06 -07:00
Christopher Milan cd2c590d50 bump tinygrad (#38176)
* bump tinygrad

* wip: try this

* origin/master

---------

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Comma Device <device@comma.ai>
2026-06-17 11:24:11 -07:00
Harald Schäfer ddc8d37136 Desire keep pulsing is dead (#38187) 2026-06-17 10:48:53 -07:00
David d4db600d4b ui diff: add multithreading and improve logging (split from #37328) (#37474)
* split out and refactor

* simplify

* rm extra line

* use threadpoolexecutor to handle exceptions

* simplify

* improve

---------

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-06-17 10:27:46 -07:00
David 5ab7e48479 fix(ui): fill and center onroad camera if too small (#37673)
* ui: ensure zoom accommodates full view area in AugmentedRoadView

* fix: use _content_rect

---------

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-06-17 09:54:24 -07:00
Andi Radulescu e2e3703ae4 ui: fix false "NO PANDA" flash on screen wake (#37404)
* ui: fix false "NO PANDA" flash on screen wake

* ui: use recv_time for panda timeout instead of frame counting

* ui: use sm.alive for panda timeout
2026-06-17 08:14:07 -07:00
stef 3939f1e92c webrtc: signal shutdown over datachannel (#38185)
signal shut down, fix enabled bug
2026-06-16 18:33:59 -07:00
Adeeb Shihadeh 82a945d145 scons: enforce max file size for build products (#38077)
* scons: enforce max file size for build products

* only for minimal build

* enable

* fix build product size check
2026-06-16 17:49:12 -07:00
hypery11 93224b3c05 speedup pytest collection (#37977)
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-06-16 16:28:37 -07:00
Robbie Wadley 8a80bd70e7 fix: cruise faults should not disable silently (#37557)
Co-authored-by: Jason Young <46612682+jyoung8607@users.noreply.github.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-06-16 16:25:40 -07:00
David 29e7f362ed clip: add wide camera stream (#37677)
clip: add support for wide camera frames in clip rendering
2026-06-16 15:25:27 -07:00
eFini 3c4790c089 bugfix: should be Error not Exception (#38071)
should be Error not Exception
2026-06-16 14:58:30 -07:00
Adeeb Shihadeh c0456590f6 mici ui: add branch switcher (#38153)
* mici ui: add branch switcher

* add icon
2026-06-16 14:01:14 -07:00
stef f3b1f97afa webrtc/athena: speed up first time stream connection (#38184)
* move streamrequest to models file, prewarm in webrtcd

* cleanup

* debug

* change print to logger.info
2026-06-16 13:48:16 -07:00
Apostolos Georgiadis 4cefe7239d fetch_image_from_route: fix missing f-string in error message (#38183) 2026-06-16 09:23:30 -07:00
stef 586dd4c611 webrtc: add error handling middleware (#38182)
* webrtc add error handling middleware

* clean diff
2026-06-15 19:19:07 -07:00
stef 291315ab25 webrtc/athena: allow webrtc connection with only data channel (#38177)
* add param that requests keyframe

* set keyframe request to false on cleanup

* timing and errors

* video_enabled option and ability to add video later

* remove loggerd changes from this PR

* video enabled default to on, clean identifier

* request keyframe on start regardless of video enabled or not

* move error handling additions to different PR
2026-06-15 19:11:28 -07:00
stef 37390743c1 webrtc/athena: revert addIceCandidate changes (#38180)
* revert addIceCandidate changes

* remove accidental file
2026-06-15 16:50:07 -07:00
stef 15fb1a809d webrtc: revert prewarm and import movements (#38181) 2026-06-15 16:49:56 -07:00
stef 0240a62fd9 encoder: add ability to request keyframes from livestream encoders (#38172)
* add param that requests keyframe

* set keyframe request to false on cleanup

* clean

* timing and errors

* remove other changes

* clean diff

* clean diff

* clean up live adaptation params and functions

* better function names

* fix syntax

* remove unused var

* clean diff
2026-06-15 14:48:13 -07:00
Willem Melching fafcee04f4 replay: fix current time (#38179) 2026-06-15 08:44:01 -07:00
Shane Smiskol 2feca929ab bump opendbc (#38175)
bump
2026-06-12 19:17:09 -07:00
Harald Schäfer a40fa3a0b0 modeld: run the driving model as a single combined onnx (#38173) 2026-06-12 18:35:52 -07:00
Daniel Koepping f9cc67896c Improve lateral maneuvers (#38159)
* improve lat maneuver report

* rm torque jerk

* add jitter maneuver

* add to 30mph

* add jerk

* color

* more state

* override

* abort reason

* abort

* rm text

* pad for small text
2026-06-11 18:59:56 -07:00
stef cd052f124e webrtc: speed ups (#38163)
- patch `aioice` to only get host address of active network interface (wlan0 when on wifi, ppp0 when not)
- use common webrtc session between `startStream` and `addIceCandidates`
- first time connection speed ups
  - move imports to top of file to move delay to webrtcd server start up not first connection
  - warm up webrtc stack by creating a webrtc connection object to import all submodule classes
2026-06-11 16:10:34 -07:00
stef 14cff000e9 feat(webrtc): addIceCandidate dispatcher (#38161)
* addIceCandidate dispatcher

* clean

* fix lint
2026-06-11 12:58:04 -07:00
YassineYousfi 9ef3cfdf9a compile modeld: use tmp file name (#38170) 2026-06-11 12:29:56 -07:00
Harald Schäfer 4024d13dda Revert "Op model16 deep (#38073)" (#38166)
This reverts commit f02d134f40.
2026-06-11 09:32:24 -07:00
Armand du Parc Locmaria b2bb71b4b7 modeld: less cross device copies (#38168)
* single copy for frames

* single copy out

* missed this

* pack npy inputs to the policy

* strict zip

* strict zip
2026-06-10 22:02:28 -07:00
stef 98e5f547ea fix(athena): outbound rpc stuck behind low prio queue timeout (#38160)
* priority queue instead of 2 queues

* add assert on priority instead of default

* fix lint
2026-06-10 19:28:26 -07:00
Kacper Rączy 5a7b710d90 lagd: invalidate cached estimates on major estimator update (#38162)
* Versioning

* comment

* Tests

* fix issues
2026-06-11 02:25:23 +00:00
Shane Smiskol 7661e03d1e bump opendbc (#38167)
bump
2026-06-10 18:51:28 -07:00
Armand du Parc Locmaria bdac9efa1e modeld: compress modelV2 into drivingModelData (#38165)
* modeld: compress modelV2 into drivingModelData

* slightly better

* less diff

* whitespace

* dedupe with migrate?

* Revert "dedupe with migrate?"

This reverts commit f31fe34994f4331f45e14a035df4847905d37540.
2026-06-10 17:06:09 -07:00
Shane Smiskol 433c52f623 radar errors: only gate enable if openpilot long (#38145)
only prevent engagement for op long cars
2026-06-09 16:56:48 -07:00
Daniel Koepping 24d373062d soundd: fix long wav playback and mic feedback (#38155)
fix soundd long duration playback and mic feedback
2026-06-09 16:37:45 -07:00
Adeeb Shihadeh 41390a95b8 Jenkinsfile: use SSH ControlMaster to multiplex device connections (#38157) 2026-06-09 16:15:54 -07:00
Trey Moen 57b5eb3113 op.sh: fix arg quoting so special chars survive eval (#37967)
op.sh: don't eval CMD, run argv directly so special chars work

Previously op_run_command joined "$@" into a single string and eval'd
it, which broke on inputs containing shell metacharacters. For example,
'op esim --download "LPA:1$[rsp.truphone.com]($url)$QRF-SPEEDTEST" name'
would fail with a syntax error on the unquoted parens. Run "$@" instead
and only use $* for the printed display string. Also quote the unquoted
$@ in the other op_run_command callers.
2026-06-09 11:44:57 -07:00
Bruce Wayne 7abe0205fd modeld test: 1ms extra buffer 2026-06-08 15:17:07 -07:00
Daniel Koepping ef3ec30fe2 bump opendbc - ID.4 release (#38150)
id4 release
2026-06-08 09:58:57 -07:00
Adeeb Shihadeh b0600a4225 rm unused updated test 2026-06-07 10:00:08 -07:00
Daniel Koepping cc110d1eb1 Add driverMonitoringEscalation for stock DM trigger (#38143)
* trigger dm

* rename

* ci

* opendbc bump
2026-06-06 12:02:46 -07:00
CommaKia bfd8d4868d cabana: fix "seperated" typo in findsignal placeholders (#38142) 2026-06-05 23:32:58 -07:00
Daniel Koepping 14a00e0cc8 Add latcontrol for curvature (#38141)
* undeprecate curvature

* use pid_log

* bump

* fix test

* bump

* bump

* fix

* tune saturation

* bump

* fix torque bar

* bump

* rm redundant

* reduce sesitivity

* pid

* bump

* reset opendbc

* preserve sorting

* bump opendbc
2026-06-05 20:32:30 -07:00
Armand du Parc Locmaria 268126f376 usbgpu: pin modeld (#38123)
usbgpu: pin modeld to core 7
2026-06-03 23:32:32 -07:00
Kacper Rączy 900a896c63 lagd: higher min speed (#38133)
* Higher min vego

* Use CV

* Bump speed in simulated tests
2026-06-04 03:24:27 +00:00
Daniel Koepping c01b8be968 set max cpu core frequency (#38132)
cap cpu core frequency
2026-06-03 19:35:51 -07:00
brian 094591793a A valid livePose should not carry a timestamp produced from uninitialized Kalman filter time (#38124)
* locationd: require finite filter time for valid livePose

* cast to bool and rename
2026-06-03 23:25:39 +00:00
Harald Schäfer f02d134f40 Op model16 deep (#38073)
* modeld: RL driving model with 3-file split

Split the driving model into vision + off_policy + on_policy ONNX
files and wire up the RL policy:

- 3-file model split (vision / off_policy / on_policy), replacing the
  combined big_driving_policy/vision models
- compiler updates for the split models
- actually consume the policy action in modeld
- add desire state to the driving model
- model iterations (smoothness, off/on-policy weight updates)

* modeld: update driving model

* 1e72cf5a-785f-45ea-888f-28cdb14785de/100

* tinygrad hack

* fix parsing

* looser timing

* big

* Remove unnecessary modeld rebase changes

* Tighten modeld split cleanup

---------

Co-authored-by: Comma Device <device@comma.ai>
Co-authored-by: Armandpl <adpl33@gmail.com>
2026-06-03 12:48:58 -07:00
Daniel Koepping 5b3d5f74ed test models: add VW MEB angle exception (#38119)
* add vw meb exception

* comment
2026-06-02 19:23:31 -07:00
stef 866cd01f32 webrtc: initialize on medium bitrate (#38129)
initialize on medium quality
2026-06-02 18:07:12 -07:00
Harald Schäfer d09a411cb4 Refactor compile_modeld model setup (#38128) 2026-06-02 16:35:40 -07:00
1826 changed files with 42796 additions and 31959 deletions
-18
View File
@@ -1,18 +0,0 @@
**/.git
.DS_Store
*.dylib
*.DSYM
*.d
*.pyc
*.pyo
.*.swp
.*.swo
.*.un~
*.tmp
*.o
*.o-*
*.os
*.os-*
venv/
.venv/
+5 -3
View File
@@ -1,8 +1,10 @@
* text=auto
* text=auto eol=lf
# to move existing files into LFS:
# git add --renormalize .
*.onnx filter=lfs diff=lfs merge=lfs -text
openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl filter=lfs diff=lfs merge=lfs -text
openpilot/sunnypilot/modeld_v2/models/*.pkl filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
@@ -10,5 +12,5 @@
*.otf filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
openpilot/selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
openpilot/common/hardware/comma/updater filter=lfs diff=lfs merge=lfs -text
-11
View File
@@ -1,11 +0,0 @@
* @sunnypilot/dev-internal
/.github/ @devtekve @sunnyhaibin
/release/ci/ @devtekve @sunnyhaibin
/tinygrad_repo @devtekve @Discountchubbs
/tinygrad/ @devtekve @Discountchubbs
/selfdrive/controls/lib/longitudinal_planner.py @devtekve @Discountchubbs
/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @devtekve @Discountchubbs
/selfdrive/modeld/ @devtekve @Discountchubbs
/sunnypilot/model* @devtekve @Discountchubbs
/sunnypilot/sunnylink/ @devtekve
/system/athena/ @devtekve
+6 -6
View File
@@ -8,24 +8,24 @@ chore:
car:
- changed-files:
- any-glob-to-all-files: '{selfdrive/car/**,opendbc_repo}'
- any-glob-to-all-files: '{openpilot/selfdrive/car/**,opendbc_repo}'
simulation:
- changed-files:
- any-glob-to-all-files: 'tools/sim/**'
- any-glob-to-all-files: 'openpilot/tools/sim/**'
ui:
- changed-files:
- any-glob-to-all-files: '{selfdrive/assets/**,selfdrive/ui/**,system/ui/**}'
- any-glob-to-all-files: '{openpilot/selfdrive/assets/**,openpilot/selfdrive/ui/**,openpilot/system/ui/**}'
tools:
- changed-files:
- any-glob-to-all-files: 'tools/**'
- any-glob-to-all-files: 'openpilot/tools/**'
multilanguage:
- changed-files:
- any-glob-to-all-files: 'selfdrive/ui/translations/**'
- any-glob-to-all-files: 'openpilot/selfdrive/ui/translations/**'
autonomy:
- changed-files:
- any-glob-to-all-files: "{selfdrive/modeld/models/**,selfdrive/test/process_replay/model_replay_ref_commit,sunnypilot/modeld*/models/**}"
- any-glob-to-all-files: "{openpilot/selfdrive/modeld/models/**,openpilot/selfdrive/test/process_replay/model_replay_ref_commit,openpilot/sunnypilot/modeld*/models/**}"
-68
View File
@@ -1,68 +0,0 @@
<!-- Please copy and paste the relevant template -->
<!--- ***** Template: Fingerprint *****
**Car**
Which car (make, model, year) this fingerprint is for
**Route**
A route with the fingerprint
-->
<!--- ***** Template: Car Bugfix *****
**Description**
A description of the bug and the fix. Also link the issue if it exists.
**Verification**
Explain how you tested this bug fix.
**Route**
Route: [a route with the bug fix]
-->
<!--- ***** Template: Bugfix *****
**Description**
A description of the bug and the fix. Also link the issue if it exists.
**Verification**
Explain how you tested this bug fix.
-->
<!--- ***** Template: Car Port *****
**Checklist**
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py)
- [ ] route with openpilot:
- [ ] route with stock system:
- [ ] car harness used (if comma doesn't sell it, put N/A):
-->
<!--- ***** Template: Refactor *****
**Description**
A description of the refactor, including the goals it accomplishes.
**Verification**
Explain how you tested the refactor for regressions.
-->
-43
View File
@@ -1,43 +0,0 @@
exclude-labels:
- 'no-changelog'
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
collapse-after: 5
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
collapse-after: 5
label: 'chore'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&'
replacers:
- search: '/[Ss][Uu][Nn][Nn][Yy][Pp][Ii][Ll][Oo][Tt]/g'
replace: 'sunnypilot'
- search: '/\b[Ss][Pp]\b/g'
replace: 'SP'
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
name-template: 'v$RESOLVED_VERSION 🚀'
tag-template: 'v$RESOLVED_VERSION'
version-template: "0.$MAJOR.$MINOR.$PATCH" # The day OP becomes v1, we need to bump this
tag-prefix: "v0." # The day OP becomes v1, we need to bump this
prerelease-identifier: "staging"
template: |
## Changes
$CHANGES
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
submodules: false
@@ -24,7 +24,7 @@ jobs:
# Check PR target branch
- name: check branch
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
uses: Vankka/pr-target-branch-action@5da68a42bcb7b43d39104295a876a6f3f8d7908b
if: github.repository == 'sunnypilot/sunnypilot'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-35
View File
@@ -1,35 +0,0 @@
name: badges
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
env:
PYTHONPATH: ${{ github.workspace }}
jobs:
badges:
name: create badges
runs-on: ubuntu-latest
if: github.repository == 'sunnypilot/sunnypilot'
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
submodules: true
- run: ./tools/op.sh setup
- name: Push badges
run: |
python3 selfdrive/ui/translations/create_badges.py
rm .gitattributes
git checkout --orphan badges
git rm -rf --cached .
git config user.email "badge-researcher@sunnypilot.ai"
git config user.name "Badge Researcher"
git add translation_badge.svg
git commit -m "Add/Update badges"
git push -f origin HEAD
+55 -188
View File
@@ -7,6 +7,24 @@ on:
description: 'Minimum selector version required for the models (see helpers.py or readme.md)'
required: true
type: string
target_hardware:
description: 'Hardware target to compile for (qcom or chestnut)'
required: true
type: choice
default: 'qcom'
options:
- qcom
- chestnut
hf_repo:
description: 'Hugging Face dataset repository'
required: false
type: string
default: 'sunnypilot/sunnypilot_models_v1'
docs_repo:
description: 'GitHub repo holding the driving_models JSON on its gh-pages branch'
required: false
type: string
default: 'sunnypilot/sunnypilot-models'
jobs:
setup:
@@ -21,7 +39,6 @@ jobs:
- name: Checkout sunnypilot repo
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot
path: sunnypilot
submodules: recursive
@@ -30,14 +47,14 @@ jobs:
run: |
cd sunnypilot
export PYTHONPATH=$(pwd)
ref=$(python3 sunnypilot/models/tinygrad_ref.py)
ref=$(python3 openpilot/sunnypilot/models/tinygrad_ref.py)
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
echo "tinygrad_ref is $ref"
- name: Checkout docs repo (sunnypilot-models, gh-pages)
- name: Checkout docs repo (gh-pages)
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot-models
repository: ${{ inputs.docs_repo }}
ref: gh-pages
path: docs
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
@@ -46,13 +63,14 @@ jobs:
id: get-json
run: |
cd docs/docs
latest=$(ls driving_models_v*.json | sed -E 's/.*_v([0-9]+)\.json/\1/' | sort -n | tail -1)
PREFIX="driving_models_${{ inputs.target_hardware == 'chestnut' && 'chestnut_' || '' }}v"
latest=$(ls ${PREFIX}*.json | sed -E "s/${PREFIX}([0-9]+)\.json/\1/" | sort -n | tail -1)
next=$((latest+1))
json_file="driving_models_v${next}.json"
cp "driving_models_v${latest}.json" "$json_file"
json_file="${PREFIX}${next}.json"
cp "${PREFIX}${latest}.json" "$json_file"
echo "json_file=docs/docs/$json_file" >> $GITHUB_OUTPUT
echo "json_version=$((next+0))" >> $GITHUB_OUTPUT
echo "SRC_JSON_FILE=docs/docs/driving_models_v${latest}.json" >> $GITHUB_ENV
echo "SRC_JSON_FILE=docs/docs/${PREFIX}${latest}.json" >> $GITHUB_ENV
- name: Extract tinygrad models
id: set-matrix
@@ -61,45 +79,24 @@ jobs:
jq -c '[.bundles[] | select(.runner=="tinygrad") | {ref, display_name: (.display_name | gsub(" \\([^)]*\\)"; "")), is_20hz}]' "$(basename "${SRC_JSON_FILE}")" > matrix.json
echo "model_matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
- run: |
mkdir -p ~/.ssh
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
- name: Clone GitLab docs repo and create new recompiled dir
- name: Get next recompiled dir number
id: create-recompiled-dir
env:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
HF_TOKEN: ${{ secrets.HF_TOKEN }}
HF_REPO: ${{ github.event.inputs.hf_repo }}
run: |
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
git checkout main
git sparse-checkout set --no-cone models/
cd models
latest_dir=$(ls -d recompiled* 2>/dev/null | sed -E 's/recompiled([0-9]+)/\1/' | sort -n | tail -1)
if [[ -z "$latest_dir" ]]; then
next_dir=1
else
next_dir=$((latest_dir+1))
fi
recompiled_dir="${next_dir}"
mkdir -p "recompiled${recompiled_dir}"
touch "recompiled${recompiled_dir}/.gitkeep"
cd ../..
pip install huggingface_hub
recompiled_dir=$(python3 -c "
from huggingface_hub import HfApi
import re, sys
api = HfApi()
files = api.list_repo_files(repo_id=sys.argv[1], repo_type='dataset')
dirs = [re.search(r'models/recompiled([0-9]+)', f) for f in files]
nums = [int(m.group(1)) for m in dirs if m]
print(max(nums) + 1)
" "$HF_REPO")
echo "recompiled_dir=$recompiled_dir" >> $GITHUB_OUTPUT
- name: Push empty recompiled dir to GitLab
run: |
cd gitlab_docs
git add models/recompiled${{ steps.create-recompiled-dir.outputs.recompiled_dir }}
git config --global user.name "GitHub Action"
git config --global user.email "action@github.com"
git commit -m "Add recompiled${{ steps.create-recompiled-dir.outputs.recompiled_dir }} for build-all" || echo "No changes to commit"
git push origin main
- name: Push new JSON to GitHub docs repo
run: |
cd docs
@@ -123,25 +120,31 @@ jobs:
is_20hz: ${{ matrix.model.is_20hz }}
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
json_version: ${{ needs.setup.outputs.json_version }}
target_hardware: ${{ github.event.inputs.target_hardware }}
hf_repo: ${{ github.event.inputs.hf_repo }}
docs_repo: ${{ inputs.docs_repo }}
set_min_version: ${{ github.event.inputs.set_min_version }}
tinygrad_ref: ${{ needs.setup.outputs.tinygrad_ref }}
secrets: inherit
retry_failed_models:
needs: [setup, get_and_build]
runs-on: ubuntu-latest
if: ${{ needs.setup.result != 'failure' && !cancelled() }}
if: ${{ !cancelled() && needs.setup.result == 'success' && (needs.get_and_build.result == 'success' || needs.get_and_build.result == 'failure') }}
outputs:
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
steps:
- uses: actions/download-artifact@v4
with:
pattern: model-*
pattern: artifact-name-*
path: output
continue-on-error: true
- id: set-retry-matrix
run: |
echo '${{ needs.setup.outputs.model_matrix }}' > matrix.json
built=(); while IFS= read -r line; do built+=("$line"); done < <(
find output -maxdepth 1 -name 'model-*' -printf "%f\n" | sed -E 's/^model-//' | sed -E 's/-[0-9]+$//' | sed -E 's/ \([^)]*\)//' | awk '{gsub(/^ +| +$/, ""); print}'
built=(); while IFS= read -r line; do [ -n "$line" ] && built+=("$line"); done < <(
find output -maxdepth 1 -name 'artifact-name-*' -printf "%f\n" 2>/dev/null | sed -E 's/^artifact-name-//' | awk '{gsub(/^ +| +$/, ""); print}'
)
jq -c --argjson built "$(printf '%s\n' "${built[@]}" | jq -R . | jq -s .)" \
'map(select(.display_name as $n | ($built | index($n | gsub("^ +| +$"; "")) | not)))' matrix.json > retry_matrix.json
@@ -149,7 +152,7 @@ jobs:
retry_get_and_build:
needs: [setup, get_and_build, retry_failed_models]
if: ${{ needs.get_and_build.result == 'failure' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '') }}
if: ${{ !cancelled() && needs.retry_failed_models.result == 'success' && needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '' }}
strategy:
matrix:
model: ${{ fromJson(needs.retry_failed_models.outputs.retry_matrix) }}
@@ -161,146 +164,10 @@ jobs:
is_20hz: ${{ matrix.model.is_20hz }}
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
json_version: ${{ needs.setup.outputs.json_version }}
target_hardware: ${{ github.event.inputs.target_hardware }}
artifact_suffix: -retry
hf_repo: ${{ github.event.inputs.hf_repo }}
docs_repo: ${{ inputs.docs_repo }}
set_min_version: ${{ github.event.inputs.set_min_version }}
tinygrad_ref: ${{ needs.setup.outputs.tinygrad_ref }}
secrets: inherit
publish_models:
name: Publish models sequentially
needs: [setup, get_and_build, retry_failed_models, retry_get_and_build]
if: ${{ !cancelled() && (needs.get_and_build.result != 'failure' || needs.retry_get_and_build.result == 'success' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '')) }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 1
matrix:
model: ${{ fromJson(needs.setup.outputs.model_matrix) }}
env:
RECOMPILED_DIR: recompiled${{ needs.setup.outputs.recompiled_dir }}
JSON_FILE: ${{ needs.setup.outputs.json_file }}
ARTIFACT_NAME_INPUT: ${{ matrix.model.display_name }}
steps:
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
- name: Add GitLab.com SSH key to known_hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
- name: Clone GitLab docs repo
env:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
echo "Cloning GitLab"
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
echo "checkout models/${RECOMPILED_DIR}"
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
git checkout main
cd ..
- name: Checkout docs repo
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot-models
ref: gh-pages
path: docs
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
- name: Validate recompiled dir and JSON version
run: |
if [ ! -d "gitlab_docs/models/$RECOMPILED_DIR" ]; then
echo "Recompiled dir $RECOMPILED_DIR does not exist in GitLab repo"
exit 1
fi
if [ ! -f "$JSON_FILE" ]; then
echo "JSON file $JSON_FILE does not exist!"
exit 1
fi
- name: Download artifact name file
uses: actions/download-artifact@v4
with:
name: artifact-name-${{ env.ARTIFACT_NAME_INPUT }}
path: artifact_name
- name: Read artifact name
id: read-artifact-name
run: |
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
- name: Download model artifact
uses: actions/download-artifact@v4
with:
name: ${{ steps.read-artifact-name.outputs.artifact_name }}
path: output
- name: Remove onnx files bc not needed for recompiled dir since they already exist from single build
run: |
find output -type f -name '*.onnx' -delete
find output -type f -name 'big_*.pkl' -delete
find output -type f -name 'dmonitoring_model_tinygrad.pkl' -delete
- name: Copy model artifacts to gitlab
env:
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
run: |
ARTIFACT_DIR="gitlab_docs/models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
mkdir -p "$ARTIFACT_DIR"
for path in output/*; do
if [ "$(basename "$path")" = "artifact_name.txt" ]; then
continue
fi
name="$(basename "$path")"
if [ -d "$path" ]; then
mkdir -p "$ARTIFACT_DIR/$name"
cp -r "$path"/* "$ARTIFACT_DIR/$name/"
echo "Copied dir $name -> $ARTIFACT_DIR/$name"
else
cp "$path" "$ARTIFACT_DIR/"
echo "Copied file $name -> $ARTIFACT_DIR/"
fi
done
- name: Push recompiled dir to GitLab
env:
GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
run: |
cd gitlab_docs
git checkout main
git pull origin main
for d in models/"$RECOMPILED_DIR"/*/; do
git sparse-checkout add "$d"
done
git add models/"$RECOMPILED_DIR"
git config --global user.name "GitHub Action"
git config --global user.email "action@github.com"
git commit -m "Update $RECOMPILED_DIR with model from build-all-tinygrad-models" || echo "No changes to commit"
git push origin main
- run: |
cd docs
git pull origin gh-pages
- name: update json
run: |
ARGS=""
[ -n "${{ inputs.set_min_version }}" ] && ARGS="$ARGS --set-min-version \"${{ inputs.set_min_version }}\""
ARGS="$ARGS --sort-by-date"
ARGS="$ARGS --tinygrad-ref \"${{ needs.setup.outputs.tinygrad_ref }}\""
eval python3 docs/json_parser.py \
--json-path "$JSON_FILE" \
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
$ARGS
- name: Push updated json to GitHub
run: |
cd docs
git config --global user.name "GitHub Action"
git config --global user.email "action@github.com"
git checkout gh-pages
git add docs/"$(basename $JSON_FILE)"
git commit -m "Update $(basename $JSON_FILE) after recompiling model" || echo "No changes to commit"
git push origin gh-pages
+516
View File
@@ -0,0 +1,516 @@
name: Build default models
on:
workflow_dispatch:
inputs:
target:
description: 'Model target to build'
required: true
type: choice
options:
- small
- big
- dm
workflow_call:
inputs:
target:
description: 'Model target to build (small, big, or dm)'
required: true
type: string
concurrency:
group: build-default-models-${{ inputs.target }}
cancel-in-progress: false
env:
HF_REPO: sunnypilot/sunnypilot_models_v1
jobs:
resolve:
runs-on: ubuntu-24.04
outputs:
model_name: ${{ steps.resolve.outputs.model_name }}
safe_model_name: ${{ steps.resolve.outputs.safe_model_name }}
onnx_ref: ${{ steps.resolve.outputs.onnx_ref }}
onnx_path: ${{ steps.resolve.outputs.onnx_path }}
hf_defaults_path: ${{ steps.resolve.outputs.hf_defaults_path }}
tinygrad_ref: ${{ steps.resolve.outputs.tinygrad_ref }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- id: resolve
run: |
export PYTHONPATH=${{ github.workspace }}
if [ "${{ inputs.target }}" = "big" ]; then
NAME=$(python3 -c "from openpilot.sunnypilot.models.model_name import DEFAULT_BIG_MODEL; print(DEFAULT_BIG_MODEL)")
HF_DEFAULTS_PATH="models/defaults/big"
elif [ "${{ inputs.target }}" = "dm" ]; then
ONNX_PATH="openpilot/selfdrive/modeld/models/dmonitoring_model.onnx"
HF_DEFAULTS_PATH="models/defaults/dm"
NAME="dmonitoring_model ($(git log -1 --format=%cd --date=format:'%B %d, %Y' -- "$ONNX_PATH"))"
else
NAME=$(python3 -c "from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL; print(DEFAULT_MODEL)")
ONNX_PATH="openpilot/selfdrive/modeld/models/driving_supercombo.onnx"
HF_DEFAULTS_PATH="models/defaults/small"
fi
ONNX_REF=""
[ -n "$ONNX_PATH" ] && ONNX_REF=$(git log -1 --format='%H' -- "$ONNX_PATH")
TINYGRAD_REF=$(python3 openpilot/sunnypilot/models/tinygrad_ref.py)
if [ -z "$TINYGRAD_REF" ]; then
echo "::error::Failed to resolve tinygrad ref"
exit 1
fi
SAFE_NAME="${NAME// /-}"
echo "model_name=${NAME}" >> $GITHUB_OUTPUT
echo "safe_model_name=${SAFE_NAME}" >> $GITHUB_OUTPUT
echo "onnx_ref=${ONNX_REF}" >> $GITHUB_OUTPUT
echo "onnx_path=${ONNX_PATH}" >> $GITHUB_OUTPUT
echo "hf_defaults_path=${HF_DEFAULTS_PATH}" >> $GITHUB_OUTPUT
echo "tinygrad_ref=${TINYGRAD_REF}" >> $GITHUB_OUTPUT
build_small_model:
needs: resolve
if: ${{ inputs.target == 'small' }}
runs-on: [self-hosted, tici]
env:
MODELS_DIR: openpilot/selfdrive/modeld/models
COMPILER: tinygrad_repo/examples/openpilot
SMALL_ONNX: openpilot/selfdrive/modeld/models/driving_supercombo.onnx
SMALL_PKL: openpilot/selfdrive/modeld/models/driving_tinygrad.pkl
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Pull small ONNX via LFS
run: git lfs pull -I "${{ env.SMALL_ONNX }}"
- name: Set environment variables
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export UV_PYTHON_PREFERENCE=managed
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
uv sync --frozen
printenv >> $GITHUB_ENV
- name: Disable powersave
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
- name: Compile small model from ONNX
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
env ${TG_FLAGS} python3 ${{ github.workspace }}/${{ env.COMPILER }}/compile_onnx.py \
${{ github.workspace }}/${{ env.SMALL_ONNX }} \
${{ github.workspace }}/${{ env.SMALL_PKL }} \
--device-input "*" --out-of-band --benchmark-runs 1
- name: Chunk small pkl
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH=${{ github.workspace }}
python3 -c "
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
import os
pkl = '${{ github.workspace }}/${{ env.SMALL_PKL }}'
size = os.path.getsize(pkl)
targets = get_chunk_targets(pkl, size)
chunk_file(pkl, targets)
print(f'Chunked into {len(targets)} files')
"
- name: Prepare output
env:
MODEL_NAME: ${{ needs.resolve.outputs.safe_model_name }}
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH=${{ github.workspace }}
MODELS_DIR="${{ github.workspace }}/openpilot/selfdrive/modeld/models"
OUTPUT_DIR="${{ github.workspace }}/small_output"
PKL_BASE="driving_tinygrad.pkl"
mkdir -p "$OUTPUT_DIR"
cp "$MODELS_DIR/${PKL_BASE}".chunk* "$OUTPUT_DIR/"
cp "$MODELS_DIR/${PKL_BASE}.chunkmanifest" "$OUTPUT_DIR/"
ONNX_SHA256=$(git cat-file blob "$(git ls-files -s "${{ github.workspace }}/${{ env.SMALL_ONNX }}" | awk '{print $2}')" | grep '^oid sha256:' | cut -d: -f2)
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
--model-dir "$MODELS_DIR" \
--output-dir "$OUTPUT_DIR" \
--custom-name "$MODEL_NAME" \
--upstream-branch "${{ needs.resolve.outputs.onnx_ref }}" \
--onnx-sha256 "$ONNX_SHA256"
echo "model-${MODEL_NAME}-${{ github.run_number }}" > "$OUTPUT_DIR/artifact_name.txt"
- name: Upload small model artifact
uses: actions/upload-artifact@v4
with:
name: model-${{ needs.resolve.outputs.safe_model_name }}-${{ github.run_number }}
path: ${{ github.workspace }}/small_output/
- name: Upload artifact name file
uses: actions/upload-artifact@v4
with:
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
path: ${{ github.workspace }}/small_output/artifact_name.txt
- name: Re-enable powersave
if: always()
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
build_big_model:
needs: resolve
if: ${{ inputs.target == 'big' }}
runs-on: [self-hosted, chestnut]
env:
MODELS_DIR: openpilot/selfdrive/modeld/models
COMPILER: tinygrad_repo/examples/openpilot
BIG_PKL: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
lfs: true
- name: Pull big PKL via LFS
run: git lfs pull -I "${{ env.BIG_PKL }}"
- name: Set environment variables
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export UV_PYTHON_PREFERENCE=managed
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
uv sync --frozen
printenv >> $GITHUB_ENV
- name: Disable powersave
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
- name: Wait for chestnut PCIe link
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
python3 -c "
import time
from openpilot.system.hardware.chestnut.flash import link_up
for i in range(10):
if link_up():
print(f'PCIe link up after {i+1} attempt(s)')
break
time.sleep(1)
else:
raise RuntimeError('Chestnut PCIe link not ready after 10 attempts')
"
- name: Chunk big pkl
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH=${{ github.workspace }}
python3 -c "
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
import os
pkl = '${{ github.workspace }}/${{ env.BIG_PKL }}'
size = os.path.getsize(pkl)
targets = get_chunk_targets(pkl, size)
chunk_file(pkl, targets)
print(f'Chunked into {len(targets)} files')
"
- name: Prepare output
env:
MODEL_NAME: ${{ needs.resolve.outputs.safe_model_name }}
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH=${{ github.workspace }}
MODELS_DIR="${{ github.workspace }}/openpilot/selfdrive/modeld/models"
OUTPUT_DIR="${{ github.workspace }}/big_output"
PKL_BASE="big_driving_tinygrad.pkl"
mkdir -p "$OUTPUT_DIR"
cp "$MODELS_DIR/${PKL_BASE}".chunk* "$OUTPUT_DIR/"
cp "$MODELS_DIR/${PKL_BASE}.chunkmanifest" "$OUTPUT_DIR/"
PKL_LFS_SHA256=$(sha256sum "$MODELS_DIR/${PKL_BASE}" | cut -d' ' -f1)
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
--model-dir "$MODELS_DIR" \
--output-dir "$OUTPUT_DIR" \
--custom-name "$MODEL_NAME" \
--upstream-branch "${{ needs.resolve.outputs.onnx_ref }}" \
--onnx-sha256 "$PKL_LFS_SHA256"
echo "model-${MODEL_NAME}-${{ github.run_number }}" > "$OUTPUT_DIR/artifact_name.txt"
- name: Upload big model artifact
uses: actions/upload-artifact@v4
with:
name: model-${{ needs.resolve.outputs.safe_model_name }}-${{ github.run_number }}
path: ${{ github.workspace }}/big_output/
- name: Upload artifact name file
uses: actions/upload-artifact@v4
with:
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
path: ${{ github.workspace }}/big_output/artifact_name.txt
- name: Re-enable powersave
if: always()
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
upload_defaults:
needs: [ resolve, build_small_model, build_big_model, build_dm_model ]
if: |
${{
!cancelled() &&
(inputs.target == 'big' && needs.build_big_model.result == 'success' ||
inputs.target == 'small' && needs.build_small_model.result == 'success' ||
inputs.target == 'dm' && needs.build_dm_model.result == 'success')
}}
runs-on: ubuntu-24.04
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4
- name: Pull LFS for resolved ONNX files
if: ${{ needs.resolve.outputs.onnx_path != '' }}
run: git lfs pull -I "${{ needs.resolve.outputs.onnx_path }}"
- name: Install huggingface_hub
run: pip install --upgrade "huggingface_hub>=0.22.0"
- name: Download artifact name
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
uses: actions/download-artifact@v4
with:
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
path: artifact_name
- name: Read artifact name
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
id: artifact
run: |
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
- name: Download model artifact
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
uses: actions/download-artifact@v4
with:
name: ${{ steps.artifact.outputs.artifact_name }}
path: output
- name: Upload model to HF
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
ARTIFACT_NAME: ${{ steps.artifact.outputs.artifact_name }}
run: |
rm -f output/artifact_name.txt
export PYTHONPATH=$(pwd)
python3 release/ci/upload_default_model.py \
--hf-repo "${{ env.HF_REPO }}" \
--hf-defaults-path "${{ needs.resolve.outputs.hf_defaults_path }}" \
--artifact-name "$ARTIFACT_NAME" \
--model-dir output \
--model-name "${{ needs.resolve.outputs.model_name }}" \
--tinygrad-ref "${{ needs.resolve.outputs.tinygrad_ref }}" \
--run-number "${{ github.run_number }}"
- name: Download DM artifact
if: ${{ inputs.target == 'dm' }}
uses: actions/download-artifact@v4
with:
name: dm-model-${{ github.run_number }}
path: dm_output
- name: Generate DM metadata and upload to HF
if: ${{ inputs.target == 'dm' }}
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
export PYTHONPATH=$(pwd)
python3 -c "
import json, hashlib
from pathlib import Path
from datetime import datetime, UTC
dm_dir = Path('dm_output')
manifest = list(dm_dir.glob('*.chunkmanifest'))
assert manifest, 'No chunkmanifest found'
pkl_name = manifest[0].name.removesuffix('.chunkmanifest')
num_chunks = int(manifest[0].read_text().strip())
chunks = []
for i in range(num_chunks):
chunk = dm_dir / f'{pkl_name}.chunk{i+1:02d}of{num_chunks:02d}'
chunks.append({
'file_name': chunk.name,
'sha256': hashlib.sha256(chunk.read_bytes()).hexdigest()
})
digest = hashlib.sha256()
for c in chunks:
with open(dm_dir / c['file_name'], 'rb') as f:
while block := f.read(1024*1024):
digest.update(block)
metadata = {
'bundles': [{
'short_name': 'DMMODEL',
'display_name': '${{ needs.resolve.outputs.model_name }}',
'ref': '${{ needs.resolve.outputs.onnx_ref }}',
'runner': 'tinygrad',
'build_time': datetime.now(UTC).strftime('%Y-%m-%dT%H:%M:%SZ'),
'models': [{
'type': 'chunked',
'artifact': {
'file_name': pkl_name,
'download_uri': {'url': '', 'sha256': digest.hexdigest()},
'chunks': chunks
}
}]
}]
}
with open(dm_dir / 'metadata.json', 'w') as f:
json.dump(metadata, f, indent=2)
print('Generated DM metadata.json')
"
python3 release/ci/upload_default_model.py \
--hf-repo "${{ env.HF_REPO }}" \
--hf-defaults-path "${{ needs.resolve.outputs.hf_defaults_path }}" \
--artifact-name "dm-model-${{ github.run_number }}" \
--model-dir dm_output \
--onnx-path "${{ needs.resolve.outputs.onnx_path }}" \
--onnx-ref "${{ needs.resolve.outputs.onnx_ref }}" \
--model-name "${{ needs.resolve.outputs.model_name }}" \
--tinygrad-ref "${{ needs.resolve.outputs.tinygrad_ref }}" \
--run-number "${{ github.run_number }}"
build_dm_model:
needs: resolve
if: ${{ inputs.target == 'dm' }}
runs-on: [self-hosted, tici]
env:
DM_ONNX: openpilot/selfdrive/modeld/models/dmonitoring_model.onnx
DM_PKL: openpilot/selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Pull DM ONNX via LFS
run: git lfs pull -I "${{ env.DM_ONNX }}"
- name: Set environment variables
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export UV_PYTHON_PREFERENCE=managed
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
uv sync --frozen
printenv >> $GITHUB_ENV
- name: Disable powersave
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
- name: Compile DM model
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
taskset -c 7 env ${TG_FLAGS} python3 \
${{ github.workspace }}/tinygrad_repo/examples/openpilot/compile3.py \
${{ github.workspace }}/${{ env.DM_ONNX }} \
${{ github.workspace }}/${{ env.DM_PKL }}
- name: Chunk DM pkl
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH=${{ github.workspace }}
python3 -c "
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
import os
pkl = '${{ github.workspace }}/${{ env.DM_PKL }}'
size = os.path.getsize(pkl)
targets = get_chunk_targets(pkl, size)
chunk_file(pkl, targets)
print(f'Chunked {pkl} into {len(targets)} chunks')
"
- name: Compile DM warp
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
COMPILER="${{ github.workspace }}/tinygrad_repo/examples/openpilot"
MODELS_DIR="${{ github.workspace }}/openpilot/selfdrive/modeld/models"
DM_SIZE=$(python3 -c "from openpilot.common.transformations.model import DM_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
for res in $(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')"); do
CAM_W=$(echo $res | cut -d x -f1)
CAM_H=$(echo $res | cut -d x -f2)
STRIDE_INFO=$(python3 -c "
import sys
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
s,y,u,sz = get_nv12_info(int(sys.argv[1]), int(sys.argv[2]))
print(f'{s},{y},{u},{sz}')
" $CAM_W $CAM_H)
WARP_PKL="${MODELS_DIR}/dm_warp_${res}_tinygrad.pkl"
taskset -c 7 env ${TG_FLAGS} python3 "${COMPILER}/compile_warp.py" \
--frame ${CAM_W},${CAM_H},${STRIDE_INFO} \
--warp-to ${DM_SIZE} \
--layout luma \
--border-fill 16 \
--transform-device NPY \
--output "${WARP_PKL}"
done
- name: Prepare DM output
run: |
mkdir -p dm_output
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunk* dm_output/
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunkmanifest dm_output/
cp ${{ github.workspace }}/openpilot/selfdrive/modeld/models/dm_warp_* dm_output/
- name: Upload DM artifact
uses: actions/upload-artifact@v4
with:
name: dm-model-${{ github.run_number }}
path: dm_output/
- name: Re-enable powersave
if: always()
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
@@ -12,11 +12,11 @@ on:
required: false
type: string
recompiled_dir:
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
description: 'Existing recompiled directory number (e.g. 1 for recompiled1)'
required: true
type: string
json_version:
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
description: 'driving_models version number to update (e.g. 18 for driving_models_v18.json)'
required: true
type: string
artifact_suffix:
@@ -29,11 +29,29 @@ on:
required: false
type: boolean
default: true
bypass_push:
description: 'Bypass pushing to GitLab for build-all'
target_hardware:
description: 'Hardware target to compile for (qcom or chestnut)'
required: false
default: true
type: boolean
type: string
default: 'qcom'
hf_repo:
description: 'Hugging Face dataset repository (e.g. sunnypilot/sunnypilot_models_v1)'
required: false
type: string
default: 'sunnypilot/sunnypilot_models_v1'
docs_repo:
description: 'GitHub repo holding the driving_models JSON on its gh-pages branch'
required: false
type: string
default: 'sunnypilot/sunnypilot-models'
set_min_version:
description: 'Minimum selector version'
required: false
type: string
tinygrad_ref:
description: 'Tinygrad reference'
required: false
type: string
workflow_dispatch:
inputs:
upstream_branch:
@@ -63,12 +81,11 @@ on:
default: 'None'
options:
- None
- Simple Plan Models
- Space Lab Models
- TR Models
- DTR Models
- Master Models
- Release Models
- 2026 World Models
- 2026 Deep RL Models
- Custom Merge Models
- FOF series models
- Other
custom_model_folder:
description: 'Custom model folder name (if "Other" selected)'
@@ -82,9 +99,27 @@ on:
description: 'Minimum selector version'
required: false
type: string
target_hardware:
description: 'Hardware target to compile for'
required: false
type: choice
default: 'qcom'
options:
- qcom
- chestnut
hf_repo:
description: 'Hugging Face dataset repository'
required: false
type: string
default: 'sunnypilot/sunnypilot_models_v1'
docs_repo:
description: 'GitHub repo holding the driving_models JSON on its gh-pages branch'
required: false
type: string
default: 'sunnypilot/sunnypilot-models'
env:
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
JSON_FILE: docs/docs/driving_models_${{ inputs.target_hardware == 'chestnut' && 'chestnut_v' || 'v' }}${{ inputs.json_version }}.json
jobs:
build_model:
@@ -94,56 +129,45 @@ jobs:
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
is_20hz: ${{ inputs.is_20hz }}
artifact_suffix: ${{ inputs.artifact_suffix }}
target_hardware: ${{ inputs.target_hardware }}
secrets: inherit
publish_model:
if: ${{ !inputs.bypass_push && !cancelled() }}
if: ${{ !cancelled() && needs.build_model.result == 'success' }}
concurrency:
group: gitlab-push-${{ inputs.recompiled_dir }}
group: hf-push-${{ inputs.recompiled_dir }}
cancel-in-progress: false
needs: build_model
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
- name: Add GitLab.com SSH key to known_hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
- name: Clone GitLab docs repo
env:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
echo "Cloning GitLab"
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
echo "checkout models/${RECOMPILED_DIR}"
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
git checkout main
cd ..
- name: Checkout docs repo
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot-models
repository: ${{ inputs.docs_repo }}
ref: gh-pages
path: docs
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
- name: Validate recompiled dir and JSON version
- name: Install huggingface_hub
run: pip install --upgrade "huggingface_hub>=0.22.0"
- name: Validate hf_repo and JSON version
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
if [ ! -d "gitlab_docs/models/$RECOMPILED_DIR" ]; then
echo "Recompiled dir $RECOMPILED_DIR does not exist in GitLab repo"
exit 1
fi
if [ ! -f "$JSON_FILE" ]; then
echo "JSON file $JSON_FILE does not exist!"
exit 1
fi
python3 -c "
import sys
from huggingface_hub import HfApi
HfApi().repo_info(repo_id=sys.argv[1], repo_type='dataset')
print(f'Success: Repo {sys.argv[1]} exists.')
" "${{ inputs.hf_repo }}"
- name: Download artifact name file
uses: actions/download-artifact@v4
@@ -163,49 +187,26 @@ jobs:
name: ${{ steps.read-artifact-name.outputs.artifact_name }}
path: output
- name: Remove unwanted files
run: |
find output -type f -name 'dmonitoring_model_tinygrad.pkl' -delete
find output -type f -name 'dmonitoring_model.onnx' -delete
- name: Copy model artifact(s) to GitLab recompiled dir
- name: Create models folder
env:
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
run: |
ARTIFACT_DIR="gitlab_docs/models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
mkdir -p "$ARTIFACT_DIR"
for path in output/*; do
if [ "$(basename "$path")" = "artifact_name.txt" ]; then
continue
fi
name="$(basename "$path")"
if [ -d "$path" ]; then
mkdir -p "$ARTIFACT_DIR/$name"
cp -r "$path"/* "$ARTIFACT_DIR/$name/"
echo "Copied dir $name -> $ARTIFACT_DIR/$name"
else
cp "$path" "$ARTIFACT_DIR/"
echo "Copied file $name -> $ARTIFACT_DIR/"
fi
done
mkdir -p "local_models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
cp -r output/* "local_models/${RECOMPILED_DIR}/${ARTIFACT_NAME}/"
rm -f "local_models/${RECOMPILED_DIR}/${ARTIFACT_NAME}/artifact_name.txt"
- name: Push recompiled dir to GitLab
- name: Upload to Hugging Face
env:
GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
HF_TOKEN: ${{ secrets.HF_TOKEN }}
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
run: |
cd gitlab_docs
git checkout main
git pull origin main
for d in models/"$RECOMPILED_DIR"/*/; do
git sparse-checkout add "$d"
done
git add models/"$RECOMPILED_DIR"
git config --global user.name "GitHub Action"
git config --global user.email "action@github.com"
git commit -m "Create/Update $RECOMPILED_DIR with new/updated model from build-single-tinygrad-model" || echo "No changes to commit"
git push origin main
hf upload ${{ inputs.hf_repo }} \
output/ \
"models/${RECOMPILED_DIR}/${ARTIFACT_NAME}/" \
--repo-type=dataset
- run: |
- name: Pull gh-pages
run: |
cd docs
git pull origin gh-pages
@@ -221,9 +222,11 @@ jobs:
fi
[ -n "${{ inputs.generation }}" ] && ARGS="$ARGS --generation \"${{ inputs.generation }}\""
[ -n "${{ inputs.version }}" ] && ARGS="$ARGS --version \"${{ inputs.version }}\""
[ -n "${{ inputs.set_min_version }}" ] && ARGS="$ARGS --set-min-version \"${{ inputs.set_min_version }}\""
[ -n "${{ inputs.tinygrad_ref }}" ] && ARGS="$ARGS --tinygrad-ref \"${{ inputs.tinygrad_ref }}\""
eval python3 docs/json_parser.py \
--json-path "$JSON_FILE" \
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
--recompiled-dir "local_models/$RECOMPILED_DIR" \
--sort-by-date \
$ARGS
+46 -14
View File
@@ -6,7 +6,7 @@ on:
- master
pull_request:
paths:
- 'cereal/**'
- 'openpilot/cereal/**'
workflow_dispatch:
workflow_call:
inputs:
@@ -30,36 +30,68 @@ jobs:
- name: Checkout sunnypilot cereal
uses: actions/checkout@v6
with:
sparse-checkout: cereal
sparse-checkout: |
openpilot/cereal
uv.lock
submodules: false
- name: Init sunnypilot opendbc submodule
run: git submodule update --init --depth 1 opendbc_repo
- name: Checkout upstream openpilot cereal
- name: Locate sunnypilot capnp import paths
id: locate-sp-capnp
run: |
SP_IMPORT_ARGS=""
SP_CAR_CAPNP=$(find opendbc_repo -maxdepth 4 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
if [ -n "$SP_CAR_CAPNP" ]; then
SP_IMPORT_ARGS="-I $SP_CAR_CAPNP"
echo "Found sunnypilot car.capnp at: $SP_CAR_CAPNP"
fi
echo "import_args=$SP_IMPORT_ARGS" >> "$GITHUB_OUTPUT"
- name: Checkout upstream openpilot
uses: actions/checkout@v6
with:
repository: 'commaai/openpilot'
path: upstream_openpilot
sparse-checkout: cereal
ref: "refs/heads/master"
- name: Init upstream opendbc submodule
working-directory: upstream_openpilot
run: git submodule update --init --depth 1 opendbc_repo
- name: Install uv
run: pip install uv
- name: Locate upstream capnp paths
id: locate-capnp
run: |
CEREAL_DIR=$(find upstream_openpilot -maxdepth 4 -name log.capnp -path '*/cereal/log.capnp' -printf '%h\n' -quit)
if [ -z "$CEREAL_DIR" ]; then
echo "::error::Could not locate cereal/log.capnp in upstream openpilot"
exit 1
fi
echo "cereal_dir=$CEREAL_DIR" >> "$GITHUB_OUTPUT"
echo "Found upstream cereal at: $CEREAL_DIR"
IMPORT_ARGS=""
CAR_CAPNP=$(find upstream_openpilot -maxdepth 5 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
if [ -n "$CAR_CAPNP" ]; then
IMPORT_ARGS="-I $CAR_CAPNP"
echo "Found car.capnp at: $CAR_CAPNP"
fi
echo "import_args=$IMPORT_ARGS" >> "$GITHUB_OUTPUT"
- name: Install pycapnp
run: |
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
pip install "pycapnp==${PYCAPNP_VER}"
- name: Generate sunnypilot schema
run: |
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
-g -f /tmp/sp_schema.json --cereal-dir cereal
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
-g -f /tmp/sp_schema.json --cereal-dir openpilot/cereal \
${{ steps.locate-sp-capnp.outputs.import_args }}
- name: Validate against upstream
run: |
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
-r -f /tmp/sp_schema.json --cereal-dir upstream_openpilot/cereal
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
-r -f /tmp/sp_schema.json --cereal-dir ${{ steps.locate-capnp.outputs.cereal_dir }} \
${{ steps.locate-capnp.outputs.import_args }}
@@ -0,0 +1,73 @@
name: Compile warp for sunnypilot modeld
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0'
jobs:
compile_warps:
runs-on: [self-hosted, chestnut]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set environment variables
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export UV_PYTHON_PREFERENCE=managed
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
uv sync --frozen
printenv >> $GITHUB_ENV
- name: Disable powersave
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
- name: Compile Warp Kernels
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
mkdir -p openpilot/sunnypilot/modeld_v2/models/
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
echo "Model size: $MODEL_SIZE"
echo "Camera resolutions: $CAMERA_RES"
for res in $CAMERA_RES; do
NV12_INFO=$(python3 -c "from openpilot.system.camerad.cameras.nv12_info import get_nv12_info; w, h = map(int, '${res}'.split('x')); print(','.join(map(str, get_nv12_info(w, h))))")
WARP_PKL="openpilot/sunnypilot/modeld_v2/models/driving_warp_${res}_tinygrad.pkl"
echo "Compiling $WARP_PKL on QCOM"
taskset -c 7 env DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1 python3 tinygrad_repo/examples/openpilot/compile_warp.py \
--frame ${res/x/,},${NV12_INFO} --warp-to ${MODEL_SIZE} --layout yuv420 --frames 2 --output "${WARP_PKL}"
BIG_WARP_PKL="openpilot/sunnypilot/modeld_v2/models/big_driving_warp_${res}_tinygrad.pkl"
echo "Compiling $BIG_WARP_PKL on AMD"
taskset -c 7 env DEBUG=1 DEV=USB+AMD:LLVM FRAME_DEV=CPU FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2 TC_MIN_GLOBALS=32 python3 tinygrad_repo/examples/openpilot/compile_warp.py \
--frame ${res/x/,},${NV12_INFO} --warp-to ${MODEL_SIZE} --layout yuv420 --frames 2 --output "${BIG_WARP_PKL}"
done
- name: Re-enable powersave
if: always()
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
- name: Create Pull Request
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
with:
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "[bot] Update warp pkl for modeld_v2"
title: "[bot] Update modeld_v2 Warp"
branch: "auto/compile-warp-kernels"
base: "master"
delete-branch: true
labels: bot
add-paths: |
openpilot/sunnypilot/modeld_v2/models/*.pkl
+26 -13
View File
@@ -17,29 +17,42 @@ jobs:
- name: Wait for process replay
id: wait
continue-on-error: true
uses: lewagon/wait-on-check-action@v1.3.4
uses: lewagon/wait-on-check-action@1d57e2c51a58d812d2765e036a028b6bdb5a6154
with:
ref: ${{ github.event.pull_request.head.sha }}
check-name: process replay
repo-token: ${{ secrets.GITHUB_TOKEN }}
allowed-conclusions: success,failure
wait-interval: 20
- name: Get tests run ID
if: steps.wait.outcome == 'success'
id: tests-run
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
run_id=$(gh api \
"repos/${{ github.repository }}/actions/workflows/tests.yaml/runs?event=pull_request&head_sha=${{ github.event.pull_request.head.sha }}&per_page=1" \
--jq '.workflow_runs[0].id // empty')
if [ -z "$run_id" ]; then
echo "No tests.yaml run found for ${{ github.event.pull_request.head.sha }}" >&2
exit 1
fi
echo "run-id=$run_id" >> "$GITHUB_OUTPUT"
- name: Download diff
if: steps.wait.outcome == 'success'
uses: dawidd6/action-download-artifact@v6
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: tests.yaml
workflow_conclusion: ''
pr: ${{ github.event.number }}
github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
run-id: ${{ steps.tests-run.outputs.run-id }}
name: diff_report_${{ github.event.number }}
path: .
allow_forks: true
- name: Comment on PR
if: steps.wait.outcome == 'success'
uses: thollander/actions-comment-pull-request@v2
if: steps.wait.outcome == 'success' && hashFiles('diff_report.txt') != ''
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
with:
filePath: diff_report.txt
comment_tag: diff_report
pr_number: ${{ github.event.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
file-path: diff_report.txt
comment-tag: diff_report
pr-number: ${{ github.event.number }}
github-token: ${{ secrets.GITHUB_TOKEN }}
+17 -12
View File
@@ -15,26 +15,31 @@ concurrency:
group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
env:
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
jobs:
docs:
name: build docs
runs-on: ubuntu-24.04
runs-on: ${{
(github.repository == 'commaai/openpilot') &&
((github.event_name != 'pull_request') ||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
&& fromJSON('["namespace-profile-amd64-8x16"]')
|| fromJSON('["ubuntu-24.04"]') }}
steps:
- uses: commaai/timeout@v1
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/checkout@v7
- run: ./tools/op.sh setup
# Build
- name: Build docs
run: |
git lfs pull
pip install zensical
python scripts/docs.py build
run: ./tools/op.sh docs --build
# Push to docs.comma.ai
- uses: actions/checkout@v6
- uses: actions/checkout@v7
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
with:
path: openpilot-docs
@@ -45,14 +50,14 @@ jobs:
run: |
set -x
source release/identity.sh
source tools/release/identity.sh
cd openpilot-docs
git checkout --orphan tmp
git rm -rf .
# copy over docs
cp -r ../docs_site/ docs/
cp -r ../docs/_site/ docs/
# GitHub pages config
touch docs/.nojekyll
@@ -0,0 +1,73 @@
name: Download HF model chunks
description: Resolve and download model chunks from HuggingFace in parallel
inputs:
hf_repo:
description: HuggingFace dataset repo
required: true
models:
description: 'JSON array of {hf_path, onnx_hash, canonical} objects'
required: true
dest_dir:
description: Destination directory for downloaded chunks
required: true
runs:
using: composite
steps:
- name: Download model chunks
shell: bash
env:
HF_REPO: ${{ inputs.hf_repo }}
MODELS_JSON: ${{ inputs.models }}
DEST_DIR: ${{ inputs.dest_dir }}
run: |
set -eo pipefail
DOWNLOAD_LIST=$(mktemp)
resolve_chunks() {
local HF_PATH="$1" ONNX_HASH="$2" CANONICAL="$3" DEST_DIR="$4"
local JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_PATH}/default_models.json"
local DEFAULTS BUNDLE ARTIFACT BASE_URL NUM_CHUNKS
DEFAULTS=$(curl -fsSL "$JSON_URL")
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)')
ARTIFACT=$(echo "$BUNDLE" | jq -r '.models[0].artifact')
BASE_URL=$(echo "$ARTIFACT" | jq -r '.download_uri.url' | sed 's|/[^/]*$||')
NUM_CHUNKS=$(echo "$ARTIFACT" | jq -r '.chunks | length')
mkdir -p "$DEST_DIR"
while IFS= read -r CHUNK_NAME; do
CHUNK_IDX=$(echo "$CHUNK_NAME" | grep -oP 'chunk\K[0-9]+of[0-9]+' || true)
if [ -z "$CHUNK_IDX" ]; then
echo "::error::Failed to parse chunk index from: $CHUNK_NAME"
return 1
fi
ENCODED_URL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BASE_URL}/${CHUNK_NAME}', safe=':/'))")
printf '%s\t%s\n' "$ENCODED_URL" "${DEST_DIR}/${CANONICAL}.chunk${CHUNK_IDX}" >> "$DOWNLOAD_LIST"
done < <(echo "$ARTIFACT" | jq -r '.chunks[].file_name')
echo "$NUM_CHUNKS" > "${DEST_DIR}/${CANONICAL}.chunkmanifest"
if [ "$CANONICAL" = "dmonitoring_model_tinygrad.pkl" ]; then
for warp in dm_warp_1928x1208_tinygrad.pkl dm_warp_1344x760_tinygrad.pkl; do
ENCODED_URL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BASE_URL}/${warp}', safe=':/'))")
printf '%s\t%s\n' "$ENCODED_URL" "${DEST_DIR}/${warp}" >> "$DOWNLOAD_LIST"
done
fi
}
echo "$MODELS_JSON" | jq -c '.[]' | while IFS= read -r model; do
HF_PATH=$(echo "$model" | jq -r '.hf_path')
ONNX_HASH=$(echo "$model" | jq -r '.onnx_hash')
CANONICAL=$(echo "$model" | jq -r '.canonical')
resolve_chunks "$HF_PATH" "$ONNX_HASH" "$CANONICAL" "$DEST_DIR"
done
TOTAL=$(wc -l < "$DOWNLOAD_LIST")
echo "Downloading $TOTAL chunks with 8 parallel connections..."
xargs -P8 -d'\n' -I{} bash -c '
URL="${1%% *}"
DEST="${1#* }"
echo "Downloading $(basename "$DEST")"
curl -fsSL --retry 3 --retry-delay 5 -o "$DEST" "$URL"
' _ {} < "$DOWNLOAD_LIST"
rm -f "$DOWNLOAD_LIST"
+4 -4
View File
@@ -11,7 +11,7 @@ jobs:
contents: write
steps:
- name: Delete stale Jenkins branches
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
@@ -52,7 +52,7 @@ jobs:
steps:
- name: Check for trigger phrase
id: check_comment
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const triggerPhrase = "trigger-jenkins";
@@ -72,7 +72,7 @@ jobs:
- name: Checkout repository
if: steps.check_comment.outputs.result == 'true'
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
ref: refs/pull/${{ github.event.issue.number }}/head
@@ -86,7 +86,7 @@ jobs:
- name: Delete trigger comment
if: steps.check_comment.outputs.result == 'true' && always()
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
await github.rest.issues.deleteComment({
+14 -1
View File
@@ -65,8 +65,21 @@ jobs:
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
echo ' locksverify = false' >> .lfsconfig
- name: Configure LFS transfer settings
run: |
git config lfs.activitytimeout 300
git config lfs.transfer.maxretries 5
git config lfs.concurrenttransfers 4
- name: Push LFS
id: sync-and-commit
run: |
git lfs ls-files -l
git lfs push --all origin
for attempt in 1 2 3; do
echo "Push attempt $attempt..."
git lfs push --all origin && exit 0
echo "Attempt $attempt failed, retrying in 30s..."
sleep 30
done
echo "All push attempts failed"
exit 1
-42
View File
@@ -1,42 +0,0 @@
name: "model review"
on:
pull_request:
types: [opened, reopened, synchronize]
paths:
- 'selfdrive/modeld/models/*.onnx'
workflow_dispatch:
jobs:
comment:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
if: github.repository == 'commaai/openpilot'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Checkout master
uses: actions/checkout@v6
with:
ref: master
path: base
- run: git lfs pull
- run: cd base && git lfs pull
- name: scripts/reporter.py
id: report
run: |
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "## Model Review" >> $GITHUB_OUTPUT
PYTHONPATH=${{ github.workspace }} MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Post model report comment
uses: marocchino/sticky-pull-request-comment@baa7203ed60924babbe5dcd0ac8eae3b66ec5e16
with:
header: model-review
message: ${{ steps.report.outputs.content }}
-39
View File
@@ -1,39 +0,0 @@
name: prebuilt
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: release/ci/docker_build_sp.sh
jobs:
build_prebuilt:
name: build prebuilt
runs-on: ubuntu-latest
if: github.repository == 'sunnypilot/sunnypilot'
env:
PUSH_IMAGE: true
permissions:
checks: read
contents: read
packages: write
steps:
- name: Wait for green check mark
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
with:
ref: master
wait-interval: 30
running-workflow-name: 'build prebuilt'
repo-token: ${{ secrets.GITHUB_TOKEN }}
check-regexp: ^((?!.*(build master-ci|create badges).*).)*$
- uses: actions/checkout@v6
with:
submodules: true
- run: git lfs pull
- name: Build and Push docker image
run: |
$DOCKER_LOGIN
eval "$BUILD"
-28
View File
@@ -1,28 +0,0 @@
name: Release Drafter
on:
push:
branches:
- master
tags:
- 'v*'
pull_request_target:
types: [opened, reopened, synchronize]
workflow_dispatch:
permissions:
contents: read
jobs:
update_release_draft:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
with:
config-name: release-drafter.yml
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+12 -3
View File
@@ -4,6 +4,11 @@ on:
- cron: '0 9 * * *'
workflow_dispatch:
env:
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
jobs:
build___nightly:
name: build __nightly
@@ -15,17 +20,21 @@ jobs:
steps:
- name: Wait for green check mark
if: ${{ github.event_name == 'schedule' }}
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
uses: lewagon/wait-on-check-action@1d57e2c51a58d812d2765e036a028b6bdb5a6154
with:
ref: master
wait-interval: 30
running-workflow-name: 'build __nightly'
repo-token: ${{ secrets.GITHUB_TOKEN }}
check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$
- uses: actions/checkout@v4
- uses: actions/checkout@v4 # checkout version > v4 breaks nightly release script
with:
submodules: true
fetch-depth: 0
- run: ./tools/op.sh setup
- name: Push __nightly
run: BRANCH=__nightly release/build_stripped.sh
run: BRANCH=__nightly tools/release/build_stripped.sh
- name: Push chestnut nightly
run: |
git lfs pull --exclude=''
INCLUDE_BIG_MODEL=1 BRANCH=__nightly-chestnut tools/release/build_stripped.sh
+59 -4
View File
@@ -3,18 +3,25 @@ name: repo maintenance
on:
schedule:
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
pull_request:
types: [closed]
workflow_dispatch:
env:
PYTHONPATH: ${{ github.workspace }}
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
jobs:
package_updates:
name: package_updates
runs-on: ubuntu-latest
if: github.repository == 'sunnypilot/sunnypilot'
if: >-
github.repository == 'sunnypilot/sunnypilot' &&
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
submodules: true
- run: ./tools/op.sh setup
@@ -50,10 +57,10 @@ jobs:
git add .
- name: update car docs
run: |
python selfdrive/car/docs.py
python openpilot/selfdrive/car/docs.py
git add docs/CARS.md
- name: Create Pull Request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1
with:
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
token: ${{ github.repository == 'commaai/openpilot' && secrets.ACTIONS_CREATE_PR_PAT || secrets.GITHUB_TOKEN }}
@@ -75,3 +82,51 @@ jobs:
${{ steps.pip_tree.outputs.PIP_TREE }}
```
labels: bot
cleanup_closed_branches:
if: github.repository == 'commaai/openpilot' && github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const upstream = `${owner}/${repo}`;
const closed = context.payload.pull_request;
if (closed) {
if (closed.head.repo?.full_name === upstream) {
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${closed.head.ref}` }).catch(console.log);
}
return;
}
for await (const response of github.paginate.iterator(github.rest.pulls.list, {
owner,
repo,
state: 'closed',
per_page: 100,
})) {
for (const pr of response.data) {
if (pr.head.repo?.full_name !== upstream) continue;
const branch = pr.head.ref;
try {
await github.rest.git.deleteRef({
owner,
repo,
ref: `heads/${branch}`,
});
console.log(`Deleted branch ${branch} (PR #${pr.number})`);
} catch (error) {
if (error.status === 422 || error.status === 403) {
console.log(`Skipping branch ${branch} (PR #${pr.number}): ${error.message}`);
continue;
}
throw error;
}
}
}
+121 -74
View File
@@ -6,7 +6,7 @@ env:
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
UPSTREAM_REPO: "commaai/openpilot"
TINYGRAD_PATH: ${{ github.workspace }}/tinygrad_repo
MODELS_DIR: ${{ github.workspace }}/selfdrive/modeld/models
MODELS_DIR: ${{ github.workspace }}/openpilot/selfdrive/modeld/models
on:
workflow_call:
@@ -30,6 +30,11 @@ on:
required: false
type: string
default: ''
target_hardware:
description: 'Hardware target to compile for (qcom or chestnut)'
required: false
type: string
default: 'qcom'
workflow_dispatch:
inputs:
upstream_branch:
@@ -46,6 +51,14 @@ on:
required: false
type: boolean
default: true
target_hardware:
description: 'Hardware target to compile for'
required: true
type: choice
options:
- qcom
- chestnut
default: 'qcom'
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
@@ -67,6 +80,7 @@ jobs:
with:
repository: commaai/openpilot
ref: ${{ inputs.upstream_branch }}
fetch-depth: 1
submodules: recursive
path: openpilot
@@ -76,26 +90,40 @@ jobs:
with:
repository: sunnypilot/sunnypilot
ref: ${{ inputs.upstream_branch }}
fetch-depth: 1
submodules: recursive
path: openpilot
- name: Get commit date
id: commit-date
run: |
cd ${{ github.workspace }}/openpilot
cd ${{ github.workspace }}/openpilot/openpilot
commit_date=$(git log -1 --format=%cd --date=format:'%B %d, %Y')
echo "model_date=${commit_date}" >> $GITHUB_OUTPUT
cat $GITHUB_OUTPUT
- run: |
cd ${{ github.workspace }}/openpilot
git lfs pull
cd ${{ github.workspace }}/openpilot/openpilot
if [ "${{ inputs.target_hardware }}" != "chestnut" ]; then
git lfs pull -X "**/selfdrive/modeld/models/big_*.onnx,**/selfdrive/modeld/models/dmonitoring_*.onnx,**/selfdrive/modeld/models/big_*.pkl,**/selfdrive/modeld/models/dmonitoring_*.pkl"
rm -f selfdrive/modeld/models/big_*.onnx selfdrive/modeld/models/dmonitoring_*.onnx selfdrive/modeld/models/big_*.pkl selfdrive/modeld/models/dmonitoring_*.pkl
else
git lfs pull -I "**/selfdrive/modeld/models/big_*.onnx,**/selfdrive/modeld/models/big_*.pkl" -X ""
find selfdrive/modeld/models -type f \( -name "*.onnx" -o -name "*.pkl" \) ! -name "big_*.onnx" ! -name "big_*.pkl" -delete
fi
if grep -lIF "version https://git-lfs.github.com/spec/v1" selfdrive/modeld/models/*.onnx selfdrive/modeld/models/*.pkl 2>/dev/null; then
echo "::error::the ONNX or PKL files above are still LFS pointers, not real models"
exit 1
fi
- name: 'Upload Artifact'
uses: actions/upload-artifact@v4
with:
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
path: ${{ github.workspace }}/openpilot/selfdrive/modeld/models/*.onnx
path: |
${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.onnx
${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.pkl
if-no-files-found: error
build_model:
runs-on: [self-hosted, tici]
runs-on: [self-hosted, "${{ inputs.target_hardware == 'chestnut' && 'chestnut' || 'tici' }}"]
needs: get_model
env:
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
@@ -103,24 +131,9 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: recursive
- run: git lfs pull
- name: Cache SCons
uses: actions/cache@v4
with:
path: ${{env.SCONS_CACHE_DIR}}
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model-${{ github.sha }}
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
restore-keys: |
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_NEW_BRANCH }}-model
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_BRANCH }}-model
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_NEW_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}
- name: Set environment variables
id: set-env
@@ -128,7 +141,10 @@ jobs:
# Set up common environment
source /etc/profile;
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export UV_PYTHON_PREFERENCE=managed
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
uv sync --frozen
printenv >> $GITHUB_ENV
if [[ "${{ runner.debug }}" == "1" ]]; then
cat $GITHUB_OUTPUT
@@ -148,78 +164,110 @@ jobs:
if [[ "${{ runner.debug }}" == "1" ]]; then
printenv
fi
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
rm -rf ${{ env.MODELS_DIR }}/*.onnx
rm -rf ${{ env.MODELS_DIR }}/*.onnx*
- name: Download model artifacts
uses: actions/download-artifact@v4
with:
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
path: ${{ github.workspace }}/selfdrive/modeld/models
- run: |
rm -f ${{ github.workspace }}/selfdrive/modeld/models/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx
path: ${{ env.MODELS_DIR }}
- name: Build Model
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
COMPILE_MODELD="${{ github.workspace }}/sunnypilot/modeld_v2/compile_modeld.py"
COMPILE_MODELD="${{ github.workspace }}/openpilot/sunnypilot/modeld_v2/compile_modeld.py"
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
# Generate metadata for all ONNX files
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
echo "Generating metadata: $onnx_file"
env ${TG_FLAGS} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
done
TG_FLAGS_QCOM="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
if [ "${{ inputs.target_hardware }}" == "chestnut" ]; then
echo "CHESTNUT build"
export CHESTNUT=1
TG_FLAGS="DEBUG=1 DEV=USB+AMD:LLVM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2 TC_OCCUPANCY_OPT=1"
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
else
echo "QCOM build"
TG_FLAGS="$TG_FLAGS_QCOM"
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
fi
# Detect model type and build compile args
VISION_ONNX="${{ env.MODELS_DIR }}/driving_vision.onnx"
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
SUPERCOMBO_ONNX="${{ env.MODELS_DIR }}/supercombo.onnx"
NATIVE_PKL=$(find "${{ env.MODELS_DIR }}" -maxdepth 1 -name "*.pkl" -print -quit)
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
if [ -f "$VISION_ONNX" ]; then
ONNX_ARGS="--vision-onnx $VISION_ONNX"
if [ -f "$ON_POLICY_ONNX" ] && [ -f "$OFF_POLICY_ONNX" ]; then
MODEL_TYPE=vision_multi_policy
ONNX_ARGS="$ONNX_ARGS --off-policy-onnx $OFF_POLICY_ONNX --on-policy-onnx $ON_POLICY_ONNX"
elif [ -f "$OFF_POLICY_ONNX" ] && [ -f "$POLICY_ONNX" ]; then
MODEL_TYPE=vision_multi_policy
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX --off-policy-onnx $OFF_POLICY_ONNX"
elif [ -f "$POLICY_ONNX" ]; then
MODEL_TYPE=vision_policy
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX"
if [ -n "$NATIVE_PKL" ]; then
echo "Found native precompiled pkl: $NATIVE_PKL"
if [ "$NATIVE_PKL" != "$OUTPUT_PKL" ]; then
mv "$NATIVE_PKL" "$OUTPUT_PKL"
fi
elif [ -f "$SUPERCOMBO_ONNX" ]; then
MODEL_TYPE=supercombo
ONNX_ARGS="--supercombo-onnx $SUPERCOMBO_ONNX"
fi
echo "Chunking pkl"
python3 -c "from openpilot.common.file_chunker import chunk_file, get_chunk_targets; import os; p='$OUTPUT_PKL'; chunk_file(p, get_chunk_targets(p, os.path.getsize(p)))"
else
# Generate metadata for all ONNX files
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
echo "Generating metadata: $onnx_file"
env ${TG_FLAGS_QCOM} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
done
if [ -n "$MODEL_TYPE" ]; then
echo "Detected: $MODEL_TYPE -> driving_tinygrad.pkl"
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
--model-type $MODEL_TYPE \
--model-size $MODEL_SIZE \
--camera-resolutions $CAMERA_RES \
$ONNX_ARGS \
--output "${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
fi
# Detect model type and build compile args
VISION_ONNX=""
for f in "${{ env.MODELS_DIR }}/driving_vision.onnx" "${{ env.MODELS_DIR }}/big_driving_vision.onnx"; do
[ -f "$f" ] && VISION_ONNX="$f" && break
done
- name: Validate Model Outputs
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
--validate-only \
--model-dir "${{ env.MODELS_DIR }}"
POLICY_ONNX=""
for f in "${{ env.MODELS_DIR }}/driving_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_policy.onnx"; do
[ -f "$f" ] && POLICY_ONNX="$f" && break
done
OFF_POLICY_ONNX=""
for f in "${{ env.MODELS_DIR }}/driving_off_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_off_policy.onnx"; do
[ -f "$f" ] && OFF_POLICY_ONNX="$f" && break
done
ON_POLICY_ONNX=""
for f in "${{ env.MODELS_DIR }}/driving_on_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_on_policy.onnx"; do
[ -f "$f" ] && ON_POLICY_ONNX="$f" && break
done
SUPERCOMBO_ONNX=""
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx" "${{ env.MODELS_DIR }}/big_supercombo.onnx" "${{ env.MODELS_DIR }}/big_driving_supercombo.onnx"; do
[ -f "$f" ] && SUPERCOMBO_ONNX="$f" && break
done
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
if [ -f "$VISION_ONNX" ]; then
ONNX_ARGS="--vision-onnx $VISION_ONNX"
if [ -f "$ON_POLICY_ONNX" ] && [ -f "$OFF_POLICY_ONNX" ]; then
MODEL_TYPE=vision_multi_policy
ONNX_ARGS="$ONNX_ARGS --off-policy-onnx $OFF_POLICY_ONNX --on-policy-onnx $ON_POLICY_ONNX"
elif [ -f "$OFF_POLICY_ONNX" ] && [ -f "$POLICY_ONNX" ]; then
MODEL_TYPE=vision_multi_policy
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX --off-policy-onnx $OFF_POLICY_ONNX"
elif [ -f "$POLICY_ONNX" ]; then
MODEL_TYPE=vision_policy
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX"
fi
elif [ -f "$SUPERCOMBO_ONNX" ]; then
MODEL_TYPE=supercombo
ONNX_ARGS="--supercombo-onnx $SUPERCOMBO_ONNX"
fi
if [ -n "$MODEL_TYPE" ]; then
echo "Detected: $MODEL_TYPE -> $OUTPUT_PKL"
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
--model-type $MODEL_TYPE \
--model-size $MODEL_SIZE \
--camera-resolutions $CAMERA_RES \
$ONNX_ARGS \
--output "$OUTPUT_PKL"
fi
fi
- name: Prepare Output
run: |
@@ -229,10 +277,8 @@ jobs:
# Copy the model files
rsync -avm \
--include='*.dlc' \
--include='*.pkl' \
--include='*.chunk*' \
--include='*.chunkmanifest' \
--include='*.onnx' \
--exclude='*' \
--delete-excluded \
--chown=comma:comma \
@@ -264,4 +310,5 @@ jobs:
- name: Re-enable powersave
if: always()
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
+394 -90
View File
@@ -4,12 +4,8 @@ env:
BUILD_DIR: "/data/openpilot"
OUTPUT_DIR: ${{ github.workspace }}/output
CI_DIR: ${{ github.workspace }}/release/ci
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
# Branch configurations
STAGING_SOURCE_BRANCH: 'master'
# Runtime configuration
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
@@ -40,8 +36,11 @@ jobs:
publish_concurrency_group: ${{ steps.strategy.outputs.publish_concurrency_group }}
is_stable_branch: ${{ steps.strategy.outputs.is_stable_branch }}
build: ${{ steps.strategy.outputs.build }}
include_big_model: ${{ steps.strategy.outputs.include_big_model }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Extract deploy strategy
id: strategy
run: |
@@ -79,9 +78,12 @@ jobs:
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
stable_version=$(cat openpilot/sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
include_big_model="$(echo "$CONFIG" | jq -r '.include_big_model // false')";
echo "include_big_model=$include_big_model" >> $GITHUB_OUTPUT
fi
echo "build=$BUILD" >> $GITHUB_OUTPUT
cat $GITHUB_OUTPUT
@@ -96,6 +98,8 @@ jobs:
}}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Wait for Tests
uses: ./.github/workflows/wait-for-action # Path to where you place the action
with:
@@ -109,11 +113,6 @@ jobs:
group: build-${{ github.head_ref || github.ref_name }}
cancel-in-progress: false
runs-on: [self-hosted, tici]
outputs:
new_branch: ${{ needs.prepare_strategy.outputs.new_branch }}
version: ${{ needs.prepare_strategy.outputs.version }}
extra_version_identifier: ${{ needs.prepare_strategy.outputs.extra_version_identifier }}
commit_sha: ${{ github.sha }}
if: ${{
(always() && !cancelled() && !failure()) &&
needs.prepare_strategy.result == 'success' &&
@@ -124,39 +123,22 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: recursive
ref: ${{ env.SOURCE_BRANCH }}
repository: ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.head.repo.full_name || github.repository }}
- run: git lfs pull
- name: Cache SCons
uses: actions/cache@v4
with:
path: ${{env.SCONS_CACHE_DIR}}
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
restore-keys: |
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_SOURCE_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}
- name: Set environment variables
id: set-env
run: |
echo "new_branch=${{ needs.prepare_strategy.outputs.new_branch }}" >> $GITHUB_OUTPUT
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
# Set up common environment
source /etc/profile;
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export UV_PYTHON_PREFERENCE=managed
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
uv sync
printenv >> $GITHUB_ENV
if [[ "${{ runner.debug }}" == "1" ]]; then
cat $GITHUB_OUTPUT
fi
- name: Setup build environment
run: |
@@ -165,67 +147,59 @@ jobs:
echo "Starting build stage..."
echo "BUILD_DIR: ${BUILD_DIR}"
echo "CI_DIR: ${CI_DIR}"
echo "VERSION: ${{ steps.set-env.outputs.version }}"
echo "VERSION: ${{ needs.prepare_strategy.outputs.version }}"
echo "UV_PROJECT_ENVIRONMENT: ${UV_PROJECT_ENVIRONMENT}"
echo "VIRTUAL_ENV: ${VIRTUAL_ENV}"
echo "-------"
if [[ "${{ runner.debug }}" == "1" ]]; then
printenv
fi
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
- name: Build Main Project
run: |
export PYTHONPATH="$BUILD_DIR"
./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
export PYTHONPATH="$BUILD_DIR:$BUILD_DIR/msgq_repo:$BUILD_DIR/opendbc_repo:$BUILD_DIR/rednose_repo:$BUILD_DIR/teleoprtc_repo:$BUILD_DIR/tinygrad_repo"
./tools/release/release_files.py | xargs -0 cp -pR --parents -t "$BUILD_DIR" --
# outside the checkout, which is wiped each run. /data/scons_cache is the device's, not ours.
SCONS_CACHE="$RUNNER_WORKSPACE/scons_cache"
mkdir -p "$SCONS_CACHE"
cd $BUILD_DIR
sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py
echo "Building sunnypilot's modeld_v2..."
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2
echo "Building sunnypilot's locationd..."
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd
echo "Building openpilot's locationd..."
scons -j1 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd
echo "Building locationd..."
# -j1: parallel rednose generators OOM the device
scons -j1 cache_dir="$SCONS_CACHE" --minimal \
openpilot/selfdrive/locationd openpilot/sunnypilot/selfdrive/locationd
echo "Building rest of sunnypilot"
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
sudo rm -rf /tmp/* || true
mkdir -p "$BUILD_DIR/tmp"
export TMPDIR="$BUILD_DIR/tmp"
SKIP_TINYGRAD_COMPILE=1 /usr/bin/time -v scons -j$(nproc) cache_dir="$SCONS_CACHE" --minimal
touch ${BUILD_DIR}/prebuilt
if [[ "${{ runner.debug }}" == "1" ]]; then
ls -la ${BUILD_DIR}
fi
- name: Prepare Output
- name: Strip release tree
run: |
sudo rm -rf ${OUTPUT_DIR}
mkdir -p ${OUTPUT_DIR}
rsync -am${RUNNER_DEBUG:+v} \
--exclude='.sconsign.dblite' \
--exclude='*.a' \
--exclude='*.o' \
--exclude='*.os' \
--exclude='*.pyc' \
--exclude='moc_*' \
--exclude='__pycache__' \
--exclude='Jenkinsfile' \
--exclude='**/release/' \
--exclude='**/.github/' \
--exclude='**/selfdrive/ui/replay/' \
--exclude='**/__pycache__/' \
--exclude='${{env.SCONS_CACHE_DIR}}' \
--exclude='**/.git/' \
--exclude='**/SConstruct' \
--exclude='**/SConscript' \
--exclude='**/.venv/' \
--exclude='selfdrive/modeld/models/*.onnx*' \
--exclude='sunnypilot/modeld*/models/*.onnx*' \
--exclude='third_party/*x86*' \
--exclude='third_party/*Darwin*' \
--delete-excluded \
--chown=comma:comma \
${BUILD_DIR}/ ${OUTPUT_DIR}/
cd $BUILD_DIR
find . -name '*.a' -delete
find . -name '*.o' -delete
find . -name '*.os' -delete
find . -name '*.pyc' -delete
find . -name 'moc_*' -delete
find . -name '__pycache__' -type d -exec rm -rf {} +
find . -name 'SConstruct' -delete
find . -name 'SConscript' -delete
rm -rf .sconsign.dblite Jenkinsfile tools/release/ release/
rm -f openpilot/selfdrive/modeld/models/*.onnx*
rm -f openpilot/sunnypilot/modeld*/models/*.onnx*
find openpilot/third_party/ -name '*x86*' -exec rm -r {} +
find openpilot/third_party/ -name '*Darwin*' -exec rm -r {} +
cd -
- name: 'Tar.gz files'
run: |
tar czf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }} .
tar czf prebuilt.tar.gz -C ${{ env.BUILD_DIR }} .
ls -la prebuilt.tar.gz
- name: 'Upload Artifact'
@@ -233,28 +207,286 @@ jobs:
with:
name: prebuilt
path: prebuilt.tar.gz
compression-level: 0
- name: Re-enable powersave
if: always()
run: |
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
prepare_chestnut:
needs: [ prepare_strategy ]
runs-on: ubuntu-24.04
if: ${{ needs.prepare_strategy.outputs.include_big_model == 'true' }}
concurrency:
group: prepare-chestnut
cancel-in-progress: false
outputs:
onnx_sha256: ${{ steps.resolve.outputs.onnx_sha256 }}
env:
GH_REPO: ${{ github.repository }}
HF_REPO: sunnypilot/sunnypilot_models_v1
HF_DEFAULTS_PATH: models/defaults/big
steps:
- name: Resolve tinygrad ref via API
id: resolve
run: |
REF="${{ github.head_ref || github.ref_name }}"
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl?ref=${REF}" --jq '.sha')
[ -n "$BLOB_SHA" ] || { echo "::error::Failed to resolve big_driving_tinygrad.pkl blob SHA"; exit 1; }
ONNX_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
echo "big PKL hash: $ONNX_HASH"
[ -n "$ONNX_HASH" ] || { echo "::error::Failed to extract big PKL hash"; exit 1; }
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
echo "tinygrad ref: $TINYGRAD_REF"
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
check_defaults() {
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
[ "$TINYGRAD_MATCH" = "true" ] || return 1
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
[ -n "$BUNDLE" ] && [ "$BUNDLE" != "null" ]
}
if check_defaults; then
echo "HF defaults match repo tinygrad ref"
exit 0
fi
echo "No matching model on HF — dispatching build"
gh workflow run build-default-models.yaml --ref "$REF" -f target=big
sleep 10
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
echo "Dispatched build run: $BUILD_RUN_ID"
echo "Waiting for build run to complete..."
for i in $(seq 1 90); do
sleep 30
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
echo "Poll $i/90: status=$STATUS conclusion=$CONCLUSION"
if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
echo "Build run succeeded, verifying HF..."
sleep 10
if check_defaults; then
echo "Big model verified on HF"
exit 0
fi
echo "::error::Build succeeded but model not found on HF"
exit 1
else
echo "::error::Build run failed with conclusion=$CONCLUSION"
exit 1
fi
fi
done
echo "::error::Build run did not complete within 45 minutes"
exit 1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cancel run on failure
if: failure()
run: gh run cancel ${{ github.run_id }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
prepare_small_model:
needs: [ prepare_strategy ]
runs-on: ubuntu-24.04
concurrency:
group: prepare-small-model
cancel-in-progress: false
outputs:
driving_onnx_sha256: ${{ steps.resolve.outputs.driving_onnx_sha256 }}
env:
GH_REPO: ${{ github.repository }}
HF_REPO: sunnypilot/sunnypilot_models_v1
HF_DEFAULTS_PATH: models/defaults/small
steps:
- name: Resolve tinygrad ref via API
id: resolve
run: |
REF="${{ github.head_ref || github.ref_name }}"
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/driving_supercombo.onnx?ref=${REF}" --jq '.sha')
DRIVING_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
echo "Driving ONNX hash: $DRIVING_HASH"
[ -n "$DRIVING_HASH" ] || { echo "::error::Failed to extract driving ONNX hash"; exit 1; }
echo "driving_onnx_sha256=$DRIVING_HASH" >> "$GITHUB_OUTPUT"
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
echo "tinygrad ref: $TINYGRAD_REF"
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
check_defaults() {
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
[ "$TINYGRAD_MATCH" = "true" ] || return 1
DRIVING=$(echo "$DEFAULTS" | jq --arg hash "$DRIVING_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
[ -n "$DRIVING" ] && [ "$DRIVING" != "null" ] || return 1
}
if check_defaults; then
echo "HF defaults match repo tinygrad ref"
exit 0
fi
echo "No matching model on HF — dispatching build"
gh workflow run build-default-models.yaml --ref "$REF" -f target=small
sleep 10
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
echo "Dispatched build run: $BUILD_RUN_ID"
echo "Waiting for build run to complete..."
for i in $(seq 1 60); do
sleep 30
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
echo "Poll $i/60: status=$STATUS conclusion=$CONCLUSION"
if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
echo "Build run succeeded, verifying HF..."
sleep 10
if check_defaults; then
echo "Small model verified on HF"
exit 0
fi
echo "::error::Build succeeded but model not found on HF"
exit 1
else
echo "::error::Build run failed with conclusion=$CONCLUSION"
exit 1
fi
fi
done
echo "::error::Small model build did not complete within 30 minutes"
exit 1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cancel run on failure
if: failure()
run: gh run cancel ${{ github.run_id }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
prepare_dm_model:
needs: [ prepare_strategy ]
runs-on: ubuntu-24.04
concurrency:
group: prepare-dm-model
cancel-in-progress: false
outputs:
dm_onnx_sha256: ${{ steps.resolve.outputs.dm_onnx_sha256 }}
env:
GH_REPO: ${{ github.repository }}
HF_REPO: sunnypilot/sunnypilot_models_v1
HF_DEFAULTS_PATH: models/defaults/dm
steps:
- name: Resolve tinygrad ref via API
id: resolve
run: |
REF="${{ github.head_ref || github.ref_name }}"
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx?ref=${REF}" --jq '.sha')
DM_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
echo "DM ONNX hash: $DM_HASH"
[ -n "$DM_HASH" ] || { echo "::error::Failed to extract DM ONNX hash"; exit 1; }
echo "dm_onnx_sha256=$DM_HASH" >> "$GITHUB_OUTPUT"
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
echo "tinygrad ref: $TINYGRAD_REF"
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
check_defaults() {
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
[ "$TINYGRAD_MATCH" = "true" ] || return 1
DM=$(echo "$DEFAULTS" | jq --arg hash "$DM_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
[ -n "$DM" ] && [ "$DM" != "null" ] || return 1
}
if check_defaults; then
echo "HF defaults match DM tinygrad ref"
exit 0
fi
echo "No matching DM model on HF — dispatching build"
gh workflow run build-default-models.yaml --ref "$REF" -f target=dm
sleep 10
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
echo "Dispatched build run: $BUILD_RUN_ID"
echo "Waiting for build run to complete..."
for i in $(seq 1 60); do
sleep 30
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
echo "Poll $i/60: status=$STATUS conclusion=$CONCLUSION"
if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
echo "Build run succeeded, verifying HF..."
sleep 10
if check_defaults; then
echo "DM model verified on HF"
exit 0
fi
echo "::error::Build succeeded but DM model not found on HF"
exit 1
else
echo "::error::Build run failed with conclusion=$CONCLUSION"
exit 1
fi
fi
done
echo "::error::DM model build did not complete within 30 minutes"
exit 1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cancel run on failure
if: failure()
run: gh run cancel ${{ github.run_id }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish:
concurrency:
# We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name.
# This means that if multiple commits come in while we're publishing, they will be queued up and publish one after the other.
# Otherwise, if a job is waiting to be published due to environment wait time, it would be canceled by a new commit and restart the wait time.
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
if: ${{ (always() && !cancelled() && !failure()) && needs.build.result == 'success' && needs.prepare_strategy.result == 'success' && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
needs: [ build, prepare_strategy ]
if: ${{
always() && !cancelled() &&
needs.build.result == 'success' &&
needs.prepare_strategy.result == 'success' &&
needs.prepare_small_model.result == 'success' &&
needs.prepare_dm_model.result == 'success' &&
(!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) &&
(needs.prepare_strategy.outputs.include_big_model != 'true' || needs.prepare_chestnut.result == 'success')
}}
needs: [ build, prepare_strategy, prepare_chestnut, prepare_small_model, prepare_dm_model ]
runs-on: ubuntu-24.04
environment: ${{ needs.prepare_strategy.outputs.environment }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Download build artifacts
- name: Download prebuilt artifact
uses: actions/download-artifact@v4
with:
name: prebuilt
@@ -264,6 +496,17 @@ jobs:
mkdir -p ${{ env.OUTPUT_DIR }}
tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }}
- name: Download model chunks from HF
uses: ./.github/workflows/download-hf-model-chunks
with:
hf_repo: sunnypilot/sunnypilot_models_v1
dest_dir: ${{ env.OUTPUT_DIR }}/openpilot/selfdrive/modeld/models
models: |
[
{"hf_path": "models/defaults/small", "onnx_hash": "${{ needs.prepare_small_model.outputs.driving_onnx_sha256 }}", "canonical": "driving_tinygrad.pkl"},
{"hf_path": "models/defaults/dm", "onnx_hash": "${{ needs.prepare_dm_model.outputs.dm_onnx_sha256 }}", "canonical": "dmonitoring_model_tinygrad.pkl"}
]
- name: Configure Git
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
@@ -273,36 +516,95 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo '${{ toJSON(needs.build.outputs) }}'
echo '${{ toJSON(needs.prepare_strategy.outputs) }}'
ls -la ${{ env.OUTPUT_DIR }}
${{ env.CI_DIR }}/publish.sh \
"${{ github.workspace }}" \
"${{ env.OUTPUT_DIR }}" \
"${{ needs.build.outputs.new_branch }}" \
"${{ needs.build.outputs.version }}" \
"${{ needs.prepare_strategy.outputs.new_branch }}" \
"${{ needs.prepare_strategy.outputs.version }}" \
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
"${{ needs.build.outputs.extra_version_identifier }}"
echo ""
echo "---- ️ To update the list of branches that auto deploy prebuilts -----"
echo ""
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
echo "3. Update as needed (JSON array with no spaces)"
"${{ needs.prepare_strategy.outputs.extra_version_identifier }}"
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
run: |
TAG="${{ needs.prepare_strategy.outputs.environment }}/${{ needs.prepare_strategy.outputs.version }}/${{ needs.prepare_strategy.outputs.build }}"
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.build.outputs.build }}."
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.prepare_strategy.outputs.build }}."
git push -f origin ${TAG}
publish_chestnut:
concurrency:
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}-chestnut
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
if: ${{
always() && !cancelled() &&
needs.build.result == 'success' &&
needs.prepare_strategy.result == 'success' &&
needs.prepare_small_model.result == 'success' &&
needs.prepare_dm_model.result == 'success' &&
needs.prepare_chestnut.result == 'success' &&
(!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
}}
needs: [ build, prepare_strategy, prepare_chestnut, prepare_small_model, prepare_dm_model ]
runs-on: ubuntu-24.04
environment: ${{ needs.prepare_strategy.outputs.environment }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Download prebuilt artifact
uses: actions/download-artifact@v4
with:
name: prebuilt
- name: Untar prebuilt
run: |
mkdir -p ${{ env.OUTPUT_DIR }}
tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }}
- name: Download model chunks from HF
uses: ./.github/workflows/download-hf-model-chunks
with:
hf_repo: sunnypilot/sunnypilot_models_v1
dest_dir: ${{ env.OUTPUT_DIR }}/openpilot/selfdrive/modeld/models
models: |
[
{"hf_path": "models/defaults/small", "onnx_hash": "${{ needs.prepare_small_model.outputs.driving_onnx_sha256 }}", "canonical": "driving_tinygrad.pkl"},
{"hf_path": "models/defaults/dm", "onnx_hash": "${{ needs.prepare_dm_model.outputs.dm_onnx_sha256 }}", "canonical": "dmonitoring_model_tinygrad.pkl"},
{"hf_path": "models/defaults/big", "onnx_hash": "${{ needs.prepare_chestnut.outputs.onnx_sha256 }}", "canonical": "big_driving_tinygrad.pkl"}
]
- name: Configure Git
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
- name: Publish chestnut branch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHESTNUT_BRANCH="${{ needs.prepare_strategy.outputs.new_branch }}-chestnut"
${{ env.CI_DIR }}/publish.sh \
"${{ github.workspace }}" \
"${{ env.OUTPUT_DIR }}" \
"$CHESTNUT_BRANCH" \
"${{ needs.prepare_strategy.outputs.version }}" \
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
"${{ needs.prepare_strategy.outputs.extra_version_identifier }}"
notify:
needs:
- prepare_strategy
- build
- publish
- publish_chestnut
- prepare_chestnut
- prepare_small_model
- prepare_dm_model
runs-on: ubuntu-24.04
if: ${{ (always() && !cancelled() && !failure())
&& needs.publish.result == 'success'
@@ -310,11 +612,12 @@ jobs:
&& (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Prepare notification message
id: message
run: |
TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}'
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
export branch_name="${{ env.SOURCE_BRANCH }}"
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
@@ -323,6 +626,7 @@ jobs:
export commit_short_sha="${commit_short_sha:0:7}"
export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}"
export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}"
export chestnut_branch="${{ needs.prepare_chestnut.result == 'success' && format('{0}-chestnut', needs.prepare_strategy.outputs.new_branch) || '' }}"
MESSAGE=$(cat << 'EOF' | envsubst
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
-78
View File
@@ -1,78 +0,0 @@
name: Debug Discourse Posting
on:
push:
jobs:
test-discourse-post:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Post test message to Discourse
uses: ./.github/workflows/post-to-discourse
with:
discourse-url: ${{ vars.DISCOURSE_URL }}
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
message: |
## 🧪 Test Post from GitHub Actions
**This is a test post to verify Discourse integration**
- **Workflow**: ${{ github.workflow }}
- **Run Number**: #${{ github.run_number }}
- **Branch**: `${{ github.ref_name }}`
- **Commit**: ${{ github.sha }}
- **Actor**: @${{ github.actor }}
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
---
### Fake Build Info (for testing)
- **Version**: 0.9.8-test
- **Build**: #42
- **Branch**: release-test
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
*This is an automated test message. Drive safe! 🚗💨*
- name: Create topic on Discourse
uses: ./.github/workflows/post-to-discourse
with:
discourse-url: ${{ vars.DISCOURSE_URL }}
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
#topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
category-id: 4
title: "This is a test of a new topic instead of a reply"
message: |
## 🧪 Test Post from GitHub Actions
**This is a test post to verify Discourse integration**
- **Workflow**: ${{ github.workflow }}
- **Run Number**: #${{ github.run_number }}
- **Branch**: `${{ github.ref_name }}`
- **Commit**: ${{ github.sha }}
- **Actor**: @${{ github.actor }}
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
---
### Fake Build Info (for testing)
- **Version**: 0.9.8-test
- **Build**: #42
- **Branch**: release-test
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
*This is an automated test message. Drive safe! 🚗💨*
- name: Display results
if: always()
run: |
echo "::notice::Discourse post test completed"
echo "Check your Discourse topic to verify the post appeared correctly"
+84
View File
@@ -0,0 +1,84 @@
name: Test Models Compatibility With Tinygrad Changes
on:
pull_request:
paths:
- 'tinygrad_repo'
workflow_dispatch:
jobs:
generate-matrix:
runs-on: ubuntu-latest
outputs:
models: ${{ steps.set-matrix.outputs.models }}
steps:
- uses: actions/checkout@v4
- name: Fetch and Parse json
id: set-matrix
run: |
python3 -c '
import json, urllib.request, os, re
with open("openpilot/sunnypilot/models/fetcher.py", "r") as f:
urls = re.findall(r"MODEL_URL(?:_CHESTNUT)?\s*=\s*[\"'"'"']([^\"'"'"']+)[\"'"'"']", f.read())
artifacts = []
for url in urls:
data = json.loads(urllib.request.urlopen(url).read())
for bundle in data.get("bundles", []):
for model in bundle.get("models", []):
if "artifact" in model:
artifacts.append(model["artifact"])
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write(f"models={json.dumps(artifacts)}\n")
'
test-model:
name: Test ${{ matrix.artifact.file_name }}
needs: generate-matrix
runs-on: ubuntu-latest
container: ghcr.io/commaai/openpilot-base:latest
strategy:
fail-fast: false
matrix:
artifact: ${{ fromJson(needs.generate-matrix.outputs.models) }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Download Model Chunks in Parallel
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
mkdir -p /tmp/model_chunks
echo '${{ toJson(matrix.artifact.chunks) }}' > chunks.json
BASE_URL="${{ matrix.artifact.download_uri.url }}"
export BASE_DIR=$(dirname "$BASE_URL")
python3 -c '
import json, os
with open("chunks.json") as f:
chunks = json.load(f)
manifest_path = f"/tmp/model_chunks/${{ matrix.artifact.file_name }}.chunkmanifest"
with open(manifest_path, "w") as f:
f.write(str(len(chunks)))
base_dir = os.environ["BASE_DIR"]
hf_token = os.environ.get("HF_TOKEN", "")
with open("/tmp/curl_config.txt", "w") as f:
for c in chunks:
fn = c["file_name"]
f.write(f"url = \"{base_dir}/{fn}\"\noutput = \"/tmp/model_chunks/{fn}\"\n")
if hf_token:
f.write(f"header = \"Authorization: Bearer {hf_token}\"\n")
'
curl -Z --parallel-immediate --parallel-max 12 --retry 3 --retry-all-errors -s -S -f -L -K /tmp/curl_config.txt
- name: Run Model Compatibility Test
env:
MODEL_BASE_NAME: ${{ matrix.artifact.file_name }}
MODEL_CHUNK_DIR: "/tmp/model_chunks"
PYTHONPATH: ".:./tinygrad_repo"
run: |
python3 -m pytest openpilot/sunnypilot/modeld_v2/tests/test_models.py
+49 -60
View File
@@ -20,7 +20,9 @@ concurrency:
env:
CI: 1
PYTHONPATH: ${{ github.workspace }}
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
jobs:
build_release:
@@ -35,27 +37,27 @@ jobs:
STRIPPED_DIR: /tmp/releasepilot
PYTHONPATH: /tmp/releasepilot
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
submodules: true
- name: Getting LFS files
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
with:
timeout_minutes: 2
max_attempts: 3
command: git lfs pull
- name: Build devel
timeout-minutes: 1
run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh
timeout-minutes: 3
run: TARGET_DIR=$STRIPPED_DIR tools/release/build_stripped.sh
- run: ./tools/op.sh setup
- name: Build openpilot and run checks
timeout-minutes: 30
working-directory: ${{ env.STRIPPED_DIR }}
run: python3 system/manager/build.py
run: python3 openpilot/system/manager/build.py
- name: Run tests
timeout-minutes: 1
working-directory: ${{ env.STRIPPED_DIR }}
run: release/check-dirty.sh
run: tools/release/check-dirty.sh
- name: Check submodules
if: github.repository == 'sunnypilot/sunnypilot'
timeout-minutes: 3
@@ -75,15 +77,13 @@ jobs:
export CHECK_PR_REFS=true
fi
fi
release/check-submodules.sh
tools/release/check-submodules.sh
build_mac:
name: build macOS
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
steps:
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/checkout@v7
- name: Remove Homebrew from environment
run: |
FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':')
@@ -101,9 +101,7 @@ jobs:
&& fromJSON('["namespace-profile-amd64-8x16"]')
|| fromJSON('["ubuntu-24.04"]') }}
steps:
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/checkout@v7
- run: ./tools/op.sh setup
- name: Static analysis
timeout-minutes: 1
@@ -118,23 +116,21 @@ jobs:
&& fromJSON('["namespace-profile-amd64-8x16"]')
|| fromJSON('["ubuntu-24.04"]') }}
steps:
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/checkout@v7
- run: ./tools/op.sh setup
- name: Install raylib headless dependencies
if: ${{ !contains(runner.name, 'nsc') }}
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
- name: Build openpilot
run: scons
- name: Run unit tests
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
run: |
source selfdrive/test/setup_xvfb.sh
# Pre-compile Python bytecode so each pytest worker doesn't need to
$PYTEST --collect-only -m 'not slow' -qq
MAX_EXAMPLES=1 $PYTEST -m 'not slow'
env:
RAYLIB_BACKEND: headless
run: tools/op.sh test
process_replay:
name: process replay
if: false # disable process_replay for forks
runs-on: ${{
(github.repository == 'commaai/openpilot') &&
((github.event_name != 'pull_request') ||
@@ -142,68 +138,60 @@ jobs:
&& fromJSON('["namespace-profile-amd64-8x16"]')
|| fromJSON('["ubuntu-24.04"]') }}
steps:
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/checkout@v7
- run: ./tools/op.sh setup
- name: Build openpilot
run: scons
- name: Run replay
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
run: selfdrive/test/process_replay/test_processes.py -j$(nproc)
run: openpilot/selfdrive/test/process_replay/test_processes.py -j$(nproc)
- name: Print diff
id: print-diff
if: always()
run: cat selfdrive/test/process_replay/diff.txt
run: cat openpilot/selfdrive/test/process_replay/diff.txt
- name: Print diff report
if: always()
run: cat selfdrive/test/process_replay/diff_report.txt
- uses: actions/upload-artifact@v6
run: cat openpilot/selfdrive/test/process_replay/diff_report.txt
- uses: actions/upload-artifact@v7
if: always()
continue-on-error: true
with:
name: process_replay_diff.txt
path: selfdrive/test/process_replay/diff.txt
path: openpilot/selfdrive/test/process_replay/diff.txt
- name: Upload diff report
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: always() && github.event_name == 'pull_request'
continue-on-error: true
with:
name: diff_report_${{ github.event.number }}
path: selfdrive/test/process_replay/diff_report.txt
path: openpilot/selfdrive/test/process_replay/diff_report.txt
- name: Checkout ci-artifacts
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
uses: actions/checkout@v4
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
uses: actions/checkout@v7
with:
repository: commaai/ci-artifacts
repository: sunnypilot/ci-artifacts
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
path: ${{ github.workspace }}/ci-artifacts
- name: Prepare refs
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
working-directory: ${{ github.workspace }}/ci-artifacts
run: |
git config user.name "GitHub Actions Bot"
git config user.email "<>"
git fetch origin process-replay || true
git checkout process-replay 2>/dev/null || git checkout --orphan process-replay
cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst .
cp ${{ github.workspace }}/openpilot/selfdrive/test/process_replay/fakedata/*.zst .
echo "${{ github.sha }}" > ref_commit
git add .
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
- name: Push refs
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
with:
timeout_minutes: 2
max_attempts: 3
command: cd ${{ github.workspace }}/ci-artifacts && git push origin process-replay --force
- name: Run regen
if: false
timeout-minutes: 4
env:
ONNXCPU: 1
run: $PYTEST selfdrive/test/process_replay/test_regen.py
simulator_driving:
name: simulator driving
@@ -215,17 +203,16 @@ jobs:
|| fromJSON('["ubuntu-24.04"]') }}
if: false # FIXME: Started to timeout recently
steps:
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/checkout@v7
- run: ./tools/op.sh setup
- name: Build openpilot
run: scons
- name: Driving test
timeout-minutes: 2
run: |
source selfdrive/test/setup_xvfb.sh
pytest -s tools/sim/tests/test_metadrive_bridge.py
env:
# MetaDrive renders offscreen through panda3d's EGL pipe on llvmpipe
EGL_PLATFORM: surfaceless
run: python openpilot/tools/sim/tests/test_metadrive_bridge.py
create_ui_report:
name: Create UI Report
@@ -236,19 +223,21 @@ jobs:
&& fromJSON('["namespace-profile-amd64-8x16"]')
|| fromJSON('["ubuntu-24.04"]') }}
steps:
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/checkout@v7
- run: ./tools/op.sh setup
- name: Install raylib headless dependencies
if: ${{ !contains(runner.name, 'nsc') }}
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
- name: Build openpilot
run: scons
- name: Create UI Report
env:
RAYLIB_BACKEND: headless
run: |
source selfdrive/test/setup_xvfb.sh
python3 selfdrive/ui/tests/diff/replay.py
python3 selfdrive/ui/tests/diff/replay.py --big
python3 openpilot/selfdrive/ui/tests/diff/replay.py
python3 openpilot/selfdrive/ui/tests/diff/replay.py --big
- name: Upload UI Report
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ui-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
path: selfdrive/ui/tests/diff/report
path: openpilot/selfdrive/ui/tests/diff/report
+19 -18
View File
@@ -8,9 +8,9 @@ on:
branches:
- 'master'
paths:
- 'selfdrive/assets/**'
- 'selfdrive/ui/**'
- 'system/ui/**'
- 'openpilot/selfdrive/assets/**'
- 'openpilot/selfdrive/ui/**'
- 'openpilot/system/ui/**'
workflow_dispatch:
env:
@@ -25,7 +25,8 @@ env:
jobs:
preview:
if: github.repository == 'sunnypilot/sunnypilot'
if: false # tmp disable due to GH API rate limiting flakiness
#if: github.repository == 'sunnypilot/sunnypilot'
name: preview
runs-on: ubuntu-latest
timeout-minutes: 20
@@ -34,12 +35,12 @@ jobs:
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
submodules: true
- name: Waiting for ui generation to end
uses: lewagon/wait-on-check-action@v1.3.4
uses: lewagon/wait-on-check-action@1d57e2c51a58d812d2765e036a028b6bdb5a6154
with:
ref: ${{ env.SHA }}
check-name: ${{ env.UI_JOB_NAME }}
@@ -53,7 +54,7 @@ jobs:
echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?<number>[0-9]+)") | .number')" >> $GITHUB_OUTPUT
- name: Getting proposed ui
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
run_id: ${{ steps.get_run_id.outputs.run_id }}
@@ -62,7 +63,7 @@ jobs:
path: ${{ github.workspace }}/pr_ui
- name: Getting mici master ui
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
repository: sunnypilot/ci-artifacts
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
@@ -70,7 +71,7 @@ jobs:
ref: openpilot_master_ui_mici_raylib
- name: Getting big master ui
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
repository: sunnypilot/ci-artifacts
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
@@ -97,7 +98,7 @@ jobs:
done
- name: Setup FFmpeg
uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae
uses: AnimMouse/setup-ffmpeg@178e0b4a408f06ce40d896c3cd79f50fa6f8b0c3
- name: Finding diffs
if: github.event_name == 'pull_request_target'
@@ -115,13 +116,13 @@ jobs:
cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4"
diff_exit_code=0
python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py \
python3 ${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/diff.py \
"${{ github.workspace }}/pr_ui/${video}_master.mp4" \
"${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \
"${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$?
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/"
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/"
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/"
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/"
REPORT_URL="https://sunnypilot.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html"
if [ $diff_exit_code -eq 0 ]; then
@@ -156,7 +157,7 @@ jobs:
for variant in $VARIANTS; do
IFS=':' read -r name _ _ <<< "$variant"
diff_name="${name}_diff"
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html"
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html"
git add "${diff_name}_pr_${{ github.event.number }}.html"
done
git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit"
@@ -164,12 +165,12 @@ jobs:
- name: Comment on PR
if: github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v2
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
with:
message: |
<!-- _(run_id_ui_preview **${{ github.run_id }}**)_ -->
## UI Preview
${{ steps.find_diff.outputs.COMMENT }}
comment_tag: run_id_ui_preview
pr_number: ${{ github.event.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
comment-tag: run_id_ui_preview
pr-number: ${{ github.event.number }}
github-token: ${{ secrets.GITHUB_TOKEN }}
+21 -10
View File
@@ -15,6 +15,13 @@ a.out
.cache/
bin/
# created at launch for comma hardware PYTHONPATH (PC uses editable installs via pyproject.toml)
/msgq
/opendbc
/rednose
/teleoprtc
/tinygrad
*.mp4
*.dylib
*.DSYM
@@ -32,6 +39,8 @@ bin/
*.os-*
*.so
*.a
st[0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]
*.unchunked
*.clb
*.class
*.pyxbldc
@@ -41,21 +50,23 @@ bin/
*.stats
*.pkl
*.pkl*
!openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
!openpilot/sunnypilot/modeld_v2/models/*.pkl
config.json
compile_commands.json
compare_runtime*.html
selfdrive/modeld/models/tg_input_devices.json
openpilot/selfdrive/modeld/models/tg_input_devices.json
# build artifacts
docs_site/
selfdrive/pandad/pandad
cereal/services.h
cereal/gen
cereal/messaging/bridge
selfdrive/ui/translations/tmp
selfdrive/car/tests/cars_dump
system/camerad/camerad
system/camerad/test/ae_gray_test
docs/_site/
openpilot/selfdrive/pandad/pandad
openpilot/cereal/services.h
openpilot/cereal/gen
openpilot/cereal/messaging/bridge
openpilot/selfdrive/ui/translations/tmp
openpilot/selfdrive/car/tests/cars_dump
openpilot/system/camerad/camerad
openpilot/system/camerad/test/ae_gray_test
.coverage*
coverage.xml
+3 -2
View File
@@ -6,7 +6,7 @@
url = https://github.com/sunnypilot/opendbc.git
[submodule "msgq"]
path = msgq_repo
url = https://github.com/commaai/msgq.git
url = https://github.com/sunnypilot/msgq.git
[submodule "rednose_repo"]
path = rednose_repo
url = https://github.com/commaai/rednose.git
@@ -17,5 +17,6 @@
path = tinygrad_repo
url = https://github.com/sunnypilot/tinygrad.git
[submodule "sunnypilot/neural_network_data"]
path = sunnypilot/neural_network_data
path = openpilot/sunnypilot/neural_network_data
url = https://github.com/sunnypilot/neural-network-data.git
+1
View File
@@ -1,4 +1,5 @@
[lfs]
url = https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs
pushurl = ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git
locksverify = false
+4 -4
View File
@@ -6,9 +6,9 @@
"type": "pickString",
"description": "Select the process to debug",
"options": [
"selfdrive/controls/controlsd.py",
"system/timed/timed.py",
"tools/sim/run_bridge.py"
"openpilot/selfdrive/controls/controlsd.py",
"openpilot/system/timed/timed.py",
"openpilot/tools/sim/run_bridge.py"
]
},
{
@@ -16,7 +16,7 @@
"type": "pickString",
"description": "Select the process to debug",
"options": [
"selfdrive/ui/ui"
"openpilot/selfdrive/ui/ui"
]
},
{
-5
View File
@@ -17,10 +17,5 @@
"**/.git",
"**/.venv",
"**/__pycache__",
// exclude directories that should be using the symlinked version
"common/**",
"selfdrive/**",
"system/**",
"tools/**",
]
}
+30 -1
View File
@@ -1,5 +1,34 @@
sunnypilot Version 2026.002.000 (2026-xx-xx)
sunnypilot Version 2026.002.000 (2026-06-28)
========================
* What's Changed (sunnypilot/sunnypilot)
* ui: update gates for certain toggles by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1830
* release: ignore upstream IsReleaseBranch by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1831
* manager: disable DEVELOPMENT_ONLY reset by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1833
* sunnylink: fix max time offroad values by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1835
* ui: show default model name by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1837
* sunnylink: add CarParams fallback for brand-specific capabilities by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1839
* sunnylink SDUI: tweak DisableUpdate param for clarity by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1842
* Revert "DM: Lancia Delta HF Integrale model" by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1849
* modeld_v2: safe model validation by @Discountchubbs in https://github.com/sunnypilot/sunnypilot/pull/1855
* Revert "deprecate `carState.brake`" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/sunnypilot/pull/1860
* sunnylink: deprecate legacy params metadata by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1862
* ui: reset Enforce Torque Control and NNLC if both are enabled by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1863
* What's Changed (sunnypilot/opendbc)
* Rivian: suppress ACM hold-the-wheel warning during MADS-only lateral by @lukasloetkolben in https://github.com/sunnypilot/opendbc/pull/465
* Sync: `commaai/opendbc:master``sunnypilot/opendbc:master` by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/479
* safety: add option to ignore frequency check for RX checks by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/480
* Revert "deprecate carState.brake" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/opendbc/pull/481
* New Contributors (sunnypilot/sunnypilot)
* @mvl-boston made their first contribution in https://github.com/sunnypilot/sunnypilot/pull/1860
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2026.001.007...v2026.002.000
************************
* Synced with commaai's openpilot (v0.11.1)
* master commit 69e2c321e49760e52f7983eaa0a5f77cb95de637 (June 02, 2026)
* New driver monitoring model
* Improved image processing pipeline for driver camera
* Improved thermal policy for comma four
* Acura MDX 2022-24 support thanks to mvl-boston!
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
sunnypilot Version 2026.001.000 (2026-05-06)
========================
-38
View File
@@ -1,38 +0,0 @@
FROM ubuntu:24.04
ENV PYTHONUNBUFFERED=1
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y --no-install-recommends sudo tzdata locales && \
rm -rf /var/lib/apt/lists/*
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
ARG USER=batman
ARG USER_UID=1001
RUN useradd -m -s /bin/bash -u $USER_UID $USER
RUN usermod -aG sudo $USER
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER $USER
ENV OPENPILOT_PATH=/home/$USER/openpilot
RUN mkdir -p ${OPENPILOT_PATH}
WORKDIR ${OPENPILOT_PATH}
COPY --chown=$USER . ${OPENPILOT_PATH}/
ENV UV_BIN="/home/$USER/.local/bin/"
ENV VIRTUAL_ENV=${OPENPILOT_PATH}/.venv
ENV PATH="$UV_BIN:$VIRTUAL_ENV/bin:$PATH"
RUN tools/setup_dependencies.sh && \
sudo rm -rf /var/lib/apt/lists/*
USER root
RUN git config --global --add safe.directory '*'
Vendored
+50 -31
View File
@@ -12,16 +12,18 @@ def retryWithDelay(int maxRetries, int delay, Closure body) {
def device(String ip, String step_label, String cmd) {
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
def ssh_cmd = """
ssh -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
ssh -o ControlMaster=no -o ControlPath=none -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=12 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec setpriv --pdeathsig HUP /usr/bin/bash <<'END'
set -e
export TERM=xterm-256color
shopt -s huponexit # kill all child processes when the shell exits
trap 'kill 0' HUP # stop this process group on SSH disconnect
export CI=1
export PYTHONWARNINGS=error
export PYTHONFAULTHANDLER=1
export COMMA_CACHE=/data/tmp/comma_download_cache
#export LOGPRINT=debug # this has gotten too spammy...
export TEST_DIR=${env.TEST_DIR}
export SOURCE_DIR=${env.SOURCE_DIR}
@@ -30,14 +32,14 @@ export GIT_COMMIT=${env.GIT_COMMIT}
export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
export AZURE_TOKEN='${env.AZURE_TOKEN}'
# only use 1 thread for tici tests since most require HIL
# only use 1 thread since most require real hardware that can't be shared
export PYTEST_ADDOPTS="-n0 -s"
export GIT_SSH_COMMAND="ssh -i /data/gitkey"
source ~/.bash_profile
if [ -f /TICI ]; then
if [ -f /AGNOS ]; then
source /etc/profile
rm -rf /tmp/tmp*
@@ -63,10 +65,13 @@ if [ -f /data/openpilot/launch_env.sh ]; then
source /data/openpilot/launch_env.sh
fi
export LD_LIBRARY_PATH="\$(python -c 'import ffmpeg; print(ffmpeg.LIB_DIR)'):/usr/local/lib:\${LD_LIBRARY_PATH:-}"
ln -snf ${env.TEST_DIR} /data/pythonpath
cd ${env.TEST_DIR} || true
time ${cmd}
time ( ${cmd} ) &
wait \$!
END"""
sh script: ssh_cmd, label: step_label
@@ -93,7 +98,7 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
retry (3) {
def date = sh(script: 'date', returnStdout: true).trim();
device(device_ip, "set time", "date -s '" + date + "'")
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
device(device_ip, "git checkout", extra + "\n" + readFile("openpilot/selfdrive/test/setup_device_ci.sh"))
}
steps.each { item ->
def name = item[0]
@@ -166,7 +171,7 @@ node {
env.GIT_BRANCH = checkout(scm).GIT_BRANCH
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
def excludeBranches = ['__nightly', 'devel', 'devel-staging',
def excludeBranches = ['__nightly', '__nightly-chestnut', 'devel', 'devel-staging',
'release-tizi', 'release-tizi-staging', 'release-mici', 'release-mici-staging', 'testing-closet*', 'hotfix-*']
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
@@ -179,7 +184,7 @@ node {
try {
if (env.BRANCH_NAME == 'devel-staging') {
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging,release-mici-staging $SOURCE_DIR/release/build_release.sh"),
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging,release-mici-staging $SOURCE_DIR/tools/release/build_release.sh"),
])
}
@@ -187,66 +192,80 @@ node {
parallel (
'nightly': {
deviceStage("build nightly", "tizi-needs-can", [], [
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/tools/release/build_release.sh"),
])
},
'nightly-dev': {
deviceStage("build nightly-dev", "tizi-needs-can", [], [
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/tools/release/build_release.sh"),
])
},
)
}
if (env.BRANCH_NAME == '__nightly-chestnut') {
deviceStage("build nightly-chestnut", "mici-chestnut-ci", [], [
step("build nightly-chestnut", "SCONSFLAGS=-j4 INCLUDE_BIG_MODEL=1 PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-chestnut $SOURCE_DIR/tools/release/build_release.sh TestChestnutOnroad"),
])
}
if (!env.BRANCH_NAME.matches(excludeRegex)) {
parallel (
'onroad tests': {
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
step("build openpilot", "cd system/manager && ./build.py"),
step("check dirty", "release/check-dirty.sh"),
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
step("check dirty", "tools/release/check-dirty.sh"),
step("onroad tests", "./openpilot/selfdrive/test/test_onroad.py", [timeout: 60]),
])
},
'HW + Unit Tests': {
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
step("test manager", "pytest system/manager/test/test_manager.py"),
step("build", "cd openpilot/system/manager && ./build.py"),
step("test power draw", "./openpilot/selfdrive/test/test_power_draw.py"),
step("test encoder", "./openpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["openpilot/system/loggerd/"]]),
step("test manager", "./openpilot/system/manager/test/test_manager.py"),
])
},
'camerad OX03C10': {
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
step("build", "cd openpilot/system/manager && ./build.py"),
step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
])
},
'camerad OS04C10': {
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
step("build", "cd openpilot/system/manager && ./build.py"),
step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
])
},
'sensord': {
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
step("build", "cd openpilot/system/manager && ./build.py"),
step("test sensord", "./openpilot/system/sensord/tests/test_sensord.py"),
])
},
'replay': {
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
step("build", "cd openpilot/system/manager && ./build.py", [diffPaths: ["openpilot/selfdrive/modeld/", "tinygrad_repo", "openpilot/selfdrive/test/process_replay/model_replay.py"]]),
step("model replay", "openpilot/selfdrive/test/process_replay/model_replay.py", [diffPaths: ["openpilot/selfdrive/modeld/", "tinygrad_repo", "openpilot/selfdrive/test/process_replay/model_replay.py"]]),
])
},
'tizi': {
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
step("build openpilot", "cd system/manager && ./build.py"),
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
step("test pandad loopback", "./openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"),
step("test pandad spi", "./openpilot/selfdrive/pandad/tests/test_pandad_spi.py"),
step("test amp", "./openpilot/common/hardware/comma/tests/test_amplifier.py"),
])
},
'chestnut': {
deviceStage("chestnut", "mici-chestnut-ci", ["UNSAFE=1", "CHESTNUT=1"], [
step("build", "./openpilot/selfdrive/test/chestnut.sh"),
step("model replay", "openpilot/selfdrive/test/process_replay/model_replay.py --chestnut"),
step("onroad tests", "./openpilot/selfdrive/test/test_onroad.py TestChestnutOnroad", [timeout: 120]),
step("test power draw", "./openpilot/selfdrive/test/test_power_draw.py"),
])
},
+12 -4
View File
@@ -1,6 +1,14 @@
Version 0.11.2 (2026-06-15)
========================
Version 0.11.2 (2026-08-12)
=======================
* New driving model
* Big model with 880M parameters
* Support for big models running on an external GPU
* Live stream cameras from comma connect
* Generate dashcam clips from comma connect
* Remote comma body control from comma connect
* New alert sounds
* CUPRA Born 2021-2023 support thanks to DaHansi!
* Volkswagen ID.4 2021-2025 support thanks to DaHansi!
Version 0.11.1 (2026-05-18)
========================
@@ -1104,7 +1112,7 @@ Version 0.2.1 (2016-12-14)
Version 0.2 (2016-12-12)
=========================
* Car/Radar abstraction layers have shipped, see cereal/car.capnp
* Car/Radar abstraction layers have shipped, see openpilot/cereal/car.capnp
* controlsd has been refactored
* Shipped plant model and testing maneuvers
* visiond exits more gracefully now
+95 -42
View File
@@ -10,7 +10,7 @@ import numpy as np
import SCons.Errors
from SCons.Defaults import _stripixes
TICI = os.path.isfile('/TICI')
COMMA_HARDWARE = os.path.isfile('/AGNOS')
SCons.Warnings.warningAsException(True)
@@ -24,25 +24,55 @@ release = not os.path.exists(File('#.gitattributes').abspath) # file absent on r
AddOption('--minimal',
action='store_false',
dest='extras',
default=(not TICI and not release),
default=(not COMMA_HARDWARE and not release),
help='the minimum build to run openpilot. no tests, tools, etc.')
submodule_python_paths = [
Dir("#").abspath,
Dir("#msgq_repo").abspath,
Dir("#opendbc_repo").abspath,
Dir("#rednose_repo").abspath,
Dir("#teleoprtc_repo").abspath,
Dir("#tinygrad_repo").abspath,
]
for p in reversed(submodule_python_paths):
if p not in sys.path:
sys.path.insert(0, p)
if external_pythonpath := os.environ.get("PYTHONPATH"):
submodule_python_paths += [p for p in external_pythonpath.split(os.pathsep) if p and p not in submodule_python_paths]
# Detect platform
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
if platform.system() == "Darwin":
arch = "Darwin"
elif arch == "aarch64" and TICI:
arch = "larch64"
elif arch == "aarch64" and COMMA_HARDWARE:
arch = "comma_arm64"
assert arch in [
"larch64", # linux tici arm64
"aarch64", # linux pc arm64
"x86_64", # linux pc x64
"Darwin", # macOS arm64 (x86 not supported)
"comma_arm64", # linux comma hardware (AGNOS) arm64
"aarch64", # linux pc arm64
"x86_64", # linux pc x64
"Darwin", # macOS arm64 (x86 not supported)
]
pkg_names = ['acados', 'bzip2', 'capnproto', 'catch2', 'eigen', 'ffmpeg', 'json11', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd']
pkg_names = ['acados', 'capnproto', 'eigen', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd']
pkgs = [importlib.import_module(name) for name in pkg_names]
acados = pkgs[pkg_names.index('acados')]
ffmpeg = pkgs[pkg_names.index('ffmpeg')]
# Shared package ships .so/.dylib; older device venvs still have static .a only.
# Keep static link deps (x264/z/va/drm) when the installed package is static so
# COMMA_HARDWARE CI works without upgrading the device venv yet.
# TODO: drop the static fallback once device venvs have comma-deps-ffmpeg>=7.1.0.post94
_ffmpeg_lib_names = os.listdir(ffmpeg.LIB_DIR) if os.path.isdir(ffmpeg.LIB_DIR) else []
ffmpeg_shared = any(
n.startswith('libavcodec.so') or (n.startswith('libavcodec') and n.endswith('.dylib'))
for n in _ffmpeg_lib_names
)
ffmpeg_libs = ['avformat', 'avcodec', 'swresample', 'avutil']
if not ffmpeg_shared:
ffmpeg_libs += ['x264', 'z']
if arch != "Darwin":
ffmpeg_libs += ['va', 'va-drm', 'drm']
acados_include_dirs = [
acados.INCLUDE_DIR,
os.path.join(acados.INCLUDE_DIR, "blasfeo", "include"),
@@ -57,7 +87,6 @@ acados_include_dirs = [
# vendored in commaai/dependencies.
allowed_system_libs = {
"EGL", "GLESv2", "GL",
"Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets",
"dl", "drm", "gbm", "m", "pthread",
}
@@ -91,7 +120,8 @@ def _libflags(target, source, env, for_signature):
env = Environment(
ENV={
"PATH": os.environ['PATH'],
"PYTHONPATH": Dir("#").abspath,
"TMPDIR": os.environ.get('TMPDIR', '/tmp'),
"PYTHONPATH": os.pathsep.join(submodule_python_paths),
"ACADOS_SOURCE_DIR": acados.DIR,
"ACADOS_PYTHON_INTERFACE_PATH": acados.TEMPLATE_DIR,
"TERA_PATH": acados.TERA_PATH
@@ -99,10 +129,11 @@ env = Environment(
CCFLAGS=[
"-g",
"-fPIC",
"-pipe",
"-O2",
"-Wunused",
"-Werror",
"-Wshadow" if arch in ("Darwin", "larch64") else "-Wshadow=local",
"-Wshadow" if arch in ("Darwin", "comma_arm64") else "-Wshadow=local",
"-Wno-unknown-warning-option",
"-Wno-inconsistent-missing-override",
"-Wno-c99-designator",
@@ -112,36 +143,46 @@ env = Environment(
CFLAGS=["-std=gnu11"],
CXXFLAGS=["-std=c++1z"],
CPPPATH=[
"#",
"#msgq",
"#openpilot",
"#msgq_repo", # #include "msgq/..."
"#opendbc_repo", # #include "opendbc/..."
"#rednose_repo", # #include "rednose/..."
"#rednose_repo/rednose", # #include "logger/..." (rednose package root)
"#openpilot/cereal/gen/cpp",
acados_include_dirs,
[x.INCLUDE_DIR for x in pkgs],
"#",
],
LIBPATH=[
"#common",
"#openpilot/common",
"#msgq_repo",
"#selfdrive/pandad",
"#rednose/helpers",
"#openpilot/selfdrive/pandad",
"#rednose_repo/rednose/helpers",
[x.LIB_DIR for x in pkgs],
],
RPATH=[],
RPATH=[ffmpeg.LIB_DIR] if ffmpeg_shared else [],
CYTHONCFILESUFFIX=".cpp",
COMPILATIONDB_USE_ABSPATH=True,
REDNOSE_ROOT="#",
REDNOSE_ROOT="#rednose_repo",
tools=["default", "cython", "compilation_db", "rednose_filter"],
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
toolpath=["#msgq_repo/site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
)
if arch != "larch64":
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
if arch == "Darwin":
env["RPATHPREFIX"] = "-Wl,-rpath,"
env["RPATHSUFFIX"] = ""
env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}"
if arch != "comma_arm64":
env['_LIBFLAGS'] = _libflags
# Arch-specific flags and paths
if arch == "larch64":
if arch == "comma_arm64":
env["CC"] = "clang"
env["CXX"] = "clang++"
env.Append(LIBPATH=[
"/usr/lib/aarch64-linux-gnu",
])
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"]
arch_flags = ["-D__COMMA_HARDWARE__", "-mcpu=cortex-a57"]
env.Append(CCFLAGS=arch_flags)
env.Append(CXXFLAGS=arch_flags)
elif arch == "Darwin":
@@ -190,10 +231,10 @@ else:
np_version = SCons.Script.Value(np.__version__)
Export('envCython', 'np_version')
Export('env', 'arch', 'acados', 'release')
Export('env', 'arch', 'acados', 'release', 'ffmpeg_libs')
# Setup cache dir
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
default_cache_dir = '/data/scons_cache' if arch == "comma_arm64" else '/tmp/scons_cache'
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
cache_size_limit = 4e9 if "CI" in os.environ else 2e9
CacheDir(cache_dir)
@@ -211,7 +252,7 @@ def prune_cache_dir(target=None, source=None, env=None):
# ********** start building stuff **********
# Build common module
SConscript(['common/SConscript'])
SConscript(['openpilot/common/SConscript'])
Import('_common')
common = [_common, 'json11', 'zmq']
Export('common')
@@ -222,7 +263,7 @@ env_swaglog = env.Clone()
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
SConscript(['cereal/SConscript'])
SConscript(['openpilot/cereal/SConscript'])
Import('socketmaster', 'msgq')
messaging = [socketmaster, msgq, 'capnp', 'kj',]
@@ -233,34 +274,33 @@ Export('messaging')
SConscript(['panda/SConscript'])
# Build rednose library
SConscript(['rednose/SConscript'])
SConscript(['rednose_repo/rednose/SConscript'])
# Build system services
SConscript([
'system/loggerd/SConscript',
'openpilot/system/loggerd/SConscript',
])
if arch == "larch64":
SConscript(['system/camerad/SConscript'])
if arch == "comma_arm64":
SConscript(['openpilot/system/camerad/SConscript'])
# Build selfdrive
SConscript([
'selfdrive/pandad/SConscript',
'selfdrive/controls/lib/lateral_mpc_lib/SConscript',
'selfdrive/controls/lib/longitudinal_mpc_lib/SConscript',
'selfdrive/locationd/SConscript',
'selfdrive/modeld/SConscript',
'selfdrive/ui/SConscript',
'openpilot/selfdrive/pandad/SConscript',
'openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript',
'openpilot/selfdrive/locationd/SConscript',
'openpilot/selfdrive/modeld/SConscript',
'openpilot/selfdrive/ui/SConscript',
])
SConscript(['sunnypilot/SConscript'])
SConscript(['openpilot/sunnypilot/SConscript'])
# Build desktop-only tools
if GetOption('extras') and arch != "larch64":
if GetOption('extras') and arch != "comma_arm64":
SConscript([
'tools/replay/SConscript',
'tools/cabana/SConscript',
'tools/jotpluggler/SConscript',
'openpilot/tools/replay/SConscript',
'openpilot/tools/cabana/SConscript',
'openpilot/tools/jotpluggler/SConscript',
])
@@ -276,6 +316,8 @@ def count_scons_nodes(nodes):
if node in seen:
continue
seen.add(node)
if hasattr(node, 'has_builder') and node.has_builder():
build_product_nodes.add(node)
executor = node.get_executor()
if executor is not None:
stack += executor.get_all_prerequisites() + executor.get_all_children()
@@ -284,6 +326,7 @@ def count_scons_nodes(nodes):
progress_interval = 5
progress_count = 0
build_product_nodes = set()
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
def progress_function(node):
@@ -299,3 +342,13 @@ def progress_function(node):
Progress(progress_function, interval=progress_interval)
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
def check_build_product_size(target, source, env):
limit = 50 * 1024 * 1024 # GitHub max size
for t in target:
if str(t).endswith('.pkl'): # chunked during release packaging
continue
if hasattr(t, 'isfile') and t.isfile() and (size := os.path.getsize(t.abspath)) > limit:
raise SCons.Errors.UserError(f"{t} is {size / (1024 * 1024):.1f} MiB, exceeding the {limit / (1024 * 1024):.1f} MiB limit")
if not GetOption('extras'):
AddPostAction(list(build_product_nodes), Action(check_build_product_size, None))
-20
View File
@@ -1,20 +0,0 @@
Import('env', 'common', 'msgq')
cereal_dir = Dir('.')
gen_dir = Dir('gen')
# Build cereal
schema_files = ['log.capnp', 'car.capnp', 'deprecated.capnp', 'custom.capnp']
env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files],
schema_files,
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files])
# Build messaging
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc', 'messaging/bridge_zmq.cc'], LIBS=[msgq, common, 'pthread'])
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
Export('cereal', 'socketmaster')
-11
View File
@@ -1,11 +0,0 @@
import os
import capnp
from importlib.resources import as_file, files
capnp.remove_import_hook()
with as_file(files("cereal")) as fspath:
CEREAL_PATH = fspath.as_posix()
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"))
-1
View File
@@ -1 +0,0 @@
../opendbc_repo/opendbc/car/car.capnp
@@ -1,256 +0,0 @@
#!/usr/bin/env python3
"""Schema-level cereal compat check between sunnypilot and upstream openpilot.
Rules (per struct matched across sides by typeId):
R1 shared ordinal must reference the same type.
R2 sunnypilot-only ordinal in a union -> FAIL (unknown discriminant upstream).
R3 sunnypilot-only ordinal on a regular field -> OK (additive struct evolution).
R4 upstream-only ordinal -> OK.
R5 sunnypilot-only struct referenced via an upstream-shared field -> FAIL.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from typing import Any
NO_DISCRIMINANT = 0xFFFF
def hex_id(value: int) -> str:
return f"0x{value:016x}"
def encode_type(type_node: Any) -> dict:
which = type_node.which()
if which == "struct":
return {"kind": "struct", "typeId": hex_id(type_node.struct.typeId)}
if which == "enum":
return {"kind": "enum", "typeId": hex_id(type_node.enum.typeId)}
if which == "interface":
return {"kind": "interface", "typeId": hex_id(type_node.interface.typeId)}
if which == "list":
return {"kind": "list", "element": encode_type(type_node.list.elementType)}
if which == "anyPointer":
return {"kind": "anyPointer"}
return {"kind": which}
def encode_field(name: str, field: Any) -> dict:
proto = field.proto
ordinal = proto.ordinal.explicit if proto.ordinal.which() == "explicit" else None
discriminant = proto.discriminantValue if proto.discriminantValue != NO_DISCRIMINANT else None
if proto.which() == "group":
type_desc = {"kind": "group", "typeId": hex_id(proto.group.typeId)}
else:
type_desc = encode_type(proto.slot.type)
return {
"name": name,
"ordinal": ordinal,
"discriminant": discriminant,
"type": type_desc,
}
def encode_struct(schema: Any) -> dict:
node = schema.node
return {
"typeId": hex_id(node.id),
"displayName": node.displayName,
"hasUnion": node.struct.discriminantCount > 0,
"fields": [encode_field(name, field) for name, field in schema.fields.items()],
}
def _child_struct_schema(field: Any) -> Any:
proto = field.proto
if proto.which() == "group":
return field.schema
type_node = proto.slot.type
which = type_node.which()
if which == "struct":
return field.schema
if which == "list":
container = field.schema
element_type = type_node.list.elementType
while element_type.which() == "list":
container = container.elementType
element_type = element_type.list.elementType
if element_type.which() == "struct":
return container.elementType
return None
def collect_schema(root: Any) -> dict[str, dict]:
structs: dict[str, dict] = {}
stack = [root]
while stack:
schema = stack.pop()
type_id = hex_id(schema.node.id)
if type_id in structs:
continue
structs[type_id] = encode_struct(schema)
for _name, field in schema.fields.items():
try:
child = _child_struct_schema(field)
except Exception:
child = None
if child is not None:
stack.append(child)
return structs
def load_log(cereal_dir: str) -> Any:
import capnp
cereal_dir = os.path.abspath(cereal_dir)
capnp.remove_import_hook()
return capnp.load(os.path.join(cereal_dir, "log.capnp"), imports=[cereal_dir])
def dump_schema(cereal_dir: str, path: str) -> None:
log = load_log(cereal_dir)
payload = {
"root": hex_id(log.Event.schema.node.id),
"structs": collect_schema(log.Event.schema),
}
with open(path, "w", encoding="utf-8") as handle:
json.dump(payload, handle, indent=2, sort_keys=True)
print(f"wrote schema dump with {len(payload['structs'])} structs to {path}")
def types_equal(a: dict, b: dict) -> bool:
if a.get("kind") != b.get("kind"):
return False
kind = a["kind"]
if kind in ("struct", "enum", "interface", "group"):
return a.get("typeId") == b.get("typeId")
if kind == "list":
return types_equal(a["element"], b["element"])
return True
def type_repr(t: dict) -> str:
kind = t.get("kind", "?")
if kind in ("struct", "enum", "interface", "group"):
return f"{kind}({t.get('typeId')})"
if kind == "list":
return f"list<{type_repr(t['element'])}>"
return kind
def field_is_union_variant(field: dict) -> bool:
return field.get("discriminant") is not None
def index_fields_by_ordinal(struct: dict) -> dict[int, dict]:
indexed: dict[int, dict] = {}
for field in struct["fields"]:
ordinal = field.get("ordinal")
if ordinal is None:
continue
indexed[ordinal] = field
return indexed
def compare(sunnypilot_dump: dict, upstream_dump: dict) -> list[str]:
violations: list[str] = []
sunnypilot_structs: dict[str, dict] = sunnypilot_dump["structs"]
upstream_structs: dict[str, dict] = upstream_dump["structs"]
sunnypilot_struct_referenced_from_shared: set[str] = set()
for type_id, sunnypilot_struct in sunnypilot_structs.items():
upstream_struct = upstream_structs.get(type_id)
if upstream_struct is None:
continue
sunnypilot_fields = index_fields_by_ordinal(sunnypilot_struct)
upstream_fields = index_fields_by_ordinal(upstream_struct)
display = sunnypilot_struct["displayName"]
for ordinal, sunnypilot_field in sunnypilot_fields.items():
upstream_field = upstream_fields.get(ordinal)
if upstream_field is None:
if field_is_union_variant(sunnypilot_field):
violations.append(
f"[R2] {display} @{ordinal} ('{sunnypilot_field['name']}', {type_repr(sunnypilot_field['type'])}): "
f"union variant not present upstream. upstream cannot parse this discriminant."
)
continue
if not types_equal(sunnypilot_field["type"], upstream_field["type"]):
violations.append(
f"[R1] {display} @{ordinal}: type mismatch. "
f"sunnypilot='{sunnypilot_field['name']}' {type_repr(sunnypilot_field['type'])} vs "
f"upstream='{upstream_field['name']}' {type_repr(upstream_field['type'])}."
)
continue
cursor = sunnypilot_field["type"]
while cursor.get("kind") == "list":
cursor = cursor["element"]
if cursor.get("kind") in ("struct", "group", "interface") and cursor.get("typeId"):
sunnypilot_struct_referenced_from_shared.add(cursor["typeId"])
for type_id, sunnypilot_struct in sunnypilot_structs.items():
if type_id in upstream_structs:
continue
if type_id in sunnypilot_struct_referenced_from_shared:
violations.append(
f"[R5] struct {sunnypilot_struct['displayName']} ({type_id}) exists only on sunnypilot "
f"but is referenced from an upstream-shared field. upstream cannot resolve this type."
)
return violations
def load_peer(path: str) -> dict:
with open(path, "r", encoding="utf-8") as handle:
return json.load(handle)
def run_read(cereal_dir: str, peer_path: str) -> int:
log = load_log(cereal_dir)
peer_dump = load_peer(peer_path)
local_dump = {
"root": hex_id(log.Event.schema.node.id),
"structs": collect_schema(log.Event.schema),
}
violations = compare(sunnypilot_dump=peer_dump, upstream_dump=local_dump)
if not violations:
print("cereal compat OK: upstream openpilot can parse sunnypilot routes "
"(no leaked structs, no ordinal collisions).")
return 0
print(f"cereal compat FAIL: upstream openpilot would misparse sunnypilot routes "
f"({len(violations)} violation(s)):")
for v in violations:
print(f" {v}")
return 1
def main() -> int:
parser = argparse.ArgumentParser(
description="sunnypilot <-> upstream cereal compatibility validator (schema-level)."
)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("-g", "--generate", action="store_true", help="dump local schema to JSON")
mode.add_argument("-r", "--read", action="store_true", help="load peer JSON and diff against local")
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path (default: schema.json)")
parser.add_argument("--cereal-dir", required=True, help="path to cereal directory containing log.capnp")
args = parser.parse_args()
if args.generate:
dump_schema(args.cereal_dir, args.file)
return 0
return run_read(args.cereal_dir, args.file)
if __name__ == "__main__":
sys.exit(main())
-23
View File
@@ -1,23 +0,0 @@
Import('env', 'envCython')
common_libs = [
'params.cc',
'swaglog.cc',
'util.cc',
'ratekeeper.cc',
]
_common = env.Library('common', common_libs, LIBS="json11")
Export('_common')
if GetOption('extras'):
env.Program('tests/test_common',
['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'],
LIBS=[_common, 'json11', 'zmq', 'pthread'])
# Cython bindings
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
common_python = [params_python]
Export('common_python')
-14
View File
@@ -1,14 +0,0 @@
from cereal import messaging
def generate_livePose():
msg = messaging.new_message('livePose')
meas = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'xStd': 0.0, 'yStd': 0.0, 'zStd': 0.0, 'valid': True}
msg.livePose.orientationNED = meas
msg.livePose.velocityDevice = meas
msg.livePose.angularVelocityDevice = meas
msg.livePose.accelerationDevice = meas
msg.livePose.inputsOK = True
msg.livePose.posenetOK = True
msg.livePose.sensorsOK = True
return msg
-54
View File
@@ -1,54 +0,0 @@
import re
import sys
import pytest
import inspect
def _to_safe_name(s):
return re.sub(r"[^a-zA-Z0-9_]+", "_", str(s)).strip("_")
class parameterized:
@staticmethod
def expand(cases):
cases = list(cases)
if not cases:
return lambda func: pytest.mark.skip("no parameterized cases")(func)
def decorator(func):
params = [p for p in inspect.signature(func).parameters if p != 'self']
normalized = [c if isinstance(c, tuple) else (c,) for c in cases]
# Infer arg count from first case so extra params (e.g. from @given) are left untouched
expand_params = params[: len(normalized[0])]
if len(expand_params) == 1:
return pytest.mark.parametrize(expand_params[0], [c[0] for c in normalized])(func)
return pytest.mark.parametrize(', '.join(expand_params), normalized)(func)
return decorator
def parameterized_class(attrs, input_list=None):
if isinstance(attrs, list) and (not attrs or isinstance(attrs[0], dict)):
params_list = attrs
else:
assert input_list is not None
attr_names = (attrs,) if isinstance(attrs, str) else tuple(attrs)
params_list = [dict(zip(attr_names, v if isinstance(v, (tuple, list)) else (v,), strict=False)) for v in input_list]
def decorator(cls):
globs = sys._getframe(1).f_globals
for i, params in enumerate(params_list):
# append sanitized string param values so pytest -k can filter by them
suffix = "_".join(filter(None, (_to_safe_name(v) for v in params.values() if isinstance(v, str))))
name = f"{cls.__name__}_{i}" + (f"_{suffix}" if suffix else "")
new_cls = type(name, (cls,), dict(params))
new_cls.__module__ = cls.__module__
new_cls.__test__ = True # override inherited False so pytest collects this subclass
globs[name] = new_cls
# Don't collect the un-parametrised base, but return it so outer decorators
# (e.g. @pytest.mark.skip) land on it and propagate to subclasses via MRO.
cls.__test__ = False
return cls
return decorator
-19
View File
@@ -1,19 +0,0 @@
from openpilot.common.params_pyx import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
assert Params
assert ParamKeyFlag
assert ParamKeyType
assert UnknownKeyName
if __name__ == "__main__":
import sys
params = Params()
key = sys.argv[1]
assert params.check_key(key), f"unknown param: {key}"
if len(sys.argv) == 3:
val = sys.argv[2]
print(f"SET: {key} = {val}")
params.put(key, val, block=True)
elif len(sys.argv) == 2:
print(f"GET: {key} = {params.get(key)}")
-192
View File
@@ -1,192 +0,0 @@
# distutils: language = c++
# cython: language_level = 3
import builtins
import datetime
import json
from libcpp cimport bool
from libcpp.string cimport string
from libcpp.vector cimport vector
from libcpp.optional cimport optional
from openpilot.common.swaglog import cloudlog
cdef extern from "common/params.h":
cpdef enum ParamKeyFlag:
PERSISTENT
CLEAR_ON_MANAGER_START
CLEAR_ON_ONROAD_TRANSITION
CLEAR_ON_OFFROAD_TRANSITION
DEVELOPMENT_ONLY
CLEAR_ON_IGNITION_ON
BACKUP
ALL
cpdef enum ParamKeyType:
STRING
BOOL
INT
FLOAT
TIME
JSON
BYTES
cdef cppclass c_Params "Params":
c_Params(string) except + nogil
string get(string, bool) nogil
bool getBool(string, bool) nogil
int remove(string) nogil
int put(string, string) nogil
void putNonBlocking(string, string) nogil
void putBoolNonBlocking(string, bool) nogil
int putBool(string, bool) nogil
bool checkKey(string) nogil
ParamKeyType getKeyType(string) nogil
optional[string] getKeyDefaultValue(string) nogil
string getParamPath(string) nogil
void clearAll(ParamKeyFlag)
vector[string] allKeys(ParamKeyFlag)
PYTHON_2_CPP = {
(str, STRING): lambda v: v,
(builtins.bool, BOOL): lambda v: "1" if v else "0",
(int, INT): str,
(float, FLOAT): str,
(datetime.datetime, TIME): lambda v: v.isoformat(),
(dict, JSON): json.dumps,
(list, JSON): json.dumps,
(bytes, BYTES): lambda v: v,
}
CPP_2_PYTHON = {
STRING: lambda v: v.decode("utf-8"),
BOOL: lambda v: v == b"1",
INT: int,
FLOAT: float,
TIME: lambda v: datetime.datetime.fromisoformat(v.decode("utf-8")),
JSON: json.loads,
BYTES: lambda v: v,
}
def ensure_bytes(v):
return v.encode() if isinstance(v, str) else v
class UnknownKeyName(Exception):
pass
cdef class Params:
cdef c_Params* p
cdef str d
def __cinit__(self, d=""):
cdef string path = <string>d.encode()
with nogil:
self.p = new c_Params(path)
self.d = d
def __reduce__(self):
return (type(self), (self.d,))
def __dealloc__(self):
del self.p
def clear_all(self, tx_flag=ParamKeyFlag.ALL):
self.p.clearAll(tx_flag)
def check_key(self, key):
key = ensure_bytes(key)
if not self.p.checkKey(key):
raise UnknownKeyName(key)
return key
def python2cpp(self, proposed_type, expected_type, value, key):
cast = PYTHON_2_CPP.get((proposed_type, expected_type))
if cast:
return cast(value)
raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}")
def _cpp2python(self, t, value, default, key):
if value is None:
return None
try:
return CPP_2_PYTHON[t](value)
except (KeyError, TypeError, ValueError):
cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}")
return self._cpp2python(t, default, None, key)
def get(self, key, bool block=False, bool return_default=False):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
cdef optional[string] default = self.p.getKeyDefaultValue(k)
cdef string val
with nogil:
val = self.p.get(k, block)
default_val = (default.value() if default.has_value() else None) if return_default else None
if val == b"":
if block:
# If we got no value while running in blocked mode
# it means we got an interrupt while waiting
raise KeyboardInterrupt
else:
return self._cpp2python(t, default_val, None, key)
return self._cpp2python(t, val, default_val, key)
def get_bool(self, key, bool block=False):
cdef string k = self.check_key(key)
cdef bool r
with nogil:
r = self.p.getBool(k, block)
return r
def _put_cast(self, key, dat):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
return ensure_bytes(self.python2cpp(type(dat), t, dat, key))
def put(self, key, dat, bool block = False):
"""
Warning: block=True blocks until the param is written to disk!
In very rare cases this can take over a second, and your code will hang.
Use block=False in time sensitive code, but in general try to avoid
writing params as much as possible.
"""
cdef string k = self.check_key(key)
cdef string dat_bytes = self._put_cast(key, dat)
with nogil:
if block:
self.p.put(k, dat_bytes)
else:
self.p.putNonBlocking(k, dat_bytes)
def put_bool(self, key, bool val, bool block = False):
cdef string k = self.check_key(key)
with nogil:
if block:
self.p.putBool(k, val)
else:
self.p.putBoolNonBlocking(k, val)
def remove(self, key):
cdef string k = self.check_key(key)
with nogil:
self.p.remove(k)
def get_param_path(self, key=""):
cdef string key_bytes = ensure_bytes(key)
return self.p.getParamPath(key_bytes).decode("utf-8")
def get_type(self, key):
return self.p.getKeyType(self.check_key(key))
def all_keys(self, flag=ParamKeyFlag.ALL):
return self.p.allKeys(flag)
def get_default_value(self, key):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
cdef optional[string] default = self.p.getKeyDefaultValue(k)
return self._cpp2python(t, default.value(), None, key) if default.has_value() else None
def cpp2python(self, key, value):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
return self._cpp2python(t, value, None, key)
-1
View File
@@ -1 +0,0 @@
test_common
-27
View File
@@ -1,27 +0,0 @@
#include "catch2/catch.hpp"
#define private public
#include "common/params.h"
#include "common/util.h"
TEST_CASE("params_nonblocking_put") {
char tmp_path[] = "/tmp/asyncWriter_XXXXXX";
const std::string param_path = mkdtemp(tmp_path);
auto param_names = {"CarParams", "IsMetric"};
{
Params params(param_path);
for (const auto &name : param_names) {
params.putNonBlocking(name, "1");
// param is empty
REQUIRE(params.get(name).empty());
}
// check if thread is running
REQUIRE(params.future.valid());
REQUIRE(params.future.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout);
}
// check results
Params p(param_path);
for (const auto &name : param_names) {
REQUIRE(p.get(name) == "1");
}
}
-2
View File
@@ -1,2 +0,0 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
-90
View File
@@ -1,90 +0,0 @@
#include <zmq.h>
#include <iostream>
#include "catch2/catch.hpp"
#include "common/swaglog.h"
#include "common/util.h"
#include "common/version.h"
#include "system/hardware/hw.h"
#include "json11/json11.hpp"
#include "sunnypilot/common/version.h"
std::string daemon_name = "testy";
std::string dongle_id = "test_dongle_id";
int LINE_NO = 0;
void log_thread(int thread_id, int msg_cnt) {
for (int i = 0; i < msg_cnt; ++i) {
LOGD("%d", thread_id);
LINE_NO = __LINE__ - 1;
usleep(1);
}
}
void recv_log(int thread_cnt, int thread_msg_cnt) {
void *zctx = zmq_ctx_new();
void *sock = zmq_socket(zctx, ZMQ_PULL);
zmq_bind(sock, Path::swaglog_ipc().c_str());
std::vector<int> thread_msgs(thread_cnt);
int total_count = 0;
for (auto start = std::chrono::steady_clock::now(), now = start;
now < start + std::chrono::seconds{1} && total_count < (thread_cnt * thread_msg_cnt);
now = std::chrono::steady_clock::now()) {
char buf[4096] = {};
if (zmq_recv(sock, buf, sizeof(buf), ZMQ_DONTWAIT) <= 0) {
if (errno == EAGAIN || errno == EINTR || errno == EFSM) continue;
break;
}
REQUIRE(buf[0] == CLOUDLOG_DEBUG);
std::string err;
auto msg = json11::Json::parse(buf + 1, err);
REQUIRE(!msg.is_null());
REQUIRE(msg["levelnum"].int_value() == CLOUDLOG_DEBUG);
REQUIRE_THAT(msg["filename"].string_value(), Catch::Contains("test_swaglog.cc"));
REQUIRE(msg["funcname"].string_value() == "log_thread");
REQUIRE(msg["lineno"].int_value() == LINE_NO);
auto ctx = msg["ctx"];
REQUIRE(ctx["daemon"].string_value() == daemon_name);
REQUIRE(ctx["dongle_id"].string_value() == dongle_id);
REQUIRE(ctx["dirty"].bool_value() == true);
REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION);
std::string device = Hardware::get_name();
REQUIRE(ctx["device"].string_value() == device);
int thread_id = atoi(msg["msg"].string_value().c_str());
REQUIRE((thread_id >= 0 && thread_id < thread_cnt));
thread_msgs[thread_id]++;
total_count++;
}
for (int i = 0; i < thread_cnt; ++i) {
INFO("thread :" << i);
REQUIRE(thread_msgs[i] == thread_msg_cnt);
}
zmq_close(sock);
zmq_ctx_destroy(zctx);
}
TEST_CASE("swaglog") {
setenv("MANAGER_DAEMON", daemon_name.c_str(), 1);
setenv("DONGLE_ID", dongle_id.c_str(), 1);
setenv("dirty", "1", 1);
const int thread_cnt = 5;
const int thread_msg_cnt = 100;
std::vector<std::thread> log_threads;
for (int i = 0; i < thread_cnt; ++i) {
log_threads.push_back(std::thread(log_thread, i, thread_msg_cnt));
}
for (auto &t : log_threads) t.join();
recv_log(thread_cnt, thread_msg_cnt);
}
-147
View File
@@ -1,147 +0,0 @@
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <algorithm>
#include <climits>
#include <fstream>
#include <random>
#include <string>
#include "catch2/catch.hpp"
#include "common/util.h"
std::string random_bytes(int size) {
std::random_device rd;
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> rbe(rd());
std::string bytes(size + 1, '\0');
std::generate(bytes.begin(), bytes.end(), std::ref(rbe));
return bytes;
}
TEST_CASE("util::read_file") {
SECTION("read /proc/version") {
std::string ret = util::read_file("/proc/version");
REQUIRE(ret.find("Linux version") != std::string::npos);
}
SECTION("read from sysfs") {
std::string ret = util::read_file("/sys/power/wakeup_count");
REQUIRE(!ret.empty());
}
SECTION("read file") {
char filename[] = "/tmp/test_read_XXXXXX";
int fd = mkstemp(filename);
REQUIRE(util::read_file(filename).empty());
std::string content = random_bytes(64 * 1024);
REQUIRE(write(fd, content.c_str(), content.size()) == (ssize_t)content.size());
std::string ret = util::read_file(filename);
bool equal = (ret == content);
REQUIRE(equal);
close(fd);
}
SECTION("read directory") {
REQUIRE(util::read_file(".").empty());
}
SECTION("read non-existent file") {
std::string ret = util::read_file("does_not_exist");
REQUIRE(ret.empty());
}
SECTION("read non-permission") {
REQUIRE(util::read_file("/proc/kmsg").empty());
}
}
TEST_CASE("util::file_exists") {
char filename[] = "/tmp/test_file_exists_XXXXXX";
int fd = mkstemp(filename);
REQUIRE(fd != -1);
close(fd);
SECTION("existent file") {
REQUIRE(util::file_exists(filename));
REQUIRE(util::file_exists("/tmp"));
}
SECTION("nonexistent file") {
std::string fn = filename;
REQUIRE(!util::file_exists(fn + "/nonexistent"));
}
SECTION("file has no access permissions") {
std::string fn = "/proc/kmsg";
std::ifstream f(fn);
REQUIRE(f.good() == false);
REQUIRE(util::file_exists(fn));
}
::remove(filename);
}
TEST_CASE("util::read_files_in_dir") {
char tmp_path[] = "/tmp/test_XXXXXX";
const std::string test_path = mkdtemp(tmp_path);
const std::string files[] = {".test1", "'test2'", "test3"};
for (auto fn : files) {
std::ofstream{test_path + "/" + fn} << fn;
}
mkdir((test_path + "/dir").c_str(), 0777);
std::map<std::string, std::string> result = util::read_files_in_dir(test_path);
REQUIRE(result.find("dir") == result.end());
REQUIRE(result.size() == std::size(files));
for (auto& [k, v] : result) {
REQUIRE(k == v);
}
}
TEST_CASE("util::safe_fwrite") {
char filename[] = "/tmp/XXXXXX";
int fd = mkstemp(filename);
close(fd);
std::string dat = random_bytes(1024 * 1024);
FILE *f = util::safe_fopen(filename, "wb");
REQUIRE(f != nullptr);
size_t size = util::safe_fwrite(dat.data(), 1, dat.size(), f);
REQUIRE(size == dat.size());
int ret = util::safe_fflush(f);
REQUIRE(ret == 0);
ret = fclose(f);
REQUIRE(ret == 0);
bool equal = (dat == util::read_file(filename));
REQUIRE(equal);
}
TEST_CASE("util::create_directories") {
REQUIRE(system("rm /tmp/test_create_directories -rf") == 0);
std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f";
auto check_dir_permissions = [](const std::string &path, mode_t mode) -> bool {
struct stat st = {};
return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode;
};
SECTION("create_directories") {
REQUIRE(util::create_directories(dir, 0755));
REQUIRE(check_dir_permissions(dir, 0755));
}
SECTION("dir already exists") {
REQUIRE(util::create_directories(dir, 0755));
REQUIRE(util::create_directories(dir, 0755));
}
SECTION("a file exists with the same name") {
REQUIRE(util::create_directories(dir, 0755));
int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT, 0644);
REQUIRE(f != -1);
close(f);
REQUIRE(util::create_directories(dir + "/file", 0755) == false);
REQUIRE(util::create_directories(dir + "/file/1/2/3", 0755) == false);
}
SECTION("end with slashes") {
REQUIRE(util::create_directories(dir + "/", 0755));
}
SECTION("empty") {
REQUIRE(util::create_directories("", 0755) == false);
}
}
-93
View File
@@ -1,93 +0,0 @@
import contextlib
import gc
import os
import pytest
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.system.manager import manager
from openpilot.system.hardware import TICI, HARDWARE
# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml
collect_ignore = [
"selfdrive/test/process_replay/test_processes.py",
"selfdrive/test/process_replay/test_regen.py",
# tinygrad JIT has process-global state. Other test files import modeld → tinygrad,
# which corrupts JIT captures for test_warp.py in the same process. Run separately in CI.
"sunnypilot/modeld_v2/tests/test_warp.py",
]
collect_ignore_glob = [
"selfdrive/debug/*.py",
]
@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_runtest_call(item):
# ensure we run as a hook after capturemanager's
if item.get_closest_marker("nocapture") is not None:
capmanager = item.config.pluginmanager.getplugin("capturemanager")
with capmanager.global_and_fixture_disabled():
yield
else:
yield
@contextlib.contextmanager
def clean_env():
starting_env = dict(os.environ)
yield
os.environ.clear()
os.environ.update(starting_env)
@pytest.fixture(scope="function", autouse=True)
def openpilot_function_fixture(request):
with clean_env():
# setup a clean environment for each test
with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix:
prefix = os.environ["OPENPILOT_PREFIX"]
yield
# ensure the test doesn't change the prefix
assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"]
# cleanup any started processes
manager.manager_cleanup()
# some processes disable gc for performance, re-enable here
if not gc.isenabled():
gc.enable()
gc.collect()
# If you use setUpClass, the environment variables won't be cleared properly,
# so we need to hook both the function and class pytest fixtures
@pytest.fixture(scope="class", autouse=True)
def openpilot_class_fixture():
with clean_env():
yield
@pytest.fixture(scope="function")
def tici_setup_fixture(request, openpilot_function_fixture):
"""Ensure a consistent state for tests on-device. Needs the openpilot function fixture to run first."""
if 'skip_tici_setup' in request.keywords:
return
HARDWARE.initialize_hardware()
HARDWARE.set_power_save(False)
os.system("pkill -9 -f athena")
@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(config, items):
skipper = pytest.mark.skip(reason="Skipping tici test on PC")
for item in items:
if "tici" in item.keywords:
if not TICI:
item.add_marker(skipper)
else:
item.fixturenames.append('tici_setup_fixture')
if "xdist_group_class_property" in item.keywords:
class_property_name = item.get_closest_marker('xdist_group_class_property').args[0]
class_property_value = getattr(item.cls, class_property_name)
item.add_marker(pytest.mark.xdist_group(class_property_value))
+44
View File
@@ -0,0 +1,44 @@
# AI policy
## Why this exists
We use AI tools ourselves, so this isn't an anti-AI stance. The problem is people submitting code, issues, or comments they don't actually understand. AI makes that very easy to do, and it creates real work for reviewers who have to figure out what you meant when you can't explain it yourself.
If you're not going to put effort into understanding and verifying your submission, we're not going to put effort into reviewing it.
## The rule
You are responsible for everything you submit: code, PR descriptions, issues, bug reports, comments.
1. Understand what you submit. If a reviewer asks why you did something, you answer from your own understanding, not by re-prompting. If you can't do that, don't submit it.
2. Test your change. AI gets things wrong all the time. Run it, break it, confirm it actually works.
3. Driving fixes need real evidence. Attach a dongle ID, upload logs, and include segments that show the fix working. A route hash by itself proves nothing.
4. No AI-generated media (images, diagrams, videos) in issues or PRs.
## Disclosure
If AI tools helped you write something, say so. Add an `Assisted-by:` line in your commit message:
```
Assisted-by: GitHub Copilot
Assisted-by: Claude
```
Disclosing won't count against your PR. It helps reviewers know where to look. Hiding it and getting caught will.
## How we review
Reviewers are looking at whether you understand your own change. Can you explain it? Can you respond to feedback without re-prompting? Does your PR description say why you made the change, not just list what changed?
Good code from someone who used AI and understands what they wrote is fine. How you got there doesn't matter as long as you can stand behind it.
## What happens
Submissions that don't meet this bar get closed. If it keeps happening, you get blocked.
## Maintainers
Maintainers use AI at their discretion. They've earned that through sustained contribution and they know the codebase.
+25 -21
View File
@@ -1,10 +1,10 @@
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->
<!--- AUTOGENERATED FROM openpilot/selfdrive/car/CARS_template.md, DO NOT EDIT. --->
# Supported Cars
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 341 Supported Cars
# 345 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -34,6 +34,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica Hybrid 2019-25">Buy Here</a></sub></details>|||
|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|<a href="https://youtu.be/VT-i3yRsX2s?t=2736" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|CUPRA[<sup>12</sup>](#footnotes)|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|CUPRA|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[<sup>16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW MEB connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Born 2021-23">Buy Here</a></sub></details>|||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Dodge Durango 2020-21">Buy Here</a></sub></details>|||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape 2020-22">Buy Here</a></sub></details>|||
@@ -78,11 +79,11 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Accord 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-26">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023-25">Buy Here</a></sub></details>|||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Civic 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Civic 2022-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-26">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
@@ -98,7 +99,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2019-22">Buy Here</a></sub></details>|||
|Honda|HR-V 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|HR-V 2023-27|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-27">Buy Here</a></sub></details>|||
|Honda|Insight 2019-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|Honda|Inspire 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|Honda|N-Box 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
@@ -158,7 +159,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Plug-in Hybrid 2024">Buy Here</a></sub></details>|||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Veloster 2019-20">Buy Here</a></sub></details>|||
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=jBe4lWnRSu4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2019-21">Buy Here</a></sub></details>|||
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
@@ -187,7 +188,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2022">Buy Here</a></sub></details>|||
|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima 2017">Buy Here</a></sub></details>|||
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima 2019-20">Buy Here</a></sub></details>|||
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Seltos 2021">Buy Here</a></sub></details>|||
|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
@@ -230,31 +231,32 @@ A supported vehicle is one that just works when you install a comma device. All
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Nissan[<sup>6</sup>](#footnotes)|Altima 2019-24|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Altima 2019-24">Buy Here</a></sub></details>|||
|Nissan[<sup>6</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Nissan[<sup>6</sup>](#footnotes)|Leaf IC 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf IC 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Nissan[<sup>6</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|Nissan[<sup>6</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 2500 2020-24">Buy Here</a></sub></details>|||
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 3500 2019-22">Buy Here</a></sub></details>|||
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/dflSSGQwYNc" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2025">Buy Here</a></sub></details>|||
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/dflSSGQwYNc" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2025">Buy Here</a></sub></details>|||
|SEAT[<sup>12</sup>](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|SEAT[<sup>12</sup>](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|Subaru|Ascent 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Ascent 2019-21|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2019-21|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Škoda|Fabia 2022-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Škoda|Kamiq 2021-23[<sup>13,15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Škoda[<sup>12</sup>](#footnotes)|Karoq 2019-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
@@ -266,8 +268,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Škoda[<sup>12</sup>](#footnotes)|Superb 2015-22[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW3) 2020-24[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-24">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW4) 2023-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2023-25">Buy Here</a></sub></details>|||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard 2019-20">Buy Here</a></sub></details>|||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Avalon 2016">Buy Here</a></sub></details>|||
@@ -333,6 +335,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Volkswagen[<sup>12</sup>](#footnotes)|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|Volkswagen[<sup>12</sup>](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Volkswagen|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[<sup>16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW MEB connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[<sup>16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW MEB connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2024-25">Buy Here</a></sub></details>|||
|Volkswagen[<sup>12</sup>](#footnotes)|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta 2019-23">Buy Here</a></sub></details>|||
|Volkswagen[<sup>12</sup>](#footnotes)|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Passat 2015-22[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
@@ -359,7 +363,7 @@ A supported vehicle is one that just works when you install a comma device. All
<sup>6</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
<sup>7</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
<sup>8</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
<sup>9</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
<sup>9</sup>Model years 2023 and 2024 can have either hardware type, depending on build date and factory. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
<sup>10</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
<sup>11</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
<sup>12</sup>The J533 harness plugs in at the CAN gateway under the dashboard, just above the steering column. More information can be found at <a href="https://docs.howtocomma.com/docs/j533-harness-install" target="_blank">this guide</a>. <br />
+5 -2
View File
@@ -1,3 +1,5 @@
> sunnypilot follows [commaai/openpilot](https://github.com/commaai/openpilot)'s contributing guidelines. The following applies to all contributions here.
# How to contribute
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
@@ -35,6 +37,7 @@ All of these are examples of good PRs:
* **UI design**: we do not have a good review process for this yet
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
* **AI-generated contributions**: see our [AI policy](AI_POLICY.md)
### First contribution
@@ -58,7 +61,7 @@ A good pull request has all of the following:
* Report bugs in GitHub issues.
* Report driving issues in the `#driving-feedback` Discord channel.
* Consider opting into driver camera uploads to improve the driver monitoring model.
* Consider opting into cabin camera uploads to improve the driver monitoring model.
* Connect your device to Wi-Fi regularly, so that we can pull data for training better driving models.
* Run the `nightly` branch and report issues. This branch is like `master` but it's built just like a release.
* Annotate images in the [comma10k dataset](https://github.com/commaai/comma10k).
@@ -68,6 +71,6 @@ A good pull request has all of the following:
### A guide for forks
In order for your fork's data to be eligible for the training set:
* **Your cereal messaging structs must be [compatible](../cereal#custom-forks)**
* **Your cereal messaging structs must be [compatible](../openpilot/cereal#custom-forks)**
* **The definitions of all the stock messaging structs must not change**: Do not change how any of the fields are set, including everything from `selfdriveState.enabled` to `carState.steeringAngleDeg`. Instead, create your own structs and set them however you'd like.
* **Do not include cars that are not supported in upstream platforms**: Instead, create new opendbc platforms for cars that you'd like to support outside of upstream, even if it's just a trim-level difference.
-24
View File
@@ -1,24 +0,0 @@
# Docs development
The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai).
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
Those commands must be run in the root directory of openpilot, **not /docs**
**1. Install the docs dependencies**
``` bash
uv pip install .[docs]
```
**2. Build the new site**
``` bash
docs build
```
**3. Run the new site locally**
``` bash
docs serve
```
References:
* https://zensical.org/docs/
+2 -2
View File
@@ -52,7 +52,7 @@ Many factors can impact the performance of openpilot DM, causing it to be unable
* Low light conditions, such as driving at night or in dark tunnels.
* Bright light (due to oncoming headlights, direct sunlight, etc.).
* The driver's face is partially or completely outside field of view of the driver facing camera.
* The driver facing camera is obstructed, covered, or damaged.
* The driver's face is partially or completely outside field of view of the cabin camera.
* The cabin camera is obstructed, covered, or damaged.
The list above does not represent an exhaustive list of situations that may interfere with proper operation of openpilot components. A driver should not rely on openpilot DM to assess their level of attention.
+14
View File
@@ -0,0 +1,14 @@
# Docs development
The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai).
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
**1. Build the site**
``` bash
op docs --build
```
**2. Run the site locally** (rebuilds on change)
``` bash
op docs
```
+2 -2
View File
@@ -35,8 +35,8 @@ For additional safety implementation details, refer to [panda safety model](http
### Forks of openpilot
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/openpilot/selfdrive/monitoring)
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/openpilot/selfdrive/selfdrived/helpers.py)
* If your fork modifies any of the code in `opendbc/safety/`:
* your fork cannot use the openpilot trademark
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
+1 -1
View File
@@ -1 +1 @@
../../selfdrive/assets/icons_mici/settings/comma_icon.png
../../openpilot/selfdrive/assets/icons_mici/settings/comma_icon.png
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e019ed5af500e8820d05934d47e0380728e1b30e01a179f786dc4edb1eccbd7
size 349
-3
View File
@@ -1,3 +0,0 @@
# openpilot glossary
{{GLOSSARY_DEFINITIONS}}
+6 -6
View File
@@ -2,25 +2,25 @@
openpilot records routes in one minute chunks called segments. A route starts on the rising edge of ignition and ends on the falling edge.
Check out our [Python library](https://github.com/commaai/openpilot/blob/master/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
Check out our [Python library](https://github.com/commaai/openpilot/blob/master/openpilot/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/openpilot/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
For each segment, openpilot records the following log types:
## rlog.zst
rlogs contain all the messages passed amongst openpilot's processes. See [cereal/services.py](https://github.com/commaai/openpilot/blob/master/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Capn Proto](https://capnproto.org/) messages.
rlogs contain all the messages passed amongst openpilot's processes. See [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Capn Proto](https://capnproto.org/) messages.
## {f,e,d}camera.hevc
## camera video files
Each camera stream is H.265 encoded and written to its respective file.
* `fcamera.hevc` is the road camera
* `fcamera.hevc` is the narrow road camera (the main forward camera)
* `ecamera.hevc` is the wide road camera
* `dcamera.hevc` is the driver camera
* `dcamera.hevc` is the cabin camera
## qlog.zst & qcamera.ts
qlogs are a decimated subset of the rlogs. Check out [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for the decimation.
qlogs are a decimated subset of the rlogs. Check out [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for the decimation.
qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras.
+3 -3
View File
@@ -21,14 +21,14 @@ In general, driver monitoring feedback is very actionable, and we can fix your c
To post your feedback:
1. Join the [community Discord](https://discord.comma.ai).
2. If driver camera recording is toggled off, temporarily enable driver camera recording in the settings until you reproduce the issue.
3. Using comma connect, identify the relevant segment and upload the segment's logs and driver camera.
2. If cabin camera recording is toggled off, temporarily enable cabin camera recording in the settings until you reproduce the issue.
3. Using comma connect, identify the relevant segment and upload the segment's logs and cabin camera.
4. Post the segment in the `#openpilot-experience` channel on Discord with a good description.
Before posting feedback, please ensure:
- **openpilot is up to date** you should be on the latest openpilot release or nightly
- **the driver camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc.
- **the cabin camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc.
- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield
## Other bugs
-216
View File
@@ -1,216 +0,0 @@
import posixpath
import re
import tomllib
import xml.etree.ElementTree as ET
from pathlib import Path
from markdown.extensions import Extension
from markdown.preprocessors import Preprocessor
from markdown.treeprocessors import Treeprocessor
from zensical.extensions.links import LinksTreeprocessor
GlossaryTerm = tuple[str, re.Pattern[str], str]
GLOSSARY_FILE = Path(__file__).with_name("glossary.toml")
GLOSSARY_PAGE = "concepts/glossary.md"
GLOSSARY_PLACEHOLDER = "{{GLOSSARY_DEFINITIONS}}"
SKIP_TAGS = {
"a",
"code",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"kbd",
"pre",
"script",
"style",
}
def clean_tooltip(description: str) -> str:
text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
text = re.sub(r"`([^`]+)`", r"\1", text)
text = re.sub(r"[*_~]", "", text)
return re.sub(r"\s+", " ", text).strip()
def load_glossary() -> tuple[list[GlossaryTerm], str]:
with GLOSSARY_FILE.open("rb") as f:
glossary_data = tomllib.load(f).get("glossary", {})
glossary: list[GlossaryTerm] = []
rendered = []
for key, value in glossary_data.items():
label = str(key).strip().replace("_", " ")
description = str(value).strip()
if not description:
continue
slug = label.replace(" ", "-").replace("_", "-").lower()
glossary.append((slug, re.compile(rf"(?<!\w){re.escape(label)}(?!\w)", re.IGNORECASE), clean_tooltip(description)))
rendered.append(f'* <span id="{slug}"></span>**{label}**: {description}')
return glossary, "\n".join(rendered)
class GlossaryPreprocessor(Preprocessor):
def __init__(self, md, glossary: str):
super().__init__(md)
self.glossary = glossary
def run(self, lines: list[str]) -> list[str]:
markdown = "\n".join(lines)
if GLOSSARY_PLACEHOLDER not in markdown:
return lines
return markdown.replace(GLOSSARY_PLACEHOLDER, self.glossary).splitlines()
class GlossaryTreeprocessor(Treeprocessor):
def __init__(self, md, glossary: list[GlossaryTerm]):
super().__init__(md)
self.glossary = glossary
self.seen: set[str] = set()
def run(self, root: ET.Element) -> None:
at = self.md.treeprocessors.get_index_for_name("zrelpath")
processor = self.md.treeprocessors[at]
if not isinstance(processor, LinksTreeprocessor):
raise TypeError("Links processor not registered")
if processor.path == GLOSSARY_PAGE:
return
self.seen.clear()
glossary_href = f"{posixpath.relpath(GLOSSARY_PAGE, posixpath.dirname(processor.path) or '.')}#"
self._walk(root, glossary_href)
def _walk(self, element: ET.Element, glossary_href: str) -> None:
if element.tag in SKIP_TAGS or element.attrib.get("data-glossary-skip") is not None:
return
self._replace(element, glossary_href)
idx = 0
while idx < len(element):
child = element[idx]
self._walk(child, glossary_href)
idx = self._replace(element, glossary_href, idx) + 1
def _replace(self, parent: ET.Element, glossary_href: str, index: int | None = None) -> int:
child = None if index is None else parent[index]
text = parent.text if child is None else child.tail
pieces = self._pieces(text or "", glossary_href)
if not pieces:
return -1 if index is None else index
if child is None:
parent.text = pieces[0] if isinstance(pieces[0], str) else ""
# Insert replacements for parent.text before the first existing child.
insert_at = -1
else:
assert index is not None
child.tail = pieces[0] if isinstance(pieces[0], str) else ""
insert_at = index
start = 1 if isinstance(pieces[0], str) else 0
previous = child
for piece in pieces[start:]:
if isinstance(piece, str):
previous.tail = (previous.tail or "") + piece
continue
insert_at += 1
parent.insert(insert_at, piece)
previous = piece
return insert_at
def _pieces(self, text: str, glossary_href: str) -> list[str | ET.Element]:
if not text.strip():
return []
pieces: list[str | ET.Element] = []
cursor = 0
while True:
best = None
for slug, pattern, tooltip in self.glossary:
if slug in self.seen:
continue
found = pattern.search(text, cursor)
if found is None:
continue
candidate = (slug, tooltip, found.start(), found.end())
if best is None:
best = candidate
continue
_, _, best_start, best_end = best
_, _, current_start, current_end = candidate
if current_start < best_start:
best = candidate
continue
if current_start == best_start and current_end - current_start > best_end - best_start:
best = candidate
if best is None:
break
slug, tooltip, start, end = best
if start > cursor:
pieces.append(text[cursor:start])
link = ET.Element(
"a",
{
"class": "glossary-term",
"data-glossary-term": "",
"href": f"{glossary_href}{slug}",
},
)
ET.SubElement(link, "span", {"class": "glossary-term__label"}).text = text[start:end]
ET.SubElement(
link,
"span",
{
"class": "glossary-term__tooltip",
"data-search-exclude": "",
},
).text = tooltip
pieces.append(link)
self.seen.add(slug)
cursor = end
if not pieces:
return []
if cursor < len(text):
pieces.append(text[cursor:])
return pieces
class GlossaryExtension(Extension):
def extendMarkdown(self, md) -> None:
md.registerExtension(self)
glossary, rendered = load_glossary()
md.preprocessors.register(
GlossaryPreprocessor(md, rendered),
"docs-ext-glossary-preprocessor",
27,
)
md.treeprocessors.register(
GlossaryTreeprocessor(md, glossary),
"docs-ext-glossary-treeprocessor",
0,
)
def makeExtension(**kwargs) -> GlossaryExtension:
return GlossaryExtension(**kwargs)
-8
View File
@@ -1,8 +0,0 @@
[glossary]
onroad = "openpilot's system state while ignition is on."
offroad = "openpilot's system state while ignition is off."
route = "A route is a recording of an onroad session."
segment = "Routes are split into one minute chunks called segments."
"comma connect" = "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai)."
panda = "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda)."
"comma four" = "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four)."
+1 -1
View File
@@ -30,7 +30,7 @@ Each car brand is supported by a standard interface structure in `opendbc/car/[b
For historical reasons, openpilot still contains a small amount of car-specific logic. This will eventually be migrated to opendbc or otherwise removed.
* `selfdrive/car/car_specific.py`: Brand-specific event logic
* `openpilot/selfdrive/car/car_specific.py`: Brand-specific event logic
# How do I port car?
+1 -1
View File
@@ -5,7 +5,7 @@ Replaying is a critical tool for openpilot development and debugging.
## Replaying a route
*Hardware required: none*
Just run `tools/replay/replay --demo`.
Just run `openpilot/tools/replay/replay --demo`.
## Replaying CAN data
*Hardware required: jungle and comma four*
+5 -5
View File
@@ -28,10 +28,10 @@ scons
We'll run the `replay` tool with the demo route to get data streaming for testing our UI changes.
```bash
# in terminal 1
tools/replay/replay --demo
openpilot/tools/replay/replay --demo
# in terminal 2
./selfdrive/ui/ui.py
./openpilot/selfdrive/ui/ui.py
```
The openpilot UI should launch and show a replay of the demo route.
@@ -45,10 +45,10 @@ Now lets update the speed display color in the UI.
Search for the function responsible for rendering the current speed:
```bash
git grep "_draw_current_speed" selfdrive/ui/onroad/hud_renderer.py
git grep "_draw_current_speed" openpilot/selfdrive/ui/onroad/hud_renderer.py
```
You'll find the relevant code inside `selfdrive/ui/onroad/hud_renderer.py`, in this function:
You'll find the relevant code inside `openpilot/selfdrive/ui/onroad/hud_renderer.py`, in this function:
```python
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
@@ -72,7 +72,7 @@ Change `COLORS.white` to make it **blue** instead of white. A nice soft blue is
After making changes, re-run the UI to see your new UI:
```bash
./selfdrive/ui/ui.py
./openpilot/selfdrive/ui/ui.py
```
![](https://blog.comma.ai/img/blue_speed_ui.png)
+555
View File
@@ -0,0 +1,555 @@
import argparse
import functools
import html
import http.server
import json
import posixpath
import re
import shutil
import threading
import time
import urllib.parse
from pathlib import Path
DOCS_DIR = Path(__file__).resolve().parent
SITE_DIR = DOCS_DIR / "_site"
TEMPLATE_FILE = DOCS_DIR / "template.html"
EXCLUDE_DIRS = {"_site", "__pycache__"}
REPO_URL = "https://github.com/commaai/openpilot/"
# (title, target) pairs. target is a page path or an absolute URL.
# A None target marks a section header.
NAV: list[tuple[str, str | None]] = [
("What is openpilot?", "index.md"),
("How-to", None),
("Turn the speed blue", "how-to/turn-the-speed-blue.md"),
("Connect to a comma 3X or four", "how-to/connect-to-comma.md"),
("Add support for a car", "how-to/car-port.md"),
("Concepts", None),
("Logs", "concepts/logs.md"),
("Safety", "concepts/safety.md"),
("Glossary", "concepts/glossary.md"),
("Contributing", None),
("Feedback", "contributing/feedback.md"),
("Roadmap", "contributing/roadmap.md"),
("Contributing Guide →", "https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md"),
("Links", None),
("Blog →", "https://blog.comma.ai"),
("Bounties →", "https://comma.ai/bounties"),
("GitHub →", "https://github.com/commaai"),
("Discord →", "https://discord.comma.ai"),
("X →", "https://x.com/comma_ai"),
]
GLOSSARY_DESCRIPTIONS = {
"onroad": "openpilot's system state while ignition is on.",
"offroad": "openpilot's system state while ignition is off.",
"route": "A route is a recording of an onroad session.",
"segment": "Routes are split into one minute chunks called segments.",
"comma connect": "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai).",
"panda": "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda).",
"comma four": "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four).",
}
GLOSSARY_PAGE = "concepts/glossary.md"
GLOSSARY_ROUTE = GLOSSARY_PAGE.removesuffix(".md")
GLOSSARY_SKIP = frozenset("a code h1 h2 h3 h4 h5 h6 kbd pre script style".split())
_ENTITY = re.compile(r"&(?:#x?[0-9a-fA-F]+|[a-zA-Z]+);")
_LIST = re.compile(r"^(\s*)([*+-]|\d+\.)\s+(.*)$")
_HEADING = re.compile(r"^(#{1,6})\s+(.*)$")
_HR = re.compile(r"^(-{3,}|\*{3,}|_{3,})$")
_ATTR_URL = re.compile(r"""(?P<pre>\b(?:href|src)=(?P<q>["']))(?P<url>.*?)(?P=q)""")
_VOID = frozenset("br img hr meta link input".split())
_URL = re.compile(r"https?://[^\s<>\[\]\"']+")
_ADMONITION = re.compile(r"^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]$", re.I)
def page_route(path: str) -> str:
path = path.removesuffix(".md")
return posixpath.dirname(path) or "." if posixpath.basename(path) == "index" else path
def page_href(current: str, target: str) -> str:
route = posixpath.relpath(page_route(target), page_route(current))
return ("." if route == "." else route) + "/"
def rewrite_relative_url(value: str, page: str) -> str | None:
url = urllib.parse.urlparse(value)
if value.startswith(("#", "/")) or url.scheme or url.netloc or not url.path:
return None
target = posixpath.normpath(posixpath.join(posixpath.dirname(page), url.path))
if target == ".." or target.startswith("../"):
return None
path = page_href(page, target) if target.endswith(".md") else posixpath.relpath(target, page_route(page))
return url._replace(path=path).geturl()
def rewrite_html_urls(fragment: str, page: str) -> str:
def repl(m: re.Match[str]) -> str:
r = rewrite_relative_url(m.group("url"), page)
return m.group(0) if r is None else f'{m.group("pre")}{r}{m.group("q")}'
return _ATTR_URL.sub(repl, fragment)
def esc(text: str, attr: bool = False) -> str:
held: list[str] = []
def hold(m: re.Match[str]) -> str:
held.append(m.group(0))
return f"\0{len(held) - 1}\0"
return re.sub(r"\0(\d+)\0", lambda m: held[int(m.group(1))], html.escape(_ENTITY.sub(hold, text), quote=attr))
def clean_tooltip(description: str) -> str:
text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
return re.sub(r"\s+", " ", re.sub(r"[*_~]", "", re.sub(r"`([^`]+)`", r"\1", text))).strip()
def glossary_slug(label: str) -> str:
return label.replace(" ", "-").replace("_", "-").lower()
GLOSSARY_TERMS = [(glossary_slug(l), re.compile(rf"(?<!\w){re.escape(l)}(?!\w)", re.I), clean_tooltip(d)) for l, d in GLOSSARY_DESCRIPTIONS.items()]
GLOSSARY_DEFINITIONS = "\n".join(f'* <span id="{glossary_slug(l)}"></span>**{l}**: {d}' for l, d in GLOSSARY_DESCRIPTIONS.items())
def inject_glossary(body: str, page: str) -> str:
if page == GLOSSARY_PAGE:
return body
route = "." if page == "index.md" else page.removesuffix(".md")
base, seen, out, skip, depth = f"{posixpath.relpath(GLOSSARY_ROUTE, route)}/#", set(), [], None, 0
for part in re.split(r"(<[^>]+>)", body):
if not part:
continue
if part.startswith("<"):
out.append(part)
if part.startswith("<!") or not (m := re.match(r"</?\s*([a-zA-Z0-9]+)", part)):
continue
tag = m.group(1).lower()
if tag not in GLOSSARY_SKIP:
continue
closing, void = part.startswith("</"), part.endswith("/>") or tag in _VOID
if closing and skip == tag and depth:
depth -= 1
skip = None if not depth else skip
elif not closing and not void:
skip, depth = (tag, 1) if skip is None else (skip, depth + (skip == tag))
continue
if depth:
out.append(part)
continue
cur, text = 0, part
while True:
best = None
for order, (slug, pat, tip) in enumerate(GLOSSARY_TERMS):
if slug in seen or (found := pat.search(text, cur)) is None:
continue
cand = (found.start(), found.start() - found.end(), order, slug, tip, found.end(), found.group(0))
if best is None or cand[:3] < best[:3]:
best = cand
if best is None:
out.append(text[cur:])
break
start, _, _, slug, tip, end, matched = best
out.append(text[cur:start])
out.append(
f'<a class="glossary-term" data-glossary-term="" href="{base}{slug}">'
+ f'<span class="glossary-term__label">{matched}</span>'
+ f'<span class="glossary-term__tooltip" data-search-exclude="">{esc(tip)}</span></a>'
)
seen.add(slug)
cur = end
return "".join(out)
def slugify(text: str) -> str:
text = html.unescape(re.sub(r"<[^>]+>", "", text)).lower()
return re.sub(r"[-\s]+", "-", re.sub(r"[^\w\s-]", "", text, flags=re.UNICODE)).strip("-")
def _parse_link(text: str, start: int) -> tuple[str, str, int] | None:
if start >= len(text) or text[start] != "[":
return None
depth, i = 0, start
while i < len(text):
depth += (text[i] == "[") - (text[i] == "]")
if text[i] == "]" and depth == 0:
label = text[start + 1 : i]
if i + 1 >= len(text) or text[i + 1] != "(":
return None
j, dp = i + 2, 1
while j < len(text) and dp:
dp += (text[j] == "(") - (text[j] == ")")
j += 1
return None if dp else (label, text[i + 2 : j - 1], j)
i += 1
return None
def autolink_plain(text: str) -> str:
parts: list[str] = []
last = 0
for m in _URL.finditer(text):
start = m.start()
if start > 0 and text[start - 1].isalnum():
continue
parts.append(esc(text[last:start]))
url = m.group(0).rstrip(".,;:!?)]")
parts.append(f'<a href="{esc(url, True)}">{esc(url)}</a>')
last = start + len(url)
parts.append(esc(text[last:]))
return "".join(parts)
def render_inline(text: str, page: str) -> str:
out, i, n = [], 0, len(text)
while i < n:
if text[i] == "\n" and i >= 2 and text[i - 2 : i] == " " and out and out[-1].endswith(" "):
out[-1] = out[-1][:-2]
out.append("<br>\n")
i += 1
continue
if text[i] == "`" and (end := text.find("`", i + 1)) != -1:
out.append(f"<code>{esc(text[i + 1 : end])}</code>")
i = end + 1
continue
if text[i] == "!" and i + 1 < n and text[i + 1] == "[" and (p := _parse_link(text, i + 1)):
label, url, end = p
src = rewrite_relative_url(url, page) or url
out.append(f'<img alt="{esc(label, True)}" src="{esc(src, True)}">')
i = end
continue
if text[i] == "[" and (p := _parse_link(text, i)):
label, url, end = p
href = rewrite_relative_url(url, page) or url
out.append(f'<a href="{esc(href, True)}">{render_inline(label, page)}</a>')
i = end
continue
if text[i] == "<":
if text.startswith("<!--", i):
end = text.find("-->", i + 4)
end = n if end < 0 else end + 3
out.append(rewrite_html_urls(text[i:end], page))
i = end
continue
if m := re.match(r"<[^>]+>", text[i:]):
out.append(rewrite_html_urls(m.group(0), page))
i += len(m.group(0))
continue
if (text.startswith("**", i) or text.startswith("__", i)) and (end := text.find(text[i : i + 2], i + 2)) != -1:
out.append(f"<strong>{render_inline(text[i + 2 : end], page)}</strong>")
i = end + 2
continue
if text[i] in "*_" and i + 1 < n and text[i + 1] not in " \t\n" and (end := text.find(text[i], i + 1)) > i + 1:
out.append(f"<em>{render_inline(text[i + 1 : end], page)}</em>")
i = end + 1
continue
j = i + 1
while j < n and text[j] not in "`[<!*_":
j += 1
out.append(autolink_plain(text[i:j]))
i = j
return "".join(out)
def _trow(line: str) -> list[str]:
return [c.strip() for c in line.strip().removeprefix("|").removesuffix("|").split("|")]
def _is_sep(line: str) -> bool:
return "|" in line and all(re.fullmatch(r":?-{3,}:?", c.strip()) for c in _trow(line))
def _align(sep: str) -> str:
s = sep.strip()
if s.startswith(":") and s.endswith(":"):
return ' style="text-align: center;"'
if s.endswith(":"):
return ' style="text-align: right;"'
if s.startswith(":"):
return ' style="text-align: left;"'
return ""
def _list_info(line: str) -> tuple[int, str, str] | None:
m = _LIST.match(line)
return None if not m else (len(m.group(1)) // 4, "ol" if m.group(2)[-1] == "." else "ul", m.group(3))
def _render_blocks(text: str, page: str) -> str:
lines, out, i, n = text.splitlines(), [], 0, 0
n = len(lines)
while i < n:
line, s = lines[i], lines[i].strip()
if not s:
i += 1
continue
if s.startswith("```"):
lang, body = s[3:].strip(), []
i += 1
while i < n and not lines[i].strip().startswith("```"):
body.append(lines[i])
i += 1
if i < n:
i += 1
code = html.escape("\n".join(body) + ("\n" if body else ""))
cls = f' class="language-{html.escape(lang)}"' if lang else ""
out.append(f"<pre><code{cls}>{code}</code></pre>")
continue
if m := _HEADING.match(s):
content, level = m.group(2).rstrip("#").strip(), len(m.group(1))
sid = slugify(content)
out.append(f'<h{level} id="{sid}">{render_inline(content, page)}<a class="headerlink" href="#{sid}" title="Permanent link">#</a></h{level}>')
i += 1
continue
if _HR.fullmatch(s):
out.append("<hr>")
i += 1
continue
if "|" in line and i + 1 < n and _is_sep(lines[i + 1]):
headers, aligns = _trow(line), [_align(c) for c in _trow(lines[i + 1])]
i += 2
rows = []
while i < n and "|" in lines[i] and lines[i].strip():
rows.append(_trow(lines[i]))
i += 1
parts = (
["<table>", "<thead>", "<tr>"]
+ [f"<th{aligns[j] if j < len(aligns) else ''}>{render_inline(h, page)}</th>" for j, h in enumerate(headers)]
+ ["</tr>", "</thead>", "<tbody>"]
)
for row in rows:
parts.append("<tr>")
for j in range(len(headers)):
parts.append(f"<td{aligns[j] if j < len(aligns) else ''}>{render_inline(row[j] if j < len(row) else '', page)}</td>")
parts.append("</tr>")
out.append("\n".join(parts + ["</tbody>", "</table>"]))
continue
if _list_info(line):
items: list[tuple[int, str, list[str]]] = []
while i < n:
if not lines[i].strip():
if i + 1 < n and _list_info(lines[i + 1]):
i += 1
continue
break
info = _list_info(lines[i])
if not info:
break
level, kind, body = info
chunk = [body]
i += 1
while i < n and lines[i].strip() and _list_info(lines[i]) is None:
t = lines[i].strip()
if t.startswith(("```", ">")) or _HEADING.match(t) or _HR.fullmatch(t):
break
chunk.append(lines[i])
i += 1
items.append((level, kind, chunk))
def render_list(items: list[tuple[int, str, list[str]]], start: int, min_level: int) -> tuple[str, int]:
if start >= len(items) or items[start][0] < min_level:
return "", start
kind, chunks, idx = items[start][1], [f"<{items[start][1]}>"], start
while idx < len(items) and items[idx][0] >= min_level:
level, ikind, body_lines = items[idx]
if level > min_level:
nested, idx = render_list(items, idx, level)
chunks[-1] = (chunks[-1][:-5] + nested + "</li>") if chunks[-1].endswith("</li>") else chunks[-1] + nested
continue
if ikind != kind:
chunks += [f"</{kind}>", f"<{ikind}>"]
kind = ikind
idx += 1
body = render_inline("\n".join(body_lines), page)
nested = ""
if idx < len(items) and items[idx][0] > min_level:
nested, idx = render_list(items, idx, min_level + 1)
chunks.append(f"<li>{body}{nested}\n</li>" if nested else f"<li>{body}</li>")
chunks.append(f"</{kind}>")
return "\n".join(chunks), idx
out.append(render_list(items, 0, items[0][0])[0])
continue
if s.startswith(">"):
q = []
while i < n and lines[i].strip().startswith(">"):
q.append(re.sub(r"^>\s?", "", lines[i].strip()))
i += 1
m = _ADMONITION.match(q[0].strip()) if q else None
if m:
kind = m.group(1).lower()
title = m.group(1).capitalize()
body = _render_blocks("\n".join(q[1:]), page)
out.append(f'<div class="admonition {kind}">\n<p class="admonition-title">{title}</p>\n{body}\n</div>')
else:
out.append(f"<blockquote>\n<p>{render_inline(chr(10).join(q), page)}</p>\n</blockquote>")
continue
if s.startswith("<!--"):
out.append(rewrite_html_urls(line, page))
i += 1
# Preserve a blank line after HTML comments (python-markdown does).
if i < n and not lines[i].strip():
out.append("")
while i < n and not lines[i].strip():
i += 1
continue
buf = [line]
i += 1
while i < n and lines[i].strip():
t = lines[i].strip()
if t.startswith(("```", ">")) or _HEADING.match(t) or _HR.fullmatch(t):
break
if "|" in lines[i] and i + 1 < n and _is_sep(lines[i + 1]):
break
buf.append(lines[i])
i += 1
out.append(f"<p>{render_inline(chr(10).join(buf), page)}</p>")
return "\n".join(out)
def render_markdown(text: str, page: str) -> str:
return inject_glossary(_render_blocks(text, page), page)
def page_title(source: str) -> str:
for line in source.splitlines():
if line.startswith("# "):
return line[2:].strip()
return "openpilot docs"
def write_html_redirect(rel: Path) -> None:
if rel.name == "index.md":
return
target = f"{rel.stem}/"
out = SITE_DIR / rel.with_suffix(".html")
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(
"\n".join(
[
"<!doctype html>",
f'<meta http-equiv="refresh" content="0; url={html.escape(target)}">',
f'<link rel="canonical" href="{html.escape(target)}">',
f"<script>location.replace({json.dumps(target)} + location.search + location.hash)</script>",
]
)
)
def copy_assets() -> None:
for src in DOCS_DIR.rglob("*"):
if not src.is_file():
continue
rel = src.relative_to(DOCS_DIR)
if any(part in EXCLUDE_DIRS for part in rel.parts):
continue
if src.suffix == ".md" or src in (Path(__file__).resolve(), TEMPLATE_FILE):
continue
dest = SITE_DIR / rel
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dest)
def render_nav_html(current_page: str) -> str:
parts: list[str] = []
for title, target in NAV:
if target is None:
parts.append(f'<div class="nav-section">{html.escape(title)}</div>')
elif target.startswith(("http://", "https://")):
parts.append(f'<a href="{html.escape(target)}">{html.escape(title)}</a>')
else:
active = ' class="active"' if target == current_page else ""
parts.append(f'<a href="{html.escape(page_href(current_page, target))}"{active}>{html.escape(title)}</a>')
return "\n".join(parts)
def build() -> None:
template = TEMPLATE_FILE.read_text()
pages = [
(path.relative_to(DOCS_DIR), path.read_text())
for path in sorted(DOCS_DIR.rglob("*.md"))
if path != DOCS_DIR / "README.md" and not any(part in EXCLUDE_DIRS for part in path.relative_to(DOCS_DIR).parts)
]
pages.append((Path(GLOSSARY_PAGE), f"# openpilot glossary\n\n{GLOSSARY_DEFINITIONS}"))
pages.sort()
if SITE_DIR.exists():
shutil.rmtree(SITE_DIR)
SITE_DIR.mkdir(parents=True)
copy_assets()
for rel_path, source in pages:
rel = rel_path.as_posix()
body = render_markdown(source, rel)
title = page_title(source)
route = page_route(rel)
root = "../" * (0 if route == "." else len(route.split("/")))
edit_path = "serve.py" if rel == GLOSSARY_PAGE else rel
page_html = template
for name, value in {
"TITLE": html.escape(title),
"ROOT": root,
"HOME_HREF": page_href(rel, "index.md"),
"NAV": render_nav_html(rel),
"BODY": body,
"EDIT_URL": html.escape(f"{REPO_URL}blob/master/docs/{edit_path}"),
}.items():
page_html = page_html.replace(f"{{{{{name}}}}}", value)
out = SITE_DIR / ("" if route == "." else route) / "index.html"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(page_html)
write_html_redirect(rel_path)
print(f"docs: built {len(pages)} pages into {SITE_DIR}")
def _watched_files() -> list[Path]:
return [p for p in DOCS_DIR.rglob("*") if p.is_file() and not any(part in EXCLUDE_DIRS for part in p.relative_to(DOCS_DIR).parts)]
def serve() -> None:
build()
mtimes = {p: p.stat().st_mtime for p in _watched_files()}
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(SITE_DIR))
httpd = http.server.ThreadingHTTPServer(("", 0), handler)
print(f"docs: serving on http://localhost:{httpd.server_port}/ (watching for changes)")
try:
threading.Thread(target=httpd.serve_forever, daemon=True).start()
while True:
time.sleep(0.5)
new_mtimes = {p: p.stat().st_mtime for p in _watched_files()}
if new_mtimes != mtimes:
mtimes = new_mtimes
print("docs: change detected, rebuilding...")
try:
build()
except Exception as e:
print(f"docs: build failed: {e}")
except KeyboardInterrupt:
pass
finally:
httpd.shutdown()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Build or serve the openpilot documentation site.")
parser.add_argument("--build", action="store_true", help="Build the site and exit.")
args = parser.parse_args()
if args.build:
build()
else:
serve()
-42
View File
@@ -1,42 +0,0 @@
.md-logo img {
filter: invert(1);
}
.glossary-term {
position: relative;
color: inherit;
text-decoration: none;
}
.glossary-term__label {
border-bottom: 1px dotted currentColor;
}
.glossary-term__tooltip {
position: absolute;
top: calc(100% + 0.4rem);
left: 50%;
width: max-content;
max-width: min(30rem, 80vw);
padding: 0.65rem 0.8rem;
border-radius: 0.6rem;
background: rgb(26 26 26 / 96%);
color: white;
box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%);
font-size: 0.85rem;
line-height: 1.45;
opacity: 0;
pointer-events: none;
transform: translateX(-50%) translateY(-0.15rem);
transition: opacity 120ms ease, transform 120ms ease;
visibility: hidden;
z-index: 20;
}
.glossary-term:hover .glossary-term__tooltip,
.glossary-term:focus-visible .glossary-term__tooltip,
.glossary-term:focus-within .glossary-term__tooltip {
opacity: 1;
transform: translateX(-50%) translateY(0);
visibility: visible;
}
+224
View File
@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{TITLE}} · openpilot docs</title>
<link rel="icon" href="{{ROOT}}assets/favicon.svg">
<style>
:root {
--bg: #fff;
--bg-elev: #f5f5f5;
--bg-hover: #eef0ff;
--fg: #262626;
--fg-dim: #666;
--accent: #4051b5;
--border: #e5e5e5;
--max-width: 76rem;
}
* { box-sizing: border-box; }
html { scrollbar-gutter: stable; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--fg);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
font-size: 15px;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
header.site {
display: flex; align-items: center; gap: 0.75rem;
min-height: 3rem;
padding: 0.5rem max(1.25rem, calc((100% - var(--max-width)) / 2));
background: var(--bg);
border-bottom: 1px solid var(--border);
position: sticky; top: 0; z-index: 10;
}
header.site img { display: block; height: 24px; filter: invert(1); }
header.site .brand { display: flex; align-items: center; gap: 0.75rem; color: var(--fg); }
header.site .brand:hover { text-decoration: none; }
header.site .site-name { font-weight: 600; font-size: 1.05rem; }
header.site .spacer { flex: 1; }
header.site .social { display: flex; gap: 1rem; }
header.site .social a { display: flex; color: var(--fg); }
header.site .social a:hover { color: var(--accent); }
header.site .social svg { width: 1.75rem; height: 1.75rem; fill: currentColor; }
header.site .social a[aria-label="Discord"] svg { transform: scale(1.15); }
.layout { display: flex; width: 100%; max-width: var(--max-width); margin: 0 auto; }
nav.sidebar {
width: 14rem; flex-shrink: 0;
padding: 3.3rem 1.25rem 2rem 0;
position: sticky; top: 3rem; align-self: start;
max-height: calc(100vh - 3rem); overflow-y: auto;
}
nav.sidebar .nav-section { font-weight: 600; margin: 1.15rem 0.8rem 0.45rem; }
nav.sidebar a {
display: block; padding: 0.28rem 0.8rem; border-radius: 0.45rem;
color: var(--fg); line-height: 1.35;
}
nav.sidebar a:hover { background: var(--bg-hover); text-decoration: none; }
nav.sidebar a.active { background: var(--bg-hover); color: var(--accent); font-weight: 600; }
main.content { width: min(100%, 46.5rem); min-width: 0; padding: 2.7rem 2rem 5rem; }
main.content h1, main.content h2, main.content h3 { line-height: 1.25; margin-top: 1.8rem; }
main.content h1 { margin-top: 0; }
main.content img { max-width: 100%; }
main.content table { border-collapse: collapse; display: block; overflow-x: auto; }
main.content th, main.content td { border: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; }
main.content th { background: var(--bg-elev); }
main.content pre {
position: relative;
background: var(--bg-elev);
padding: 0.9rem 1rem;
border-radius: 0.25rem;
overflow-x: auto;
}
main.content code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
main.content :not(pre) > code { background: var(--bg-elev); padding: 0.1rem 0.35rem; border-radius: 0.2rem; font-size: 0.88em; }
main.content blockquote { border-left: 3px solid var(--border); margin: 1rem 0; padding: 0.2rem 1rem; color: var(--fg-dim); }
main.content .admonition {
margin: 1rem 0; padding: 0.5rem 1rem; border-radius: 0.3rem; border-left: 3px solid var(--border);
background: var(--bg-elev);
}
main.content .admonition .admonition-title { margin: 0.2rem 0 0.4rem; font-weight: 600; }
main.content .admonition.note { border-left-color: #0969da; }
main.content .admonition.note .admonition-title { color: #0969da; }
main.content .admonition.tip { border-left-color: #1a7f37; }
main.content .admonition.tip .admonition-title { color: #1a7f37; }
main.content .admonition.important { border-left-color: #8250df; }
main.content .admonition.important .admonition-title { color: #8250df; }
main.content .admonition.warning { border-left-color: #9a6700; }
main.content .admonition.warning .admonition-title { color: #9a6700; }
main.content .admonition.caution { border-left-color: #cf222e; }
main.content .admonition.caution .admonition-title { color: #cf222e; }
main.content details { margin: 0.5rem 0; }
main.content hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
.headerlink { margin-left: 0.25rem; opacity: 0; font-size: 0.7em; }
h1:hover .headerlink, h2:hover .headerlink, h3:hover .headerlink { opacity: 1; }
.edit-link { margin-top: 3rem; font-size: 0.85rem; }
.copy-btn {
position: absolute; top: 0.4rem; right: 0.4rem;
background: var(--bg); color: var(--fg-dim);
border: 1px solid var(--border); border-radius: 0.35rem;
padding: 0.2rem 0.5rem; font-size: 0.78rem; cursor: pointer;
opacity: 0; transition: opacity 120ms;
}
main.content pre:hover .copy-btn { opacity: 1; }
@media (max-width: 48rem) {
nav.sidebar { display: none; }
main.content { padding: 2rem 1.25rem 4rem; }
}
.glossary-term {
position: relative;
color: inherit;
}
.glossary-term__label {
border-bottom: 1px dotted currentColor;
}
.glossary-term__tooltip {
position: absolute;
top: calc(100% + 0.4rem);
left: 50%;
width: max-content;
max-width: min(30rem, 80vw);
padding: 0.65rem 0.8rem;
border-radius: 0.6rem;
background: rgb(26 26 26 / 96%);
color: white;
box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%);
font-size: 0.85rem;
line-height: 1.45;
opacity: 0;
pointer-events: none;
transform: translateX(-50%) translateY(-0.15rem);
transition: opacity 120ms ease, transform 120ms ease;
visibility: hidden;
z-index: 20;
}
.glossary-term:hover .glossary-term__tooltip,
.glossary-term:focus-visible .glossary-term__tooltip,
.glossary-term:focus-within .glossary-term__tooltip {
opacity: 1;
transform: translateX(-50%) translateY(0);
visibility: visible;
}
</style>
</head>
<body>
<header class="site">
<a class="brand" href="{{HOME_HREF}}">
<img src="{{ROOT}}assets/comma-logo.png" alt="">
<span class="site-name">openpilot docs</span>
</a>
<span class="spacer"></span>
<div class="social">
<a href="https://github.com/commaai" aria-label="GitHub">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
M12 .7A11.5 11.5 0 0 0 8.4 23c.6.1.8-.2.8-.5v-2c-3.3.7-4-1.4-4-1.4-.5-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7
1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-5.7 0-1.3.5-2.3 1.2-3.1
-.1-.3-.5-1.6.1-3.1 0 0 1-.3 3.2 1.2a11 11 0 0 1 5.8 0C15.8 6 16.8 6.3 16.8 6.3c.6 1.5.2 2.8.1 3.1
.8.8 1.2 1.8 1.2 3.1 0 4.4-2.8 5.4-5.5 5.7.4.4.8 1.1.8 2.2v2.1c0 .3.2.6.8.5A11.5 11.5 0 0 0 12 .7Z"/></svg>
</a>
<a href="https://discord.comma.ai" aria-label="Discord">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
M20.3 4.4A16 16 0 0 0 16.3 3l-.5 1.1a15 15 0 0 0-7.6 0L7.7 3a16 16 0 0 0-4 1.4C1.1 8.2.4 11.9.8 15.5
a16 16 0 0 0 4.9 2.6l1.2-1.7-1.8-.9.4-.3c3.5 1.6 9.4 1.6 13 0l.4.3-1.8.9 1.2 1.7a16 16 0 0 0 4.9-2.6
c.5-4.2-.8-7.8-2.9-11.1ZM8.3 13.3c-1.1 0-1.9-1-1.9-2.2 0-1.3.8-2.3 1.9-2.3s2 1 1.9 2.3c0 1.2-.8 2.2-1.9 2.2Z
m7.4 0c-1.1 0-1.9-1-1.9-2.2 0-1.3.8-2.3 1.9-2.3s2 1 1.9 2.3c0 1.2-.8 2.2-1.9 2.2Z"/></svg>
</a>
<a href="https://x.com/comma_ai" aria-label="X">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
M18.2 2.3h3.3l-7.2 8.3 8.5 11.2h-6.7l-5.2-6.8-6 6.8H1.6l7.8-8.9L1.2 2.3h6.8l4.7 6.2 5.5-6.2Z
m-1.2 17.5h1.8L7 4.2H5Z"/></svg>
</a>
</div>
</header>
<div class="layout">
<nav class="sidebar">
{{NAV}}
</nav>
<main class="content">
{{BODY}}
<div class="edit-link">
<a href="{{EDIT_URL}}">Edit this page on GitHub</a>
</div>
</main>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('pre').forEach(function (el) {
var btn = document.createElement('button');
btn.className = 'copy-btn';
btn.textContent = 'copy';
btn.addEventListener('click', function () {
var code = el.querySelector('code');
if (!code) return;
navigator.clipboard.writeText(code.innerText).then(function () {
btn.textContent = 'copied';
setTimeout(function () { btn.textContent = 'copy'; }, 1200);
});
});
el.appendChild(btn);
});
});
</script>
</body>
</html>
+23 -13
View File
@@ -18,19 +18,21 @@ function agnos_init {
sudo chmod 660 /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0
# Check if AGNOS update is required
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
MANIFEST="$DIR/system/hardware/tici/agnos.json"
if $AGNOS_PY --verify $MANIFEST; then
if [ "$(< /VERSION)" != "$AGNOS_VERSION" ]; then
AGNOS_PY="$DIR/openpilot/common/hardware/comma/agnos.py"
MANIFEST="$DIR/openpilot/system/hardware/comma/agnos.json"
if "$AGNOS_PY" --verify "$MANIFEST"; then
sudo reboot
fi
$DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
while true; do
"$DIR/openpilot/common/hardware/comma/updater" "$AGNOS_PY" "$MANIFEST"
done
fi
}
function launch {
# Remove orphaned git lock if it exists on boot
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
[ -f "$DIR/.git/index.lock" ] && rm -f "$DIR/.git/index.lock"
# Check to see if there's a valid overlay-based update available. Conditions
# are as follows:
@@ -42,7 +44,7 @@ function launch {
# that completed successfully and synced to disk.
if [ -f "${DIR}/.overlay_init" ]; then
find ${DIR}/.git -newer ${DIR}/.overlay_init | grep -q '.' 2> /dev/null
find "${DIR}/.git" -newer "${DIR}/.overlay_init" | grep -q '.' 2> /dev/null
if [ $? -eq 0 ]; then
echo "${DIR} has been modified, skipping overlay update installation"
else
@@ -51,9 +53,9 @@ function launch {
echo "Valid overlay update found, installing"
LAUNCHER_LOCATION="${BASH_SOURCE[0]}"
mv $DIR /data/safe_staging/old_openpilot
mv "${STAGING_ROOT}/finalized" $DIR
cd $DIR
mv "$DIR" /data/safe_staging/old_openpilot
mv "${STAGING_ROOT}/finalized" "$DIR"
cd "$DIR"
echo "Restarting launch script ${LAUNCHER_LOCATION}"
unset AGNOS_VERSION
@@ -67,9 +69,17 @@ function launch {
fi
# handle pythonpath
ln -sfn $(pwd) /data/pythonpath
ln -sfn "$(pwd)" /data/pythonpath
export PYTHONPATH="$PWD"
# submodule package symlinks for PYTHONPATH imports on device.
# on PC these come from editable installs via pyproject.toml / uv.
ln -sfn msgq_repo/msgq msgq
ln -sfn opendbc_repo/opendbc opendbc
ln -sfn rednose_repo/rednose rednose
ln -sfn teleoprtc_repo/teleoprtc teleoprtc
ln -sfn tinygrad_repo/tinygrad tinygrad
# hardware specific init
if [ -f /AGNOS ]; then
agnos_init
@@ -79,8 +89,8 @@ function launch {
tmux capture-pane -pq -S-1000 > /tmp/launch_log
# start manager
cd system/manager
if [ ! -f $DIR/prebuilt ]; then
cd openpilot/system/manager
if [ ! -f "$DIR/prebuilt" ]; then
./build.py
fi
./manager.py
+1 -1
View File
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="18.4"
export AGNOS_VERSION="19.7"
fi
export STAGING_ROOT="/data/safe_staging"
-17
View File
@@ -1,20 +1,3 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# On any failure, run the fallback launcher
trap 'exec ./launch_chffrplus.sh' ERR
C3_LAUNCH_SH="./sunnypilot/system/hardware/c3/launch_chffrplus.sh"
MODEL="$(tr -d '\0' < "/sys/firmware/devicetree/base/model")"
export MODEL
if [ "$MODEL" = "comma tici" ]; then
# Force a failure if the launcher doesn't exist
[ -x "$C3_LAUNCH_SH" ] || false
# If it exists, run it
exec "$C3_LAUNCH_SH"
fi
exec ./launch_chffrplus.sh
-1
View File
@@ -1 +0,0 @@
msgq_repo/msgq
-1
View File
@@ -1 +0,0 @@
opendbc_repo/opendbc
@@ -24,17 +24,17 @@ things are not. Read more details [here](https://capnproto.org/language.html).
### Custom forks
Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging
spec, however this could conflict with future changes made in mainline cereal/openpilot. Rebasing against mainline openpilot
spec, however this could conflict with future changes made in mainline openpilot's cereal spec. Rebasing against mainline openpilot
then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in
[custom.capnp](custom.capnp) that we will leave empty in mainline cereal/openpilot. **If you only modify those, you can ensure your
[custom.capnp](custom.capnp) that we will leave empty in mainline openpilot's cereal spec. **If you only modify those, you can ensure your
fork will remain backwards-compatible with all versions of mainline openpilot and your fork.**
An example of compatible changes:
```diff
diff --git a/cereal/custom.capnp b/cereal/custom.capnp
diff --git a/openpilot/cereal/custom.capnp b/openpilot/cereal/custom.capnp
index 3348e859e..3365c7b98 100644
--- a/cereal/custom.capnp
+++ b/cereal/custom.capnp
--- a/openpilot/cereal/custom.capnp
+++ b/openpilot/cereal/custom.capnp
@@ -10,7 +10,11 @@ $Cxx.namespace("cereal");
# DO rename the structs
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
@@ -48,10 +48,10 @@ index 3348e859e..3365c7b98 100644
}
struct CustomReserved1 @0xaedffd8f31e7b55d {
diff --git a/cereal/log.capnp b/cereal/log.capnp
diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp
index 1209f3fd9..b189f58b6 100644
--- a/cereal/log.capnp
+++ b/cereal/log.capnp
--- a/openpilot/cereal/log.capnp
+++ b/openpilot/cereal/log.capnp
@@ -2558,14 +2558,14 @@ struct Event {
# DO change the name of the field
@@ -76,7 +76,7 @@ index 1209f3fd9..b189f58b6 100644
Example
---
```python
import cereal.messaging as messaging
import openpilot.cereal.messaging as messaging
# in subscriber
sm = messaging.SubMaster(['sensorEvents'])
+22
View File
@@ -0,0 +1,22 @@
Import('env', 'common', 'msgq')
cereal_dir = Dir('.')
gen_dir = Dir('gen')
# Build cereal
schema_files = ['log.capnp', 'deprecated.capnp', 'custom.capnp']
car_capnp = '#opendbc_repo/opendbc/car/car.capnp'
all_output = schema_files + ['car.capnp']
env.Command([f'gen/cpp/{s}.c++' for s in all_output] + [f'gen/cpp/{s}.h' for s in all_output],
schema_files + [car_capnp],
f"capnpc --src-prefix={cereal_dir.path} --src-prefix=opendbc_repo/opendbc/car --import-path=opendbc_repo/opendbc/car $SOURCES -o c++:{gen_dir.path}/cpp/")
cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in all_output])
# Build messaging
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc', 'messaging/bridge_zmq.cc'], LIBS=[msgq, common, 'pthread'])
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
Export('cereal', 'socketmaster')
+11
View File
@@ -0,0 +1,11 @@
import os
import capnp
from importlib.resources import as_file, files
capnp.remove_import_hook()
with as_file(files("openpilot.cereal")) as fspath, as_file(files("opendbc")) as opendbc_path:
CEREAL_PATH = fspath.as_posix()
opendbc_import_path = os.path.join(os.path.realpath(opendbc_path.as_posix()), 'car')
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"), imports=[opendbc_import_path])
custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"), imports=[opendbc_import_path])
@@ -1,4 +1,4 @@
using Cxx = import "./include/c++.capnp";
using Cxx = import "/include/c++.capnp";
$Cxx.namespace("cereal");
@0xb526ba661d550a59;
@@ -69,6 +69,8 @@ struct LeadData {
struct SelfdriveStateSP @0x81c2f05a394cf4af {
mads @0 :ModularAssistiveDrivingSystem;
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
buttonsPressed @2 :UInt16;
buttonsReleaseToggle @3 :UInt16;
enum AudibleAlert {
none @0;
@@ -129,6 +131,7 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
downloaded @2;
cached @3;
failed @4;
verifying @5;
}
struct DownloadProgress {
@@ -137,10 +140,16 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
eta @2 :UInt32;
}
struct Chunk {
fileName @0 :Text;
sha256 @1 :Text;
}
struct Artifact {
fileName @0 :Text;
downloadUri @1 :DownloadUri;
downloadProgress @2 :DownloadProgress;
chunks @3 :List(Chunk);
}
struct Model {
@@ -155,6 +164,7 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
policy @3;
offPolicy @4;
onPolicy @5;
chunked @6;
}
}
@@ -342,6 +352,8 @@ struct OnroadEventSP @0xda96579883444c35 {
speedLimitChanged @21;
speedLimitPending @22;
e2eChime @23;
laneChangeRoadEdge @24;
bigModelReady @25;
}
}
@@ -448,6 +460,8 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
struct ModelDataV2SP @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
leftLaneChangeEdgeBlock @1 :Bool;
rightLaneChangeEdgeBlock @2 :Bool;
enum TurnDirection {
none @0;
@@ -1,4 +1,4 @@
using Cxx = import "./include/c++.capnp";
using Cxx = import "/include/c++.capnp";
$Cxx.namespace("cereal");
@0x80ef1ec4889c2a63;
@@ -760,18 +760,6 @@ struct LateralLQRState @0x9024e2d790c82ade {
steeringAngleDesiredDeg @6 :Float32;
}
struct LateralCurvatureState @0xad9d8095c06f7c61 {
active @0 :Bool;
actualCurvature @1 :Float32;
desiredCurvature @2 :Float32;
error @3 :Float32;
p @4 :Float32;
i @5 :Float32;
f @6 :Float32;
output @7 :Float32;
saturated @8 :Bool;
}
struct LateralPlannerSolution @0x84caeca5a6b4acfe {
x @0 :List(Float32);
y @1 :List(Float32);
@@ -787,3 +775,13 @@ struct GpsTrajectory @0x8cfeb072f5301000 {
x @0 :List(Float32);
y @1 :List(Float32);
}
struct AudioFeedbackDEPRECATED @0xed47e3c075be372a {
audio @0 :AudioData;
blockNum @1 :UInt16;
struct AudioData {
data @0 :Data;
sampleRate @1 :UInt32;
}
}
+154 -68
View File
@@ -1,7 +1,7 @@
using Cxx = import "./include/c++.capnp";
using Cxx = import "/include/c++.capnp";
$Cxx.namespace("cereal");
using Car = import "car.capnp";
using Car = import "/car.capnp";
using Deprecated = import "deprecated.capnp";
using Custom = import "custom.capnp";
@@ -75,7 +75,6 @@ struct OnroadEvent @0xc4fa6047f024e718 {
driverUnresponsive2 @37;
driverUnresponsive3 @38;
belowSteerSpeed @39;
lowBattery @40;
accFaulted @41;
sensorDataInvalid @42;
commIssue @43;
@@ -107,18 +106,17 @@ struct OnroadEvent @0xc4fa6047f024e718 {
noGps @68;
wrongCruiseMode @69;
modeldLagging @70;
deviceFalling @71;
fanMalfunction @72;
cameraMalfunction @73;
cameraFrameRate @74;
processNotRunning @75;
dashcamMode @76;
selfdriveInitializing @77;
usbError @78;
cruiseMismatch @79;
canBusMissing @80;
selfdrivedLagging @81;
resumeBlocked @82;
carNotReady @103;
steerTimeLimit @83;
vehicleSensorsInvalid @84;
locationdTemporaryError @85;
@@ -131,9 +129,15 @@ struct OnroadEvent @0xc4fa6047f024e718 {
aeb @92;
userBookmark @95;
excessiveActuation @96;
audioFeedback @97;
bigModelLoading @100;
bigModelFailed @102;
lowBatteryDEPRECATED @40;
soundsUnavailableDEPRECATED @47;
deviceFallingDEPRECATED @71;
usbErrorDEPRECATED @78;
audioFeedbackDEPRECATED @97;
bigModelReadyDEPRECATED @101;
}
}
@@ -176,13 +180,13 @@ struct InitData {
enum DeviceType {
unknown @0;
neo @1;
neo @1; # NEO, EON, & comma two
chffrAndroid @2;
chffrIos @3;
tici @4;
tici @4; # comma three
pc @5;
tizi @6;
mici @7;
tizi @6; # comma 3X
mici @7; # comma four
}
struct PandaInfo {
@@ -414,6 +418,10 @@ struct CanData {
struct DeviceState @0xa4d8b5af2aa492eb {
deviceType @45 :InitData.DeviceType;
# usb
chestnutPresent @51 :Bool;
usbState @52 :UsbState;
networkType @22 :NetworkType;
networkInfo @31 :NetworkInfo;
networkStrength @24 :NetworkStrength;
@@ -684,33 +692,68 @@ struct PeripheralState {
}
}
struct UsbState {
devices @0 :List(Device);
struct Device {
busnum @0 :UInt8;
devnum @1 :UInt8;
vendorId @2 :UInt16;
productId @3 :UInt16;
speedMbps @4 :UInt16;
manufacturer @6 :Text;
product @5 :Text;
linkErrorCount @7 :UInt16;
usb3Lane @8 :Usb3Lane;
enum Usb3Lane {
unknown @0;
a @1;
b @2;
}
}
}
struct ChestnutState {
tempC @0 :Float32;
memoryTempC @1 :Float32;
powerDrawW @2 :Float32;
powerLimitW @3 :Float32;
gpuUsagePercent @4 :UInt8;
gpuClockMhz @5 :UInt16;
fanSpeedRpm @6 :UInt16;
pcieLtssm @7 :UInt8;
supplyVoltage @8 :UInt16; # mV
supplyCurrent @9 :Int16; # mA
supplyFault @10 :Bool;
}
struct RadarState @0x9a185389d6fdd05f {
mdMonoTime @6 :UInt64;
carStateMonoTime @11 :UInt64;
mdMonoTime @6 :UInt64; # for debugging
radarErrors @13 :Car.RadarData.Error;
leadOne @3 :LeadData;
leadTwo @4 :LeadData;
struct LeadData {
dRel @0 :Float32;
yRel @1 :Float32;
vRel @2 :Float32;
aRel @3 :Float32;
vLead @4 :Float32;
dPath @6 :Float32;
vLat @7 :Float32;
vLeadK @8 :Float32;
aLeadK @9 :Float32;
fcw @10 :Bool;
status @11 :Bool;
aLeadTau @12 :Float32;
modelProb @13 :Float32;
radar @14 :Bool;
radarTrackId @15 :Int32 = -1;
dRel @0 :Float32; # m from the front bumper of the car
yRel @1 :Float32; # m in car frame, left positive
vRel @2 :Float32; # m/s relative longitudinal speed
vLead @4 :Float32; # m/s absolute lead speed
vLeadK @8 :Float32; # kalman-filtered lead speed
aLeadK @9 :Float32; # kalman-filtered lead accel
present @11 :Bool; # true if a lead is present
aLeadTau @12 :Float32; # lead accel time constant
modelProb @13 :Float32; # vision model lead probability
radar @14 :Bool; # true if lead is radar-matched (vs vision-only)
radarTrackId @15 :Int32 = -1; # for debugging
deprecated :group {
aRel @3 :Float32;
aLead @5 :Float32;
dPath @6 :Float32;
vLat @7 :Float32;
fcw @10 :Bool;
}
}
@@ -723,11 +766,12 @@ struct RadarState @0x9a185389d6fdd05f {
calPerc @9 :Int8;
canMonoTimes @10 :List(UInt64);
cumLagMs @5 :Float32;
carStateMonoTime @11 :UInt64;
radarErrors @12 :List(Car.RadarData.ErrorDEPRECATED);
}
}
struct LiveCalibrationData {
struct ExtrinsicsCalibration @0x96df70754d8390bc {
calStatus @11 :Status;
calCycle @2 :Int32;
calPerc @3 :Int8;
@@ -771,13 +815,31 @@ struct SelfdriveState {
alertStatus @5 :AlertStatus;
alertSize @6 :AlertSize;
alertType @7 :Text;
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
alertSound @13 :AudibleAlert;
alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert;
# configurable driving settings
experimentalMode @10 :Bool;
personality @11 :LongitudinalPersonality;
enum AudibleAlert {
none @0;
engage @1;
disengage @2;
refuse @3;
warningSoft @4;
warningImmediate @5;
prompt @6;
promptRepeat @7;
promptDistracted @8;
preAlert @9;
complete @10;
}
enum OpenpilotState @0xdbe58b96d2d1ac61 {
disabled @0;
preEnabled @1;
@@ -798,6 +860,10 @@ struct SelfdriveState {
mid @2;
full @3;
}
deprecated :group {
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
}
}
struct ControlsState @0x97ff69c53601abf1 {
@@ -818,7 +884,7 @@ struct ControlsState @0x97ff69c53601abf1 {
debugState @59 :LateralDebugState;
torqueState @60 :LateralTorqueState;
curvatureStateDEPRECATED @65 :Deprecated.LateralCurvatureState;
curvatureState @65 :LateralCurvatureState;
lqrStateDEPRECATED @55 :Deprecated.LateralLQRState;
indiStateDEPRECATED @52 :Deprecated.LateralINDIState;
}
@@ -867,6 +933,18 @@ struct ControlsState @0x97ff69c53601abf1 {
saturated @3 :Bool;
}
struct LateralCurvatureState @0xad9d8095c06f7c61 {
active @0 :Bool;
actualCurvature @1 :Float32;
desiredCurvature @2 :Float32;
error @3 :Float32;
p @4 :Float32;
i @5 :Float32;
f @6 :Float32;
output @7 :Float32;
saturated @8 :Bool;
}
deprecated :group {
vEgo @0 :Float32;
vEgoRaw @32 :Float32;
@@ -906,7 +984,7 @@ struct ControlsState @0x97ff69c53601abf1 {
alertStatus @38 :SelfdriveState.AlertStatus;
alertSize @39 :SelfdriveState.AlertSize;
alertType @44 :Text;
alertSound2 @56 :Car.CarControl.HUDControl.AudibleAlert;
alertSound2 @56 :SelfdriveState.AudibleAlert;
engageable @41 :Bool; # can OP be engaged?
state @31 :SelfdriveState.OpenpilotState;
enabled @19 :Bool;
@@ -927,6 +1005,7 @@ struct DrivingModelData {
frameIdExtra @1 :UInt32;
frameDropPerc @6 :Float32;
modelExecutionTime @7 :Float32;
big @8 :Bool;
action @2 :ModelDataV2.Action;
@@ -973,6 +1052,7 @@ struct ModelDataV2 {
timestampEof @3 :UInt64;
modelExecutionTime @15 :Float32;
rawPredictions @16 :Data;
big @27 :Bool;
# predicted future position, orientation, etc..
position @4 :XYZTData;
@@ -989,7 +1069,6 @@ struct ModelDataV2 {
roadEdgeStds @14 :List(Float32);
# predicted lead cars
leads @11 :List(LeadDataV2);
leadsV3 @18 :List(LeadDataV3);
meta @12 :MetaData;
@@ -999,8 +1078,9 @@ struct ModelDataV2 {
action @26: Action;
lateralPlannerSolutionDEPRECATED @25: Deprecated.LateralPlannerSolution;
leadsDEPRECATED @11 :List(LeadDataV2DEPRECATED);
struct LeadDataV2 {
struct LeadDataV2DEPRECATED {
prob @0 :Float32; # probability that car is your lead at time t
t @1 :Float32;
@@ -1120,7 +1200,7 @@ struct EncodeIndex {
}
}
struct AndroidLogEntry {
struct OperatingSystemLogEntry {
id @0 :UInt8;
ts @1 :UInt64;
priority @2 :UInt8;
@@ -1258,7 +1338,7 @@ struct LateralPlan @0xe1e9318e2ae8b51e {
struct LiveLocationKalman {
# More info on reference frames:
# https://github.com/commaai/openpilot/tree/master/common/transformations
# https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations
positionECEF @0 : Measurement;
positionGeodetic @1 : Measurement;
@@ -1313,9 +1393,9 @@ struct LiveLocationKalman {
}
struct LivePose {
struct DeviceMotion @0xc24ca2b57206b44d {
# More info on reference frames:
# https://github.com/commaai/openpilot/tree/master/common/transformations
# https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations
orientationNED @0 :XYZMeasurement;
velocityDevice @1 :XYZMeasurement;
accelerationDevice @2 :XYZMeasurement;
@@ -2108,8 +2188,11 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
struct DriverMonitoringState {
lockout @0 :Bool;
alertCountLockoutPercent @1 :Int8;
alertTimeLockoutPercent @2 :Int8;
lockoutCount @15 :Int8;
lockoutMinutesRemaining @11 :Int8;
alert3Count @12 :Int8;
noResponseCount @13 :Int8;
noResponseForceDecel @14 :Bool;
alwaysOn @3 :Bool;
alwaysOnLockout @4 :Bool;
@@ -2173,6 +2256,11 @@ struct DriverMonitoringState {
calibratedPercent @0 :Int8;
offset @1 :Float32;
}
deprecated :group {
alertCountLockoutPercent @1 :Int8;
alertTimeLockoutPercent @2 :Int8;
}
}
struct Boot {
@@ -2187,7 +2275,7 @@ struct Boot {
}
}
struct LiveParametersData {
struct VehicleParameters @0xd9058dcb967c2753 {
valid @0 :Bool;
gyroBias @1 :Float32;
angleOffsetDeg @2 :Float32;
@@ -2221,8 +2309,8 @@ struct LiveParametersData {
}
}
struct LiveTorqueParametersData {
liveValid @0 :Bool;
struct LateralTorqueParameters @0xe61690eb0b091692 {
valid @0 :Bool;
latAccelFactorRaw @1 :Float32;
latAccelOffsetRaw @2 :Float32;
frictionCoefficientRaw @3 :Float32;
@@ -2238,7 +2326,7 @@ struct LiveTorqueParametersData {
calPerc @13 :Int8;
}
struct LiveDelayData {
struct LateralDelay @0x98dfdb22c44df8d4 {
lateralDelay @0 :Float32;
validBlocks @1 :Int32;
status @2 :Status;
@@ -2247,6 +2335,7 @@ struct LiveDelayData {
lateralDelayEstimateStd @5 :Float32;
points @4 :List(Float32);
calPerc @6 :Int8;
version @7 :Int32;
enum Status {
unestimated @0;
@@ -2423,11 +2512,6 @@ struct AudioData {
sampleRate @1 :UInt32;
}
struct AudioFeedback {
audio @0 :AudioData;
blockNum @1 :UInt16;
}
struct Touch {
sec @0 :Int64;
usec @1 :Int64;
@@ -2460,9 +2544,9 @@ struct Event {
pandaStates @81 :List(PandaState);
peripheralState @80 :PeripheralState;
radarState @13 :RadarState;
liveTracks @131 :Car.RadarData;
radarTracks @131 :Car.RadarData;
sendcan @17 :List(CanData);
liveCalibration @19 :LiveCalibrationData;
extrinsicsCalibration @19 :ExtrinsicsCalibration;
carState @22 :Car.CarState;
carControl @23 :Car.CarControl;
carOutput @127 :Car.CarOutput;
@@ -2473,42 +2557,44 @@ struct Event {
qcomGnss @31 :QcomGnss;
gpsLocationExternal @48 :GpsLocationData;
gpsLocation @21 :GpsLocationData;
liveParameters @61 :LiveParametersData;
liveTorqueParameters @94 :LiveTorqueParametersData;
liveDelay @146 : LiveDelayData;
vehicleParameters @61 :VehicleParameters;
lateralTorqueParameters @94 :LateralTorqueParameters;
lateralDelay @146 : LateralDelay;
cameraOdometry @63 :CameraOdometry;
thumbnail @66: Thumbnail;
onroadEvents @134: List(OnroadEvent);
carParams @69: Car.CarParams;
driverMonitoringState @151 :DriverMonitoringState;
livePose @129 :LivePose;
deviceMotion @129 :DeviceMotion;
modelV2 @75 :ModelDataV2;
drivingModelData @128 :DrivingModelData;
driverStateV2 @92 :DriverStateV2;
# camera stuff, each camera state has a matching encode idx
roadCameraState @2 :FrameData;
driverCameraState @70: FrameData;
narrowRoadCameraState @2 :FrameData;
cabinCameraState @70: FrameData;
wideRoadCameraState @74: FrameData;
roadEncodeIdx @15 :EncodeIndex;
driverEncodeIdx @76 :EncodeIndex;
narrowRoadEncodeIdx @15 :EncodeIndex;
cabinEncodeIdx @76 :EncodeIndex;
wideRoadEncodeIdx @77 :EncodeIndex;
qRoadEncodeIdx @90 :EncodeIndex;
qNarrowRoadEncodeIdx @90 :EncodeIndex;
livestreamRoadEncodeIdx @117 :EncodeIndex;
livestreamNarrowRoadEncodeIdx @117 :EncodeIndex;
livestreamWideRoadEncodeIdx @118 :EncodeIndex;
livestreamDriverEncodeIdx @119 :EncodeIndex;
livestreamCabinEncodeIdx @119 :EncodeIndex;
# microphone data
soundPressure @103 :SoundPressure;
rawAudioData @147 :AudioData;
# systems stuff
androidLog @20 :AndroidLogEntry;
operatingSystemLog @20 :OperatingSystemLogEntry;
managerState @78 :ManagerState;
procLog @33 :ProcLog;
clocks @35 :Clocks;
deviceState @6 :DeviceState;
chestnutState @152 :ChestnutState;
chestnutGpuState @153 :ChestnutState;
logMessage @18 :Text;
errorLogMessage @85 :Text;
@@ -2521,21 +2607,20 @@ struct Event {
# driving feedback
userBookmark @93 :UserBookmark;
bookmarkButton @148 :UserBookmark;
audioFeedback @149 :AudioFeedback;
lateralManeuverPlan @150 :LateralManeuverPlan;
# *********** debug ***********
testJoystick @52 :Joystick;
roadEncodeData @86 :EncodeData;
driverEncodeData @87 :EncodeData;
narrowRoadEncodeData @86 :EncodeData;
cabinEncodeData @87 :EncodeData;
wideRoadEncodeData @88 :EncodeData;
qRoadEncodeData @89 :EncodeData;
qNarrowRoadEncodeData @89 :EncodeData;
alertDebug @133 :DebugAlert;
livestreamRoadEncodeData @120 :EncodeData;
livestreamNarrowRoadEncodeData @120 :EncodeData;
livestreamWideRoadEncodeData @121 :EncodeData;
livestreamDriverEncodeData @122 :EncodeData;
livestreamCabinEncodeData @122 :EncodeData;
# *********** Custom: reserved for forks ***********
@@ -2571,6 +2656,7 @@ struct Event {
# *********** legacy + deprecated ***********
model @9 :Deprecated.ModelData; # TODO: rename modelV2 and mark this as deprecated
audioFeedbackDEPRECATED @149 :Deprecated.AudioFeedbackDEPRECATED;
liveMpcDEPRECATED @36 :Deprecated.LiveMpcData;
liveLongitudinalMpcDEPRECATED @37 :Deprecated.LiveLongitudinalMpcData;
liveLocationKalmanDeprecatedDEPRECATED @51 :Deprecated.LiveLocationData;
@@ -7,12 +7,43 @@ import os
import capnp
import time
from typing import Optional, List, Union, Dict
from typing import Union
from cereal import log
from cereal.services import SERVICE_LIST
from openpilot.cereal import log
from openpilot.cereal.services import SERVICE_LIST
from openpilot.common.utils import MovingAverage
__all__ = (
"NO_TRAVERSAL_LIMIT",
"Context",
"FrequencyTracker",
"IpcError",
"MultiplePublishersError",
"Poller",
"PubMaster",
"PubSocket",
"SocketEventHandle",
"SubMaster",
"SubSocket",
"delete_fake_prefix",
"drain_sock",
"drain_sock_raw",
"fake_event_handle",
"get_fake_prefix",
"log_from_bytes",
"new_message",
"pub_sock",
"recv_one",
"recv_one_or_none",
"recv_one_retry",
"recv_sock",
"reset_context",
"set_fake_prefix",
"sub_sock",
"toggle_fake_events",
"wait_for_one_event",
)
NO_TRAVERSAL_LIMIT = 2**64-1
@@ -22,8 +53,8 @@ def pub_sock(endpoint: str) -> PubSocket:
return msgq.pub_sock(endpoint, segment_size)
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
def sub_sock(endpoint: str, poller: Poller | None = None, addr: str = "127.0.0.1",
conflate: bool = False, timeout: int | None = None) -> SubSocket:
service = SERVICE_LIST.get(endpoint)
segment_size = service.queue_size if service else 0
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
@@ -39,7 +70,7 @@ def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event
return msg
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
args = {
'valid': False,
'logMonoTime': int(time.monotonic() * 1e9),
@@ -54,14 +85,14 @@ def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) ->
return dat
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> list[capnp.lib.capnp._DynamicStructReader]:
"""Receive all message currently available on the queue"""
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
return [log_from_bytes(m) for m in msgs]
# TODO: print when we drop packets?
def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]:
def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicStructReader | None:
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
dat = None
@@ -82,14 +113,14 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._
return dat
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
def recv_one(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
dat = sock.receive()
if dat is not None:
dat = log_from_bytes(dat)
return dat
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
def recv_one_or_none(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
dat = sock.receive(non_blocking=True)
if dat is not None:
dat = log_from_bytes(dat)
@@ -148,27 +179,27 @@ class FrequencyTracker:
class SubMaster:
def __init__(self, services: List[str], poll: Optional[str] = None,
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
def __init__(self, services: list[str], poll: str | None = None,
ignore_alive: list[str] | None = None, ignore_avg_freq: list[str] | None = None,
ignore_valid: list[str] | None = None, addr: str = "127.0.0.1", frequency: float | None = None):
self.frame = -1
self.services = services
self.seen = {s: False for s in services}
self.updated = {s: False for s in services}
self.recv_time = {s: 0. for s in services}
self.recv_frame = {s: 0 for s in services}
self.seen = dict.fromkeys(services, False)
self.updated = dict.fromkeys(services, False)
self.recv_time = dict.fromkeys(services, 0.0)
self.recv_frame = dict.fromkeys(services, 0)
self.sock = {}
self.data = {}
self.logMonoTime = {s: 0 for s in services}
self.logMonoTime = dict.fromkeys(services, 0)
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
self.static_freq_services = set(s for s in services if not on_demand[s])
self.static_freq_services = {s for s in services if not on_demand[s]}
self.alive = {s: on_demand[s] for s in services}
self.freq_ok = {s: on_demand[s] for s in services}
self.valid = {s: on_demand[s] for s in services}
self.freq_tracker: Dict[str, FrequencyTracker] = {}
self.freq_tracker: dict[str, FrequencyTracker] = {}
self.poller = Poller()
polled_services = set([poll, ] if poll is not None else services)
self.non_polled_services = set(services) - polled_services
@@ -211,7 +242,7 @@ class SubMaster:
msgs.append(recv_one_or_none(self.sock[s]))
self.update_msgs(time.monotonic(), msgs)
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
def update_msgs(self, cur_time: float, msgs: list[capnp.lib.capnp._DynamicStructReader]) -> None:
self.frame += 1
self.updated = dict.fromkeys(self.services, False)
for msg in msgs:
@@ -234,21 +265,21 @@ class SubMaster:
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
def all_alive(self, service_list: list[str] | None = None) -> bool:
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool:
def all_freq_ok(self, service_list: list[str] | None = None) -> bool:
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
def all_valid(self, service_list: Optional[List[str]] = None) -> bool:
def all_valid(self, service_list: list[str] | None = None) -> bool:
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
def all_checks(self, service_list: Optional[List[str]] = None) -> bool:
def all_checks(self, service_list: list[str] | None = None) -> bool:
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
class PubMaster:
def __init__(self, services: List[str]):
def __init__(self, services: list[str]):
self.sock = {}
for s in services:
self.sock[s] = pub_sock(s)
@@ -1,7 +1,7 @@
#include <cassert>
#include "cereal/messaging/msgq_to_zmq.h"
#include "cereal/services.h"
#include "openpilot/cereal/messaging/msgq_to_zmq.h"
#include "openpilot/cereal/services.h"
#include "common/util.h"
ExitHandler do_exit;
@@ -1,4 +1,4 @@
#include "cereal/messaging/bridge_zmq.h"
#include "openpilot/cereal/messaging/bridge_zmq.h"
#include <cassert>
#include <cstring>
@@ -8,7 +8,7 @@
#include <capnp/serialize.h>
#include "cereal/gen/cpp/log.capnp.h"
#include "openpilot/cereal/gen/cpp/log.capnp.h"
#include "common/timing.h"
#include "msgq/ipc.h"
@@ -1,8 +1,8 @@
#include "cereal/messaging/msgq_to_zmq.h"
#include "openpilot/cereal/messaging/msgq_to_zmq.h"
#include <cassert>
#include "cereal/services.h"
#include "openpilot/cereal/services.h"
#include "common/util.h"
extern ExitHandler do_exit;
@@ -8,7 +8,7 @@
#include <vector>
#include "msgq/impl_msgq.h"
#include "cereal/messaging/bridge_zmq.h"
#include "openpilot/cereal/messaging/bridge_zmq.h"
class MsgqToZmq {
public:
@@ -3,8 +3,8 @@
#include <string>
#include <mutex>
#include "cereal/services.h"
#include "cereal/messaging/messaging.h"
#include "openpilot/cereal/services.h"
#include "openpilot/cereal/messaging/messaging.h"
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
@@ -1,16 +1,16 @@
import os
import capnp
import multiprocessing
import numbers
import random
import threading
import time
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.parameterized import parameterized
import pytest
from cereal import log, car
import cereal.messaging as messaging
from cereal.services import SERVICE_LIST
from openpilot.cereal import log
from opendbc.car.structs import car
import openpilot.cereal.messaging as messaging
from openpilot.cereal.services import SERVICE_LIST
events = [evt for evt in log.Event.schema.union_fields if evt in SERVICE_LIST.keys()]
@@ -23,10 +23,6 @@ def random_socks(num_socks=10):
def random_bytes(length=1000):
return bytes([random.randrange(0xFF) for _ in range(length)])
def zmq_sleep(t=1):
if "ZMQ" in os.environ:
time.sleep(t)
# TODO: this should take any capnp struct and returrn a msg with random populated data
def random_carstate():
@@ -51,17 +47,7 @@ def delayed_send(delay, sock, dat):
threading.Timer(delay, send_func).start()
class TestMessaging:
def setUp(self):
# TODO: ZMQ tests are too slow; all sleeps will need to be
# replaced with logic to block on the necessary condition
if "ZMQ" in os.environ:
pytest.skip()
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep()
class TestMessaging(OpenpilotTestCase):
@parameterized.expand(events)
def test_new_message(self, evt):
try:
@@ -88,7 +74,6 @@ class TestMessaging:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=1000)
zmq_sleep()
# no wait and no msgs in queue
msgs = func(sub_sock)
@@ -96,10 +81,11 @@ class TestMessaging:
assert len(msgs) == 0
# no wait but msgs are queued up
pub_sock.send(messaging.new_message(sock).to_bytes())
assert sub_sock.receive() is not None # synchronize the PUB/SUB connection
num_msgs = random.randrange(3, 10)
for _ in range(num_msgs):
pub_sock.send(messaging.new_message(sock).to_bytes())
time.sleep(0.1)
msgs = func(sub_sock)
assert isinstance(msgs, list)
assert all(isinstance(msg, expected_type) for msg in msgs)
@@ -109,7 +95,6 @@ class TestMessaging:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=100)
zmq_sleep()
# no wait and no msg in queue, socket should timeout
recvd = messaging.recv_sock(sub_sock)
@@ -127,8 +112,7 @@ class TestMessaging:
def test_recv_one(self):
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=1000)
zmq_sleep()
sub_sock = messaging.sub_sock(sock, timeout=10)
# no msg in queue, socket should timeout
recvd = messaging.recv_one(sub_sock)
@@ -141,12 +125,10 @@ class TestMessaging:
assert isinstance(recvd, capnp._DynamicStructReader)
assert_carstate(msg.carState, recvd.carState)
@pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
def test_recv_one_or_none(self):
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock)
zmq_sleep()
# no msg in queue, socket shouldn't block
recvd = messaging.recv_one_or_none(sub_sock)
@@ -161,19 +143,16 @@ class TestMessaging:
def test_recv_one_retry(self):
sock = "carState"
sock_timeout = 0.1
sock_timeout = 0.005
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
zmq_sleep()
# this test doesn't work with ZMQ since multiprocessing interrupts it
if "ZMQ" not in os.environ:
# wait 5 socket timeouts and make sure it's still retrying
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
p.start()
time.sleep(sock_timeout*5)
assert p.is_alive()
p.terminate()
# wait 5 socket timeouts and make sure it's still retrying
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
p.start()
time.sleep(sock_timeout*5)
assert p.is_alive()
p.terminate()
# wait 5 socket timeouts before sending
msg = random_carstate()
@@ -1,20 +1,16 @@
import random
import time
from typing import Sized, cast
from typing import cast
from collections.abc import Sized
import cereal.messaging as messaging
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
random_bytes, random_carstate, assert_carstate, \
zmq_sleep
from cereal.services import SERVICE_LIST
from openpilot.common.test import OpenpilotTestCase
import openpilot.cereal.messaging as messaging
from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
random_bytes, random_carstate, assert_carstate
from openpilot.cereal.services import SERVICE_LIST
class TestSubMaster:
def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
class TestSubMaster(OpenpilotTestCase):
def test_init(self):
sm = messaging.SubMaster(events)
@@ -42,7 +38,6 @@ class TestSubMaster:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,])
zmq_sleep()
msg = random_carstate()
pub_sock.send(msg.to_bytes())
@@ -54,7 +49,6 @@ class TestSubMaster:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,])
zmq_sleep()
for i in range(10):
msg = messaging.new_message(sock)
@@ -66,17 +60,17 @@ class TestSubMaster:
def test_update_timeout(self):
sock = random_sock()
sm = messaging.SubMaster([sock,])
timeout = random.randrange(1000, 3000)
timeout = random.randrange(10, 30)
start_time = time.monotonic()
sm.update(timeout)
t = time.monotonic() - start_time
assert t >= timeout/1000.
assert t < 3
assert t < 0.1
assert not any(sm.updated.values())
def test_avg_frequency_checks(self):
for poll in (True, False):
sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "liveCalibration"],
sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "extrinsicsCalibration"],
poll=("modelV2" if poll else None),
frequency=(20. if not poll else None))
@@ -84,50 +78,39 @@ class TestSubMaster:
"carState": (20, 20),
"modelV2": (20, 20 if poll else 10),
"cameraOdometry": (20, 10),
"liveCalibration": (4, 4),
"extrinsicsCalibration": (4, 4),
"carParams": (None, None),
"userBookmark": (None, None),
}
for service, (max_freq, min_freq) in checks.items():
if max_freq is not None:
assert min_freq is not None
assert sm._check_avg_freq(service)
assert sm.freq_tracker[service].max_freq == max_freq*1.2
assert sm.freq_tracker[service].min_freq == min_freq*0.8
else:
assert not sm._check_avg_freq(service)
def test_alive(self):
pass
def test_ignore_alive(self):
pass
def test_valid(self):
pass
# SubMaster should always conflate
def test_conflate(self):
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,])
pub_sock.send(messaging.new_message(sock).to_bytes())
sm.update(1000) # synchronize the PUB/SUB connection
n = 10
for i in range(n+1):
msg = messaging.new_message(sock)
msg.carState.vEgo = i
pub_sock.send(msg.to_bytes())
time.sleep(0.01)
sm.update(1000)
assert sm[sock].vEgo == n
class TestPubMaster:
def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
class TestPubMaster(OpenpilotTestCase):
def test_init(self):
messaging.PubMaster(events)
@@ -136,7 +119,6 @@ class TestPubMaster:
socks = random_socks()
pm = messaging.PubMaster(socks)
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
zmq_sleep()
# PubMaster accepts either a capnp msg builder or bytes
for capnp in [True, False]:
@@ -1,13 +1,14 @@
import os
import subprocess
import tempfile
from typing import Dict
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.parameterized import parameterized
import cereal.services as services
from cereal.services import SERVICE_LIST
import openpilot.cereal.services as services
from openpilot.cereal.services import SERVICE_LIST
class TestServices:
class TestServices(OpenpilotTestCase):
@parameterized.expand(SERVICE_LIST.keys())
def test_services(self, s):
@@ -17,5 +18,5 @@ class TestServices:
def test_generated_header(self):
with tempfile.NamedTemporaryFile(suffix=".h") as f:
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name} -std=c++11")
ret = subprocess.run(f"python3 {services.__file__} > {f.name} && clang++ {f.name} -std=c++11", shell=True).returncode
assert ret == 0, "generated services header is not valid C"

Some files were not shown because too many files have changed in this diff Show More