Compare commits

...

270 Commits

Author SHA1 Message Date
DevTekVE a86b3a576a test sat based torque 2025-08-13 10:22:47 +02:00
DevTekVE 86093765d8 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-08-13 07:55:46 +02:00
Jason Wen c490b3ca12 Sync: commaai/openpilot:master into sunnypilot/sunnypilot:master (#1153) 2025-08-13 00:31:44 -04:00
Jason Wen 6c4c0c00b4 Merge branch 'upstream/openpilot/master' into sync-20250812
# Conflicts:
#	opendbc_repo
#	selfdrive/controls/controlsd.py
#	selfdrive/controls/lib/latcontrol_angle.py
#	selfdrive/controls/lib/latcontrol_pid.py
#	selfdrive/controls/lib/latcontrol_torque.py
2025-08-12 23:11:18 -04:00
James Vecellio-Grant 68625222b6 chore: sync tinygrad (#1151)
* commence the sync!

* !cancelled

* Thats it folks
2025-08-12 22:33:06 -04:00
YassineYousfi 8deb1bf285 Down To Ride model 🏎️ (#35982)
* e9237324-4b92-48f5-acaa-ebdf7fe46339/400

* ff4c292c-8e5a-44c0-9b75-e79c60152da2/400

* 1496451e-897b-4a1b-a284-37d244bfddb3/400

* Revert "Revert TR again (#35179)"

This reverts commit e9cea3ae5c.

* try stopping closer

* 5e4cb3d3-b9cc-45c7-a476-38083e75029c/400

* 2164d501-7d2c-467d-b132-be4f85db4164/60

* Revert "2164d501-7d2c-467d-b132-be4f85db4164/60"

This reverts commit 1f4b98ed7d63971507dff94e5ac20223ee15e067.

* 9a836aee-dec6-4f26-8d7e-6db4bb9c8176

* no replace ln

* Revert "no replace ln"

This reverts commit fb5173ced84bb8a07a4e06a5bec43d115404973b.

* opset_version 17

* rebase

* 5f255b73-2e54-46bc-8f80-82c5838165a3/400

* a423dec7-7dcc-4523-aaae-a4012d56b9b5/400

---------

Co-authored-by: Bruce Wayne <harald.the.engineer@gmail.com>
2025-08-12 19:01:56 -07:00
James Vecellio-Grant 28b17858be sunnypilot modeld: refactor gen12 parsing (#1150)
* gen12

* lint

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-08-12 19:07:07 -04:00
DevTekVE 05fa1c8ae8 Adjust default params and cleanup 2025-08-12 21:36:22 +02:00
commaci-public 91aec49cee [bot] Update Python packages (#35976)
Update Python packages

Co-authored-by: Vehicle Researcher <user@comma.ai>
2025-08-11 18:43:52 -07:00
Harald Schäfer cd087a561e Simple plan (#35980)
* squash

* double

* proper merge

* better organization
2025-08-11 17:42:03 -07:00
Shane Smiskol 10cc87b80b raylib: rm some common colors (#35979)
common colors
2025-08-11 17:06:11 -07:00
Jimmy 13d4c6a167 ui: replace "Hotspot" with link icon (#35978)
replace "Hotspot" with link icon
2025-08-11 16:26:07 -07:00
Harald Schäfer 455a6a586a Misc PID refactors (#35844)
* Misc PID refactors

* dead

* finish rename

* unused import

* whitespace

* typo

* fix fan controller

* pid_log

* whitespace

* integral clipping in pid

* update ref

* cleaner

* rm print

* update ref

* revert fan changes

* forgot this
2025-08-11 14:25:29 -07:00
YassineYousfi c78b302b93 Space Lab 3 🛰️🛰️🛰️ (#35905)
* c147a591-1f86-4ea4-b2b7-391eff1178b5/400

* 6d6639ee-643e-4f72-bd1c-dda546383854/400
2025-08-11 14:07:01 -07:00
Maxime Desroches a11a8591e4 bump version to 0.10.1 2025-08-11 13:14:48 -07:00
Nayan ec3f044c83 ui: refresh model list (#1074)
that's all folks

Co-authored-by: Kumar <36933347+rav4kumar@users.noreply.github.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2025-08-11 10:24:05 -04:00
Jason Wen 6eaae848c9 Revert "registration required to go onroad" (#1143)
This reverts commit f4b017a7
2025-08-11 08:47:52 -04:00
Jason Wen fc9a79406f NNLC: cleanup unused attributes (#1142)
* NNLC: cleanup unused attributes

* more cleanup
2025-08-11 08:28:29 -04:00
Maxime Desroches 0bbceb8539 wifi_manager: wait for wifi device (#35974)
wait
2025-08-10 19:41:13 -07:00
royjr e97ae07589 macOS: fix font-noto-color-emoji (#35972)
Update mac_setup.sh
2025-08-10 09:49:52 -07:00
DevTekVE e8a40d6b85 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	system/manager/process_config.py
2025-08-10 14:24:10 +02:00
Jason Wen f45ad6bab9 panda: skip flash and firmware check if deprecated panda is detected (#1117)
* flash: skip if deprecated panda is detected

* skip firmware checks

* wrong

* typing

* fix

* revert
2025-08-10 02:52:02 -04:00
github-actions[bot] b5cbb980fb [bot] Update translations (#1140)
Update translations

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-08-10 02:47:39 -04:00
github-actions[bot] 876738e96a [bot] Update Python packages (#1141)
* Update Python packages

* revert

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-08-10 02:42:02 -04:00
Jason Wen 4116b412a3 Sync: commaai/openpilot:master into sunnypilot/sunnypilot:master (#1134) 2025-08-10 02:20:07 -04:00
Jason Wen 75509aec16 Disable record feedback with LKAS button when MADS is enabled 2025-08-10 02:05:09 -04:00
Jason Wen 97c2d7e655 Merge branch 'upstream/openpilot/master' into sync-20250809
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
#	panda
#	selfdrive/car/cruise.py
#	selfdrive/controls/controlsd.py
#	selfdrive/selfdrived/selfdrived.py
#	selfdrive/test/process_replay/process_replay.py
#	selfdrive/ui/qt/setup/setup.cc
#	tinygrad_repo
2025-08-10 02:05:05 -04:00
Jason Wen 1bc12f1e21 Reapply "LagdToggle: refactor and only instantiate once" (#1137) (#1138)
* Reapply "`LagdToggle`: refactor and only instantiate once" (#1137)

This reverts commit b4f19d4860.

* infinite woo gone

* use them hz
2025-08-09 22:50:29 -04:00
Jason Young 430079113c Revert "Honda: Temporary test exception for driver regen paddle" (#35969)
Revert "Honda: Temporary test exception for driver regen paddle (#35929)"

This reverts commit d15d3c73b8.
2025-08-09 20:57:04 -04:00
Jason Wen b4f19d4860 Revert "LagdToggle: refactor and only instantiate once" (#1137)
Revert "`LagdToggle`: refactor and only instantiate once (#1130)"

This reverts commit 6ae668e987.
2025-08-09 20:06:10 -04:00
Adeeb Shihadeh 1c46640ea6 Remove more Qt, part 2 (#35968) 2025-08-09 15:04:26 -07:00
Adeeb Shihadeh 2d8030de0b ui: move watch3 to raylib (#35967)
* move to py

* cleaner

* clean that up
2025-08-09 15:00:36 -07:00
Jason Wen 80a4ace1ab ci: use upstream docker build script for cereal validation (#1136) 2025-08-09 17:57:49 -04:00
Jason Wen b391708b3d Revert "ci: update cereal validation repo reference to sunnypilot" (#1135)
Revert "ci: update cereal validation repo reference to sunnypilot (#1108)"

This reverts commit 08216c1ea4.
2025-08-09 17:40:29 -04:00
Jason Wen 6ef386da3d NNLC: fix friction not being applied to error (#1113)
* params: fix type

* NNLC: fix friction not being applied to error

* another PR

* apply suggestions

* lint
2025-08-09 16:56:37 -04:00
Jason Wen 6ae668e987 LagdToggle: refactor and only instantiate once (#1130)
* wrap the params

* just 1 class and use a single param for now

* refactor

* fix

* cache itself

* no longer

* rename

* type hint

* in helpers instead

* lint

* all

* init as 0 to pass ci

* init as 0 to pass ci

* return_default

* fix init

* add LAT_SMOOTH_SECONDS directly in modeld, temp remove dynamic desc, red difffffffff
2025-08-09 16:51:31 -04:00
github-actions[bot] 72b71d57bc [bot] Update translations (#35965)
Update translations

Co-authored-by: Vehicle Researcher <user@comma.ai>
2025-08-09 12:21:05 -07:00
commaci-public 5339a89e81 [bot] Update Python packages (#35964)
Update Python packages

Co-authored-by: Vehicle Researcher <user@comma.ai>
2025-08-09 12:19:52 -07:00
James Vecellio-Grant 2f60026c22 ci: crosscheck tinygrad ref during tests (#1106)
* Add tinygrad ref testing

* BaseDir and give a fake ref id to test

* one more before the real thing

* Add the correct ref

* test

* Update test_tinygrad_ref.py

* Update tinygrad_ref

* Update test_tinygrad_ref.py

* Update tinygrad_ref

* This SHOULD FAIL

* Revert "This SHOULD FAIL"

This reverts commit 58862f8a73.

* bit of red

* Move ref to models so compiled branches can read it

* step one

* step 2

* Update build-all-tinygrad-models.yaml

* Update build-all-tinygrad-models.yaml

* Update build-all-tinygrad-models.yaml

* Update build-all-tinygrad-models.yaml

* Update build-all-tinygrad-models.yaml

* Update build-all-tinygrad-models.yaml

* Update tinygrad_ref.py

* Update build-all-tinygrad-models.yaml

* bump to fail test

* Revert "bump to fail test"

This reverts commit 4f58991f32.

* pytest should take care of it

* lint

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-08-09 14:55:52 -04:00
Shane Smiskol e0f51bdbb6 Reapply "LogReader: wrap events to cache which() (#35882)" (#35909)
* Reapply "LogReader: wrap events to cache which() (#35882)"

This reverts commit ba2dced54c.

* fix lr

* speed up

* clean up

* more

* should be fast

* clean up

* only supports Event

* rmrmr

* bye

* simple

* gix
2025-08-08 23:42:54 -07:00
Maxime Desroches 63d8c6c7f7 ui: adapt InputBox to new touch api (#35962)
new
2025-08-08 22:50:35 -07:00
Maxime Desroches 83f6843a48 ci: run all unit tests (#35959)
* more please

* back
2025-08-08 20:07:33 -07:00
Maxime Desroches 4bb5986c14 setup: fix url for urllib (#35958)
fix
2025-08-08 19:57:20 -07:00
Maxime Desroches e596704644 ui: remove gui_label usages from setup (#35955)
clean
2025-08-08 16:00:00 -07:00
James Vecellio-Grant bcc416c7eb ci: adjusting build-single to comply with githubs new input type (#1132) 2025-08-08 15:12:47 -07:00
DevTekVE 38de06232e bugfix: fix backup manager after params change (#1123)
* why? because why not!? damn.

* test

* more disable

* i am hating this

* fuck mypy.

* hopefully this is fixing json

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-08-08 18:02:31 +02:00
Jason Wen 567c5459db params: fix auto type cast (#1127)
* params: fix auto type cast on put

* literally

* lint

* pls comma why dis a string but actually json
2025-08-08 10:26:58 -04:00
Shane Smiskol fd32fcd20d raylib ui: only process mouse events when enabled (#35948) 2025-08-08 01:09:03 -07:00
Shane Smiskol a79a5e5a16 revert
i thought i pushed!
2025-08-08 00:36:13 -07:00
Shane Smiskol 5117a8c3a6 ui: test raylib ui (#35949)
* add raylib ui

* test

* this is better for now

* rm

rm

* finalize it

* need this?

* ?

* shite

shite

* try

* ?

* huh

* simp

* ?

* wtf is going on

* ???????????????

* lock

* stash

* no 2 packages

* Revert "stash"

This reverts commit 9efb0d9bda6a6309e7a567634d1921bf1cd0fb59.

* debug

* noo

* debug

* ?

* and

* yeah yeah

* init one

* 2

* i wonder

* oooh

* make sure

* fix dat

* try this

* see if wifiman

* forgot

* ?

* ???

* fuck this we can rewrite it later
2025-08-08 00:34:53 -07:00
Maxime Desroches 1555c0b5fe ui: custom software warning (#35953)
cu
2025-08-07 23:19:48 -07:00
Maxime Desroches 0e9de8f1b1 ui: support text wrapping in Label (#35952)
* lb

* t

* Revert "t"

This reverts commit a9b8e2b9faa5e9d1b189c1dc2ed1aa876e4df476.

* tr

* Revert "tr"

This reverts commit 8de8719ded0fed2b0e5469230e83c13714f88319.

* better

* much better
2025-08-07 23:08:42 -07:00
Shane Smiskol 7bfac9d050 raylib ui: improve is_pressed (#35950)
* stash

* clean up exp

* come on

* fix

* ?

* maybe better

* fix

* same order

* clean up
2025-08-07 16:28:16 -07:00
Maxime Desroches a800c129b0 run setup and reset at 20FPS for now 2025-08-07 14:33:40 -07:00
Maxime Desroches f13ec6cb27 wifi manager: correctly handle emoji ssid 2025-08-07 14:22:34 -07:00
Maxime Desroches f04bb6b9fa ui: reduce network selection lag (#35945)
lag
2025-08-07 13:43:27 -07:00
Brett Sanderson ed0346980c Update plotjuggler README.md (#35946)
Update README.md

Real example using segment range.  Remove segment count as its not a parameter.
2025-08-07 11:47:50 -07:00
Shane Smiskol 6cf710d4cb Widget: add enabled property (#35944)
* add enabled

* sort

* rename

* rest

* rm that
2025-08-06 22:00:12 -07:00
ZwX1616 8b90c210f8 encoderd: more efficient compression for low res frames (#35924)
* shein says inline

* Update system/loggerd/loggerd.h

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

* Revert "Update system/loggerd/loggerd.h"

This reverts commit 3602523cefdeb2a46d77946f7f2cc7fc21bd5a4f.

* Revert "shein says inline"

This reverts commit d3c079e137c5d98068501df636975c5fbf8810ee.

* EncoderSettings

* getter

* update test_encoder

* def

---------

Co-authored-by: Comma Device <device@comma.ai>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
2025-08-06 21:17:10 -07:00
Maxime Desroches 62bbf6db8d ui: use label in confirm dialog (#35943)
forget
2025-08-06 20:11:30 -07:00
Maxime Desroches a51477d40d ui: use Label in keyboard (#35941)
better
2025-08-06 18:07:06 -07:00
ZwX1616 a84089c6e5 EncoderInfo: encoder setting factorys (#35940) 2025-08-06 16:53:16 -07:00
Jimmy bb8a2ff65b Remove rerun (#35939)
remove rerun
2025-08-06 16:50:26 -07:00
Maxime Desroches 3a78eee2f9 ui: emoji (#35913)
* emoji

* label

* back

* default

* type

* more

* ico

* device

* clean

* brew
2025-08-06 16:04:19 -07:00
Shane Smiskol 52a4b52628 FileName clean up (#35938)
two spaces!
2025-08-06 14:07:02 -07:00
Jason Young 839a773345 enable lateral accel factor learning for Honda (#35936)
enable torqued for Honda
2025-08-06 16:11:07 -04:00
DevTekVE c236f472a9 update ISO_LATERAL_ACCEL import + VM changes (#35865)
* refactor: move lateral methods from init to lateral.py (#2594)

* Extracting lateral methods to lateral.py

* cleaning

* more cleaning

* more cleaning

* Making sure it remains where it should

* Leave rate_limit where it belongs

* Moving things to `car/controls/`

* Moving rate limit to get a taste of the changes

* clean

* copy verbatim

* clean up

* more

* now we can format

---------

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

* Merge branch 'master' into move-common-vm-methods. dunno what happend with ci

* now we need to move this import

* bump opendbc

---------

Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-08-06 11:53:57 -07:00
Bruce Wayne ac3d96d2fd Revert "selfdrive.ui.feedback: add init (#35935)"
This reverts commit eca2f40341.
2025-08-05 23:11:19 -07:00
Harald Schäfer eca2f40341 selfdrive.ui.feedback: add init (#35935)
add init
2025-08-05 23:08:09 -07:00
Maxime Desroches 5e6f942234 gitignore .cache/ from clangd 2025-08-05 22:02:10 -07:00
Maxime Desroches 69ca699773 clip: fix params (#35934)
fix
2025-08-05 21:50:30 -07:00
Harald Schäfer d1e0a60408 Filename refactor: no enum (#35930)
* conflict

* typing

* typing

* no value

* fix typing

* whitespace

* whitespace

* unused

* Reapply "Filename: minor refactor (#35927)"

This reverts commit 8c7d53004f.

* unused import

* done
2025-08-05 20:37:09 -07:00
Harald Schäfer 999db5b426 Update RELEASES.md 2025-08-05 20:14:01 -07:00
Harald Schäfer e8135c5431 Update RELEASES.md 2025-08-05 20:11:56 -07:00
Adeeb Shihadeh 978d1c38f1 clip: add speed up support (#35933) 2025-08-05 19:17:58 -07:00
Adeeb Shihadeh 8c7d53004f Revert "Filename: minor refactor (#35927)"
This reverts commit 96313fa4c0.
2025-08-05 19:07:17 -07:00
Mitchell Goff 7413982f0d Lower ALLOW_THROTTLE_THRESHOLD (#35928)
* Lower ALLOW_THROTTLE_THRESHOLD

* Bumped process_replay refs
2025-08-05 17:35:54 -07:00
Maxime Desroches c95cac3b06 update to latest userdata partition (#35931)
update
2025-08-05 17:11:19 -07:00
Maxime Desroches fbbb2ef5d0 update release checklist 2025-08-05 16:57:39 -07:00
Jason Young d15d3c73b8 Honda: Temporary test exception for driver regen paddle (#35929)
Honda: Test exception for driver regen
2025-08-05 19:35:06 -04:00
Harald Schäfer 96313fa4c0 Filename: minor refactor (#35927)
* Filename

* rest of refactor
2025-08-05 16:29:25 -07:00
Shane Smiskol c35494c19f Check for excessive lateral acceleration (#35921)
* here?

* nah card shouldn't become bloated

* better

* import

* actually selfdrived is probably best since it already manages alerts

card is car interfacing, controlsd is for calculating control input, selfdrived is rest

* consistent name

* add to params

* ai

* maybe better?

* shorter

* build out lockout

* do

* check active

* descriptive

* this is a terrible experience just to get lat accel

* just pass sm

* not iso

* type

* rm

* math

* use calibrated roll

* fix

* fix borkenness

* cmt

* compare some methods

* rolling window

* 1 and 2 are the same

* rm it

* stuff

* plot

* plot kf

* generic implementation

* adjust limits

* fix from merge

* clean up

* revert filter to master

* and here

* and

* run_process_on_route imps

* clean up

* why not

* extrapolate

* this doesn't generically work for angle/curvature cars

Revert "extrapolate"

This reverts commit 556f0c3a92b82f07ceb6422f0e39322e79a10dcd.

* cmt

* move

* rm debug

rm debug

and

* others use helpers

* two counters might be too much to return

* turn into class

* clean up

* cmt

* kinda obvious

* impossible for this not to be true, but make it explicit

* clean up
2025-08-05 16:15:07 -07:00
Adeeb Shihadeh c321fa72e2 one more 2025-08-05 16:08:14 -07:00
Adeeb Shihadeh 2c654500d2 update release README 2025-08-05 16:07:29 -07:00
YassineYousfi bb4f9651ba Update RELEASES.md 2025-08-05 14:57:57 -07:00
Adeeb Shihadeh c23537b4d5 lil copy updates 2025-08-05 14:55:30 -07:00
Adeeb Shihadeh 865e6fa9d8 update release notes 2025-08-05 13:42:51 -07:00
Jimmy d7b0a5fa7e Record feedback with LKAS button (#35888)
* record feedback with LKAS button

* fix alert test

* slightly simplify feedbackd

* "Audio Feedback Saved" upon time expiration or early stop

* earlySend --> earlyStop

* userFlag --> userBookmark

* RecordAudioFeedback param/toggle

* add audioFeedback test

* simplify feedbackd

* send bookmark regardless of toggle, show feedback event with higher priority

* add userBookmark to selfdrived sm

* fix mispelled param name

* default off and move to main

* segmentNum --> blockNum, earlyStop --> lastBlock

* preserve audioFeedback

* get rid of lastBlock and just send bookmark saved at the end

* update raylib side

* update toggle description and add raylib toggle

---------

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-08-05 13:41:41 -07:00
Maxime Desroches 112d615ac9 ci: fix setup device variable 2025-08-04 21:28:43 -07:00
Maxime Desroches fb34e7ccd3 ci: kick power watchdog on ci devices 2025-08-04 21:06:28 -07:00
Maxime Desroches f08d95b95a AGNOS 12.6 (#35922)
* bump

* production
2025-08-04 20:40:20 -07:00
Shane Smiskol 1c9bbb290a run_process_on_route.py: qol improvements (#35923)
* take from upstrema/exc-lat-accel

* see ya

* sort

* rm

* duh

duh
2025-08-04 17:11:53 -07:00
Shane Smiskol 2c8415f81c ui.py: gas is deprecated 2025-08-04 16:21:20 -07:00
mvl-boston bae7a610fa Honda: allow sport gear (#35911)
add honda sport gear
2025-08-04 16:19:26 -07:00
Adeeb Shihadeh aecb6d13e7 cabana: add independent panda lib (#35920)
* cabana: add separate panda lib

* cleanup
2025-08-04 16:09:22 -07:00
Maxime Desroches 1ca8a4ca75 raylib: bump commit 2025-08-04 15:43:50 -07:00
Maxime Desroches c316c400f8 reset: proper button scale (#35919)
* reset scale

* r
2025-08-04 15:41:29 -07:00
Adeeb Shihadeh 408cef2d46 Delete Jenkins trigger comments (#35916) 2025-08-04 13:29:22 -07:00
pencilpusher be0626f7e3 improved safe_ioctl (#35908)
* improved safe_ioctl

* readability

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>

* use correct ioctl command

* ameliorated api

* use try/catch to prevent spi_fd leak

* Update common/util.h

* use correct ioctl command

* error log message is more readable

---------

Co-authored-by: Test User <test@example.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-08-04 13:25:24 -07:00
commaci-public f06c98018f [bot] Update Python packages (#35915)
Update Python packages

Co-authored-by: Vehicle Researcher <user@comma.ai>
2025-08-04 09:44:14 -07:00
Maxime Desroches 976dfa3982 ui: multi touch keyboard support (#35912)
* start

* better

* 2

* dumb
2025-08-03 18:14:48 -07:00
Willem Melching 623de0e22a cabana: PandaStream use noOutput safety mode instead silent (#35910) 2025-08-03 09:22:52 -07:00
Maxime Desroches 86146981c4 ui: fix connection check 2025-08-03 01:32:51 -07:00
Maxime Desroches 56dcf71774 ui: fix non-ascii access points 2025-08-03 01:21:40 -07:00
Maxime Desroches a1f073921c test_messaging: less flaky wait time check 2025-08-03 00:31:01 -07:00
Maxime Desroches cccd60a28b ui: make wifi selection usable (#35895)
* start

* wrong

* more

* more

* better

* better

* more better
2025-08-03 00:14:36 -07:00
Maxime Desroches 181ea39a83 ui: re-compute text size (#35907)
* one

* app

* fix
2025-08-02 20:38:37 -07:00
Maxime Desroches 8cce8cf3f3 ui: keyboard improvements (#35906)
* better

* miss this one
2025-08-02 19:01:59 -07:00
Simon Kuang 0b855a93d7 scons: support build on single processor (#35904)
Update SConstruct
2025-08-02 16:50:45 -07:00
Jason Young 8c78749846 sim: fix "msg not found" errors (#35903)
* garbage-collect CRUISE_PARAMS

* follow GEARBOX message refactor
2025-08-02 19:10:49 -04:00
Adeeb Shihadeh aa2a3b3c8f hw: remove unused volume properties 2025-08-02 16:08:58 -07:00
Adeeb Shihadeh ba2dced54c Revert "LogReader: wrap events to cache which() (#35882)"
This reverts commit 0ebee55050.
2025-08-02 15:53:20 -07:00
Shane Smiskol 2e15ac5f4f test manager in CI (#35900)
* test manager

* not now

* try

* fix
2025-08-02 13:18:30 -07:00
Shane Smiskol c92add1280 process replay: don't wait for process to start (#35897)
* hmm

* test proc replay determinism

* clean up

* rm

* clean up
2025-08-02 12:34:13 -07:00
Adeeb Shihadeh bab251b287 fix conversions import path (#35899) 2025-08-02 12:02:17 -07:00
DevTekVE 9dc98b36be refactor: cleanup gravity constant handling (#35866)
* refactor: move lateral methods from init to lateral.py (#2594)

* Extracting lateral methods to lateral.py

* cleaning

* more cleaning

* more cleaning

* Making sure it remains where it should

* Leave rate_limit where it belongs

* Moving things to `car/controls/`

* Moving rate limit to get a taste of the changes

* clean

* copy verbatim

* clean up

* more

* now we can format

---------

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

* No need to change order of import

* refactor: consolidate ACCELERATION_DUE_TO_GRAVITY import path

* bump opendbc

* update refs

* don't import from opendbc

---------

Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-08-02 11:20:18 -07:00
DevTekVE c265e0bb85 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
#	system/manager/manager.py
2025-08-02 09:56:58 +02:00
Shane Smiskol 313f36712c process replay: lock polled socket only (#35887)
* stash

* Revert "stash"

This reverts commit 333818b80f498e8e3dac3c1cd36e669e97521d52.

* works for paramsd

* INSANE

* format

* fater

* clean up

* more

* huh i thought order matterred?

* clean that up

* can remove this

* cmt

* check isisntance

* rename

* clean up

* clean up

* more

* more!

* sounds better
2025-08-02 00:45:29 -07:00
Maxime Desroches 3ff874d6c2 ui: fix keyboard lint 2025-08-02 00:24:52 -07:00
Maxime Desroches eb751a3804 setup: convert to raylib touch api (#35862)
* first

* lint

* c

* simple first

* btn

* n

* more

* more

* bring back
2025-08-02 00:22:28 -07:00
Shane Smiskol 5a8e3470ff selfdrived: feed PoseCalibrator with updates (#35893)
this is also slow
2025-08-02 00:09:54 -07:00
Shane Smiskol 07909906d4 controlsd: speed up number checking (#35890)
Update controlsd.py
2025-08-02 00:08:18 -07:00
Shane Smiskol 7c87ada8d8 Simplify radarFault handling (#35891)
* Revert "Fix up `radarFault` handling (#35880)"

This reverts commit 4d01b7bec8.

* Reapply "Fix up `radarFault` handling (#35880)"

This reverts commit 597d7ec1ed78206035b924a6e8464cd9239b5db4.

* can do this

* yeah this is fine
2025-08-01 23:55:16 -07:00
Shane Smiskol bdd6ff4f3e process replay: remove frequency based recv callback (#35886)
* wtf is going on?

* rm it

* default
2025-08-01 21:46:32 -07:00
Shane Smiskol f2e100b0e1 process replay: clean up recv callbacks (#35889)
clean up callbacks
2025-08-01 21:36:15 -07:00
Shane Smiskol 8b0bfd7910 match on /test/ 2025-08-01 20:52:36 -07:00
Shane Smiskol db55f1275d process replay: set selfdrived main_pub (#35885)
* save 1-2s for full route

* now more than halve the time on top of previous speedup!

* stash

* default should be most common!

* revert

* revert

* clean up

* clean up

* clean up

* clean up
2025-08-01 20:49:45 -07:00
Shane Smiskol 8f9ee43d34 process replay: flip main_pub_drained default 2025-08-01 20:44:33 -07:00
Shane Smiskol 37c4ee1532 process replay: only enter prefix when interacting with process (#35884)
* save 1-2s for full route

* cu

* stock

* Revert "stock"

This reverts commit 7cfb550817b124c3085cf005fda8c102ae53ae9d.

* clean up
2025-08-01 20:13:02 -07:00
Shane Smiskol 0ebee55050 LogReader: wrap events to cache which() (#35882)
* speed up lr

* lazy caching

* clean up

* it fast

* stash

* stash

* chatgpt code is bad as usual

* clean up

* clean up

* clean up

* clean up

* clean up

* clean up

* match behavior

* cmt
2025-08-01 19:07:16 -07:00
Maxime Desroches cb5299be5a ui: adapt network to raylib touch api (#35881)
* start

* for now

* con

* more
2025-08-01 18:40:43 -07:00
Shane Smiskol 5c73681be8 process replay: rm dummy sockets (#35883)
* rm dummy sockets

* debug

* clean up

* cu
2025-08-01 18:38:42 -07:00
Shane Smiskol dd09c4f341 process replay: speed up startup (#35879)
* format

* containers might not be set

* opts

* halves startup time for 12 procs (1.6 to 0.8s)

* stash

* clean up

* who knew going through entire list of msgs each time is so slow

* rewrite this to be more readable

* speed up lr

* clean up

* more

* more
2025-08-01 17:51:39 -07:00
Adeeb Shihadeh 4d01b7bec8 Fix up radarFault handling (#35880)
* fixup radarFault handling

* catch all

---------

Co-authored-by: Comma Device <device@comma.ai>
2025-08-01 16:27:26 -07:00
Maxime Desroches 42ebab1334 ui: add missing keyboard function 2025-08-01 16:02:25 -07:00
Shane Smiskol 9117a414bb process replay clean up (#35878)
* format

* containers might not be set

* opts

* halves startup time for 12 procs (1.6 to 0.8s)

* stash

* Revert "stash"

This reverts commit 3e119a9602e495bd5a57b94e73fa53d4f45051b1.

* Revert "halves startup time for 12 procs (1.6 to 0.8s)"

This reverts commit a39edf0a579f0c861ccb904a2718254fe32e03d0.

* Revert "opts"

This reverts commit 4dc1f75f0909a93650f8f7e8525af3e4eae08205.

* already set!
2025-08-01 15:20:50 -07:00
DevTekVE 1966845fc9 refactor: move lateral methods from init to lateral.py (#35856)
* refactor: move lateral methods from init to lateral.py (#2594)

* Extracting lateral methods to lateral.py

* cleaning

* more cleaning

* more cleaning

* Making sure it remains where it should

* Leave rate_limit where it belongs

* Moving things to `car/controls/`

* Moving rate limit to get a taste of the changes

* clean

* copy verbatim

* clean up

* more

* now we can format

---------

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

* No need to change order of import

---------

Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-08-01 15:17:37 -07:00
Maxime Desroches 889e386dbc ui: adapt keyboard to raylib touch api (#35875)
* key

* cancel

* more

* wow mypy very usefull as always

* _

* b

* std
2025-08-01 14:07:12 -07:00
Maxime Desroches 4e97a29e83 ui: add icon to Button (#35874)
ico
2025-08-01 12:03:22 -07:00
Adeeb Shihadeh b695715753 sensord: reset LSM (#35872)
* sensord: reset LSM

* they'll be ready in time

* switch to SW_RESET, BOOT not working for some reason
2025-08-01 10:13:39 -07:00
Jason Wen f5991caf6f params: update AthenadPid to use integer type (#35871)
* params: update `AthenadPid` to use integer type

* fix type
2025-08-01 09:29:25 -07:00
Shane Smiskol 2e4de9b7d8 process replay: speed up multi-process replay (#35867)
* holy shit

* benchmark without this main pub drain stuff

* revert

* ??

* actually this is what we want

* what is going on this is python 3.11 sir

* stash

* this is how you dew it

* minor clean up

* fix

* clean up

* clean up

* this is madness!

* typing

* clean up
2025-08-01 03:32:03 -07:00
Shane Smiskol f2c17dd688 process replay: ordered dict is in Python 2025-08-01 03:26:45 -07:00
Shane Smiskol c4298ce287 process replay: create openpilot prefix directories once (#35864)
this is so slow
2025-07-31 23:42:02 -07:00
Maxime Desroches 1de1640689 ui: improve Button widget (#35861)
* bnt

* more

* dup
2025-07-31 22:28:58 -07:00
Adeeb Shihadeh fc58c866c6 AGNOS power monitoring watchdog (#35860)
* AGNOS power monitoring watchdog

* manager should do this
2025-07-31 19:43:21 -07:00
DevTekVE c6b118788b Merge branch 'master-new' into hkg-angle-steering-2025 2025-07-31 18:25:08 +02:00
DevTekVE b004f6dbdc Merge branch 'master-new' into hkg-angle-steering-2025 2025-07-31 18:17:34 +02:00
DevTekVE cd4930b680 Bump opendbc 2025-07-26 10:56:49 +02:00
DevTekVE f861aca628 Refactor vehicle model initialization and adjust angle limits for baseline model 2025-07-26 08:12:06 +02:00
DevTekVE 1ff0d8e2ee please don't bother me anymore! 2025-07-26 08:09:51 +02:00
DevTekVE d76d70764e Update slip factor precision for Hyundai steering parameters
- Adjusted `slip_factor` in Hyundai CANFD safety modes for improved consistency and accuracy.
- Ensured proper representation of `slip_factor` output in test logs.
2025-07-25 20:17:19 +02:00
DevTekVE dc73ce0b71 save tools replay 2025-07-25 19:47:31 +02:00
DevTekVE 3f666748af bump opendbc 2025-07-25 14:16:02 +02:00
DevTekVE 8de8a8838c bumo opendbc 2025-07-25 14:08:53 +02:00
DevTekVE f563b7eb71 Refactor steering angle limit application for improved safety and model compliance 2025-07-25 12:12:42 +02:00
DevTekVE 3c18b83708 save temp 2025-07-25 09:56:30 +02:00
DevTekVE bd35f5904b Enhance steering angle rate limiting and safety enforcement logic
- Introduced explicit post-rate limiting using model-specific dynamics.
- Improved low-speed smoothing and precision of applied angles.
2025-07-25 08:12:41 +02:00
DevTekVE 474f2737f6 Refactor steering angle limit logic for conservative seleion
- Removed unused lateral accel/jerk logic for simplicity.
- Updated angle limit calculations to choose the smallest delta for safer control.
2025-07-24 21:48:24 +02:00
DevTekVE ea1ac4a212 Revert "Add configurable max lateral accel and jerk parameters for Hyundai vehicles"
This reverts commit b95f8c5929.

Revert "Add baseline safety model and improve steering angle limiting logic"

This reverts commit b53cbb2e18.

Revert "Disable lateral accel/jerk params and ensure float consistency in angle limits"

This reverts commit 165d7c7b36.
2025-07-24 10:02:48 +02:00
DevTekVE 165d7c7b36 Disable lateral accel/jerk params and ensure float consistency in angle limits
- Commented out unused lateral accel/jerk parameters for clarity.
- Ensured `np.clip` always returns a float for precision.
2025-07-24 09:41:52 +02:00
DevTekVE b53cbb2e18 Add baseline safety model and improve steering angle limiting logic
- Introduced a baseline safety model (`GENESIS_GV80_2025`) for comparison.
- Enhanced steer angle limit calculation using both baseline and current limits for improved safety and precision.
2025-07-24 09:38:03 +02:00
DevTekVE b95f8c5929 Add configurable max lateral accel and jerk parameters for Hyundai vehicles
- Introduced user-configurable options for max lateral acceleration and jerk.
- Enables fine-tuning of vehicle handling for smoother control.
2025-07-24 08:39:10 +02:00
DevTekVE e564bb0b85 bumo openbc 2025-07-23 18:46:52 +02:00
DevTekVE e2ec8a7b13 Refine lateral control limits and simplify safety model handling
- Reduced max lateral acceleration and jerk by 20% for smoother handling.
- Removed unused `get_safety_CP` function, simplifying `VehicleModel` initialization.
2025-07-22 08:07:51 +02:00
DevTekVE 75c6f0f10e Test with gv80 as baseline for limits 2025-07-20 22:14:18 +02:00
DevTekVE af38044b42 Merge branch 'master-new' into hkg-angle-steering-2025 2025-07-20 10:14:46 +02:00
DevTekVE 684fa846d8 Merge branch 'master-new' into hkg-angle-steering-2025
# Conflicts:
#	.codespellignore
#	opendbc_repo
#	system/manager/manager.py
2025-07-19 21:51:46 +02:00
DevTekVE 416e722855 Update baseline model to IONIQ 5 PE for improved angle safety tuning
- Replaced conservative GENESIS_GV80_2025 model with IONIQ 5 PE parameters.
- Adjusted steering parameters (ratio, slip factor, wheelbase) for better lateral control performance.
2025-07-19 21:46:17 +02:00
DevTekVE 9fd4613bbb Add 2025 Kia EV6 support with updated radar and camera fingerprints 2025-07-09 09:47:31 +02:00
DevTekVE a0362e3c5f "Refined UI labels and tooltips for HKG tuning options to improve clarity and user understanding." 2025-06-29 16:48:53 +02:00
DevTekVE e32ef1cdc0 Fix incorrect torque sign usage in torque reduction calculation
Ensure `actuators.torque` uses its absolute value in the `calculate_angle_torque_reduction_gain` method to prevent sign-related issues during Hyundai steering angle control.
2025-06-29 14:33:30 +02:00
DevTekVE 20673ec8a6 Adjust warning font size in angle tuning settings panel. 2025-06-29 13:35:09 +02:00
DevTekVE 53fbdf7329 Rename "IdleTorque" to "ActiveTorque" for clarity.
The parameter name "HkgTuningAngleIdleTorqueReductionGain" was updated to "HkgTuningAngleActiveTorqueReductionGain" across multiple files for better clarity and alignment with its functionality. This change ensures consistency in naming conventions and improves code readability.
2025-06-29 13:23:56 +02:00
DevTekVE 6f72b74fac Add idle torque reduction for Hyundai lateral control
Introduced `ANGLE_IDLE_TORQUE_REDUCTION_GAIN` to manage torque when the vehicle is stationary, ensuring smoother handling and better lane centering. Updated parsing, parameters, and UI settings to support this new idle torque parameter. Adjusted torque calculation logic and smoothing factor behavior for enhanced control flexibility.
2025-06-29 13:22:18 +02:00
DevTekVE e83705a32e Merge branch 'master-new' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-06-29 09:44:01 +02:00
DevTekVE 95d36b9ba2 Refactor and enhance HKG angle tuning logic.
Introduced a toggle for angle smoothing factor and renamed related parameters for clarity. Refactored backend settings to use new parameter names and expanded smoothing matrices for better tuning granularity. Updated UI elements to reflect these changes, emphasizing usability and consistency.
2025-06-28 21:25:44 +02:00
DevTekVE b9e74254bd Merge branch 'master-new' into hkg-angle-steering-2025 2025-06-27 10:46:22 +02:00
DevTekVE b4405b200d Merge remote-tracking branch 'origin/master-new' into hkg-angle-steering-2025 2025-06-25 09:27:21 +02:00
DevTekVE a8a3fdac54 Rename parameter in calculate_target_torque for clarity. 2025-06-23 22:53:45 +02:00
DevTekVE 4eddc622a7 Refactor torque calculations in Hyundai controller
Rename methods and variables for clarity in torque reduction and override calculations. Adjust logic to streamline handling of steering inputs and improve maintainability.
2025-06-23 22:51:40 +02:00
DevTekVE 4e42ada240 Refactor torque management in Hyundai controller for cleaner override and ramp logic
Extract torque ramping and override functionality into dedicated methods within `LkasTorqueManager` to improve maintainability and reduce redundancy. Simplify `update` logic by delegating state-specific operations to new methods.
2025-06-23 22:47:51 +02:00
DevTekVE 6266217655 Introduce LkasTorqueManager for LKAS torque handling in Hyundai controller
Encapsulate LKAS torque calculations, ramping, and override logic into the new `LkasTorqueManager` class to improve modularity and maintainability. Replace existing torque logic with calls to the manager.
2025-06-23 22:20:10 +02:00
DevTekVE ea09d32e98 Remove lateral acceleration logic from Hyundai steering controller 2025-06-23 21:55:17 +02:00
DevTekVE 15958c88d3 Refactor lateral acceleration scaling logic in Hyundai controller
Move scaling of `max_angle_delta` under high lateral acceleration to improve clarity and prevent redundant operations.
2025-06-23 20:22:03 +02:00
DevTekVE b80d7fb5ea Adding GV70 electrified 2026 2025-06-22 14:30:59 +02:00
DevTekVE 1c3d25c6ff Refine lateral acceleration handling in Hyundai steering logic
Enforce absolute check for `real_a_lat` against `MAX_LATERAL_ACCEL` to improve angle scaling under high lateral acceleration conditions.
2025-06-22 11:03:35 +02:00
rav4kumar c9f22b32c7 Revert "Incorporate lateral acceleration in Hyundai angle steering logic"
This reverts commit c0524985bb.
2025-06-21 11:40:58 -07:00
DevTekVE c0524985bb Incorporate lateral acceleration in Hyundai angle steering logic
Add handling for IMU lateral acceleration to refine steering angle limits in CAN FD configurations. Parse and utilize `IMU_LatAccelVal` signal for enhanced lateral control accuracy.
2025-06-21 17:34:13 +02:00
DevTekVE 83839c7ea7 Add angle steering support and refactor related logic for Hyundai CAN FD.
Introduced support for CAN FD angle steering, including updated parameters, signal parsing, and new tests. Refactored related steering logic for clarity, reducing unused code and enhancing maintainability.
2025-06-21 13:38:50 +02:00
DevTekVE c1a1d4b4c3 Update lint script to exclude .xml files in layouts directory
Added `layouts/.*\.xml` to `IGNORED_FILES` in `lint.sh` to prevent linting of layout XML files.
2025-06-20 10:50:45 +02:00
DevTekVE b5af7a905a Merge branch 'master-new' into hkg-angle-steering-2025 2025-06-18 20:13:45 +02:00
DevTekVE 96b1b2f55f Update steering request logic in Hyundai controller
Ensure steering request activation depends on lateral control being active. This adds clarity and aligns better with control logic requirements.
2025-06-17 18:51:54 +02:00
DevTekVE 9361ba5d70 Refactor Hyundai steering angle handling logic
Streamline steering angle calculations and fault avoidance logic by removing redundant comments and unused code. Simplified `round_angle` implementation for clarity and consistency.
2025-06-17 12:08:06 +02:00
DevTekVE 4e9014311e Refactor steeringPressed logic in Hyundai carstate.py.
Revised the determination of `steeringPressed` to account for both hands-on-wheel detection and torque overriding in CAN FD setups. Simplified fallback logic for non-CAN FD configurations for better code clarity and maintainability.
2025-06-12 00:31:07 +02:00
DevTekVE 232873fc70 Refine steering press detection logic.
Adjusted the sensitivity and threshold values for `HOD_Dir_Status` in steering press updates, improving accuracy in detecting steering input. This change aligns with updated parameter requirements for better responsiveness.
2025-06-12 00:14:05 +02:00
DevTekVE 0a61fca9c9 Fix steering press detection for Hyundai models.
Updated the condition to detect steering press by changing HOD_Dir_Status threshold from `> 2` to `>= 2`. This ensures the detection logic aligns correctly with expected behavior.
2025-06-12 00:06:52 +02:00
DevTekVE 480bdc34dc Add support for CANFD angle steering in Hyundai cars
Introduced handling for the `HOD_FD_01_100ms` message when the CANFD angle steering flag is enabled. This ensures proper message parsing and extends compatibility for specific Hyundai vehicle configurations.
2025-06-12 00:04:44 +02:00
DevTekVE 716b475a13 Update Hyundai controls for HOD status and steer limits
Adjusted the steering override frame window and incorporated new HOD_Dir_Status to improve hands-on detection. Added parsing for new signals in Hyundai CAN FD, enhancing steering override responsiveness and reliability.
2025-06-12 00:01:31 +02:00
DevTekVE b1ec5ec034 Adjust override angle cap in Hyundai car controller
Increased the minimum override angle cap from 0.01 to 0.1 and explicitly cast the maximum cap to a float. This change improves consistency and ensures proper handling of steering limits.
2025-06-11 23:20:19 +02:00
DevTekVE 470613c2b7 Adjust Hyundai steer override parameters for improved control.
Reduced the override frame window and updated the angle cap logic to use MAX_ANGLE_RATE. These changes aim to enhance steering responsiveness and safety by fine-tuning steer angle limits.
2025-06-11 23:07:49 +02:00
DevTekVE 336c5b4154 Remove smoothing_factor from Hyundai car controller logic
The `smoothing_factor` parameter and related logic have been removed to simplify the steering angle smoothing approach. All references and usage of this parameter have been eliminated, relying solely on speed-based dynamic interpolation. This change streamlines the code while maintaining functionality.
2025-06-11 23:04:32 +02:00
DevTekVE abdb9dc750 Adjust Hyundai steering override frame logic
Reduced `OVERRIDE_FRAME_WINDOW` and updated condition to properly respect override frame limits. This ensures smoother handling and more precise steering adjustments under certain driving scenarios.
2025-06-11 22:49:04 +02:00
DevTekVE ab98683973 Refactor steering override logic in Hyundai carcontroller
Replaced `recently_overridden` with `frames_since_override` for better granularity and added dynamic override angle limits using interpolation. These changes enhance steering control accuracy during user overrides and improve overall code readability.
2025-06-11 22:42:05 +02:00
DevTekVE 186c24dbe6 Refine Hyundai steering override handling logic
Adjusted logic for recently overridden steering to improve angle limits and torque smoothing. Removed unused or redundant code, optimizing the functionality and maintaining cleaner readability.
2025-06-11 21:49:18 +02:00
DevTekVE 9cdf6340a1 Refactor steering angle smoothing for clarity and reuse.
Extracted the steering angle smoothing logic into a standalone function `sp_smooth_angle` to enhance readability and reusability. Adjusted angle smoothing parameters and introduced a maximum vehicle speed threshold for applying smoothing. Minor updates improve maintainability and ensure consistent behavior across speed ranges.
2025-06-11 10:07:44 +02:00
DevTekVE 2855b1341c Adjust steering thresholds for Hyundai CAN FD vehicles
Updated `STEER_THRESHOLD` to 350 and `NO_LONGER_OVERRIDING_THRESHOLD` to 150 for better alignment with Hyundai CAN FD steering behavior. These changes ensure improved compatibility and more accurate steering response.
2025-06-10 09:53:08 +02:00
DevTekVE cf28f99976 Revert "Add twilsonco's LKAS torque calculator for improved lateral control"
This reverts commit b1770fb0e7aece0e160b1b083cb260edbbdc53dd.
2025-06-10 09:40:59 +02:00
DevTekVE a39d67dc47 Fix apply_angle_last reset logic in Hyundai carcontroller
Re-enables resetting `apply_angle_last` to `steering_angle` when steering is recently overridden. This ensures proper handling of steering angle limits during transitions.
2025-06-08 19:04:02 +02:00
DevTekVE 7e75257f12 Refine Hyundai steering control logic.
Simplified torque ramp-up logic by combining conditions and adjusted `STEER_THRESHOLD` for CANFD angle steering. These changes aim to enhance control precision and maintain consistency in overrides.
2025-06-08 19:02:49 +02:00
DevTekVE df38449553 Reduce override timeout for Hyundai carcontroller
Decrease the override timeout from 100 to 50 frames, ensuring quicker recognition of driver input override. This improves responsiveness and aligns with refined control behavior.
2025-06-08 18:22:44 +02:00
DevTekVE 1385ef3bc5 Fix steering control behavior during user override
Removed restrictive rate limiting during recent user overrides to improve steering response. Adjusted logic to ensure correct handling of steering angle when lateral control is inactive or overridden.
2025-06-08 18:01:47 +02:00
DevTekVE 7c23c11c51 Refine steering logic with override detection.
Adjust steering behavior to account for recent user overrides, improving safety and control. Introduced a "recently_overridden" check to limit angle rates and torque adjustments when user intervention is detected.
2025-06-08 17:52:16 +02:00
DevTekVE aeff2e12ec Refine steering logic with user override handling.
Added logic to use the current steering angle when the steering wheel is pressed, ensuring smoother transitions during user overrides. Updated function parameters and implementation to reflect this enhancement.
2025-06-08 17:38:34 +02:00
DevTekVE 7274899671 Refactor Hyundai override logic for steering thresholds
Removed redundant `recently_overridden` logic and introduced a more robust approach for tracking user steering overrides. Added `NO_LONGER_OVERRIDING_THRESHOLD` and updated conditions to improve steer override handling. Adjustments ensure smoother torque transitions and more accurate steering state detection.
2025-06-08 17:00:44 +02:00
DevTekVE 6c00fd608f pass tests? 2025-06-08 12:28:11 +02:00
DevTekVE df6a034c11 Bump opendbc 2025-06-08 12:26:09 +02:00
DevTekVE acb109c290 adding plotjuggler stuff 2025-06-08 10:02:44 +02:00
DevTekVE b227b00249 Update torque clamping to use parameterized min torque
Replaced hardcoded `angle_min_active_torque` with `ANGLE_MIN_TORQUE` from params for better configurability and consistency. This ensures the torque clamping logic aligns with defined parameters.
2025-06-07 19:43:01 +02:00
DevTekVE 3ec9d6c18a Merge branch 'master-new' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
2025-06-07 15:04:16 +02:00
DevTekVE 86db8b95f0 Refactor torque calculation and deactivate live tuning.
Updated torque calculation logic with a new optional parameter for minimum active torque, streamlining control behavior. Deactivated and cleaned up references to HkgAngleLiveTuning, simplifying configuration and reducing runtime complexities. Updated relevant UI and parameter descriptions for clarity.
2025-06-07 11:55:57 +02:00
DevTekVE 4cfff8a35f Merge branch 'master-new' into hkg-angle-steering-2025 2025-06-06 23:08:37 +02:00
DevTekVE 962fedf48c Merge branch 'master-new' into hkg-angle-steering-2025
# Conflicts:
#	opendbc/car/tests/routes.py
2025-06-06 20:49:06 +02:00
DevTekVE 04494414d1 Merge branch 'master-new' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-06-05 09:18:53 +02:00
DevTekVE 0b83576e9b Adjust torque ramping logic and update steering thresholds
Increase the override window and refine torque ramp-up behavior to avoid conflicts during recent overrides. Updated steering driver allowance and threshold values for CANFD angle steering to improve compatibility and performance.
2025-06-02 09:49:31 +02:00
DevTekVE ce4ef0f817 Refine steering override logic in Hyundai car controller
Added logic to track recent steering overrides and adjust LKAS torque behavior accordingly. This ensures smoother transitions when the steering is overridden and reduces potential conflicts with driver input. Updated CANFD-specific steering thresholds for enhanced compatibility.
2025-06-02 09:12:03 +02:00
DevTekVE f0b15c1c56 Adding twil's torque calculation 2025-06-01 19:04:34 +02:00
DevTekVE f898e9fdfe Merge branch 'master-new' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-06-01 09:46:45 +02:00
DevTekVE 8ee7804b0e Bump opendbc (no tesla controls, no twil yet) 2025-05-29 16:31:42 +02:00
DevTekVE 923228194e bump opendbc to prior tesla changes until i can pass safety validations 2025-05-28 12:44:44 +02:00
DevTekVE 8837b2e3f6 Merge branch 'master-new' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-05-28 12:36:02 +02:00
DevTekVE f48c9dc1c2 bump opendbc 2025-05-25 17:34:24 +02:00
DevTekVE 74aa07a8cd Ingore something i dont control thx 2025-05-25 17:31:37 +02:00
DevTekVE 5236e4860f Make lint happy, maybe 2025-05-25 17:31:37 +02:00
DevTekVE 3d174da1c3 adding some of my tests and validaitons 2025-05-25 17:31:25 +02:00
DevTekVE 8faa40f3a3 clean 2025-05-25 17:31:25 +02:00
DevTekVE 3e03275f28 Add PlotJuggler layout for analyzing torque and angle data
This new layout visualizes actuator data, CAN steering messages, and car state variables. It provides multiple time-series plots to aid in debugging and analysis. Plugin configurations are also included for extended functionality.
2025-05-25 17:31:22 +02:00
DevTekVE 7595cf8a25 Refine Hyundai angle and torque control logic.
Simplified control flag handling for angle steering, adjusted torque calculations for smoother ramp rates, and updated tuning parameters for the Hyundai Ioniq 5 PE. Minor adjustment to return value handling in lateral control functions.
2025-05-25 17:31:22 +02:00
DevTekVE 2675d43adb bump opendbc
Remove duplicate STEER_ANGLE_SATURATION_THRESHOLD import

Cleaned up an unnecessary duplicate import of STEER_ANGLE_SATURATION_THRESHOLD from latcontrol_angle_torque. This simplifies the module imports and prevents potential redundancy or confusion.

Refactor lateral control to combine torque and angle logic

Merged functionalities of LatControlTorque and LatControlAngle into a single LatControlAngleTorque class. Refactored code to utilize methods from both parent classes, reducing duplication and improving maintainability.

Add angle-torque hybrid lateral control for Hyundai CAN FD

Introduces `LatControlAngleTorque` to enable hybrid angle and torque-based steering for specific Hyundai models. Updates related logic in carcontroller, interface, and controlsd to accommodate this new lateral control method. Adjusts torque parameters for enhanced control in supported models.
2025-05-25 17:31:21 +02:00
DevTekVE d9f4ce82e6 clean 2025-05-25 17:31:21 +02:00
DevTekVE 648a1845d8 cleanup the mess 2025-05-25 17:31:21 +02:00
DevTekVE a87eff6d1c Add HKG Angle Live Tuning parameter and update related handling 2025-05-25 17:31:21 +02:00
DevTekVE dd6ad37e23 Absolutely zero clue on this, I did it with AI and it's for me to play. Don't take this notebook seriously please 2025-05-25 17:31:21 +02:00
DevTekVE c5e778b939 How annoying the linter on a comment lol 2025-05-25 17:31:21 +02:00
DevTekVE a9ab81a77a useless but should keep linter happy 2025-05-25 17:31:21 +02:00
DevTekVE 2d40e1d8e5 Refactor torque parameter handling in Hyundai carcontroller
Replaced direct access to `params` with instance variables for torque parameters to improve code clarity and maintainability. Updated smoothing factor description in angle tuning settings to include speed-related behavior. This enhances readability and prepares for further tuning adjustments.
2025-05-25 17:31:20 +02:00
DevTekVE 7c8f367a5d Fix data type for HkgTuningOverridingCycles value
Updated the value of HkgTuningOverridingCycles to a string for consistency with other parameters in the tuning configuration. This ensures proper handling and avoids potential issues with type mismatches.

Add overriding cycles parameter for torque adjustment

Introduced "HkgTuningOverridingCycles" for configurable user override torque ramp-down cycles. Updated relevant logic in torque control and UI settings to handle the new parameter. This improves flexibility in adjusting steering torque override behavior.
2025-05-25 17:31:20 +02:00
DevTekVE ff4cf558aa Add HKG angle tuning settings with min/max torque parameters
Introduce separate angle tuning controls for HKG vehicles, including smoothing factor, min torque, and max torque parameters. Refactor developer panel to integrate the new settings into a dedicated UI panel, enhancing modularity and customization capabilities.
2025-05-25 17:31:20 +02:00
DevTekVE 0e151e51bc Update HKG Angle Smoothing Factor description in Developer Panel
Enhanced the description to clarify its effect on steering behavior. Included details on how the smoothing factor impacts steering smoothness using EMA, aiding user understanding.
2025-05-25 17:31:20 +02:00
DevTekVE 60cc0031b0 Revert "Revert "Revert the EMA calculation on the curvature to test another approach""
This reverts commit 58fcda8c
2025-05-25 17:31:20 +02:00
DevTekVE d24cac0998 Refactor steering angle logic for smoother control adjustments
Refactored the calculation and application of the steering angle to improve code clarity and ensure smoother transitions. Removed unused parameter update logic in `latcontrol_angle.py` and enhanced handling of driver overrides in `carcontroller.py`.
2025-05-25 17:31:20 +02:00
DevTekVE 45d110830c Fix typo in parameter access method.
Replaced `self._params` with `self.params` to correctly access the parameter `HkgTuningAngleSmoothingFactor`. This ensures the smoothing factor is updated as intended during the control loop.
2025-05-25 17:31:20 +02:00
DevTekVE ea3a9ae911 Improve angle smoothing by integrating dynamic parameter tuning
Introduced a dynamic smoothing factor using the `HkgTuningAngleSmoothingFactor` parameter. This allows more granular control over curvature smoothing based on customizable user input, enhancing driving smoothness. Added necessary logic to process and apply this parameter efficiently.
2025-05-25 17:31:19 +02:00
DevTekVE 6bff8c0e7c Revert "Revert the EMA calculation on the curvature to test another approach"
This reverts commit bd471b3498.
2025-05-25 17:31:19 +02:00
DevTekVE bc6b8802b8 Add HKG angle smoothing factor for steering adjustments
Introduced a new parameter, `HkgTuningAngleSmoothingFactor`, to apply exponential moving average (EMA) smoothing to steering angle changes, reducing sudden adjustments. Added associated UI controls, parameter persistence, and integration into Hyundai carcontroller logic for improved steering stability.
2025-05-25 17:31:19 +02:00
DevTekVE 252ef572d3 Revert the EMA calculation on the curvature to test another approach 2025-05-25 17:31:19 +02:00
DevTekVE 414d397e3f Handle missing pygame import gracefully
Wrap the pygame import in a try-except block to catch ImportError. This prevents the script from crashing and provides a clear message prompting the user to install pygame if it's missing.

Remove "inputs" package and update "pygame" dependency

The "inputs" package has been removed from the lockfile and dependency list, while "pygame" is now included universally without the "dev" extra marker. This change simplifies dependencies and ensures consistency across environments.

Update dependencies: replace 'inputs' with 'pygame'

Replaced the 'inputs' library with 'pygame' for joystickd dependencies in `pyproject.toml`. Additionally, removed a redundant 'pygame' entry from the general dependencies.

Ugly, I know, but soundd is unhappy with joystick

Allowing lat with mads

Invert steering input for joystick control

The steering axis input is now multiplied by -1 to reverse its direction. This ensures correct handling of the left stick's horizontal input, aligning behavior with expected control dynamics.

Refactor joystick control to use pygame for broader support

Replaced the `inputs` library with `pygame` for joystick handling, providing improved compatibility with Xbox and PlayStation controllers. Added initialization, adaptive mappings, deadzone handling, and enhanced event processing for robust joystick operation. Updated README with dependencies and usage information for Xbox controllers.
2025-05-25 17:31:19 +02:00
DevTekVE 11b7b3789d Adjust speed thresholds in filter_speed_matrox.
Updated the `filter_speed_matrox` values to improve curvature filtering behavior at different speeds. This change ensures better handling and stability across a wider range of driving conditions.
2025-05-25 17:31:19 +02:00
DevTekVE 871ac53717 Optimize curvature filtering by adding speed-dependent logic.
Introduced speed-based dynamic alpha adjustment using interpolation for smoother curvature filtering. This improves steering angle calculations by adapting filter sensitivity to vehicle speed, enhancing control performance.
2025-05-25 17:31:19 +02:00
DevTekVE 64ea66b6e6 chsnge alpha to nicer value 2025-05-25 17:31:19 +02:00
DevTekVE 6d7c6759b3 Adjust curvature handling and filtering parameters
Updated curvature breakpoints and torque scaling for improved control in sharp turns. Increased filter alpha for faster curvature response while maintaining system stability.
2025-05-25 17:31:18 +02:00
DevTekVE 4cea013570 Adjust curvature handling and filtering parameters
Updated curvature breakpoints in Hyundai carcontroller to improve torque scaling for curved driving. Slightly refined the filter coefficient in lateral control for smoother curvature filtering and more accurate steering adjustments.
2025-05-25 17:31:18 +02:00
DevTekVE eb375c0587 Refactor curvature-based steering angle and torque logic.
Introduced dynamic torque scaling based on curvature for smoother and more adaptive steering control. Replaced raw curvature inputs with filtered curvature for enhanced stability and reduced noise in steering angle calculations. Removed unused speed scaling logic to simplify the lateral control flow.
2025-05-25 17:31:18 +02:00
DevTekVE 7fd8a5a4bd Reapply "Significant improvement on the jerkiness"
This reverts commit 85ce84e7b7.
2025-05-25 17:31:18 +02:00
DevTekVE b3c90216bb Revert "Significant improvement on the jerkiness"
This reverts commit ea1af879ba2905b076ccfe65993a9db701d689dd.

Revert "More improvement but still not quite"

This reverts commit ad95493c5c61b2ace7c459d2ebc151ddaa80040f.

Revert "Adjust low-speed scaling for lateral control angle"

This reverts commit 6f789ac1ebb66b0239b4028303573c2d7d386b39.

Revert "Refactor speed-based steering scaling logic."

This reverts commit 1d40735ab8db8d470ff3b287a6b42847beffff7d.
2025-05-25 17:31:18 +02:00
DevTekVE 10f345f956 Refactor speed-based steering scaling logic.
Updated the steering angle computation to use a clearer and more descriptive speed-scaling configuration. Replaced low-speed-specific logic with a generalized approach based on speed breakpoints and corresponding influence factors. This improves maintainability and ensures smoother steering adjustments at varying speeds.
2025-05-25 17:31:18 +02:00
DevTekVE 956d2c36d0 Adjust low-speed scaling for lateral control angle
Refined the low-speed scaling parameters by modifying speed breakpoints and factors. This improves handling at lower speeds for smoother and more predictable behavior.
2025-05-25 17:31:18 +02:00
DevTekVE 55e688b6f2 More improvement but still not quite 2025-05-25 17:31:17 +02:00
DevTekVE f017954027 Significant improvement on the jerkiness 2025-05-25 17:31:17 +02:00
DevTekVE 7e992d11b1 bump panda and opendbc 2025-05-25 17:31:15 +02:00
204 changed files with 20510 additions and 4279 deletions
+1
View File
@@ -2,4 +2,5 @@ Wen
REGIST
PullRequest
cancelled
indeces
FOF
+1 -1
View File
@@ -1,6 +1,6 @@
ci:
- changed-files:
- any-glob-to-all-files: "{.github/**,**/test_*,Jenkinsfile}"
- any-glob-to-all-files: "{.github/**,**/test_*,**/test/**,Jenkinsfile}"
chore:
- changed-files:
@@ -16,7 +16,24 @@ jobs:
recompiled_dir: ${{ steps.create-recompiled-dir.outputs.recompiled_dir }}
json_file: ${{ steps.get-json.outputs.json_file }}
model_matrix: ${{ steps.set-matrix.outputs.model_matrix }}
tinygrad_ref: ${{ steps.get-tinygrad-ref.outputs.tinygrad_ref }}
steps:
- name: Checkout sunnypilot repo
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot
path: sunnypilot
submodules: recursive
- name: Get tinygrad_repo ref
id: get-tinygrad-ref
run: |
cd sunnypilot
export PYTHONPATH=$(pwd)
ref=$(python3 sunnypilot/models/tinygrad_ref.py)
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
echo "tinygrad_ref is $ref"
- name: Checkout docs repo (sunnypilot-docs, gh-pages)
uses: actions/checkout@v4
with:
@@ -110,7 +127,7 @@ jobs:
retry_failed_models:
needs: [setup, get_and_build]
runs-on: ubuntu-latest
if: ${{ needs.setup.result != 'failure' && (failure() && !cancelled()) }}
if: ${{ needs.setup.result != 'failure' && !cancelled() }}
outputs:
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
steps:
@@ -269,6 +286,7 @@ jobs:
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" \
@@ -42,11 +42,11 @@ on:
recompiled_dir:
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
required: true
type: number
type: string
json_version:
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
required: true
type: number
type: string
model_folder:
description: 'Model folder'
type: choice
@@ -67,11 +67,11 @@ on:
generation:
description: 'Model generation'
required: false
type: number
type: string
version:
description: 'Minimum selector version'
required: false
type: number
type: string
env:
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
+3 -3
View File
@@ -21,8 +21,8 @@ concurrency:
env:
PYTHONWARNINGS: error
BASE_IMAGE: sunnypilot-base
BUILD: release/ci/docker_build_sp.sh base
BASE_IMAGE: openpilot-base
BUILD: selfdrive/test/docker_build.sh base
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
jobs:
@@ -59,7 +59,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
repository: 'sunnypilot/sunnypilot'
repository: 'commaai/openpilot'
submodules: true
ref: "refs/heads/master"
- uses: ./.github/workflows/setup-with-retry
+14
View File
@@ -9,6 +9,9 @@ jobs:
scan-comments:
runs-on: ubuntu-latest
if: ${{ github.event.issue.pull_request }}
permissions:
contents: write
issues: write
steps:
- name: Check for trigger phrase
id: check_comment
@@ -43,3 +46,14 @@ jobs:
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b tmp-jenkins-${{ github.event.issue.number }}
GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
- name: Delete trigger comment
if: steps.check_comment.outputs.result == 'true' && always()
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
});
+2 -1
View File
@@ -189,7 +189,8 @@ jobs:
- name: Run unit tests
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }}
run: |
${{ env.RUN }} "$PYTEST --collect-only -m 'not slow' &> /dev/null && \
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
$PYTEST --collect-only -m 'not slow' &> /dev/null && \
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
+2
View File
@@ -13,9 +13,11 @@ venv/
model2.png
a.out
.hypothesis
.cache/
/docs_site/
*.mp4
*.dylib
*.DSYM
*.d
+7
View File
@@ -21,5 +21,12 @@
</clean>
</configuration>
</target>
<target id="f2590b2b-9b93-49f9-8510-da3f3724a2ae" name="replay" defaultType="TOOL">
<configuration id="d475264f-6f4c-4092-9b4e-6773309f38b7" name="replay" toolchainName="Default">
<build type="TOOL">
<tool actionId="Tool_External Tools_uv build tools replay" />
</build>
</configuration>
</target>
</component>
</project>
+7
View File
@@ -20,4 +20,11 @@
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
<tool name="uv build tools replay" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec>
<option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) tools/replay/&quot;" />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
</toolSet>
+1 -1
View File
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="sunnypilot" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<envs>
<env name="QT_DBL_CLICK_DIST" value="150" />
</envs>
+27
View File
@@ -0,0 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Debug Route Controls" type="PythonConfigurationType" factoryName="Python">
<module name="openpilot-special" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="FINGERPRINT" value="KIA_EV9" />
<env name="SKIP_FW_QUERY" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/selfdrive/car" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/selfdrive/car/card.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Replay for controls + ui" type="Multirun" separateTabs="false" reuseTabsWithFailures="false" startOneByOne="true" markFailedProcess="true" hideSuccessProcess="false" delayTime="0.0">
<runConfiguration name="replay for controls" type="Native Application" />
<runConfiguration name="Build Debug" type="Custom Build Application" />
<method v="2" />
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="replay for controls" type="CLionNativeAppRunConfigurationType" focusToolWindowBeforeRun="true" PROGRAM_PARAMS="&quot;$Prompt$&quot; --block &quot;sendcan,carState,carParams,carOutput,liveTracks,carParamsSP,carStateSP&quot;" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="true" WORKING_DIR="file://$ProjectFileDir$/tools/replay" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="replay" CONFIG_NAME="replay" version="1" RUN_PATH="replay">
<method v="2">
<option name="CLION.COMPOUND.BUILD" enabled="true" />
</method>
</configuration>
</component>
+11 -4
View File
@@ -1,10 +1,17 @@
Version 0.10.0 (2025-07-07)
Version 0.10.1 (2025-09-08)
========================
Version 0.10.0 (2025-08-05)
========================
* New driving model
* Lead car ground-truth fixes
* Ported over VAE from the MLSIM stack
* New training architecture described in CVPR paper
* New training architecture
* Architecture outlined in CVPR paper: "Learning to Drive from a World Model"
* Longitudinal MPC replaced by E2E planning from worldmodel in experimental mode
* Action from lateral MPC as training objective replaced by E2E planning from worldmodel
* Low-speed lead car ground-truth fixes
* Enable live-learned steering actuation delay
* Record driving feedback using LKAS button when MADS is disabled
* Opt-in audio recording for dashcam video
Version 0.9.9 (2025-05-23)
+1 -1
View File
@@ -17,7 +17,7 @@ AGNOS = TICI
Decider('MD5-timestamp')
SetOption('num_jobs', int(os.cpu_count()/2))
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
AddOption('--kaitai',
action='store_true',
+13 -3
View File
@@ -127,8 +127,9 @@ struct OnroadEvent @0xc4fa6047f024e718 {
espActive @90;
personalityChanged @91;
aeb @92;
userFlag @95;
userBookmark @95;
excessiveActuation @96;
audioFeedback @97;
soundsUnavailableDEPRECATED @47;
}
@@ -2468,7 +2469,7 @@ struct DebugAlert {
alertText2 @1 :Text;
}
struct UserFlag {
struct UserBookmark @0xfe346a9de48d9b50 {
}
struct SoundPressure @0xdc24138990726023 {
@@ -2486,6 +2487,11 @@ struct AudioData {
sampleRate @1 :UInt32;
}
struct AudioFeedback {
audio @0 :AudioData;
blockNum @1 :UInt16;
}
struct Touch {
sec @0 :Int64;
usec @1 :Int64;
@@ -2586,9 +2592,13 @@ struct Event {
mapRenderState @105: MapRenderState;
# UI services
userFlag @93 :UserFlag;
uiDebug @102 :UIDebug;
# driving feedback
userBookmark @93 :UserBookmark;
bookmarkButton @148 :UserBookmark;
audioFeedback @149 :AudioFeedback;
# *********** debug ***********
testJoystick @52 :Joystick;
roadEncodeData @86 :EncodeData;
+1 -1
View File
@@ -177,8 +177,8 @@ class TestMessaging:
# wait 5 socket timeouts before sending
msg = random_carstate()
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
start_time = time.monotonic()
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
recvd = messaging.recv_one_retry(sub_sock)
assert (time.monotonic() - start_time) >= sock_timeout*5
assert isinstance(recvd, capnp._DynamicStructReader)
@@ -86,7 +86,7 @@ class TestSubMaster:
"cameraOdometry": (20, 10),
"liveCalibration": (4, 4),
"carParams": (None, None),
"userFlag": (None, None),
"userBookmark": (None, None),
}
for service, (max_freq, min_freq) in checks.items():
+3 -1
View File
@@ -72,9 +72,11 @@ _services: dict[str, tuple] = {
"navRoute": (True, 0.),
"navThumbnail": (True, 0.),
"qRoadEncodeIdx": (False, 20.),
"userFlag": (True, 0., 1),
"userBookmark": (True, 0., 1),
"soundPressure": (True, 10., 10),
"rawAudioData": (False, 20.),
"bookmarkButton": (True, 0., 1),
"audioFeedback": (True, 0., 1),
# sunnypilot
"modelManagerSP": (False, 1., 1),
@@ -1,6 +1,7 @@
import numpy as np
class Conversions:
# conversions
class CV:
# Speed
MPH_TO_KPH = 1.609344
KPH_TO_MPH = 1. / MPH_TO_KPH
@@ -17,3 +18,6 @@ class Conversions:
# Mass
LB_TO_KG = 0.453592
ACCELERATION_DUE_TO_GRAVITY = 9.81 # m/s^2
+1 -1
View File
@@ -1 +1 @@
#define DEFAULT_MODEL "Space Lab 2 (Default)"
#define DEFAULT_MODEL "Down To Ride (Default)"
+16 -8
View File
@@ -12,7 +12,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ApiCache_Device", {PERSISTENT, STRING}},
{"ApiCache_FirehoseStats", {PERSISTENT, JSON}},
{"AssistNowToken", {PERSISTENT, STRING}},
{"AthenadPid", {PERSISTENT, STRING}},
{"AthenadPid", {PERSISTENT, INT}},
{"AthenadUploadQueue", {PERSISTENT, JSON}},
{"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}},
{"BootCount", {PERSISTENT, INT}},
@@ -105,6 +105,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
{"PrimeType", {PERSISTENT, INT}},
{"RecordAudio", {PERSISTENT | BACKUP, BOOL}},
{"RecordAudioFeedback", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RecordFront", {PERSISTENT | BACKUP, BOOL}},
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
{"SecOCKey", {PERSISTENT | DONT_LOG | BACKUP, STRING}},
@@ -139,7 +140,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CarParamsSP", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
{"CarParamsSPCache", {CLEAR_ON_MANAGER_START, BYTES}},
{"CarParamsSPPersistent", {PERSISTENT, BYTES}},
{"CarPlatformBundle", {PERSISTENT | BACKUP, STRING}},
{"CarPlatformBundle", {PERSISTENT | BACKUP, JSON}},
{"ChevronInfo", {PERSISTENT | BACKUP, INT, "4"}},
{"CustomAccIncrementsEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
@@ -162,11 +163,11 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"MadsUnifiedEngagementMode", {PERSISTENT | BACKUP, BOOL, "1"}},
// Model Manager params
{"ModelManager_ActiveBundle", {PERSISTENT, STRING}},
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, STRING}},
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
// Neural Network Lateral Control
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -177,7 +178,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SunnylinkCache_Roles", {PERSISTENT, STRING}},
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
{"SunnylinkDongleId", {PERSISTENT, STRING}},
{"SunnylinkdPid", {PERSISTENT, STRING}},
{"SunnylinkdPid", {PERSISTENT, INT}},
{"SunnylinkEnabled", {PERSISTENT, BOOL}},
// Backup Manager params
@@ -192,19 +193,19 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// model panel params
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
{"LagdToggleDesc", {PERSISTENT, STRING}},
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
// mapd
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
{"MapdVersion", {PERSISTENT, STRING, ""}},
{"MapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT, "0.0"}},
{"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
{"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}},
{"OsmDbUpdatesCheck", {CLEAR_ON_MANAGER_START, BOOL}}, // mapd database update happens with device ON, reset on boot
{"OSMDownloadBounds", {PERSISTENT, STRING}},
{"OsmDownloadedDate", {PERSISTENT, STRING, "0.0"}},
{"OSMDownloadLocations", {PERSISTENT, STRING}},
{"OSMDownloadLocations", {PERSISTENT, JSON}},
{"OSMDownloadProgress", {CLEAR_ON_MANAGER_START, JSON}},
{"OsmLocal", {PERSISTENT, BOOL}},
{"OsmLocationName", {PERSISTENT, STRING}},
@@ -214,4 +215,11 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OsmStateTitle", {PERSISTENT, STRING}},
{"OsmWayTest", {PERSISTENT, STRING}},
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING, ""}},
// Tuning keys
{"EnableHkgTuningAngleSmoothingFactor", {PERSISTENT | BACKUP, BOOL, "1"}},
{"HkgTuningAngleMinTorqueReductionGain", {PERSISTENT | BACKUP, INT, "10"}},
{"HkgTuningAngleMaxTorqueReductionGain", {PERSISTENT | BACKUP, INT, "100"}},
{"HkgTuningAngleActiveTorqueReductionGain", {PERSISTENT | BACKUP, INT, "100"}},
{"HkgTuningOverridingCycles", {PERSISTENT | BACKUP, INT, "17"}},
};
+10 -5
View File
@@ -103,14 +103,14 @@ cdef class Params:
return cast(value)
raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}")
def cpp2python(self, t, value, default, key):
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)
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)
@@ -127,8 +127,8 @@ cdef class Params:
# 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)
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)
@@ -189,4 +189,9 @@ cdef class Params:
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
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)
+8 -17
View File
@@ -17,7 +17,6 @@ class PIDController:
self.pos_limit = pos_limit
self.neg_limit = neg_limit
self.i_unwind_rate = 0.3 / rate
self.i_rate = 1.0 / rate
self.speed = 0.0
@@ -35,10 +34,6 @@ class PIDController:
def k_d(self):
return np.interp(self.speed, self._k_d[0], self._k_d[1])
@property
def error_integral(self):
return self.i/self.k_i
def reset(self):
self.p = 0.0
self.i = 0.0
@@ -46,25 +41,21 @@ class PIDController:
self.f = 0.0
self.control = 0
def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False):
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
self.speed = speed
self.p = float(error) * self.k_p
self.f = feedforward * self.k_f
self.d = error_rate * self.k_d
if override:
self.i -= self.i_unwind_rate * float(np.sign(self.i))
else:
if not freeze_integrator:
self.i = self.i + error * self.k_i * self.i_rate
if not freeze_integrator:
i = self.i + error * self.k_i * self.i_rate
# Clip i to prevent exceeding control limits
control_no_i = self.p + self.d + self.f
control_no_i = np.clip(control_no_i, self.neg_limit, self.pos_limit)
self.i = np.clip(self.i, self.neg_limit - control_no_i, self.pos_limit - control_no_i)
# Don't allow windup if already clipping
test_control = self.p + i + self.d + self.f
i_upperbound = self.i if test_control > self.pos_limit else self.pos_limit
i_lowerbound = self.i if test_control < self.neg_limit else self.neg_limit
self.i = np.clip(i, i_lowerbound, i_upperbound)
control = self.p + self.i + self.d + self.f
self.control = np.clip(control, self.neg_limit, self.pos_limit)
return self.control
+12 -6
View File
@@ -9,20 +9,19 @@ from openpilot.system.hardware.hw import Paths
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
class OpenpilotPrefix:
def __init__(self, prefix: str = None, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
self.msgq_path = os.path.join(Paths.shm_path(), self.prefix)
self.create_dirs_on_enter = create_dirs_on_enter
self.clean_dirs_on_exit = clean_dirs_on_exit
self.shared_download_cache = shared_download_cache
def __enter__(self):
self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None)
os.environ['OPENPILOT_PREFIX'] = self.prefix
try:
os.mkdir(self.msgq_path)
except FileExistsError:
pass
os.makedirs(Paths.log_root(), exist_ok=True)
if self.create_dirs_on_enter:
self.create_dirs()
if self.shared_download_cache:
os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT
@@ -40,6 +39,13 @@ class OpenpilotPrefix:
pass
return False
def create_dirs(self):
try:
os.mkdir(self.msgq_path)
except FileExistsError:
pass
os.makedirs(Paths.log_root(), exist_ok=True)
def clean_dirs(self):
symlink_path = Params().get_param_path()
if os.path.exists(symlink_path):
+2 -2
View File
@@ -37,9 +37,9 @@ class TestParams:
def test_params_two_things(self):
self.params.put("DongleId", "bob")
self.params.put("AthenadPid", "123")
self.params.put("AthenadPid", 123)
assert self.params.get("DongleId") == "bob"
assert self.params.get("AthenadPid") == "123"
assert self.params.get("AthenadPid") == 123
def test_params_get_block(self):
def _delayed_writer():
+7 -1
View File
@@ -1,4 +1,5 @@
#include "common/util.h"
#include "common/swaglog.h"
#include <sys/ioctl.h>
#include <sys/stat.h>
@@ -151,11 +152,16 @@ int safe_fflush(FILE *stream) {
return ret;
}
int safe_ioctl(int fd, unsigned long request, void *argp) {
int safe_ioctl(int fd, unsigned long request, void *argp, const char* exception_msg) {
int ret;
do {
ret = ioctl(fd, request, argp);
} while ((ret == -1) && (errno == EINTR));
if (ret == -1 && exception_msg) {
LOGE("safe_ioctl error: %s %s(%d) (fd: %d request: %lx argp: %p)", exception_msg, strerror(errno), errno, fd, request, argp);
throw std::runtime_error(exception_msg);
}
return ret;
}
+1 -1
View File
@@ -88,7 +88,7 @@ int write_file(const char* path, const void* data, size_t size, int flags = O_WR
FILE* safe_fopen(const char* filename, const char* mode);
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
int safe_fflush(FILE *stream);
int safe_ioctl(int fd, unsigned long request, void *argp);
int safe_ioctl(int fd, unsigned long request, void *argp, const char* exception_msg = nullptr);
std::string readlink(const std::string& path);
bool file_exists(const std::string& fn);
+1 -1
View File
@@ -1 +1 @@
#define COMMA_VERSION "0.10.0"
#define COMMA_VERSION "0.10.1"
+11 -3
View File
@@ -4,7 +4,7 @@
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.
# 313 Supported Cars
# 321 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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -72,6 +72,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Genesis|GV80 2023[<sup>6</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 Hyundai M connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV80 2023">Buy Here</a></sub></details>|||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2018-22|All|openpilot available[<sup>1</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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></a>||
|Honda|Accord 2023|All|openpilot|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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></a>||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>5</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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></a>||
@@ -117,6 +118,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|Hyundai|Ioniq Plug-in Hybrid 2020-22|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 H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2020">Buy Here</a></sub></details>|||
|Hyundai|Kona 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 O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2018-21|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 G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2018-21">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2022-23|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 O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2022-23">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|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 R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
@@ -179,7 +181,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Sportage Hybrid 2023[<sup>6</sup>](#footnotes)|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 N connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sportage Hybrid 2023">Buy Here</a></sub></details>|||
|Kia|Stinger 2018-20|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 C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Kia|Stinger 2022-23|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 K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2022-23">Buy Here</a></sub></details>|||
|Kia|Telluride 2020-22|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 H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Telluride 2020-22">Buy Here</a></sub></details>|||
|Kia|Telluride 2020-22|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 H connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Telluride 2020-22">Buy Here</a></sub></details>|||
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus CT Hybrid 2017-18">Buy Here</a></sub></details>|||
|Lexus|ES 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2017-18">Buy Here</a></sub></details>|||
|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2019-25">Buy Here</a></sub></details>|||
@@ -212,7 +214,9 @@ A supported vehicle is one that just works when you install a comma device. All
|Nissan[<sup>7</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 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></a>||
|Nissan[<sup>7</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 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|Nissan[<sup>7</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 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">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 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></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 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></a>|
|SEAT|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 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
@@ -220,10 +224,14 @@ A supported vehicle is one that just works when you install a comma device. All
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</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 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</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 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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"></img></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>|||
+1 -1
View File
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="12.4"
export AGNOS_VERSION="12.6"
fi
export STAGING_ROOT="/data/safe_staging"
+1 -1
Submodule panda updated: e46680ff6f...0e7a3fd8cf
+2 -3
View File
@@ -58,7 +58,7 @@ dependencies = [
"future-fstrings",
# joystickd
"inputs",
"pygame",
# these should be removed
"psutil",
@@ -107,7 +107,6 @@ dev = [
"opencv-python-headless",
"parameterized >=0.8, <0.9",
"pyautogui",
"pygame",
"pyopencl; platform_machine != 'aarch64'", # broken on arm64
"pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version
"pywinctl",
@@ -120,7 +119,6 @@ dev = [
tools = [
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')",
#"rerun-sdk >= 0.18", # this is pretty big, so only enable once we use it
]
[project.urls]
@@ -152,6 +150,7 @@ markers = [
testpaths = [
"common",
"selfdrive",
"system/manager",
"system/updated",
"system/athena",
"system/camerad",
+7 -3
View File
@@ -4,20 +4,24 @@
## release checklist
**Go to `devel-staging`**
- [ ] make issue to track release
- [ ] update RELEASES.md
- [ ] update `devel-staging`: `git reset --hard origin/master-ci`
- [ ] trigger new nightly build: https://github.com/commaai/openpilot/actions/workflows/release.yaml
- [ ] update `devel-staging`: `git reset --hard origin/__nightly`
- [ ] build new userdata partition from `release3-staging`
- [ ] open a pull request from `devel-staging` to `devel`
- [ ] post on Discord
- [ ] post on Discord, tag `@release crew`
**Go to `devel`**
- [ ] bump version on master: `common/version.h` and `RELEASES.md`
- [ ] before merging the pull request
- [ ] before merging the pull request, test the following:
- [ ] update from previous release -> new release
- [ ] update from new release -> previous release
- [ ] fresh install with `openpilot-test.comma.ai`
- [ ] drive on fresh install
- [ ] no submodules or LFS
- [ ] check sentry, MTBF, etc.
- [ ] stress test in production
**Go to `release3`**
- [ ] publish the blog post
+1 -1
View File
@@ -13,7 +13,7 @@ cd $ROOT
FAILED=0
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md"
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md|layouts\/.*\.xml"
IGNORED_DIRS="^third_party.*|^msgq.*|^msgq_repo.*|^opendbc.*|^opendbc_repo.*|^cereal.*|^panda.*|^rednose.*|^rednose_repo.*|^tinygrad.*|^tinygrad_repo.*|^teleoprtc.*|^teleoprtc_repo.*"
function run() {
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a69514fe68dd1b4c73f28771640dcba10fc40989d1c7e771cb48bfd830fef206
size 5713
+1 -1
View File
@@ -57,7 +57,7 @@ class CarSpecificEvents:
events.add(EventName.belowSteerSpeed)
elif self.CP.brand == 'honda':
events = self.create_common_events(CS, CS_prev, pcm_enable=False)
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.sport], pcm_enable=False)
if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed:
events.add(EventName.belowEngageSpeed)
+1 -2
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
import json
import os
import time
import threading
@@ -107,7 +106,7 @@ class Car:
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
cached_params = _cached_params
fixed_fingerprint = json.loads(self.params.get("CarPlatformBundle") or "{}").get("platform", None)
fixed_fingerprint = (self.params.get("CarPlatformBundle") or {}).get("platform", None)
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params, fixed_fingerprint)
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
+1 -1
View File
@@ -2,7 +2,7 @@ import math
import numpy as np
from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.sunnypilot.selfdrive.car.cruise_ext import VCruiseHelperSP
+1 -1
View File
@@ -6,7 +6,7 @@ from parameterized import parameterized_class
from cereal import log
from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
ButtonEvent = car.CarState.ButtonEvent
+19 -12
View File
@@ -2,11 +2,11 @@
import math
import threading
import time
from typing import SupportsFloat
from numbers import Number
from cereal import car, log
import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.common.swaglog import cloudlog
@@ -17,10 +17,13 @@ from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD
from openpilot.selfdrive.controls.lib.latcontrol_angle_torque import LatControlAngleTorque
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.controlsd_ext import ControlsExt
State = log.SelfdriveState.OpenpilotState
@@ -30,15 +33,16 @@ LaneChangeDirection = log.LaneChangeDirection
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
class Controls(ControlsExt):
class Controls(ControlsExt, ModelStateBase):
def __init__(self) -> None:
self.params = Params()
cloudlog.info("controlsd is waiting for CarParams")
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
cloudlog.info("controlsd got CarParams")
# Initialize sunnypilot controlsd extension
# Initialize sunnypilot controlsd extension and base model state
ControlsExt.__init__(self, self.CP, self.params)
ModelStateBase.__init__(self)
self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP)
@@ -48,7 +52,7 @@ class Controls(ControlsExt):
poll='selfdriveState')
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + self.pm_services_ext)
self.steer_limited_by_controls = False
self.steer_limited_by_safety = False
self.curvature = 0.0
self.desired_curvature = 0.0
@@ -58,7 +62,9 @@ class Controls(ControlsExt):
self.LoC = LongControl(self.CP)
self.VM = VehicleModel(self.CP)
self.LaC: LatControl
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
if self.CP.steerControlType == car.CarParams.SteerControlType.angle and self.CP.lateralTuning.which() == 'torque':
self.LaC = LatControlAngleTorque(self.CP, self.CP_SP, self.CI)
elif self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.LaC = LatControlAngle(self.CP, self.CP_SP, self.CI)
elif self.CP.lateralTuning.which() == 'pid':
self.LaC = LatControlPID(self.CP, self.CP_SP, self.CI)
@@ -93,8 +99,9 @@ class Controls(ControlsExt):
torque_params.frictionCoefficientFiltered)
self.LaC.extension.update_model_v2(self.sm['modelV2'])
calculated_lag = self.LaC.extension.lagd_torqued_main(self.CP, self.sm['liveDelay'])
self.LaC.extension.update_lateral_lag(calculated_lag)
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
self.LaC.extension.update_lateral_lag(self.lat_delay)
long_plan = self.sm['longitudinalPlan']
model_v2 = self.sm['modelV2']
@@ -136,14 +143,14 @@ class Controls(ControlsExt):
actuators.curvature = self.desired_curvature
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited_by_controls, self.desired_curvature,
self.steer_limited_by_safety, self.desired_curvature,
self.calibrated_pose, curvature_limited) # TODO what if not available
actuators.torque = float(steer)
actuators.steeringAngleDeg = float(steeringAngleDeg)
# Ensure no NaNs/Infs
for p in ACTUATOR_FIELDS:
attr = getattr(actuators, p)
if not isinstance(attr, SupportsFloat):
if not isinstance(attr, Number):
continue
if not math.isfinite(attr):
@@ -183,10 +190,10 @@ class Controls(ControlsExt):
if self.sm['selfdriveState'].active:
CO = self.sm['carOutput']
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.steer_limited_by_controls = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
self.steer_limited_by_safety = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
STEER_ANGLE_SATURATION_THRESHOLD
else:
self.steer_limited_by_controls = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2
self.steer_limited_by_safety = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2
# TODO: both controlsState and carControl valids should be set by
# sm.all_checks(), but this creates a circular dependency
+1 -1
View File
@@ -1,5 +1,5 @@
from cereal import log
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
+1 -1
View File
@@ -1,5 +1,5 @@
import numpy as np
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from openpilot.common.realtime import DT_CTRL, DT_MDL
MIN_SPEED = 1.0
+3 -3
View File
@@ -15,15 +15,15 @@ class LatControl(ABC):
self.steer_max = 1.0
@abstractmethod
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
pass
def reset(self):
self.sat_count = 0.
def _check_saturation(self, saturated, CS, steer_limited_by_controls, curvature_limited):
def _check_saturation(self, saturated, CS, steer_limited_by_safety, curvature_limited):
# Saturated only if control output is not being limited by car torque/angle rate limits
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_controls and not CS.steeringPressed:
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_safety and not CS.steeringPressed:
self.sat_count += self.sat_count_rate
else:
self.sat_count -= self.sat_count_rate
+5 -4
View File
@@ -3,6 +3,7 @@ import math
from cereal import log
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
# TODO This is speed dependent
STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees
@@ -10,9 +11,9 @@ class LatControlAngle(LatControl):
def __init__(self, CP, CP_SP, CI):
super().__init__(CP, CP_SP, CI)
self.sat_check_min_speed = 5.
self.use_steer_limited_by_controls = CP.brand == "tesla"
self.use_steer_limited_by_safety = CP.brand == "tesla"
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
angle_log = log.ControlsState.LateralAngleState.new_message()
if not active:
@@ -23,9 +24,9 @@ class LatControlAngle(LatControl):
angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
angle_steers_des += params.angleOffsetDeg
if self.use_steer_limited_by_controls:
if self.use_steer_limited_by_safety:
# these cars' carcontrollers calculate max lateral accel and jerk, so we can rely on carOutput for saturation
angle_control_saturated = steer_limited_by_controls
angle_control_saturated = steer_limited_by_safety
else:
# for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota)
# or relying on EPS (Ford Q3), carOutput does not capture maxing out torque # TODO: this can be improved
@@ -0,0 +1,13 @@
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
class LatControlAngleTorque(LatControlTorque, LatControlAngle):
def __init__(self, CP, CP_SP, CI):
LatControlTorque.__init__(self, CP, CP_SP, CI)
LatControlAngle.__init__(self, CP, CP_SP, CI)
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
torque, _, _ = LatControlTorque.update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited)
_, angle, angle_log = LatControlAngle.update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited)
return torque, angle, angle_log
+13 -13
View File
@@ -13,11 +13,7 @@ class LatControlPID(LatControl):
k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.get_steer_feedforward = CI.get_steer_feedforward_function()
def reset(self):
super().reset()
self.pid.reset()
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
pid_log = log.ControlsState.LateralPIDState.new_message()
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
pid_log.steeringRateDeg = float(CS.steeringRateDeg)
@@ -29,20 +25,24 @@ class LatControlPID(LatControl):
pid_log.steeringAngleDesiredDeg = angle_steers_des
pid_log.angleError = error
if not active:
output_steer = 0.0
output_torque = 0.0
pid_log.active = False
self.pid.reset()
else:
# offset does not contribute to resistive torque
steer_feedforward = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
ff = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_torque = self.pid.update(error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
output_steer = self.pid.update(error, override=CS.steeringPressed,
feedforward=steer_feedforward, speed=CS.vEgo)
pid_log.active = True
pid_log.p = float(self.pid.p)
pid_log.i = float(self.pid.i)
pid_log.f = float(self.pid.f)
pid_log.output = float(output_steer)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited_by_controls, curvature_limited))
pid_log.output = float(output_torque)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
return output_steer, angle_steers_des, pid_log
return output_torque, angle_steers_des, pid_log
+5 -5
View File
@@ -2,9 +2,9 @@ import math
import numpy as np
from cereal import log
from opendbc.car import FRICTION_THRESHOLD, get_friction
from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction
from opendbc.car.interfaces import LatControlInputs
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.common.pid import PIDController
@@ -41,7 +41,7 @@ class LatControlTorque(LatControl):
self.torque_params.latAccelOffset = latAccelOffset
self.torque_params.friction = friction
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
pid_log = log.ControlsState.LateralTorqueState.new_message()
if not active:
output_torque = 0.0
@@ -76,7 +76,7 @@ class LatControlTorque(LatControl):
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature)
freeze_integrator = steer_limited_by_controls or CS.steeringPressed or CS.vEgo < 5
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_torque = self.pid.update(pid_log.error,
feedforward=ff,
speed=CS.vEgo,
@@ -90,7 +90,7 @@ class LatControlTorque(LatControl):
pid_log.output = float(-output_torque)
pid_log.actualLateralAccel = float(actual_lateral_accel)
pid_log.desiredLateralAccel = float(desired_lateral_accel)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_controls, curvature_limited))
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
# TODO left is positive in this convention
return -output_torque, 0.0, pid_log
+1 -1
View File
@@ -1,6 +1,6 @@
from cereal import log
from openpilot.common.realtime import DT_CTRL
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
CAMERA_OFFSET = 0.04
@@ -4,7 +4,7 @@ import numpy as np
import cereal.messaging as messaging
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.modeld.constants import ModelConstants
@@ -21,7 +21,7 @@ LON_MPC_STEP = 0.2 # first step is 0.2s
A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6]
A_CRUISE_MAX_BP = [0., 10.0, 25., 40.]
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
ALLOW_THROTTLE_THRESHOLD = 0.5
ALLOW_THROTTLE_THRESHOLD = 0.4
MIN_ALLOW_THROTTLE_SPEED = 2.5
# Lookup table for turns
@@ -188,7 +188,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
def publish(self, sm, pm):
plan_send = messaging.new_message('longitudinalPlan')
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'selfdriveState'])
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'selfdriveState', 'radarState'])
longitudinalPlan = plan_send.longitudinalPlan
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
+10 -8
View File
@@ -1,23 +1,25 @@
#!/usr/bin/env python3
import argparse
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process
from openpilot.selfdrive.test.process_replay.test_processes import EXCLUDED_PROCS
from openpilot.tools.lib.logreader import LogReader, save_log
ALLOW_PROCS = {c.proc_name for c in CONFIGS}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run process on route and create new logs",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--fingerprint", help="The fingerprint to use")
parser.add_argument("route", help="The route name to use")
parser.add_argument("process", nargs='+', help="The process(s) to run")
parser.add_argument("--fingerprint", help="The fingerprint to use")
parser.add_argument("--whitelist-procs", nargs='*', default=ALLOW_PROCS, help="Whitelist given processes (e.g. controlsd)")
parser.add_argument("--blacklist-procs", nargs='*', default=EXCLUDED_PROCS, help="Blacklist given processes (e.g. controlsd)")
args = parser.parse_args()
cfgs = [c for c in CONFIGS if c.proc_name in args.process]
lr = LogReader(args.route)
inputs = list(lr)
allowed_procs = set(args.whitelist_procs) - set(args.blacklist_procs)
cfgs = [c for c in CONFIGS if c.proc_name in allowed_procs]
inputs = list(LogReader(args.route))
outputs = replay_process(cfgs, inputs, fingerprint=args.fingerprint)
# Remove message generated by the process under test and merge in the new messages
@@ -25,6 +27,6 @@ if __name__ == "__main__":
inputs = [i for i in inputs if i.which() not in produces]
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
fn = f"{args.route.replace('/', '_')}_{'_'.join(args.process)}.zst"
fn = f"{args.route.replace('/', '_')}_{'_'.join(allowed_procs)}.zst"
print(f"Saving log to {fn}")
save_log(fn, outputs)
+1 -1
View File
@@ -14,7 +14,7 @@ from typing import NoReturn
from cereal import log, car
import cereal.messaging as messaging
from openpilot.system.hardware import HARDWARE
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process
from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
+6
View File
@@ -12,6 +12,7 @@ from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose, fft_next_good_size, parabolic_peak_interp
from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
BLOCK_SIZE = 100
BLOCK_NUM = 50
@@ -374,6 +375,8 @@ def main():
lag, valid_blocks = initial_lag_params
lag_learner.reset(lag, valid_blocks)
lagd_toggle = LagdToggle(CP)
while True:
sm.update()
if sm.all_checks():
@@ -392,3 +395,6 @@ def main():
if sm.frame % 1200 == 0: # cache every 60 seconds
params.put_nonblocking("LiveDelay", lag_msg_dat)
if sm.frame % 60 == 0: # read from and write to params every 3 seconds
lagd_toggle.update(lag_msg)
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Any
import numpy as np
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.locationd.models.constants import ObservationKind
from openpilot.common.swaglog import cloudlog
+6 -6
View File
@@ -4,14 +4,13 @@ from collections import deque, defaultdict
import cereal.messaging as messaging
from cereal import car, log
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
HISTORY = 5 # secs
POINTS_PER_BUCKET = 1500
@@ -34,7 +33,7 @@ MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100])
MIN_ENGAGE_BUFFER = 2 # secs
VERSION = 1 # bump this to invalidate old parameter caches
ALLOWED_CARS = ['toyota', 'hyundai', 'rivian']
ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda']
def slope2rot(slope):
@@ -51,7 +50,7 @@ class TorqueBuckets(PointBuckets):
break
class TorqueEstimator(ParameterEstimator, LagdToggle):
class TorqueEstimator(ParameterEstimator):
def __init__(self, CP, decimated=False, track_all_points=False):
super().__init__()
self.CP = CP
@@ -99,6 +98,7 @@ class TorqueEstimator(ParameterEstimator, LagdToggle):
# try to restore cached params
params = Params()
self.params = params
params_cache = params.get("CarParamsPrevRoute")
torque_cache = params.get("LiveTorqueParameters")
if params_cache is not None and torque_cache is not None:
@@ -180,7 +180,7 @@ class TorqueEstimator(ParameterEstimator, LagdToggle):
elif which == "liveCalibration":
self.calibrator.feed_live_calib(msg)
elif which == "liveDelay":
self.lag = self.lagd_torqued_main(self.CP, msg)
self.lag = get_lat_delay(self.params, msg.lateralDelay)
# calculate lateral accel from past steering torque
elif which == "livePose":
if len(self.raw_points['steer_torque']) == self.hist_len:
+7 -5
View File
@@ -31,7 +31,8 @@ from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
PROCESS_NAME = "selfdrive.modeld.modeld"
@@ -79,13 +80,14 @@ class FrameMeta:
if vipc is not None:
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
class ModelState:
class ModelState(ModelStateBase):
frames: dict[str, DrivingModelFrame]
inputs: dict[str, np.ndarray]
output: np.ndarray
prev_desire: np.ndarray # for tracking the rising edge of the pulse
def __init__(self, context: CLContext):
ModelStateBase.__init__(self)
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
with open(VISION_METADATA_PATH, 'rb') as f:
vision_metadata = pickle.load(f)
@@ -249,8 +251,6 @@ def main(demo=False):
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("modeld got CarParams: %s", CP.brand)
modeld_lagd = LagdToggle()
# TODO this needs more thought, use .2s extra for now to estimate other delays
# TODO Move smooth seconds to action function
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
@@ -296,7 +296,9 @@ def main(demo=False):
is_rhd = sm["driverMonitoringState"].isRHD
frame_id = sm["roadCameraState"].frameId
v_ego = max(sm["carState"].vEgo, 0.)
lat_delay = modeld_lagd.lagd_main(CP, sm, model)
if sm.frame % 60 == 0:
model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay)
lat_delay = model.lat_delay + LAT_SMOOTH_SECONDS
lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
+1 -1
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:22aec22a10ce09384d4a4af2a0bbff08d54af7e0c888503508f356fae4ff0e29
oid sha256:1af87c38492444521632a0e75839b5684ee46bf255b3474773784bffb9fe4f57
size 15583374
+12 -6
View File
@@ -94,15 +94,21 @@ class Parser:
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH))
self.parse_binary_crossentropy('meta', outs)
self.parse_binary_crossentropy('lead_prob', outs)
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
if outs['lead'].shape[1] == 2 * ModelConstants.LEAD_MHP_SELECTION *ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH:
self.parse_mdn('lead', outs, in_N=0, out_N=0,
out_shape=(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
else:
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
return outs
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
if 'lat_planner_solution' in outs:
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
if outs['plan'].shape[1] == 2 * ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH:
self.parse_mdn('plan', outs, in_N=0, out_N=0,
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
else:
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
if 'desired_curvature' in outs:
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
+9
View File
@@ -84,6 +84,15 @@ Panda *connect(std::string serial="", uint32_t index=0) {
panda->set_can_fd_auto(i, true);
}
bool is_deprecated_panda = std::find(DEPRECATED_PANDA_TYPES.begin(),
DEPRECATED_PANDA_TYPES.end(),
panda->hw_type) != DEPRECATED_PANDA_TYPES.end();
if (is_deprecated_panda) {
LOGW("panda %s is deprecated (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
return panda.release();
}
if (!panda->up_to_date() && !getenv("BOARDD_SKIP_FW_CHECK")) {
throw std::runtime_error("Panda firmware out of date. Run pandad.py to update.");
}
+11
View File
@@ -8,6 +8,17 @@
void pandad_main_thread(std::vector<std::string> serials);
// deprecated devices
static const std::vector<cereal::PandaState::PandaType> DEPRECATED_PANDA_TYPES = {
cereal::PandaState::PandaType::WHITE_PANDA,
cereal::PandaState::PandaType::GREY_PANDA,
cereal::PandaState::PandaType::BLACK_PANDA,
cereal::PandaState::PandaType::PEDAL,
cereal::PandaState::PandaType::UNO,
cereal::PandaState::PandaType::RED_PANDA_V2
};
class PandaSafety {
public:
PandaSafety(const std::vector<Panda *> &pandas) : pandas_(pandas) {}
+6
View File
@@ -36,6 +36,12 @@ def flash_panda(panda_serial: str) -> Panda:
panda_signature = b"" if panda.bootstub else panda.get_signature()
cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}")
# skip flashing if the detected device is deprecated from upstream
hw_type = panda.get_type()
if hw_type in Panda.DEPRECATED_DEVICES:
cloudlog.warning(f"Panda {panda_serial} is deprecated (hw_type: {hw_type}), skipping flash...")
return panda
if panda.bootstub or panda_signature != fw_signature:
cloudlog.info("Panda firmware out of date, update required")
panda.flash()
+34 -48
View File
@@ -66,58 +66,44 @@ PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
// 50MHz is the max of the 845. note that some older
// revs of the comma three may not support this speed
uint32_t spi_speed = 50000000;
if (!util::file_exists(SPI_DEVICE)) {
goto fail;
}
spi_fd = open(SPI_DEVICE.c_str(), O_RDWR);
if (spi_fd < 0) {
LOGE("failed opening SPI device %d", spi_fd);
goto fail;
}
// SPI settings
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode);
if (ret < 0) {
LOGE("failed setting SPI mode %d", ret);
goto fail;
}
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
if (ret < 0) {
LOGE("failed setting SPI speed");
goto fail;
}
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word);
if (ret < 0) {
LOGE("failed setting SPI bits per word");
goto fail;
}
// get hw UID/serial
ret = control_read(0xc3, 0, 0, uid, uid_len, 100);
if (ret == uid_len) {
std::stringstream stream;
for (int i = 0; i < uid_len; i++) {
stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]);
try {
if (!util::file_exists(SPI_DEVICE)) {
throw std::runtime_error("Error connecting to panda: SPI device not found");
}
hw_serial = stream.str();
} else {
LOGD("failed to get serial %d", ret);
goto fail;
}
if (!serial.empty() && (serial != hw_serial)) {
goto fail;
}
spi_fd = open(SPI_DEVICE.c_str(), O_RDWR);
if (spi_fd < 0) {
LOGE("failed opening SPI device %d", spi_fd);
throw std::runtime_error("Error connecting to panda: failed to open SPI device");
}
// SPI settings
util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode, "failed setting SPI mode");
util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed, "failed setting SPI speed");
util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word, "failed setting SPI bits per word");
// get hw UID/serial
ret = control_read(0xc3, 0, 0, uid, uid_len, 100);
if (ret == uid_len) {
std::stringstream stream;
for (int i = 0; i < uid_len; i++) {
stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]);
}
hw_serial = stream.str();
} else {
LOGD("failed to get serial %d", ret);
throw std::runtime_error("Error connecting to panda: failed to get serial");
}
if (!serial.empty() && (serial != hw_serial)) {
throw std::runtime_error("Error connecting to panda: serial mismatch");
}
} catch (...) {
cleanup();
throw;
}
return;
fail:
cleanup();
throw std::runtime_error("Error connecting to panda");
}
PandaSpiHandle::~PandaSpiHandle() {
+1 -1
View File
@@ -46,7 +46,7 @@
"severity": 0
},
"Offroad_ExcessiveActuation": {
"text": "openpilot has detected excessive %1 actuation. This may be due to a software bug. Please contact support at https://comma.ai/support.",
"text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.",
"severity": 1,
"_comment": "Set extra field to lateral or longitudinal."
}
+18 -2
View File
@@ -4,10 +4,12 @@ import os
from cereal import log, car
import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.common.git import get_short_branch
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \
NoEntryAlert, SoftDisableAlert, UserSoftDisableAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, \
@@ -40,6 +42,7 @@ class Events(EventsBase):
return log.OnroadEvent
# ********** helper functions **********
def get_display_speed(speed_ms: float, metric: bool) -> str:
speed = int(round(speed_ms * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH)))
@@ -92,6 +95,14 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
def audio_feedback_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
duration = FEEDBACK_MAX_DURATION - ((sm['audioFeedback'].blockNum + 1) * SAMPLE_BUFFER / SAMPLE_RATE)
return NormalPermanentAlert(
"Recording Audio Feedback",
f"{round(duration)} second{'s' if round(duration) != 1 else ''} remaining. Press again to save early.",
priority=Priority.LOW)
# *** debug alerts ***
def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
@@ -203,6 +214,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin
return NormalPermanentAlert("Invalid LKAS setting", text)
EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
# ********** events with no alerts **********
@@ -824,9 +836,13 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.WARNING: personality_changed_alert,
},
EventName.userFlag: {
EventName.userBookmark: {
ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5),
},
EventName.audioFeedback: {
ET.PERMANENT: audio_feedback_alert,
},
}
+54
View File
@@ -0,0 +1,54 @@
import math
from enum import StrEnum, auto
from cereal import car, messaging
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.locationd.helpers import Pose
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
from opendbc.car.lateral import ISO_LATERAL_ACCEL
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
MIN_EXCESSIVE_ACTUATION_COUNT = int(0.25 / DT_CTRL)
MIN_LATERAL_ENGAGE_BUFFER = int(1 / DT_CTRL)
class ExcessiveActuationType(StrEnum):
LONGITUDINAL = auto()
LATERAL = auto()
class ExcessiveActuationCheck:
def __init__(self):
self._excessive_counter = 0
self._engaged_counter = 0
def update(self, sm: messaging.SubMaster, CS: car.CarState, calibrated_pose: Pose) -> ExcessiveActuationType | None:
# CS.aEgo can be noisy to bumps in the road, transitioning from standstill, losing traction, etc.
# longitudinal
accel_calibrated = calibrated_pose.acceleration.x
excessive_long_actuation = sm['carControl'].longActive and (accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2)
# lateral
yaw_rate = calibrated_pose.angular_velocity.yaw
roll = sm['liveParameters'].roll
roll_compensated_lateral_accel = (CS.vEgo * yaw_rate) - (math.sin(roll) * ACCELERATION_DUE_TO_GRAVITY)
# Prevent false positives after overriding
excessive_lat_actuation = False
self._engaged_counter = self._engaged_counter + 1 if sm['carControl'].latActive and not CS.steeringPressed else 0
if self._engaged_counter > MIN_LATERAL_ENGAGE_BUFFER:
if abs(roll_compensated_lateral_accel) > ISO_LATERAL_ACCEL * 2:
excessive_lat_actuation = True
# livePose acceleration can be noisy due to bad mounting or aliased livePose measurements
livepose_valid = abs(CS.aEgo - accel_calibrated) < 2
self._excessive_counter = self._excessive_counter + 1 if livepose_valid and (excessive_long_actuation or excessive_lat_actuation) else 0
excessive_type = None
if self._excessive_counter > MIN_EXCESSIVE_ACTUATION_COUNT:
if excessive_long_actuation:
excessive_type = ExcessiveActuationType.LONGITUDINAL
else:
excessive_type = ExcessiveActuationType.LATERAL
return excessive_type
+29 -37
View File
@@ -7,7 +7,6 @@ import cereal.messaging as messaging
from cereal import car, log, custom
from msgq.visionipc import VisionIpcClient, VisionStreamType
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
from openpilot.common.params import Params
@@ -18,6 +17,7 @@ from openpilot.common.gps import get_gps_location_service
from openpilot.selfdrive.car.car_specific import CarSpecificEvents
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
from openpilot.selfdrive.selfdrived.events import Events, ET
from openpilot.selfdrive.selfdrived.helpers import ExcessiveActuationCheck
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
@@ -34,7 +34,6 @@ SIMULATION = "SIMULATION" in os.environ
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
LONGITUDINAL_PERSONALITY_MAP = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()}
MIN_EXCESSIVE_ACTUATION_COUNT = int(0.25 / DT_CTRL)
ThermalStatus = log.DeviceState.ThermalStatus
State = log.SelfdriveState.OpenpilotState
@@ -48,21 +47,6 @@ SafetyModel = car.CarParams.SafetyModel
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
def check_excessive_actuation(sm: messaging.SubMaster, CS: car.CarState, calibrator: PoseCalibrator, counter: int) -> tuple[int, bool]:
# CS.aEgo can be noisy to bumps in the road, transitioning from standstill, losing traction, etc.
device_pose = Pose.from_live_pose(sm['livePose'])
calibrated_pose = calibrator.build_calibrated_pose(device_pose)
accel_calibrated = calibrated_pose.acceleration.x
# livePose acceleration can be noisy due to bad mounting or aliased livePose measurements
accel_valid = abs(CS.aEgo - accel_calibrated) < 2
excessive_actuation = accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2
counter = counter + 1 if sm['carControl'].longActive and excessive_actuation and accel_valid else 0
return counter, counter > MIN_EXCESSIVE_ACTUATION_COUNT
class SelfdriveD(CruiseHelper):
def __init__(self, CP=None, CP_SP=None):
self.params = Params()
@@ -85,7 +69,11 @@ class SelfdriveD(CruiseHelper):
self.CP_SP = CP_SP
self.car_events = CarSpecificEvents(self.CP)
self.calibrator = PoseCalibrator()
self.pose_calibrator = PoseCalibrator()
self.calibrated_pose: Pose | None = None
self.excessive_actuation_check = ExcessiveActuationCheck()
self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None
# Setup sockets
self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] + ['selfdriveStateSP', 'onroadEventsSP'])
@@ -107,7 +95,7 @@ class SelfdriveD(CruiseHelper):
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userFlag'] + \
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL))
@@ -143,8 +131,6 @@ class SelfdriveD(CruiseHelper):
self.experimental_mode = False
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
self.recalibrating_seen = False
self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None
self.excessive_actuation_counter = 0
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
@@ -205,9 +191,12 @@ class SelfdriveD(CruiseHelper):
self.events.add(EventName.selfdriveInitializing)
return
# Check for user flag (bookmark) press
if self.sm.updated['userFlag']:
self.events.add(EventName.userFlag)
# Check for user bookmark press (bookmark button or end of LKAS button feedback)
if self.sm.updated['userBookmark']:
self.events.add(EventName.userBookmark)
if self.sm.updated['audioFeedback']:
self.events.add(EventName.audioFeedback)
# Don't add any more events while in dashcam mode
if self.CP.passive:
@@ -276,14 +265,18 @@ class SelfdriveD(CruiseHelper):
if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture:
self.events.add(EventName.ldw)
# Check for excessive (longitudinal) actuation
# Check for excessive actuation
if self.sm.updated['liveCalibration']:
self.calibrator.feed_live_calib(self.sm['liveCalibration'])
self.pose_calibrator.feed_live_calib(self.sm['liveCalibration'])
if self.sm.updated['livePose']:
device_pose = Pose.from_live_pose(self.sm['livePose'])
self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose)
self.excessive_actuation_counter, excessive_actuation = check_excessive_actuation(self.sm, CS, self.calibrator, self.excessive_actuation_counter)
if not self.excessive_actuation and excessive_actuation:
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text="longitudinal")
self.excessive_actuation = True
if self.calibrated_pose is not None:
excessive_actuation = self.excessive_actuation_check.update(self.sm, CS, self.calibrated_pose)
if not self.excessive_actuation and excessive_actuation is not None:
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text=str(excessive_actuation))
self.excessive_actuation = True
if self.excessive_actuation:
self.events.add(EventName.excessiveActuation)
@@ -339,13 +332,12 @@ class SelfdriveD(CruiseHelper):
self.events.add(EventName.cameraFrameRate)
if not REPLAY and self.rk.lagging:
self.events.add(EventName.selfdrivedLagging)
if not self.sm.valid['radarState']:
if self.sm['radarState'].radarErrors.canError:
self.events.add(EventName.canError)
elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
self.events.add(EventName.radarTempUnavailable)
else:
self.events.add(EventName.radarFault)
if self.sm['radarState'].radarErrors.canError:
self.events.add(EventName.canError)
elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
self.events.add(EventName.radarTempUnavailable)
elif any(self.sm['radarState'].radarErrors.to_dict().values()):
self.events.add(EventName.radarFault)
if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError)
if CS.canTimeout:
+74 -111
View File
@@ -4,8 +4,9 @@ import time
import copy
import heapq
import signal
from collections import Counter, OrderedDict
from collections import Counter
from dataclasses import dataclass, field
from itertools import islice
from typing import Any
from collections.abc import Callable, Iterable
from tqdm import tqdm
@@ -16,12 +17,13 @@ import cereal.messaging as messaging
from cereal import car
from cereal.services import SERVICE_LIST
from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name
from opendbc.car.can_definitions import CanData
from opendbc.car.car_helpers import get_car, interfaces
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.common.timeout import Timeout
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.car.card import can_comm_callbacks, convert_to_capnp
from openpilot.selfdrive.car.card import convert_to_capnp
from openpilot.system.manager.process_config import managed_processes
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams
from openpilot.selfdrive.test.process_replay.migration import migrate_all
@@ -30,22 +32,10 @@ from openpilot.tools.lib.logreader import LogIterable
from openpilot.tools.lib.framereader import FrameReader
# Numpy gives different results based on CPU features after version 19
NUMPY_TOLERANCE = 1e-7
NUMPY_TOLERANCE = 1e-2
PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__))
FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/")
class DummySocket:
def __init__(self):
self.data: list[bytes] = []
def receive(self, non_blocking: bool = False) -> bytes | None:
if non_blocking:
return None
return self.data.pop()
def send(self, data: bytes):
self.data.append(data)
class LauncherWithCapture:
def __init__(self, capture: ProcessOutputCapture, launcher: Callable):
@@ -63,8 +53,7 @@ class ReplayContext:
self.pubs = cfg.pubs
self.main_pub = cfg.main_pub
self.main_pub_drained = cfg.main_pub_drained
self.unlocked_pubs = cfg.unlocked_pubs
assert(len(self.pubs) != 0 or self.main_pub is not None)
assert len(self.pubs) != 0 or self.main_pub is not None
def __enter__(self):
self.open_context()
@@ -79,9 +68,8 @@ class ReplayContext:
messaging.set_fake_prefix(self.proc_name)
if self.main_pub is None:
self.events = OrderedDict()
pubs_with_events = [pub for pub in self.pubs if pub not in self.unlocked_pubs]
for pub in pubs_with_events:
self.events = {}
for pub in self.pubs:
self.events[pub] = messaging.fake_event_handle(pub, enable=True)
else:
self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)}
@@ -138,16 +126,21 @@ class ProcessConfig:
processing_time: float = 0.001
timeout: int = 30
simulation: bool = True
# Set to service process receives on first
main_pub: str | None = None
main_pub_drained: bool = True
main_pub_drained: bool = False
vision_pubs: list[str] = field(default_factory=list)
ignore_alive_pubs: list[str] = field(default_factory=list)
unlocked_pubs: list[str] = field(default_factory=list)
def __post_init__(self):
# If the process is polling a service, we can just lock that one to speed up replay
if self.main_pub is None and isinstance(self.should_recv_callback, MessageBasedRcvCallback):
self.main_pub = self.should_recv_callback.trigger_msg_type
class ProcessContainer:
def __init__(self, cfg: ProcessConfig):
self.prefix = OpenpilotPrefix(clean_dirs_on_exit=False)
self.prefix = OpenpilotPrefix(create_dirs_on_enter=False, clean_dirs_on_exit=False)
self.cfg = copy.deepcopy(cfg)
self.process = copy.deepcopy(managed_processes[cfg.proc_name])
self.msg_queue: list[capnp._DynamicStructReader] = []
@@ -229,6 +222,7 @@ class ProcessContainer:
fingerprint: str | None, capture_output: bool
):
with self.prefix as p:
self.prefix.create_dirs()
self._setup_env(params_config, environ_config)
if self.cfg.config_callback is not None:
@@ -254,11 +248,6 @@ class ProcessContainer:
if self.cfg.init_callback is not None:
self.cfg.init_callback(self.rc, self.pm, all_msgs, fingerprint)
# wait for process to startup
with Timeout(10, error_msg=f"timed out waiting for process to start: {repr(self.cfg.proc_name)}"):
while not all(self.pm.all_readers_updated(s) for s in self.cfg.pubs if s not in self.cfg.ignore_alive_pubs):
time.sleep(0)
def stop(self):
with self.prefix:
self.process.signal(signal.SIGKILL)
@@ -267,28 +256,42 @@ class ProcessContainer:
self.prefix.clean_dirs()
self._clean_env()
def get_output_msgs(self, start_time: int):
assert self.rc and self.sockets
output_msgs = []
self.rc.wait_for_recv_called()
for socket in self.sockets:
ms = messaging.drain_sock(socket)
for m in ms:
m = m.as_builder()
m.logMonoTime = start_time + int(self.cfg.processing_time * 1e9)
output_msgs.append(m.as_reader())
return output_msgs
def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] | None) -> list[capnp._DynamicStructReader]:
assert self.rc and self.pm and self.sockets and self.process.proc
output_msgs = []
with self.prefix, Timeout(self.cfg.timeout, error_msg=f"timed out testing process {repr(self.cfg.proc_name)}"):
end_of_cycle = True
if self.cfg.should_recv_callback is not None:
end_of_cycle = self.cfg.should_recv_callback(msg, self.cfg, self.cnt)
self.msg_queue.append(msg)
if end_of_cycle:
self.rc.wait_for_recv_called()
end_of_cycle = True
if self.cfg.should_recv_callback is not None:
end_of_cycle = self.cfg.should_recv_callback(msg, self.cfg, self.cnt)
self.msg_queue.append(msg)
if end_of_cycle:
with self.prefix, Timeout(self.cfg.timeout, error_msg=f"timed out testing process {repr(self.cfg.proc_name)}"):
# call recv to let sub-sockets reconnect, after we know the process is ready
if self.cnt == 0:
for s in self.sockets:
messaging.recv_one_or_none(s)
# empty recv on drained pub indicates the end of messages, only do that if there're any
# certain processes use drain_sock. need to cause empty recv to break from this loop
trigger_empty_recv = False
if self.cfg.main_pub and self.cfg.main_pub_drained:
trigger_empty_recv = next((True for m in self.msg_queue if m.which() == self.cfg.main_pub), False)
trigger_empty_recv = any(m.which() == self.cfg.main_pub for m in self.msg_queue)
# get output msgs from previous inputs
output_msgs = self.get_output_msgs(msg.logMonoTime)
for m in self.msg_queue:
self.pm.send(m.which(), m.as_builder())
@@ -303,14 +306,8 @@ class ProcessContainer:
self.msg_queue = []
self.rc.unlock_sockets()
self.rc.wait_for_next_recv(trigger_empty_recv)
for socket in self.sockets:
ms = messaging.drain_sock(socket)
for m in ms:
m = m.as_builder()
m.logMonoTime = msg.logMonoTime + int(self.cfg.processing_time * 1e9)
output_msgs.append(m.as_reader())
if trigger_empty_recv:
self.rc.unlock_sockets()
self.cnt += 1
assert self.process.proc.is_alive()
@@ -320,7 +317,7 @@ class ProcessContainer:
def card_fingerprint_callback(rc, pm, msgs, fingerprint):
print("start fingerprinting")
params = Params()
canmsgs = [msg for msg in msgs if msg.which() == "can"][:300]
canmsgs = list(islice((m for m in msgs if m.which() == "can"), 300))
# card expects one arbitrary can and pandaState
rc.send_sync(pm, "can", messaging.new_message("can", 1))
@@ -345,36 +342,27 @@ def get_car_params_callback(rc, pm, msgs, fingerprint):
CP = CarInterface.get_non_essential_params(fingerprint)
CP_SP = CarInterface.get_non_essential_params_sp(CP, fingerprint)
else:
can = DummySocket()
sendcan = DummySocket()
canmsgs = [msg for msg in msgs if msg.which() == "can"]
can_msgs = ([CanData(can.address, can.dat, can.src) for can in m.can] for m in msgs if m.which() == "can")
cached_params_raw = params.get("CarParamsCache")
has_cached_cp = cached_params_raw is not None
assert len(canmsgs) != 0, "CAN messages are required for fingerprinting"
assert os.environ.get("SKIP_FW_QUERY", False) or has_cached_cp, \
assert next(can_msgs, None), "CAN messages are required for fingerprinting"
assert os.environ.get("SKIP_FW_QUERY", False) or cached_params_raw is not None, \
"CarParamsCache is required for fingerprinting. Make sure to keep carParams msgs in the logs."
for m in canmsgs[:300]:
can.send(m.as_builder().to_bytes())
can_callbacks = can_comm_callbacks(can, sendcan)
def can_recv(wait_for_one: bool = False) -> list[list[CanData]]:
return [next(can_msgs, [])]
cached_params = None
if has_cached_cp:
if cached_params_raw is not None:
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
cached_params = _cached_params
_CI = get_car(*can_callbacks, lambda obd: None, Params().get_bool("AlphaLongitudinalEnabled"), False, cached_params=cached_params)
_CI = get_car(can_recv, lambda _msgs: None, lambda obd: None, params.get_bool("AlphaLongitudinalEnabled"), False, cached_params=cached_params)
CP, CP_SP = _CI.CP, _CI.CP_SP
params.put("CarParams", CP.to_bytes())
params.put("CarParamsSP", convert_to_capnp(CP_SP).to_bytes())
def selfdrived_rcv_callback(msg, cfg, frame):
return (frame - 1) == 0 or msg.which() == 'carState'
def card_rcv_callback(msg, cfg, frame):
# no sendcan until card is initialized
if msg.which() != "can":
@@ -389,21 +377,6 @@ def card_rcv_callback(msg, cfg, frame):
return len(socks) > 0
def calibration_rcv_callback(msg, cfg, frame):
# calibrationd publishes 1 calibrationData every 5 cameraOdometry packets.
# should_recv always true to increment frame
return (frame - 1) == 0 or msg.which() == 'cameraOdometry'
def torqued_rcv_callback(msg, cfg, frame):
# should_recv always true to increment frame
return (frame - 1) == 0 or msg.which() == 'livePose'
def dmonitoringmodeld_rcv_callback(msg, cfg, frame):
return msg.which() == "driverCameraState"
class ModeldCameraSyncRcvCallback:
def __init__(self):
self.road_present = False
@@ -428,26 +401,13 @@ class ModeldCameraSyncRcvCallback:
class MessageBasedRcvCallback:
def __init__(self, trigger_msg_type):
def __init__(self, trigger_msg_type: str, first_frame: bool = False):
self.trigger_msg_type = trigger_msg_type
self.first_frame = first_frame
def __call__(self, msg, cfg, frame):
return msg.which() == self.trigger_msg_type
class FrequencyBasedRcvCallback:
def __init__(self, trigger_msg_type):
self.trigger_msg_type = trigger_msg_type
def __call__(self, msg, cfg, frame):
if msg.which() != self.trigger_msg_type:
return False
resp_sockets = [
s for s in cfg.subs
if frame % max(1, int(SERVICE_LIST[msg.which()].frequency / SERVICE_LIST[s].frequency)) == 0
]
return bool(len(resp_sockets))
# publish on first frame or trigger msg
return ((frame - 1) == 0 and self.first_frame) or msg.which() == self.trigger_msg_type
def selfdrived_config_callback(params, cfg, lr):
@@ -462,16 +422,16 @@ CONFIGS = [
proc_name="selfdrived",
pubs=[
"carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState",
"longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState",
"modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState",
"liveTorqueParameters", "accelerometer", "gyroscope", "carOutput",
"gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug",
"longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState", "modelV2",
"driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "liveTorqueParameters",
"accelerometer", "gyroscope", "carOutput", "gpsLocationExternal", "gpsLocation", "controlsState",
"carControl", "driverAssistance", "alertDebug", "audioFeedback",
],
subs=["selfdriveState", "onroadEvents"],
ignore=["logMonoTime"],
config_callback=selfdrived_config_callback,
init_callback=get_car_params_callback,
should_recv_callback=selfdrived_rcv_callback,
should_recv_callback=MessageBasedRcvCallback("carState", True),
tolerance=NUMPY_TOLERANCE,
processing_time=0.004,
),
@@ -496,6 +456,7 @@ CONFIGS = [
tolerance=NUMPY_TOLERANCE,
processing_time=0.004,
main_pub="can",
main_pub_drained=True,
),
ProcessConfig(
proc_name="radard",
@@ -503,7 +464,7 @@ CONFIGS = [
subs=["radarState"],
ignore=["logMonoTime"],
init_callback=get_car_params_callback,
should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
should_recv_callback=MessageBasedRcvCallback("modelV2"),
),
ProcessConfig(
proc_name="plannerd",
@@ -511,7 +472,7 @@ CONFIGS = [
subs=["longitudinalPlan", "driverAssistance"],
ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"],
init_callback=get_car_params_callback,
should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
should_recv_callback=MessageBasedRcvCallback("modelV2"),
tolerance=NUMPY_TOLERANCE,
),
ProcessConfig(
@@ -520,14 +481,14 @@ CONFIGS = [
subs=["liveCalibration"],
ignore=["logMonoTime"],
init_callback=get_car_params_callback,
should_recv_callback=calibration_rcv_callback,
should_recv_callback=MessageBasedRcvCallback("cameraOdometry", True),
),
ProcessConfig(
proc_name="dmonitoringd",
pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState"],
subs=["driverMonitoringState"],
ignore=["logMonoTime"],
should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"),
should_recv_callback=MessageBasedRcvCallback("driverStateV2"),
tolerance=NUMPY_TOLERANCE,
),
ProcessConfig(
@@ -539,7 +500,6 @@ CONFIGS = [
ignore=["logMonoTime"],
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
tolerance=NUMPY_TOLERANCE,
unlocked_pubs=["accelerometer", "gyroscope"],
),
ProcessConfig(
proc_name="paramsd",
@@ -547,7 +507,7 @@ CONFIGS = [
subs=["liveParameters"],
ignore=["logMonoTime"],
init_callback=get_car_params_callback,
should_recv_callback=FrequencyBasedRcvCallback("livePose"),
should_recv_callback=MessageBasedRcvCallback("livePose"),
tolerance=NUMPY_TOLERANCE,
processing_time=0.004,
),
@@ -572,7 +532,7 @@ CONFIGS = [
subs=["liveTorqueParameters"],
ignore=["logMonoTime"],
init_callback=get_car_params_callback,
should_recv_callback=torqued_rcv_callback,
should_recv_callback=MessageBasedRcvCallback("livePose", True),
tolerance=NUMPY_TOLERANCE,
),
ProcessConfig(
@@ -584,7 +544,6 @@ CONFIGS = [
tolerance=NUMPY_TOLERANCE,
processing_time=0.020,
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("roadCameraState").stream),
main_pub_drained=False,
vision_pubs=["roadCameraState", "wideRoadCameraState"],
ignore_alive_pubs=["wideRoadCameraState"],
init_callback=get_car_params_callback,
@@ -594,11 +553,10 @@ CONFIGS = [
pubs=["liveCalibration", "driverCameraState"],
subs=["driverStateV2"],
ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"],
should_recv_callback=dmonitoringmodeld_rcv_callback,
should_recv_callback=MessageBasedRcvCallback("driverCameraState"),
tolerance=NUMPY_TOLERANCE,
processing_time=0.020,
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("driverCameraState").stream),
main_pub_drained=False,
vision_pubs=["driverCameraState"],
ignore_alive_pubs=["driverCameraState"],
),
@@ -706,8 +664,8 @@ def _replay_multi_process(
all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime)
log_msgs = []
containers = []
try:
containers = []
for cfg in cfgs:
container = ProcessContainer(cfg)
containers.append(container)
@@ -742,6 +700,11 @@ def _replay_multi_process(
internal_pub_queue.append(m)
heapq.heappush(internal_pub_index_heap, (m.logMonoTime, len(internal_pub_queue) - 1))
log_msgs.extend(output_msgs)
# flush last set of messages from each process
for container in containers:
last_time = log_msgs[-1].logMonoTime if len(log_msgs) > 0 else int(time.monotonic() * 1e9)
log_msgs.extend(container.get_output_msgs(last_time))
finally:
for container in containers:
container.stop()
+1 -1
View File
@@ -1 +1 @@
c289a0359d1b1f26cf4d9e73a2c04b2bbfec840f
543bd2347fa35f8300478a3893fdd0a03a7c1fe6
+1
View File
@@ -54,6 +54,7 @@ while true; do
# /data/ciui.py &
#fi
awk '{print \$1}' /proc/uptime > /var/tmp/power_watchdog
sleep 5s
done
+1
View File
@@ -55,6 +55,7 @@ PROCS = {
"selfdrive.locationd.paramsd": 9.0,
"selfdrive.locationd.lagd": 11.0,
"selfdrive.ui.soundd": 3.0,
"selfdrive.ui.feedback.feedbackd": 1.0,
"selfdrive.monitoring.dmonitoringd": 4.0,
"./proclogd": 2.0,
"system.logmessaged": 1.0,
+1 -11
View File
@@ -70,14 +70,8 @@ if GetOption('extras'):
qt_src.remove("main.cc") # replaced by test_runner
qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs)
qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs)
# setup
qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", asset_obj],
LIBS=qt_libs + ['curl', 'common'])
# build installers
if arch != "Darwin":
# build installers
raylib_env = env.Clone()
raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/']
raylib_env['LINKFLAGS'].append('-Wl,-strip-debug')
@@ -109,7 +103,3 @@ if GetOption('extras'):
f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs)
# keep installers small
assert f[0].get_size() < 1900*1e3, f[0].get_size()
# build watch3
if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'):
qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'msgq', 'visionipc'])
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import cereal.messaging as messaging
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from cereal import car
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
FEEDBACK_MAX_DURATION = 10.0
ButtonType = car.CarState.ButtonEvent.Type
def main():
params = Params()
pm = messaging.PubMaster(['userBookmark', 'audioFeedback'])
sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton', 'carState', 'selfdriveStateSP'])
should_record_audio = False
block_num = 0
waiting_for_release = False
early_stop_triggered = False
while True:
sm.update()
should_send_bookmark = False
# only allow the LKAS button to record feedback when MADS is disabled
if sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available:
for be in sm['carState'].buttonEvents:
if be.type == ButtonType.lkas:
if be.pressed:
if not should_record_audio:
if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set
should_record_audio = True
block_num = 0
waiting_for_release = False
early_stop_triggered = False
cloudlog.info("LKAS button pressed - starting 10-second audio feedback")
else:
should_send_bookmark = True # immediately send bookmark if toggle false
cloudlog.info("LKAS button pressed - bookmarking")
elif should_record_audio and not waiting_for_release: # Wait for release of second press to stop recording early
waiting_for_release = True
elif waiting_for_release: # Second press released
waiting_for_release = False
early_stop_triggered = True
cloudlog.info("LKAS button released - ending recording early")
if should_record_audio and sm.updated['rawAudioData']:
raw_audio = sm['rawAudioData']
msg = messaging.new_message('audioFeedback', valid=True)
msg.audioFeedback.audio.data = raw_audio.data
msg.audioFeedback.audio.sampleRate = raw_audio.sampleRate
msg.audioFeedback.blockNum = block_num
block_num += 1
if (block_num * SAMPLE_BUFFER / SAMPLE_RATE) >= FEEDBACK_MAX_DURATION or early_stop_triggered: # Check for timeout or early stop
should_send_bookmark = True # send bookmark at end of audio segment
should_record_audio = False
early_stop_triggered = False
cloudlog.info("10-second recording completed or second button press - stopping audio feedback")
pm.send('audioFeedback', msg)
if sm.updated['bookmarkButton']:
cloudlog.info("Bookmark button pressed!")
should_send_bookmark = True
if should_send_bookmark:
msg = messaging.new_message('userBookmark', valid=True)
pm.send('userBookmark', msg)
if __name__ == '__main__':
main()
+6 -6
View File
@@ -19,7 +19,7 @@ class MainLayout(Widget):
def __init__(self):
super().__init__()
self._pm = messaging.PubMaster(['userFlag'])
self._pm = messaging.PubMaster(['bookmarkButton'])
self._sidebar = Sidebar()
self._current_mode = MainState.HOME
@@ -40,7 +40,7 @@ class MainLayout(Widget):
def _setup_callbacks(self):
self._sidebar.set_callbacks(on_settings=self._on_settings_clicked,
on_flag=self._on_flag_clicked)
on_flag=self._on_bookmark_clicked)
self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE))
self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
self._layouts[MainState.ONROAD].set_callbacks(on_click=self._on_onroad_clicked)
@@ -76,10 +76,10 @@ class MainLayout(Widget):
def _on_settings_clicked(self):
self.open_settings(PanelType.DEVICE)
def _on_flag_clicked(self):
user_flag = messaging.new_message('userFlag')
user_flag.valid = True
self._pm.send('userFlag', user_flag)
def _on_bookmark_clicked(self):
user_bookmark = messaging.new_message('bookmarkButton')
user_bookmark.valid = True
self._pm.send('bookmarkButton', user_bookmark)
def _on_onroad_clicked(self):
self._sidebar.set_visible(not self._sidebar.is_visible)
+1 -1
View File
@@ -28,7 +28,7 @@ PANEL_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_PRESSED = rl.Color(59, 59, 59, 255)
TEXT_NORMAL = rl.Color(128, 128, 128, 255)
TEXT_SELECTED = rl.Color(255, 255, 255, 255)
TEXT_SELECTED = rl.WHITE
class PanelType(IntEnum):
+10
View File
@@ -23,6 +23,10 @@ DESCRIPTIONS = {
'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
"IsMetric": "Display speed in km/h instead of mph.",
"RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.",
"RecordAudioFeedback": (
"Press the LKAS button to record audio feedback about openpilot. When this toggle is disabled, the button acts as a bookmark button. " +
"The event will be highlighted in comma connect and the segment will be preserved on your device's storage."
),
}
@@ -81,6 +85,12 @@ class TogglesLayout(Widget):
self._params.get_bool("RecordAudio"),
icon="microphone.png",
),
toggle_item(
"Record Audio Feedback with LKAS button",
DESCRIPTIONS["RecordAudioFeedback"],
self._params.get_bool("RecordAudioFeedback"),
icon="microphone.png",
),
toggle_item(
"Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="metric.png"
),
+4 -4
View File
@@ -24,18 +24,18 @@ NetworkType = log.DeviceState.NetworkType
# Color scheme
class Colors:
SIDEBAR_BG = rl.Color(57, 57, 57, 255)
WHITE = rl.Color(255, 255, 255, 255)
WHITE = rl.WHITE
WHITE_DIM = rl.Color(255, 255, 255, 85)
GRAY = rl.Color(84, 84, 84, 255)
# Status colors
GOOD = rl.Color(255, 255, 255, 255)
GOOD = rl.WHITE
WARNING = rl.Color(218, 202, 37, 255)
DANGER = rl.Color(201, 34, 49, 255)
# UI elements
METRIC_BORDER = rl.Color(255, 255, 255, 85)
BUTTON_NORMAL = rl.Color(255, 255, 255, 255)
BUTTON_NORMAL = rl.WHITE
BUTTON_PRESSED = rl.Color(255, 255, 255, 166)
@@ -146,7 +146,7 @@ class Sidebar(Widget):
def _draw_buttons(self, rect: rl.Rectangle):
mouse_pos = rl.get_mouse_position()
mouse_down = self._is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
mouse_down = self.is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
# Settings button
settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN)
+7 -13
View File
@@ -141,6 +141,9 @@ class CameraView(Widget):
self.client = None
def __del__(self):
self.close()
def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray:
if not self.frame:
return np.eye(3)
@@ -337,16 +340,7 @@ class CameraView(Widget):
if __name__ == "__main__":
gui_app.init_window("watch3")
road_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD)
driver_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER)
wide_road_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD)
try:
for _ in gui_app.render():
road_camera_view.render(rl.Rectangle(gui_app.width // 4, 0, gui_app.width // 2, gui_app.height // 2))
driver_camera_view.render(rl.Rectangle(0, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2))
wide_road_camera_view.render(rl.Rectangle(gui_app.width // 2, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2))
finally:
road_camera_view.close()
driver_camera_view.close()
wide_road_camera_view.close()
gui_app.init_window("camera view")
road = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD)
for _ in gui_app.render():
road.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
+1 -1
View File
@@ -50,7 +50,7 @@ class ExpButton(Widget):
center_y = int(self._rect.y + self._rect.height // 2)
mouse_over = rl.check_collision_point_rec(rl.get_mouse_position(), self._rect)
mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed
mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed
self._white_color.a = 180 if (mouse_down and mouse_over) or not self._engageable else 255
texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel
+3 -3
View File
@@ -1,6 +1,6 @@
import pyray as rl
from dataclasses import dataclass
from openpilot.common.conversions import Conversions as CV
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.onroad.exp_button import ExpButton
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
from openpilot.system.ui.lib.application import gui_app, FontWeight
@@ -34,7 +34,7 @@ class FontSizes:
@dataclass(frozen=True)
class Colors:
white: rl.Color = rl.Color(255, 255, 255, 255)
white: rl.Color = rl.WHITE
disengaged: rl.Color = rl.Color(145, 155, 149, 255)
override: rl.Color = rl.Color(145, 155, 149, 255) # Added
engaged: rl.Color = rl.Color(128, 216, 166, 255)
@@ -47,7 +47,7 @@ class Colors:
white_translucent: rl.Color = rl.Color(255, 255, 255, 200)
border_translucent: rl.Color = rl.Color(255, 255, 255, 75)
header_gradient_start: rl.Color = rl.Color(0, 0, 0, 114)
header_gradient_end: rl.Color = rl.Color(0, 0, 0, 0)
header_gradient_end: rl.Color = rl.BLANK
UI_CONFIG = UIConfig()
+7
View File
@@ -75,6 +75,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/icons/microphone.png",
true,
},
{
"RecordAudioFeedback",
tr("Record Audio Feedback with LKAS button"),
tr("Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device's storage.\n\nNote that this feature is only compatible with select cars."),
"../assets/icons/microphone.png",
false,
},
{
"IsMetric",
tr("Use Metric System"),
-23
View File
@@ -1,23 +0,0 @@
import os
import platform
from cffi import FFI
import sip
from openpilot.common.basedir import BASEDIR
def suffix():
return ".dylib" if platform.system() == "Darwin" else ".so"
def get_ffi():
lib = os.path.join(BASEDIR, "selfdrive", "ui", "qt", "libpython_helpers" + suffix())
ffi = FFI()
ffi.cdef("void set_main_window(void *w);")
return ffi, ffi.dlopen(lib)
def set_main_window(widget):
ffi, lib = get_ffi()
lib.set_main_window(ffi.cast('void*', sip.unwrapinstance(widget)))
-541
View File
@@ -1,541 +0,0 @@
#include "selfdrive/ui/qt/setup/setup.h"
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <string>
#include <QApplication>
#include <QLabel>
#include <QVBoxLayout>
#include <curl/curl.h>
#include "common/util.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/network/networking.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/input.h"
const std::string USER_AGENT = "AGNOSSetup-";
const QString OPENPILOT_URL = "https://openpilot.comma.ai";
bool is_elf(char *fname) {
FILE *fp = fopen(fname, "rb");
if (fp == NULL) {
return false;
}
char buf[4];
size_t n = fread(buf, 1, 4, fp);
fclose(fp);
return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F';
}
void Setup::download(QString url) {
// autocomplete incomplete urls
if (QRegularExpression("^([^/.]+)/([^/]+)$").match(url).hasMatch()) {
url.prepend("https://installer.comma.ai/");
}
CURL *curl = curl_easy_init();
if (!curl) {
emit finished(url, tr("Something went wrong. Reboot the device."));
return;
}
auto version = util::read_file("/VERSION");
struct curl_slist *list = NULL;
std::string header = "X-openpilot-serial: " + Hardware::get_serial();
list = curl_slist_append(list, header.c_str());
char tmpfile[] = "/tmp/installer_XXXXXX";
FILE *fp = fdopen(mkstemp(tmpfile), "wb");
curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
int ret = curl_easy_perform(curl);
long res_status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status);
if (ret != CURLE_OK || res_status != 200) {
emit finished(url, tr("Ensure the entered URL is valid, and the devices internet connection is good."));
} else if (!is_elf(tmpfile)) {
emit finished(url, tr("No custom software found at this URL."));
} else {
rename(tmpfile, "/tmp/installer");
FILE *fp_url = fopen("/tmp/installer_url", "w");
fprintf(fp_url, "%s", url.toStdString().c_str());
fclose(fp_url);
emit finished(url);
}
curl_slist_free_all(list);
curl_easy_cleanup(curl);
fclose(fp);
}
QWidget * Setup::low_voltage() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
main_layout->setContentsMargins(55, 0, 55, 55);
main_layout->setSpacing(0);
// inner text layout: warning icon, title, and body
QVBoxLayout *inner_layout = new QVBoxLayout();
inner_layout->setContentsMargins(110, 144, 365, 0);
main_layout->addLayout(inner_layout);
QLabel *triangle = new QLabel();
triangle->setPixmap(QPixmap(ASSET_PATH + "icons/warning.png"));
inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(80);
QLabel *title = new QLabel(tr("WARNING: Low Voltage"));
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(25);
QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk."));
body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300;");
inner_layout->addWidget(body);
inner_layout->addStretch();
// power off + continue buttons
QHBoxLayout *blayout = new QHBoxLayout();
blayout->setSpacing(50);
main_layout->addLayout(blayout, 0);
QPushButton *poweroff = new QPushButton(tr("Power off"));
poweroff->setObjectName("navBtn");
blayout->addWidget(poweroff);
QObject::connect(poweroff, &QPushButton::clicked, this, [=]() {
Hardware::poweroff();
});
QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn");
blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
return widget;
}
QWidget * Setup::custom_software_warning() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
main_layout->setContentsMargins(55, 0, 55, 55);
main_layout->setSpacing(0);
QVBoxLayout *inner_layout = new QVBoxLayout();
inner_layout->setContentsMargins(110, 110, 300, 0);
main_layout->addLayout(inner_layout);
QLabel *title = new QLabel(tr("WARNING: Custom Software"));
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(25);
QLabel *body = new QLabel(tr("Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.\n\nIf you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later."));
body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 65px; font-weight: 300;");
inner_layout->addWidget(body);
inner_layout->addStretch();
QHBoxLayout *blayout = new QHBoxLayout();
blayout->setSpacing(50);
main_layout->addLayout(blayout, 0);
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
blayout->addWidget(back);
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn");
blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, this, [=]() {
QTimer::singleShot(0, [=]() {
setCurrentWidget(downloading_widget);
});
QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
if (!url.isEmpty()) {
QTimer::singleShot(1000, this, [=]() {
download(url);
});
} else {
setCurrentWidget(software_selection_widget);
}
});
return widget;
}
QWidget * Setup::getting_started() {
QWidget *widget = new QWidget();
QHBoxLayout *main_layout = new QHBoxLayout(widget);
main_layout->setMargin(0);
QVBoxLayout *vlayout = new QVBoxLayout();
vlayout->setContentsMargins(165, 280, 100, 0);
main_layout->addLayout(vlayout);
QLabel *title = new QLabel(tr("Getting Started"));
title->setStyleSheet("font-size: 90px; font-weight: 500;");
vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
vlayout->addSpacing(90);
QLabel *desc = new QLabel(tr("Before we get on the road, lets finish installation and cover some details."));
desc->setWordWrap(true);
desc->setStyleSheet("font-size: 80px; font-weight: 300;");
vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft);
vlayout->addStretch();
QPushButton *btn = new QPushButton();
btn->setIcon(QIcon(":/images/button_continue_triangle.svg"));
btn->setIconSize(QSize(54, 106));
btn->setFixedSize(310, 1080);
btn->setProperty("primary", true);
btn->setStyleSheet("border: none;");
main_layout->addWidget(btn, 0, Qt::AlignRight);
QObject::connect(btn, &QPushButton::clicked, this, &Setup::nextPage);
return widget;
}
QWidget * Setup::network_setup() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
main_layout->setContentsMargins(55, 50, 55, 50);
// title
QLabel *title = new QLabel(tr("Connect to Wi-Fi"));
title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
main_layout->addSpacing(25);
// wifi widget
Networking *networking = new Networking(this, false);
networking->setStyleSheet("Networking {background-color: #292929; border-radius: 13px;}");
main_layout->addWidget(networking, 1);
main_layout->addSpacing(35);
// back + continue buttons
QHBoxLayout *blayout = new QHBoxLayout;
main_layout->addLayout(blayout);
blayout->setSpacing(50);
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back);
QPushButton *cont = new QPushButton();
cont->setObjectName("navBtn");
cont->setProperty("primary", true);
cont->setEnabled(false);
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
blayout->addWidget(cont);
// setup timer for testing internet connection
HttpRequest *request = new HttpRequest(this, false, 2500);
QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) {
cont->setEnabled(success);
if (success) {
const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI;
cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi"));
} else {
cont->setText(tr("Waiting for internet"));
}
repaint();
});
request->sendRequest(OPENPILOT_URL);
QTimer *timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, [=]() {
if (!request->active() && cont->isVisible()) {
request->sendRequest(OPENPILOT_URL);
}
});
timer->start(1000);
return widget;
}
QWidget * radio_button(QString title, QButtonGroup *group) {
QPushButton *btn = new QPushButton(title);
btn->setCheckable(true);
group->addButton(btn);
btn->setStyleSheet(R"(
QPushButton {
height: 230;
padding-left: 100px;
padding-right: 100px;
text-align: left;
font-size: 80px;
font-weight: 400;
border-radius: 10px;
background-color: #4F4F4F;
}
QPushButton:checked {
background-color: #465BEA;
}
)");
// checkmark icon
QPixmap pix(":/icons/circled_check.svg");
btn->setIcon(pix);
btn->setIconSize(QSize(0, 0));
btn->setLayoutDirection(Qt::RightToLeft);
QObject::connect(btn, &QPushButton::toggled, [=](bool checked) {
btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0));
});
return btn;
}
QWidget * Setup::software_selection() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
main_layout->setContentsMargins(55, 50, 55, 50);
main_layout->setSpacing(0);
// title
QLabel *title = new QLabel(tr("Choose Software to Install"));
title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
main_layout->addSpacing(50);
// sunnypilot + custom radio buttons
QButtonGroup *group = new QButtonGroup(widget);
group->setExclusive(true);
QWidget *openpilot = radio_button(tr("sunnypilot"), group);
main_layout->addWidget(openpilot);
main_layout->addSpacing(30);
QWidget *custom = radio_button(tr("Custom Software"), group);
main_layout->addWidget(custom);
main_layout->addStretch();
// back + continue buttons
QHBoxLayout *blayout = new QHBoxLayout;
main_layout->addLayout(blayout);
blayout->setSpacing(50);
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back);
QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn");
cont->setEnabled(false);
cont->setProperty("primary", true);
blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, [=]() {
if (group->checkedButton() != openpilot) {
QTimer::singleShot(0, [=]() {
setCurrentWidget(custom_software_warning_widget);
});
} else {
QTimer::singleShot(0, [=]() {
setCurrentWidget(downloading_widget);
});
QTimer::singleShot(1000, this, [=]() {
download(OPENPILOT_URL);
});
}
});
connect(group, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) {
btn->setChecked(true);
cont->setEnabled(true);
});
return widget;
}
QWidget * Setup::downloading() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
QLabel *txt = new QLabel(tr("Downloading..."));
txt->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(txt, 0, Qt::AlignCenter);
return widget;
}
QWidget * Setup::download_failed(QLabel *url, QLabel *body) {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
main_layout->setContentsMargins(55, 185, 55, 55);
main_layout->setSpacing(0);
QLabel *title = new QLabel(tr("Download Failed"));
title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
main_layout->addSpacing(67);
url->setWordWrap(true);
url->setAlignment(Qt::AlignTop | Qt::AlignLeft);
url->setStyleSheet("font-family: \"JetBrains Mono\"; font-size: 64px; font-weight: 400; margin-right: 100px;");
main_layout->addWidget(url);
main_layout->addSpacing(48);
body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;");
main_layout->addWidget(body);
main_layout->addStretch();
// reboot + start over buttons
QHBoxLayout *blayout = new QHBoxLayout();
blayout->setSpacing(50);
main_layout->addLayout(blayout, 0);
QPushButton *reboot = new QPushButton(tr("Reboot device"));
reboot->setObjectName("navBtn");
blayout->addWidget(reboot);
QObject::connect(reboot, &QPushButton::clicked, this, [=]() {
Hardware::reboot();
});
QPushButton *restart = new QPushButton(tr("Start over"));
restart->setObjectName("navBtn");
restart->setProperty("primary", true);
blayout->addWidget(restart);
QObject::connect(restart, &QPushButton::clicked, this, [=]() {
setCurrentIndex(1);
});
widget->setStyleSheet(R"(
QLabel {
margin-left: 117;
}
)");
return widget;
}
void Setup::prevPage() {
setCurrentIndex(currentIndex() - 1);
}
void Setup::nextPage() {
setCurrentIndex(currentIndex() + 1);
}
Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
if (std::getenv("MULTILANG")) {
selectLanguage();
}
std::stringstream buffer;
buffer << std::ifstream("/sys/class/hwmon/hwmon1/in1_input").rdbuf();
float voltage = (float)std::atoi(buffer.str().c_str()) / 1000.;
if (voltage < 7) {
addWidget(low_voltage());
}
addWidget(getting_started());
addWidget(network_setup());
software_selection_widget = software_selection();
addWidget(software_selection_widget);
custom_software_warning_widget = custom_software_warning();
addWidget(custom_software_warning_widget);
downloading_widget = downloading();
addWidget(downloading_widget);
QLabel *url_label = new QLabel();
QLabel *body_label = new QLabel();
failed_widget = download_failed(url_label, body_label);
addWidget(failed_widget);
QObject::connect(this, &Setup::finished, [=](const QString &url, const QString &error) {
qDebug() << "finished" << url << error;
if (error.isEmpty()) {
// hide setup on success
QTimer::singleShot(3000, this, &QWidget::hide);
} else {
url_label->setText(url);
body_label->setText(error);
setCurrentWidget(failed_widget);
}
});
// TODO: revisit pressed bg color
setStyleSheet(R"(
* {
color: white;
font-family: Inter;
}
Setup {
background-color: black;
}
QPushButton#navBtn {
height: 160;
font-size: 55px;
font-weight: 400;
border-radius: 10px;
background-color: #333333;
}
QPushButton#navBtn:disabled, QPushButton[primary='true']:disabled {
color: #808080;
background-color: #333333;
}
QPushButton#navBtn:pressed {
background-color: #444444;
}
QPushButton[primary='true'], #navBtn[primary='true'] {
background-color: #465BEA;
}
QPushButton[primary='true']:pressed, #navBtn:pressed[primary='true'] {
background-color: #3049F4;
}
)");
}
void Setup::selectLanguage() {
QMap<QString, QString> langs = getSupportedLanguages();
QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), "", this);
if (!selection.isEmpty()) {
QString selectedLang = langs[selection];
Params().put("LanguageSetting", selectedLang.toStdString());
if (translator.load(":/" + selectedLang)) {
qApp->installTranslator(&translator);
}
}
}
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
Setup setup;
setMainWindow(&setup);
return a.exec();
}
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include <QLabel>
#include <QStackedWidget>
#include <QString>
#include <QTranslator>
#include <QWidget>
class Setup : public QStackedWidget {
Q_OBJECT
public:
explicit Setup(QWidget *parent = 0);
private:
void selectLanguage();
QWidget *low_voltage();
QWidget *custom_software_warning();
QWidget *getting_started();
QWidget *network_setup();
QWidget *software_selection();
QWidget *downloading();
QWidget *download_failed(QLabel *url, QLabel *body);
QWidget *failed_widget;
QWidget *downloading_widget;
QWidget *custom_software_warning_widget;
QWidget *software_selection_widget;
QTranslator translator;
signals:
void finished(const QString &url, const QString &error = "");
public slots:
void nextPage();
void prevPage();
void download(QString url);
};
+10 -4
View File
@@ -29,6 +29,7 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(
flag_img = loadPixmap("../assets/images/button_flag.png", home_btn.size());
settings_img = loadPixmap("../assets/images/button_settings.png", settings_btn.size(), Qt::IgnoreAspectRatio);
mic_img = loadPixmap("../assets/icons/microphone.png", QSize(30, 30));
link_img = loadPixmap("../assets/icons/link.png", QSize(60, 60));
connect(this, &Sidebar::valueChanged, [=] { update(); });
@@ -38,7 +39,7 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(
QObject::connect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState);
pm = std::make_unique<PubMaster>(std::vector<const char*>{"userFlag"});
pm = std::make_unique<PubMaster>(std::vector<const char*>{"bookmarkButton"});
}
void Sidebar::mousePressEvent(QMouseEvent *event) {
@@ -61,8 +62,8 @@ void Sidebar::mouseReleaseEvent(QMouseEvent *event) {
}
if (onroad && home_btn.contains(event->pos())) {
MessageBuilder msg;
msg.initEvent().initUserFlag();
pm->send("userFlag", msg);
msg.initEvent().initBookmarkButton();
pm->send("bookmarkButton", msg);
} else if (settings_btn.contains(event->pos())) {
emit openSettings();
} else if (recording_audio && mic_indicator_btn.contains(event->pos())) {
@@ -154,7 +155,12 @@ void Sidebar::drawSidebar(QPainter &p) {
p.setFont(InterFont(35));
p.setPen(QColor(0xff, 0xff, 0xff));
const QRect r = QRect(58, 247, width() - 100, 50);
p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter, net_type);
if (net_type == "Hotspot") {
p.drawPixmap(r.x(), r.y() + (r.height() - link_img.height()) / 2, link_img);
} else {
p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter, net_type);
}
#ifndef SUNNYPILOT
// metrics
+1 -1
View File
@@ -43,7 +43,7 @@ protected:
void drawMetric(QPainter &p, const QPair<QString, QString> &label, QColor c, int y);
virtual void drawSidebar(QPainter &p);
QPixmap home_img, flag_img, settings_img, mic_img;
QPixmap home_img, flag_img, settings_img, mic_img, link_img;
bool onroad, recording_audio, flag_pressed, settings_pressed, mic_indicator_pressed;
const QMap<cereal::DeviceState::NetworkType, QString> network_type = {
{cereal::DeviceState::NetworkType::NONE, tr("--")},
+1
View File
@@ -47,6 +47,7 @@ lateral_panel_qt_src = [
"sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/angle_tuning_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
]
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/angle_tuning_settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
AngleTunningSettings::AngleTunningSettings(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
main_layout->setSpacing(20);
// Back button
PanelBackButton *back = new PanelBackButton();
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
auto *list = new ListWidgetSP(this, false);
main_layout->addWidget(new QWidget());
enableHkgAngleSmoothingFactor = new ExpandableToggleRow("EnableHkgTuningAngleSmoothingFactor", tr("HKG Angle Smoothing Factor"), tr("Applies EMA (Exponential Moving Average) to the desired angle steering and avoid overcorrections."), "../assets/offroad/icon_blank.png");
list->addItem(enableHkgAngleSmoothingFactor);
auto first_row = new QHBoxLayout();
hkgTuningOverridingCycles = new OptionControlSP("HkgTuningOverridingCycles", tr("Override Ramp-Down Cycles"), tr("Number of cycles to ramp down the current amount of torque on the steering wheel.<br/>A smaller value means a faster override by the user (less effort)"), "../assets/offroad/icon_blank.png", {10, 30}, 1);
connect(hkgTuningOverridingCycles, &OptionControlSP::updateLabels, hkgTuningOverridingCycles, [=]() {
this->updateToggles(offroad);
});
first_row->addWidget(hkgTuningOverridingCycles);
hkgAngleMinTorque = new OptionControlSP("HkgTuningAngleMinTorqueReductionGain", tr("Override Steering Effort"), tr("Sets the steering effort percentage used when the driver is overriding lateral control.<br/>Higher values increase resistance and make the wheel feel stiffer."), "../assets/offroad/icon_blank.png", {5, 60}, 1);
connect(hkgAngleMinTorque, &OptionControlSP::updateLabels, hkgAngleMinTorque, [=]() {
this->updateToggles(offroad);
});
first_row->addWidget(hkgAngleMinTorque);
list->addItem(first_row);
auto second_row = new QHBoxLayout();
hkgAngleActiveTorque = new OptionControlSP("HkgTuningAngleActiveTorqueReductionGain", tr("Min Active Torque"), tr("Torque applied when lateral control is active but the vehicle is not turning.<br/>Used to maintain lane centering on straight paths when no user input is detected."), "../assets/offroad/icon_blank.png", {10, 100}, 1);
connect(hkgAngleActiveTorque, &OptionControlSP::updateLabels, hkgAngleActiveTorque, [=]() {
this->updateToggles(offroad);
});
second_row->addWidget(hkgAngleActiveTorque);
hkgAngleMaxTorque = new OptionControlSP("HkgTuningAngleMaxTorqueReductionGain", tr("Max Torque Allowance"), tr("Sets the maximum torque reduction percentage the controller can apply during normal lateral control.<br/>"), "../assets/offroad/icon_blank.png", {10, 100}, 1);
connect(hkgAngleMaxTorque, &OptionControlSP::updateLabels, hkgAngleMaxTorque, [=]() {
this->updateToggles(offroad);
});
second_row->addWidget(hkgAngleMaxTorque);
list->addItem(second_row);
QObject::connect(uiState(), &UIState::offroadTransition, this, &AngleTunningSettings::updateToggles);
main_layout->addWidget(new ScrollViewSP(list, this));
auto *warning = new QLabel(tr("Reboot required for settings to apply; Tap on each setting to see more details."));
warning->setStyleSheet("font-size: 30px; font-weight: 500; font-family: 'Noto Color Emoji'; color: orange;");
main_layout->addWidget(warning, 0, Qt::AlignCenter);
}
void AngleTunningSettings::showEvent(QShowEvent *event) {
updateToggles(offroad);
}
void AngleTunningSettings::updateToggles(bool _offroad) {
auto HkgAngleSmoothingFactorValue = params.getBool("EnableHkgTuningAngleSmoothingFactor");
enableHkgAngleSmoothingFactor->toggleFlipped(HkgAngleSmoothingFactorValue);
auto HkgAngleMinTorqueValue = QString::fromStdString(params.get("HkgTuningAngleMinTorqueReductionGain")).toInt();
hkgAngleMinTorque->setLabel(QString::number(HkgAngleMinTorqueValue)+"%");
auto HkgAngleActiveTorqueValue = QString::fromStdString(params.get("HkgTuningAngleActiveTorqueReductionGain")).toInt();
hkgAngleActiveTorque->setLabel(QString::number(HkgAngleActiveTorqueValue)+"%");
auto HkgAngleMaxTorqueValue = QString::fromStdString(params.get("HkgTuningAngleMaxTorqueReductionGain")).toInt();
hkgAngleMaxTorque->setLabel(QString::number(HkgAngleMaxTorqueValue)+"%");
auto HkgTuningOverridingCyclesValue = QString::fromStdString(params.get("HkgTuningOverridingCycles")).toInt();
hkgTuningOverridingCycles->setLabel(QString::number(HkgTuningOverridingCyclesValue));
offroad = _offroad;
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
class AngleTunningSettings : public QWidget {
Q_OBJECT
public:
explicit AngleTunningSettings(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles(bool _offroad);
private:
Params params;
bool offroad;
ExpandableToggleRow* enableHkgAngleSmoothingFactor;
OptionControlSP* hkgAngleMinTorque;
OptionControlSP* hkgAngleActiveTorque;
OptionControlSP* hkgAngleMaxTorque;
OptionControlSP* hkgTuningOverridingCycles;
};
@@ -89,6 +89,36 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
nnlcToggle->updateToggle();
});
#pragma region hkg angle tuning
list->addItem(vertical_space());
list->addItem(horizontal_line());
list->addItem(vertical_space());
// HKG Angle Tuning
// angleTuningToggle = new ParamControl(
// "AngleTuning",
// tr("Modular Assistive Driving System (MADS)"),
// tr("Enable the beloved MADS feature. Disable toggle to revert back to stock sunnypilot engagement/disengagement."),
// "");
// angleTuningToggle->setConfirmation(true, false);
// list->addItem(angleTuningToggle);
angleTuningSettingsButton = new PushButtonSP(tr("Customize ANGLE Tuning"));
angleTuningSettingsButton->setObjectName("angle_btn");
connect(angleTuningSettingsButton, &QPushButton::clicked, [=]() {
sunnypilotScroller->setLastScrollPosition();
main_layout->setCurrentWidget(angleTuningWidget);
});
// QObject::connect(angleTuningToggle, &ToggleControl::toggleFlipped, angleTuningSettingsButton, &PushButtonSP::setEnabled);
angleTuningWidget = new AngleTunningSettings(this);
connect(angleTuningWidget, &AngleTunningSettings::backPress, [=]() {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
list->addItem(angleTuningSettingsButton);
#pragma endregion
toggleOffroadOnly = {
madsToggle, nnlcToggle,
};
@@ -99,6 +129,7 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
main_layout->addWidget(sunnypilotScreen);
main_layout->addWidget(madsWidget);
main_layout->addWidget(angleTuningWidget);
main_layout->addWidget(laneChangeWidget);
setStyleSheet(R"(
@@ -149,6 +180,7 @@ void LateralPanel::updateToggles(bool _offroad) {
}
madsSettingsButton->setEnabled(madsToggle->isToggled());
// angleTuningSettingsButton->setEnabled(angleTuningToggle->isToggled());
blinkerPauseLateralSettings->refresh();
@@ -13,6 +13,7 @@
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/angle_tuning_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h"
#include "selfdrive/ui/qt/util.h"
@@ -39,8 +40,11 @@ private:
bool offroad;
ParamControl *madsToggle;
// ParamControl *angleTuningToggle;
PushButtonSP *madsSettingsButton;
PushButtonSP *angleTuningSettingsButton;
MadsSettings *madsWidget = nullptr;
AngleTunningSettings *angleTuningWidget = nullptr;
PushButtonSP *laneChangeSettingsButton;
LaneChangeSettings *laneChangeWidget = nullptr;
NeuralNetworkLateralControl *nnlcToggle = nullptr;
@@ -68,6 +68,14 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels);
list->addItem(currentModelLblBtn);
refreshAvailableModelsBtn = new ButtonControlSP(tr("Refresh Model List"), tr("REFRESH"), "", this);
connect(refreshAvailableModelsBtn, &ButtonControlSP::clicked, this, [=]() {
params.put("ModelManager_LastSyncTime", "0");
ConfirmationDialog::alert(tr("Fetching Latest Models"), this);
});
list->addItem(refreshAvailableModelsBtn);
clearModelCacheBtn = new ButtonControlSP(tr("Clear Model Cache"), tr("CLEAR"), "", this);
connect(clearModelCacheBtn, &ButtonControlSP::clicked, this, &ModelsPanel::clearModelCache);
@@ -361,10 +369,6 @@ void ModelsPanel::updateLabels() {
QString desc = tr("Enable this for the car to learn and adapt its steering response time. "
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. "
"The Current value is updated automatically when the vehicle is Onroad.");
QString current = QString::fromStdString(params.get("LagdToggleDesc", false));
if (!current.isEmpty()) {
desc += "<br><br><b><span style=\"color:#e0e0e0\">" + tr("Current:") + "</span></b> <span style=\"color:#e0e0e0\">" + current + "</span>";
}
lagd_toggle_control->setDescription(desc);
delay_control->setVisible(!params.getBool("LagdToggle"));
@@ -79,5 +79,6 @@ private:
QFrame *policyFrame;
Params params;
ButtonControlSP *clearModelCacheBtn;
ButtonControlSP *refreshAvailableModelsBtn;
};
+52
View File
@@ -0,0 +1,52 @@
import pytest
import cereal.messaging as messaging
from cereal import car
from openpilot.common.params import Params
from openpilot.system.manager.process_config import managed_processes
class TestFeedbackd:
def setup_method(self):
self.pm = messaging.PubMaster(['carState', 'rawAudioData'])
self.sm = messaging.SubMaster(['audioFeedback'])
def _send_lkas_button(self, pressed: bool):
msg = messaging.new_message('carState')
msg.carState.canValid = True
msg.carState.buttonEvents = [{'type': car.CarState.ButtonEvent.Type.lkas, 'pressed': pressed}]
self.pm.send('carState', msg)
def _send_audio_data(self, count: int = 5):
for _ in range(count):
audio_msg = messaging.new_message('rawAudioData')
audio_msg.rawAudioData.data = bytes(1600) # 800 samples of int16
audio_msg.rawAudioData.sampleRate = 16000
self.pm.send('rawAudioData', audio_msg)
self.sm.update(timeout=100)
@pytest.mark.parametrize("record_feedback", [False, True])
def test_audio_feedback(self, record_feedback):
Params().put_bool("RecordAudioFeedback", record_feedback)
managed_processes["feedbackd"].start()
assert self.pm.wait_for_readers_to_update('carState', timeout=5)
assert self.pm.wait_for_readers_to_update('rawAudioData', timeout=5)
self._send_lkas_button(pressed=True)
self._send_audio_data()
self._send_lkas_button(pressed=False)
self._send_audio_data()
if record_feedback:
assert self.sm.updated['audioFeedback'], "audioFeedback should be published when enabled"
else:
assert not self.sm.updated['audioFeedback'], "audioFeedback should not be published when disabled"
self._send_lkas_button(pressed=True)
self._send_audio_data()
self._send_lkas_button(pressed=False)
self._send_audio_data()
assert not self.sm.updated['audioFeedback'], "audioFeedback should not be published after second press"
managed_processes["feedbackd"].stop()
+8
View File
@@ -0,0 +1,8 @@
import time
from openpilot.selfdrive.test.helpers import with_processes
@with_processes(["raylib_ui"])
def test_raylib_ui():
"""Test initialization of the UI widgets is successful."""
time.sleep(1)
+2 -3
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python3
import capnp
import json
import pathlib
import shutil
import sys
@@ -265,12 +264,12 @@ def setup_settings_trips(click, pm: PubMaster, scroll=None):
time.sleep(UI_DELAY)
def setup_settings_vehicle(click, pm: PubMaster, scroll=None):
Params().put("CarPlatformBundle", json.dumps(
Params().put("CarPlatformBundle",
{
"platform": "HONDA_CIVIC_2022",
"name": "Honda Civic 2022-24"
}
))
)
setup_settings_device(click, pm)
scroll(-400, 278, 962)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

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