Compare commits

...

119 Commits

Author SHA1 Message Date
James Vecellio-Grant
1f8cf99c2a Merge branch 'master' into optional-turn-desire-nudge 2025-09-18 06:11:15 -07:00
Jason Wen
1f8941367d Intelligent Cruise Button Management (ICBM) (#1242)
* init

* slightly more

* check across all

* publish on CC_SP

* more infra setup

* try it out for HKG for now

* slight cleanup

* oops

* legacy

* send

* actually take over

* expose toggle

* icbm

* need to allow

* fix

* name

* small fixes

* actually send it now

* set default

* use cs is_metric

* offroad only lol

* allow them all

* fix

* send desire as-is

* use stock method

* clean up hysteresis

* speed cluster may be more accurate

* rename

* allow init and resume from pcmCruise

* just send it

* fix

* only allow custom v cruise after no button press at initial enabled

* no hysteresis for now

* fix tests

* fix min check

* only apply to non pcm changes

* add ICBM

* some more ui

* bump

* slight cleanup

* fixup

* cleanup

* type hints

* type hints

* bump

* bump

* bring back hysteresis

* fix ui

* do not spam if overriding or below allowed speed
2025-09-18 00:06:16 -04:00
Jason Wen
784e1d6658 Smart Cruise Control: Vision (SCC-V) (#997)
* Controls: Vision Turn Speed Control

* fix

* Data type temp fix

* format

* more

* even more

* self contain targets

* state cleanup

* fix

* param updates

* no need

* use similar state machine

* raise exception if not found

* new state

* entirely internal

* use long active

* more

* rename and expose aTarget

* rename to SCC-V

* init tests

* slight tests

* expose toggle

* lint

* todo

* remove lat planner sub and mock sm data

* introduce aTarget

* rename

* rename

* update fill_model_msg.py to calculate PLAN_T_IDXS for lanelines and road edges

* sync upstream

* no SCC-V yet

* Revert "no SCC-V yet"

This reverts commit b67281bcac.

* wrap it with SCC main

* leave enabled out of here

* wat

* enabled and active on cereal

* OP long for now, enable for ICBM once merged

* need this

* unused

* let's go hybrid

* fix

* add override state

* update tests

* huh

* don't math here if not enabled

* ui: Smart Cruise Control - Vision (SCC-V) (#1253)

* vtsc-ui

* slight cleanup

* more cleanup

* unused

* a bit more

* pulse like it's hot

* draw only enabled and active

* let's try this for now

* settle

* finalize UI

* brighter color so we blind devtekve

* add long override

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>

* slight cleanup

* more

* type hints

---------

Co-authored-by: discountchubbs <alexgrant990@gmail.com>
Co-authored-by: Kumar <36933347+rav4kumar@users.noreply.github.com>
2025-09-17 22:55:36 -04:00
Jason Wen
cb94d3b055 Longitudinal: Smart Cruise Control prerequisites (#1249)
* Controls: Vision Turn Speed Control

* fix

* Data type temp fix

* format

* more

* even more

* self contain targets

* state cleanup

* fix

* param updates

* no need

* use similar state machine

* raise exception if not found

* new state

* entirely internal

* use long active

* more

* rename and expose aTarget

* rename to SCC-V

* init tests

* slight tests

* expose toggle

* lint

* todo

* remove lat planner sub and mock sm data

* introduce aTarget

* rename

* rename

* update fill_model_msg.py to calculate PLAN_T_IDXS for lanelines and road edges

* sync upstream

* no SCC-V yet

* Revert "no SCC-V yet"

This reverts commit b67281bcac.

* wrap it with SCC main

* no SCC-V yet

* noqa now

* fix

* OP long for now, enable for ICBM once merged

* type hints

* let's get it straight from carcontrol instead

* not needed

* unused

* add source to track

* we can do this

---------

Co-authored-by: discountchubbs <alexgrant990@gmail.com>
2025-09-16 22:51:32 -04:00
DevTekVE
94f93a9f26 ui: Add standstill timer to HUD (#1251)
* Add standstill timer to HUD

- Introduced a timer displaying elapsed time when the car is at a standstill.
- Added settings toggle to enable/disable this feature.
- Updated UI elements and related logic to support the standstill timer.

* ruff be happy

* stop screaming

* c stands for not cereal

* slight cleanup

* a bit more cleanup

* unused

---------

Co-authored-by: nayan <nayan8teen@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-09-16 10:05:34 +02:00
James Vecellio-Grant
7aa9d43187 Merge branch 'master' into optional-turn-desire-nudge 2025-09-15 06:14:24 -07:00
Jason Wen
747460363f panda: consolidate supported panda checks (#1248)
* panda: fix upstream merge conflicts

* move it higher

* consolidate checks

* update

* bump

* actual bump
2025-09-14 23:51:29 -04:00
James Vecellio-Grant
b32c6dafee modeld: add laneline helper for plan indices calculation (#1240)
* modeld: add laneline_helper for plan X indices calculation

* spacing

* keep type hints

* openpilot

* sunnypilot/models/helpers

add modeld helpers to helpers

* Send it from each fill message

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-09-14 21:45:22 -04:00
DevTekVE
bffb2fb6fa bugfix: param store to support the latest param changes (#1244)
* bugfix: update parameter handling to use custom CarControlSP.Param and improve param retrieval

* bump opendbc
2025-09-14 20:22:51 +02:00
Jason Wen
688b694266 Sync: commaai/openpilot:master into sunnypilot/sunnypilot:master (#1229) 2025-09-13 17:42:17 -04:00
Jason Wen
ec8f036850 Merge branch 'upstream/openpilot/master' into sync-20250908
# Conflicts:
#	.github/workflows/selfdrive_tests.yaml
#	README.md
#	docs/CARS.md
#	opendbc_repo
#	panda
#	selfdrive/car/tests/test_car_interfaces.py
#	selfdrive/modeld/modeld.py
#	selfdrive/selfdrived/selfdrived.py
#	selfdrive/ui/translations/main_ar.ts
#	selfdrive/ui/translations/main_de.ts
#	selfdrive/ui/translations/main_es.ts
#	selfdrive/ui/translations/main_fr.ts
#	selfdrive/ui/translations/main_ja.ts
#	selfdrive/ui/translations/main_ko.ts
#	selfdrive/ui/translations/main_pt-BR.ts
#	selfdrive/ui/translations/main_th.ts
#	selfdrive/ui/translations/main_tr.ts
#	selfdrive/ui/translations/main_zh-CHS.ts
#	selfdrive/ui/translations/main_zh-CHT.ts
#	system/hardware/hardwared.py
#	system/updated/updated.py
#	tinygrad_repo
#	uv.lock
2025-09-13 15:23:36 -04:00
Nayan
4f44d6e643 Reapply "UI: Developer UI" (#1238) (#1239)
This reverts commit 1be13fdc55.

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-09-12 10:58:59 -04:00
James Vecellio-Grant
920fd823ca Merge branch 'master' into optional-turn-desire-nudge 2025-09-12 06:46:53 -07:00
Jason Wen
1be13fdc55 Revert "UI: Developer UI" (#1238)
* Revert "Revert & Reapply "UI: Developer UI" temporarily due to QT version mismatch (#1237)"

This reverts commit 810a2d9448.

* Revert "UI: Developer UI (#1233)"

This reverts commit 1bb4ca2547.
2025-09-12 08:37:24 -04:00
DevTekVE
810a2d9448 Revert & Reapply "UI: Developer UI" temporarily due to QT version mismatch (#1237)
* Revert "UI: Developer UI (#1233)"

This reverts commit 1bb4ca2547.

* Reapply "UI: Developer UI (#1233)"

This reverts commit b0a77049da.

* QColorConstants is not on device's QT version. Thanks @kumar for the fix
2025-09-12 09:03:17 +02:00
Jimmy
c9dbf97649 jotpluggler: add icons, use monospace font, and fix ui quirks (#36141)
* use play/pause icons

* use monospace font

* x button for delete

* add icons for splitting

* many scaling + scrollbar fixes and niceties

* simplify texture loading code
2025-09-11 23:31:32 -07:00
Nayan
1bb4ca2547 UI: Developer UI (#1233) 2025-09-12 01:00:05 -04:00
Adeeb Shihadeh
2c04a27a2a ubloxd: cleanup unused files 2025-09-11 14:03:37 -07:00
DevTekVE
b7f8dd11a5 SL: bugfix parameter handling in sunnylink restore and remote setting (#1234)
* refactor: improve parameter handling in sunnylink for robustness

- Updated `get_param_as_byte` to return `None` for nonexistent parameters.
- Enhanced param compression and encoding in `sunnylinkd`.

* refactor: centralize parameter restoration with new helper function

- Added `save_param_from_base64_encoded_string` to handle param decoding and saving.
- Updated backup manager and sunnylinkd to use the new method.
- Improved code readability and reduced duplication in parameter handling logic.

* don't bother

* clean
2025-09-11 21:44:43 +02:00
Adeeb Shihadeh
70c0592e84 CI: re-enable macOS build (#36120)
* CI: re-enable macOS build

* Update selfdrive_tests.yaml with new env variable
2025-09-11 11:03:59 -07:00
Jimmy
572c03dbac jotpluggler: fix flashing while searching (#36128)
* modify in place instead of recreating nodes

* don't delete DataTreeNodes and simplify code

* faster: more efficient state tracking, better handler deletion
2025-09-11 10:48:45 -07:00
Adeeb Shihadeh
fa498221da still thinking about this one 2025-09-11 10:48:32 -07:00
vanillagorillaa
67238d5045 Update release notes (#36137)
Update RELEASES.md
2025-09-11 10:47:23 -07:00
Jimmy
994170ddb5 fix qcom decoder compilation on mac with platform check (#36131) 2025-09-11 10:45:36 -07:00
Jason Young
3c28188d7a Honda: Add Honda N-Box 2018 to release (#36134)
* bump opendbc

* regen CARS.md

* add to RELEASES.md
2025-09-11 07:04:15 -04:00
Jason Young
4ccd17903b correction to Honda release notes (#36133)
correction to release notes
2025-09-11 05:59:15 -04:00
Jason Young
0e1b573f89 Honda: Add Honda Odyssey 2021-25 to release (#36132)
* bump opendbc

* regen CARS.md

* add to RELEASES.md

* forgot this was originally VG's PR

* correctly typo the typo

* follow recent DBC cleanup
2025-09-11 05:32:44 -04:00
Maxime Desroches
10580aca92 ci: adjust power draw bounds (#36130)
* consider min

* bounds
2025-09-10 15:06:25 -07:00
Jimmy
6b13175338 jotpluggler: better handle sparse message data and bools (#36124)
* better handle sparse message data

* fix plotting of of bools

* add type for msg._valid

* fix typing

* add assert in case something changes in future
2025-09-10 00:09:08 -07:00
YassineYousfi
d0171084b5 Update RELEASES.md 0.10.1 2025-09-09 15:40:39 -07:00
Jason Young
2bfdd0d61d PlotJuggler: Updated layout for torque controller (#36123)
* PlotJuggler: Updated layout for torque controller

* yeah, no
2025-09-09 18:02:31 -04:00
Jimmy
ea53111afc basic jotpluggler (#36045)
* jotpluggler!

* demo, executable, fontfile

* calc max and min, numpy, cloudlog

* mypy things

* simplified data.py

* multiprocessed data ingest

* allow verrryyy long search results

* stream in multiprocessed segments

* bug fixes

* simplify and speed up timeseries

* small fixes

* rewrite layout

* resizable layouts

* cleanup

* downsampling

* deque for consistency

* use item_visible_handler

* only build visible UI

* don't delete item handlers, add locks, don't expand large lists

* delete item handlers after a frame

* small data tree improvements

* seperate datatree into its own file

* reset when loading new segments

* fix plot window resizing and recursive split resizing logic
2025-09-09 10:56:07 -07:00
github-actions[bot]
0739d4ac2d [bot] Update translations (#36089)
Update translations

Co-authored-by: Vehicle Researcher <user@comma.ai>
2025-09-08 09:01:53 -07:00
discountchubbs
d450a4479f not used 2025-09-08 05:40:02 -07:00
discountchubbs
70b4de2c6b Merge remote-tracking branch 'origin/optional-turn-desire-nudge' into optional-turn-desire-nudge 2025-09-07 20:54:53 -07:00
discountchubbs
7611857719 remove 2025-09-07 20:54:45 -07:00
James Vecellio-Grant
82467e9436 Merge branch 'master' into optional-turn-desire-nudge 2025-09-07 20:46:57 -07:00
discountchubbs
bff22dd699 comments 2025-09-07 20:46:11 -07:00
James Vecellio-Grant
71e1149ee3 Update selfdrive/controls/lib/desire_helper.py
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-09-07 20:43:33 -07:00
Jason Wen
698e0ca00f migration: new branch names (#1225)
* migration: new branch names

* more migration

* update channel type

* no more var

* update

* more

* more
2025-09-07 23:23:03 -04:00
discountchubbs
b63d0a1d18 modeld: Add lane turn direction handling to desire logic 2025-09-07 16:22:40 -07:00
discountchubbs
f6b30ef364 what 2025-09-07 15:56:46 -07:00
discountchubbs
c85acd8d7f state handling 2025-09-07 15:52:14 -07:00
Adeeb Shihadeh
8dca43881a Rewrite ubloxd in Python (#36112)
* Rewrite ubloxd in Python

* lil more

* rm from third_party/

* cleanup

* ubx replay

* try this

* back to kaitai

* Revert "ubx replay"

This reverts commit 570bd3d25fbabc590379ce0a9f98d30de55cf1b3.
2025-09-07 15:40:48 -07:00
Adeeb Shihadeh
a885111c0c agnos 13.1 (#36113) 2025-09-07 14:21:14 -07:00
discountchubbs
fd3df55a0c Refactor LaneTurnController to remove dependency on DesireHelper 2025-09-07 13:39:00 -07:00
discountchubbs
566210d678 Refactor lane turn desire handling in DesireHelper and LaneTurnController 2025-09-07 12:48:29 -07:00
Adeeb Shihadeh
bd73664f4c add kaitai python package 2025-09-07 12:21:13 -07:00
Adeeb Shihadeh
608c16007e Rewrite logcatd in Python (#36111)
* Add Python logcatd implementation

* lil more
2025-09-07 11:32:44 -07:00
discountchubbs
d5e3410c65 remove 2025-09-07 11:23:11 -07:00
discountchubbs
7028301532 steer press 2025-09-07 11:22:37 -07:00
Adeeb Shihadeh
275abc1eb5 Rewrite proclogd in Python (#36110)
* Rewrite proclogd in Python

* lil more

* lil more

* Update system/proclogd.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update system/proclogd.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update system/proclogd.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-07 11:13:39 -07:00
Jason Young
ff34b8af76 selfdrived: disable HUD VisualAlert for belowSteerSpeed events (#36109) 2025-09-07 11:59:48 -04:00
discountchubbs
15a3d5fa7e keep -> turn 2025-09-07 08:51:07 -07:00
discountchubbs
255ca767cd Implement nudge mode for lane turn desires in LaneTurnController and DesireHelper 2025-09-07 08:47:42 -07:00
Jason Wen
ff4d1923f0 tici: fix staging root updates (#1223) 2025-09-06 18:46:10 -04:00
Jason Wen
3a91ae08a9 update: actually detect device type as TICI (#1221) 2025-09-06 18:03:16 -04:00
Jason Wen
b161764b1e update: sunnypilot branch migrations for tici (#1212)
* update: sunnypilot branch migrations for tici

* block onroad and notify

* type

* check channel type

* update

* ui init

* no search and locked list for tici

* whenever available
2025-09-06 15:26:32 -04:00
DevTekVE
03e9777c3f Improve debugging for safety (#36055)
* feat: add debugging configurations for replay drive and LLDB attachment

* Add readme with video demo

* clean

* docs: update debugging safety documentation with demo link

* no need for mp4 then added on PR

* Update SConstruct

* bump opendbc

* updating readme

* updating readme

* updating readme

* is this better / worth it?

* final cleanups

* hacky. but does it work?

* Yep that worked!

---------

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-09-06 12:05:15 -07:00
Shane Smiskol
1033d3d80e Desire helper: set lane change direction on entering preLaneChange state (#36074)
* set immediately to avoid flash on right lane changes

* one function

* name

* comment
2025-09-05 22:34:04 -07:00
Jason Wen
7057c57419 ui: cleanup cereal event params parsing (#1219)
* Revert "bugfix: streamline LiveDelay parameter loading with safe handling (#1204)"

This reverts commit 288a5e14da.

* ui: use AlignedBuffer for cereal data processing for Models panel

* align

* separate

* split

* event it

* no more backup

* Revert "no more backup"

This reverts commit fa66ce5e77.
2025-09-05 20:51:58 -04:00
pencilpusher
1f1efec4c9 replay: C3/C3X hardware decoder (#35821)
* bump msgq

* add third_party/linux/include/media/msm_vidc.h

* add sde_rotator hw interface

* add msm_vidc hw decoder interface

* update SConscript to build qcom decoder and rotator

* use qcom decoder in replay framereader

* decode directly to NV12 with the correct stride without using the hw rotator

* bump msgq back to master

* don't compile rotator

* cleanup

* works now but much to simplify

* rm signals

* rm header

---------

Co-authored-by: Test User <test@example.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-09-05 16:05:06 -07:00
DevTekVE
7d4df73ea5 hotfix: model fetcher warning instead of exception when fetching fail 2025-09-05 14:42:08 +02:00
James Vecellio-Grant
29fe152bd3 modeld_v2: desire rename and add many parts from thneed modeld (#1197)
* Add model metadata lookup and update desire handling

* Bump selector version to 10

* meh

* Refactor shape mode parameters for desire handling in test buffer logic

* loop more models

* Refactor buffer handling for temporal inputs and streamline desire updates

* Refactor lateral control input handling and remove unused code
2025-09-04 22:26:59 -04:00
Jason Wen
31918c067a events: add sunnypilot/openpilot to remote origin check (#1216)
events: add sunnypilot/openpilot to remote origin check
2025-09-04 22:22:48 -04:00
Jason Wen
daf5ea2783 update: remove git cleanup in finalized stage (#1210)
* updater: remove git cleanup in finalized stage for quicker updates

* nah
2025-09-04 22:10:08 -04:00
Harald Schäfer
f0f04d4b5b Firehose model (#36087)
816ce390-c41a-42fa-a5df-f393cbe2dcc4/400
2025-09-04 18:51:29 -07:00
Shane Smiskol
2b7707ecf6 Deduplicate car interface test (#36101)
deduplicate test car interfaces
2025-09-04 18:20:43 -07:00
Shane Smiskol
ef870d5533 bump opendbc (#36103)
* bump

* update refs
2025-09-04 18:11:44 -07:00
github-actions[bot]
fd7295c980 [bot] Update Python packages (#1214)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-09-04 14:31:01 -04:00
Jason Wen
220cfff04d ci: skip uv lock upgrade on forks (#1213) 2025-09-04 14:29:11 -04:00
Jason Wen
ee0fb6bf8e Revert "[bot] Update Python packages" (#1211)
Revert "[bot] Update Python packages (#1201)"

This reverts commit 0cd2bbf6c0.
2025-09-04 14:19:31 -04:00
github-actions[bot]
0cd2bbf6c0 [bot] Update Python packages (#1201)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-09-04 10:08:51 -04:00
DevTekVE
0871abcf55 bugfix: fix fetching params for sunnylink and backup (#1177)
* Hotfix for the params stuff until I rework this properly and leverage the new data types

* Revert "Hotfix for the params stuff until I rework this properly and leverage the new data types"

This reverts commit c6031b29d92d3ff5b679ffce3ba53611bb2dba0e.

* refactor: enhance getParams function to support JSON and bytes types with optional compression

* refactor: add TODO for enhancing server support of metadata in sunnylinkd.py

* lint and clean

* refactor: update value handling in getParams to return decoded values for JSON serialization

* refactor: simplify params_dict initialization by removing type hint

* refactor: update response handling in getParams to include JSON serialization of params

* refactor: update response handling in getParams to include JSON serialization of params

* Add to dic types

* refactor: extract get_param_as_byte function for improved parameter handling and fix backup inconsistencies

* cleanup

* ensure error propagates on backup fail
2025-09-04 14:45:37 +02:00
DevTekVE
8ccb777192 bugfix: improve exception handling for sunnylinkd (SUN-89) (#1207)
* bugfix: improve exception handling for WebSocket connections in sunnylinkd

* bugfix: enhance exception handling for WebSocket connections in sunnylinkd

* bugfix: improve OSError handling in sunnylinkd for better error reporting
2025-09-04 14:45:03 +02:00
DevTekVE
0593667601 bugfix: improve error handling in model fetching process (SUN-87) (#1205)
* bugfix: improve error handling in model fetching process

* cleanup

* bugfix: refine error handling in model fetching process
2025-09-04 14:44:42 +02:00
Shane Smiskol
a5044302a2 auto source: auto source 2025-09-03 16:06:41 -07:00
Trey Moen
6a4f685d04 feat(esim): bootstrap (#36094)
* bootstrap

* more

* fix

* simple

* moar

* clarify

---------

Co-authored-by: Comma Device <device@comma.ai>
2025-09-03 11:34:03 -07:00
Trey Moen
355499a8de feat(esim): hotswap (#36096)
feat(esim): device hw reboot modem
2025-09-03 11:31:48 -07:00
DevTekVE
288a5e14da bugfix: streamline LiveDelay parameter loading with safe handling (#1204) 2025-09-03 17:18:18 +02:00
James Vecellio-Grant
9447aa0e3d modeld: turn desires (#1182)
* Add modelDataV2SP and lane turn logic implementation

Note: still need to hook up to other modeld's create unit test, fix stuff, and do the UI for it

* add unit tests for lane turn logic

* Add lane turn desire controls to models panel

* use `events_sp` instead of `events`

* integrate modelDataV2SP messaging to the other modeld controllers

* move this to that

* use min for general population here, on custom branches, change this to max :)

* Update events.py

Co-authored-by: royjr <royjr96@gmail.com>

* Update events.py

Co-authored-by: royjr <royjr96@gmail.com>

* refactor lane turn value control into one method

* Update selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc

* add integration tests for lane turn desire

* 10 updates is possibly more representative of real life

* real objects ofc

* desc: add toggle description for clarity

---------

Co-authored-by: royjr <royjr96@gmail.com>
2025-09-03 05:49:12 -07:00
commaci-public
67fd6c80dd [bot] Update Python packages (#36090)
* Update Python packages

* no more stall

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2025-09-01 09:37:18 -07:00
YassineYousfi
c990515eaf update RELEASES.md for 0.10.1 2025-08-29 14:16:52 -07:00
Kacper Rączy
76e91da3ad process_replay: use LiveParametersV2 in custom params (#36080)
Fill LiveParametersV2 in get_custom_params_from_lr
2025-08-28 20:11:20 +00:00
Adeeb Shihadeh
9fcac06297 op.sh: fix switch on fresh install 2025-08-28 11:49:31 -07:00
Adeeb Shihadeh
3e2549f2b8 remove tici-specific code (#36078)
* remove tici-specific code

* lil more

* update those
2025-08-28 08:19:39 -07:00
Maxime Desroches
63961dec45 jenkins: run pandad test once 2025-08-27 19:04:16 -07:00
Maxime Desroches
7a19a11001 jenkins: tizi loopback device 2025-08-27 18:46:50 -07:00
Maxime Desroches
93f7925c4d jenkins: tizi ox 2025-08-27 18:36:10 -07:00
ZwX1616
a2c5fca787 modeld input queues class (#36072)
* move from xx

* no get_single

* stupid name

* thats fine

* desire_pulse

* 1less

* desire->desire_pulse

* simplify

* reduce copies

* more less
2025-08-27 17:54:53 -07:00
Adeeb Shihadeh
bb06468ead safety standards for forks (#36077)
standards for forks
2025-08-27 16:41:57 -07:00
Adeeb Shihadeh
1d8dc8a69a camerad: remove AR0231 (#36070) 2025-08-27 15:11:58 -07:00
Maxime Desroches
a254a05df0 jenkins: replace tici-common (#36073)
* common

* remove

* test

* Revert "test"

This reverts commit 2c76a8f818e42e0af1d4540dede3595fe0d59ed9.

* Reapply "test"

This reverts commit d9974dd8564d0699dcfa3aac0ffb2dca33f3b47d.

* Revert "Reapply "test""

This reverts commit 2377c6ab20df5dd06886f3dd9a0be07abfce9df6.

* tizi bounds
2025-08-27 14:39:02 -07:00
Maxime Desroches
2aa7648bb8 jenkins: remove ar device 2025-08-27 14:29:39 -07:00
Maxime Desroches
b309bf4173 jenkins: sensord device 2025-08-27 14:26:56 -07:00
Maxime Desroches
a3fcde2ae8 jenkins: tizi-replay 2025-08-27 14:19:23 -07:00
ZwX1616
f8ff156869 modeld: desire->desire_pulse (#36076)
consistent naming
2025-08-27 13:48:23 -07:00
Maxime Desroches
375dfe16a8 jenkins: remove bmx device 2025-08-27 13:39:21 -07:00
felsager
b976135d2f torqued: apply offset (with more robust unit test) (#36075)
* torqued: apply latAccelOffset to torque control feed forward

* test learned latAccelOffset captures roll compensation bias on straight road driving, when the device is not flush in roll relative to the car

* test correct torqued latAccelOffset parameter convergence
2025-08-27 13:06:01 -07:00
Maxime Desroches
f40f7f9ece Revert "torqued: apply offset (#36005)"
This reverts commit 1d74a97ba6.
2025-08-26 21:45:49 -07:00
Maxime Desroches
ea6677c464 AGNOS 13 (#36069)
* staging

* prod
2025-08-26 17:16:57 -07:00
Maxime Desroches
8258257658 ci: modernize test onroad (#36059)
* start

* fix

* better

* more

* test

* Revert "test"

This reverts commit 17066ac123668cb7280cf85e3f21a3043b4785b0.

* remove
2025-08-26 15:34:34 -07:00
Jaume Balust
f5d67b5eee cereal: fix frequency precision by changing from int to float (#36060) 2025-08-26 15:23:24 -07:00
Maxime Desroches
8ee3c7b485 add back dbus-next 2025-08-26 11:52:58 -07:00
Adeeb Shihadeh
8450f9f333 update: more migration 2025-08-26 09:57:23 -07:00
Shane Smiskol
4cd76f4966 raylib networking: prevent concurrently updating networks (#36066)
* dont run by multiple threads at the same time!

* this doesn't work since we rely on is_connected

* Revert "this doesn't work since we rely on is_connected"

This reverts commit 7455b2fe831bf5c9524e8ee71a9966de32a9755a.
2025-08-26 03:55:05 -07:00
Shane Smiskol
ec254074d1 raylib: prevent Firehose from blocking UI (#36067)
* stop blocking ui thread for 1s!!

* rm

* whoopsiedaisy

whoopsiedaisy

* meh
2025-08-26 03:51:45 -07:00
Shane Smiskol
8059106cae raylib networking: reduce DBus calls (#36065)
* this reduces getsettings calls from n*n to n

* these are combined now

* same check
2025-08-26 03:33:08 -07:00
Shane Smiskol
23b4aaf2a5 raylib networking: remove locking on UI thread (#36063)
* use callback queue to make this thread safe and remove locks (which lag ui thread?)

* woah this works

* no more lock!

* always run signal handler and store callbacks, like qt

* debug

* more

* okay not for now

* combine _get_connections and _connection_by_ssid, closer to qt and not an explosion of GetSettings dbus calls

* debug

* try this

* skip

* len

* skip hidden networks

* actually slower

* stash

* back to 8929f37d495a524d4a996d66b82d4a947fbf4f1c

* clean up
2025-08-26 03:25:01 -07:00
Shane Smiskol
5359f6d354 raylib: clean up networking (#36039)
* stasj

* remove one of many classes

* clean up and fix

* clean up

* stash/draft: oh this is sick

* so epic

* some clean up

* what the fuck, it doesn't even use these

* more epic initializers + make it kind of work

* so simple, wonder if we should further 2x reduce line count

* i've never ever seen this pattern b4, rm

* remove bs add niceness

* minor organization

* set security type and support listing and rming conns

* forget and connect

* jeepney is actually pretty good, it's 2x faster to get wifi device (0.005s to 0.002s)

* temp

* do blocking add in worker thread

* add jeepney

* lets finish with python-dbus first then evaluate - revert jeepney

This reverts commit 7de04b11c2285c298bb1ec907782026c795ab207.

and

* safe wrap

* missing

* saved connections

* set rest of callbacks

* skip hidden APs, simplify _running

* add state management

* either wrong password or disconnected for now

* i can't believe we didn't check this...

* disable button if unsupported!!!

* hide/show event no lag hopefully yayay

* fix hide event

* remove old wifi manager

* cache wifi device path + some clean up

* more clean up

* more clean up

* temp disable blocking prime thread

* hackily get device path once

* ok

* debug

* fix open networks

* debug

* clean up

* all threads wait for device path, and user functions dont ever attempt to get, just skip

* same place

* helper

* Revert "helper"

This reverts commit e237d9a720915fb6bd67c539123d3e2d9d582ce1.

* organize?

* Revert "organize?"

This reverts commit 3aca3e5d629c947711ade88799febeb3f61eda87.

* c word is a bad word

* rk monitor debug for now

* nothing crazy

* improve checkmark responsiveness

* when forgetting: this is correct, but feels unresponsive

* this feels good

* need these two to keep forgetting and activating responsive

* sort by connected, strength, then name

* handle non-critical race condition

* log more

* unused

* oh jubilee is sick you can block on signals!!

* proof of concept to see if works on device

whoiops

* so sucking fick

* ah this is not generic, it's a filter on the return vals

* flip around to not drop

* oh thank god

* fix

* stash

* atomic replace

* clean up

* add old to keep track of what's moved over

* these are already moved

* so much junk

* so much junk

* more

* tethering wasn't used so we can ignore that for now

* no params now

* rm duplicate imports

* not used anymore

* move get wifi device over to jeepney! ~no additional lines

* request scan w/ keepney

* get_conns

* _connection_by_ssid_jeepney is 2x faster (0.01 vs 0.02s)

* do forget and activate

* _update_networks matches!

* rm old update_networks

* replace connect_to_network, about same time (yes i removed thread call)

* no more python-dbus!k

* doesn't hurt

* AP.from_dbus: actually handle incorrect paths w/ jeep + more efficient single call

* properly handle errors

* it's jeepney now

* less state

* using the thread safe router passes a race condition test that conn failed!

* bad to copy from old wifimanager

* fix conn usage

* clean up

* curious if locks are lagging

* not for now

* Revert "curious if locks are lagging"

This reverts commit 085dd185b083f5905a4e71ba3e8c0565175e04aa.

* clean up _monitor_state

* remove tests

* clean up dataclasses

* sort

* lint: okay fine it can be non by virtue of exiting right at the perfect time

* some network clean up

* some wifi manager clean up

* this is handled

* stop can be called manually, from deleting wifimanager, or exiting python. some protection

* its not mutable anymore

* scan on enter

* clean up

* back

* lint

* catch dbus fail to connect

catch dbus fail to connect
2025-08-26 01:23:59 -07:00
Shane Smiskol
a70e4c3074 raylib: rm debug print 2025-08-25 22:30:27 -07:00
Shane Smiskol
7a2f2ddf32 raylib: speed up network panel first load (#36062)
* debug

* debug

* clean up
2025-08-25 22:30:09 -07:00
Shane Smiskol
2dc0f97c93 raylib: fix slow Toggles panel first load (#36061)
fix slow load on toggles page
2025-08-25 22:29:14 -07:00
Shane Smiskol
15fcbf24f1 raylib home ui: show/hide events (#36058)
* it's a widget

* proper events

* bottom
2025-08-25 15:52:13 -07:00
Shane Smiskol
e89c6b3b88 raylib: remove redundant networking class (#36057)
remove a class
2025-08-25 15:23:14 -07:00
Shane Smiskol
c4a7f25b62 raylib: refactor NetworkManager constants (#36056)
* new file

* import

* and this
2025-08-25 15:17:37 -07:00
Harald Schäfer
1d74a97ba6 torqued: apply offset (#36005)
* torqued: apply latAccelOffset to torque control feed forward 

* test learned latAccelOffset captures roll compensation bias on straight road driving, when the device is not flush in roll relative to the car

* test correct torqued latAccelOffset parameter convergence

---------

Co-authored-by: felsager <d.felsager@gmail.com>
2025-08-25 13:50:10 -07:00
commaci-public
aea467ff02 [bot] Update Python packages (#36053)
* Update Python packages

* bump

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
2025-08-25 11:43:49 -07:00
Comma Device
b501ad4d51 nice encoder debugging script 2025-08-24 00:48:54 +00:00
205 changed files with 6988 additions and 7174 deletions

View File

@@ -40,10 +40,10 @@ jobs:
runs-on: ubuntu-latest
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
env:
PR_LABEL: 'dev-c3'
PR_LABEL: 'dev'
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
steps:
- name: Check if PR has dev-c3 label
- name: Check if PR has dev label
id: check-labels
uses: actions/github-script@v7
with:
@@ -62,11 +62,11 @@ jobs:
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
core.setOutput('has-dev-c3', hasDevC3Label ? 'true' : 'false');
core.setOutput('has-dev', hasDevC3Label ? 'true' : 'false');
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
- name: Remove trust-fork-pr label if present
if: steps.check-labels.outputs.has-dev-c3 == 'true' && steps.check-labels.outputs.has-trust == 'true'
if: steps.check-labels.outputs.has-dev == 'true' && steps.check-labels.outputs.has-trust == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -43,6 +43,7 @@ jobs:
with:
submodules: true
- name: uv lock
if: github.repository == 'commaai/openpilot'
run: |
python3 -m ensurepip --upgrade
pip3 install uv

View File

@@ -107,7 +107,6 @@ jobs:
build_mac:
name: build macOS
if: false # temp disable since homebrew install is getting stuck
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
steps:
- uses: actions/checkout@v4
@@ -125,8 +124,8 @@ jobs:
- name: Install dependencies
run: ./tools/mac_setup.sh
env:
# package install has DeprecationWarnings
PYTHONWARNINGS: default
PYTHONWARNINGS: default # package install has DeprecationWarnings
HOMEBREW_DISPLAY_INSTALL_TIMES: 1
- name: Save Homebrew cache
uses: actions/cache/save@v4
if: github.ref == 'refs/heads/master'

View File

@@ -8,14 +8,14 @@ env:
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
# Branch configurations
STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master' }} # vars are set on repo settings.
STAGING_SOURCE_BRANCH: 'master'
# Runtime configuration
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
on:
push:
branches: [ master, master-dev-c3-new ]
branches: [ master, master-dev ]
tags: [ 'release/*' ]
pull_request_target:
types: [ labeled ]
@@ -138,7 +138,7 @@ jobs:
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
restore-keys: |
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_C3_SOURCE_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_SOURCE_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}
- name: Set environment variables

View File

@@ -1,9 +1,9 @@
name: Build dev-c3-new
name: Build dev
env:
DEFAULT_SOURCE_BRANCH: "master"
DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
PR_LABEL: "dev-c3"
DEFAULT_TARGET_BRANCH: "master-dev"
PR_LABEL: "dev"
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
@@ -25,7 +25,7 @@ on:
target_branch:
description: 'Target branch to reset and squash into'
required: true
default: 'master-dev-c3-new'
default: 'master-dev'
type: string
cancel_in_progress:
description: 'Cancel any in-progress runs of this workflow'
@@ -43,7 +43,7 @@ jobs:
if: (
(github.event_name == 'workflow_dispatch')
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
)
steps:
- uses: actions/checkout@v4
@@ -55,7 +55,7 @@ jobs:
uses: ./.github/workflows/wait-for-action # Path to where you place the action
if: (
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
)
with:
workflow: selfdrive_tests.yaml # The workflow file to monitor

1
.gitignore vendored
View File

@@ -50,7 +50,6 @@ cereal/services.h
cereal/gen
cereal/messaging/bridge
selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd
selfdrive/ui/translations/tmp
selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump

41
.vscode/launch.json vendored
View File

@@ -23,6 +23,11 @@
"id": "args",
"description": "Arguments to pass to the process",
"type": "promptString"
},
{
"id": "replayArg",
"type": "promptString",
"description": "Enter route or segment to replay."
}
],
"configurations": [
@@ -40,7 +45,41 @@
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/${input:cpp_process}",
"cwd": "${workspaceFolder}",
"cwd": "${workspaceFolder}"
},
{
"name": "Attach LLDB to Replay drive",
"type": "lldb",
"request": "attach",
"pid": "${command:pickMyProcess}",
"initCommands": [
"script import time; time.sleep(3)"
]
},
{
"name": "Replay drive",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/opendbc/safety/tests/safety_replay/replay_drive.py",
"args": [
"${input:replayArg}"
],
"console": "integratedTerminal",
"justMyCode": false,
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"subProcess": true,
"stopOnEntry": false
}
],
"compounds": [
{
"name": "Replay drive + Safety LLDB",
"configurations": [
"Replay drive",
"Attach LLDB to Replay drive"
]
}
]
}

32
Jenkinsfile vendored
View File

@@ -178,7 +178,7 @@ node {
try {
if (env.BRANCH_NAME == 'devel-staging') {
deviceStage("build release3-staging", "tici-needs-can", [], [
deviceStage("build release3-staging", "tizi-needs-can", [], [
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
])
}
@@ -186,12 +186,12 @@ node {
if (env.BRANCH_NAME == '__nightly') {
parallel (
'nightly': {
deviceStage("build nightly", "tici-needs-can", [], [
deviceStage("build nightly", "tizi-needs-can", [], [
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
])
},
'nightly-dev': {
deviceStage("build nightly-dev", "tici-needs-can", [], [
deviceStage("build nightly-dev", "tizi-needs-can", [], [
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
])
},
@@ -200,39 +200,30 @@ node {
if (!env.BRANCH_NAME.matches(excludeRegex)) {
parallel (
// tici tests
'onroad tests': {
deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
step("build openpilot", "cd system/manager && ./build.py"),
step("check dirty", "release/check-dirty.sh"),
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
])
},
'HW + Unit Tests': {
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
step("test manager", "pytest system/manager/test/test_manager.py"),
])
},
'loopback': {
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
step("build openpilot", "cd system/manager && ./build.py"),
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
])
},
'camerad AR0231': {
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
])
},
'camerad OX03C10': {
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
@@ -246,17 +237,13 @@ node {
])
},
'sensord': {
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
])
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
])
},
'replay': {
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
])
@@ -266,7 +253,6 @@ node {
step("build openpilot", "cd system/manager && ./build.py"),
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
// TODO: enable once new AGNOS is available
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),

View File

@@ -1,7 +1,13 @@
Version 0.10.1 (2025-09-08)
========================
* Record driving feedback using LKAS button
* Honda City 2023 support thanks to drFritz!
* New driving model
* World Model: removed global localization inputs
* World Model: 2x the number of parameters
* World Model: trained on 4x the number of segments
* Driving Vision Model: trained on 4x the number of segments
* Honda City 2023 support thanks to vanillagorillaa and drFritz!
* Honda N-Box 2018 support thanks to miettal!
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
Version 0.10.0 (2025-08-05)
========================

View File

@@ -359,11 +359,6 @@ SConscript([
'system/ubloxd/SConscript',
'system/loggerd/SConscript',
])
if arch != "Darwin":
SConscript([
'system/logcatd/SConscript',
'system/proclogd/SConscript',
])
if arch == "larch64":
SConscript(['system/camerad/SConscript'])

View File

@@ -25,6 +25,26 @@ struct ModularAssistiveDrivingSystem {
}
}
struct IntelligentCruiseButtonManagement {
state @0 :IntelligentCruiseButtonManagementState;
sendButton @1 :SendButtonState;
vTarget @2 :Float32;
enum IntelligentCruiseButtonManagementState {
inactive @0; # No button press or default state
preActive @1; # Pre-active state before transitioning to increasing or decreasing
increasing @2; # Increasing speed
decreasing @3; # Decreasing speed
holding @4; # Holding steady speed
}
enum SendButtonState {
none @0;
increase @1;
decrease @2;
}
}
# Same struct as Log.RadarState.LeadData
struct LeadData {
dRel @0 :Float32;
@@ -48,6 +68,7 @@ struct LeadData {
struct SelfdriveStateSP @0x81c2f05a394cf4af {
mads @0 :ModularAssistiveDrivingSystem;
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
}
struct ModelManagerSP @0xaedffd8f31e7b55d {
@@ -122,6 +143,8 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
dec @0 :DynamicExperimentalControl;
longitudinalPlanSource @1 :LongitudinalPlanSource;
smartCruiseControl @2 :SmartCruiseControl;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
@@ -133,6 +156,34 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
blended @1;
}
}
struct SmartCruiseControl {
vision @0 :Vision;
struct Vision {
state @0 :VisionState;
vTarget @1 :Float32;
aTarget @2 :Float32;
currentLateralAccel @3 :Float32;
maxPredictedLateralAccel @4 :Float32;
enabled @5 :Bool;
active @6 :Bool;
}
enum VisionState {
disabled @0; # System disabled or inactive.
enabled @1; # No predicted substantial turn on vision range.
entering @2; # A substantial turn is predicted ahead, adapting speed to turn comfort levels.
turning @3; # Actively turning. Managing acceleration to provide a roll on turn feeling.
leaving @4; # Road ahead straightens. Start to allow positive acceleration.
overriding @5; # System overriding with manual control.
}
}
enum LongitudinalPlanSource {
cruise @0;
sccVision @1;
}
}
struct OnroadEventSP @0xda96579883444c35 {
@@ -172,12 +223,16 @@ struct OnroadEventSP @0xda96579883444c35 {
experimentalModeSwitched @14;
wrongCarModeAlertOnly @15;
pedalPressedAlertOnly @16;
laneTurnLeft @17;
laneTurnRight @18;
}
}
struct CarParamsSP @0x80ae746ee2596b11 {
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
pcmCruiseSpeed @3 :Bool = true;
intelligentCruiseButtonManagementAvailable @4 :Bool;
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
@@ -197,10 +252,24 @@ struct CarControlSP @0xa5cd762cd951a455 {
params @1 :List(Param);
leadOne @2 :LeadData;
leadTwo @3 :LeadData;
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
struct Param {
key @0 :Text;
value @1 :Text;
type @2 :ParamType;
value @3 :Data;
valueDEPRECATED @1 :Text; # The data type change may cause issues with backwards compatibility.
}
enum ParamType {
string @0;
bool @1;
int @2;
float @3;
time @4;
json @5;
bytes @6;
}
}
@@ -258,9 +327,16 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
roadName @5 :Text;
}
struct CustomReserved9 @0xa1680744031fdb2d {
struct ModelDataV2SP @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
}
enum TurnDirection {
none @0;
turnLeft @1;
turnRight @2;
}
struct CustomReserved10 @0xcb9fd56c7057593a {
}

View File

@@ -585,7 +585,6 @@ struct PandaState @0xa7649e2575e4591e {
heartbeatLost @22 :Bool;
interruptLoad @25 :Float32;
fanPower @28 :UInt8;
fanStallCount @34 :UInt8;
spiErrorCount @33 :UInt16;
@@ -714,6 +713,7 @@ struct PandaState @0xa7649e2575e4591e {
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
safetyParamDEPRECATED @20 :Int16;
safetyParam2DEPRECATED @26 :UInt32;
fanStallCountDEPRECATED @34 :UInt8;
}
struct PeripheralState {
@@ -2631,7 +2631,7 @@ struct Event {
backupManagerSP @113 :Custom.BackupManagerSP;
carStateSP @114 :Custom.CarStateSP;
liveMapDataSP @115 :Custom.LiveMapDataSP;
customReserved9 @116 :Custom.CustomReserved9;
modelDataV2SP @116 :Custom.ModelDataV2SP;
customReserved10 @136 :Custom.CustomReserved10;
customReserved11 @137 :Custom.CustomReserved11;
customReserved12 @138 :Custom.CustomReserved12;

View File

@@ -33,7 +33,7 @@ MessageContext message_context;
struct SubMaster::SubMessage {
std::string name;
SubSocket *socket = nullptr;
int freq = 0;
float freq = 0.0f;
bool updated = false, alive = false, valid = false, ignore_alive;
uint64_t rcv_time = 0, rcv_frame = 0;
void *allocated_msg_reader = nullptr;

View File

@@ -88,6 +88,7 @@ _services: dict[str, tuple] = {
"carControlSP": (True, 100., 10),
"carStateSP": (True, 100., 10),
"liveMapDataSP": (True, 1., 1),
"modelDataV2SP": (True, 20.),
# debug
"uiDebug": (True, 0., 1),
@@ -120,12 +121,12 @@ def build_header():
h += "#include <map>\n"
h += "#include <string>\n"
h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n"
h += "struct service { std::string name; bool should_log; float frequency; int decimation; };\n"
h += "static std::map<std::string, service> services = {\n"
for k, v in SERVICE_LIST.items():
should_log = "true" if v.should_log else "false"
decimation = -1 if v.decimation is None else v.decimation
h += ' { "%s", {"%s", %s, %d, %d}},\n' % \
h += ' { "%s", {"%s", %s, %f, %d}},\n' % \
(k, k, should_log, v.frequency, decimation)
h += "};\n"

View File

@@ -1 +1 @@
#define DEFAULT_MODEL "Steam Powered (Default)"
#define DEFAULT_MODEL "Firehose (Default)"

View File

@@ -94,7 +94,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_StorageMissing", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
@@ -146,18 +145,23 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SmartCruiseControlVision", {PERSISTENT | BACKUP, BOOL, "0"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
@@ -195,10 +199,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
// model panel params
// sunnypilot model params
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
{"LaneTurnDesire", {PERSISTENT | BACKUP, INT, "0"}},
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
// mapd
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},

View File

@@ -39,7 +39,7 @@ All of these are examples of good PRs:
### First contribution
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
There's lot of bounties that don't require a comma 3/3X or a car.
There's lot of bounties that don't require a comma 3X or a car.
## Pull Requests

30
docs/DEBUGGING_SAFETY.md Normal file
View File

@@ -0,0 +1,30 @@
# Debugging Panda Safety with Replay Drive + LLDB
## 1. Start the debugger in VS Code
* Select **Replay drive + Safety LLDB**.
* Enter the route or segment when prompted.
[<img src="https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604">](https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604)
## 2. Attach LLDB
* When prompted, pick the running **`replay_drive` process**.
* ⚠️ Attach quickly, or `replay_drive` will start consuming messages.
> [!TIP]
> Add a Python breakpoint at the start of `replay_drive.py` to pause execution and give yourself time to attach LLDB.
## 3. Set breakpoints in VS Code
Breakpoints can be set directly in `modes/xxx.h` (or any C file).
No extra LLDB commands are required — just place breakpoints in the editor.
## 4. Resume execution
Once attached, you can step through both Python (on the replay) and C safety code as CAN logs are replayed.
> [!NOTE]
> * Use short routes for quicker iteration.
> * Pause `replay_drive` early to avoid wasting log messages.
## Video
View a demo of this workflow on the PR that added it: https://github.com/commaai/openpilot/pull/36055#issue-3352911578

View File

@@ -16,7 +16,7 @@ industry standards of safety for Level 2 Driver Assistance Systems. In particula
ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf)
released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/))
on parts of openpilot that are safety relevant. We also perform software-in-the-loop,
hardware-in-the-loop and in-vehicle tests before each software release.
hardware-in-the-loop, and in-vehicle tests before each software release.
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
ensuring two main safety requirements.
@@ -29,8 +29,18 @@ ensuring two main safety requirements.
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
not fully meeting the above requirements.
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
---
### Forks of openpilot
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
* If your fork modifies any of the code in `opendbc/safety/`:
* your fork cannot use the openpilot trademark
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
Failure to comply with these standards will get you and your users banned from comma.ai servers.
**comma.ai strongly discourages the use of openpilot forks with safety code either missing or not fully meeting the above requirements.**

View File

@@ -1,11 +1,11 @@
# connect to a comma 3/3X
# connect to a comma 3X
A comma 3/3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
## Serial Console
On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
Connect the comma 3/3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect.
@@ -45,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
> [!NOTE]
> The default port for ADB is 5555 on the comma 3/3X.
> The default port for ADB is 5555 on the comma 3X.
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).

View File

@@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging.
Just run `tools/replay/replay --demo`.
## Replaying CAN data
*Hardware required: jungle and comma 3/3X*
*Hardware required: jungle and comma 3X*
1. Connect your PC to a jungle.
2.

View File

@@ -3,7 +3,7 @@
In 30 minutes, we'll get an openpilot development environment set up on your computer and make some changes to openpilot's UI.
And if you have a comma 3/3X, we'll deploy the change to your device for testing.
And if you have a comma 3X, we'll deploy the change to your device for testing.
## 1. Set up your development environment

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.8"
export AGNOS_VERSION="13.1"
fi
export STAGING_ROOT="/data/safe_staging"

View File

@@ -21,7 +21,7 @@ nav:
- What is openpilot?: getting-started/what-is-openpilot.md
- How-to:
- Turn the speed blue: how-to/turn-the-speed-blue.md
- Connect to a comma 3/3X: how-to/connect-to-comma.md
- Connect to a comma 3X: how-to/connect-to-comma.md
# - Make your first pull request: how-to/make-first-pr.md
#- Replay a drive: how-to/replay-a-drive.md
- Concepts:

2
panda

Submodule panda updated: f10ddc6a89...69ab12ee2a

View File

@@ -36,6 +36,9 @@ dependencies = [
"pyopenssl < 24.3.0",
"pyaudio",
# ubloxd (TODO: just use struct)
"kaitaistruct",
# panda
"libusb1",
"spidev; platform_system == 'Linux'",
@@ -101,8 +104,9 @@ dev = [
"av",
"azure-identity",
"azure-storage-blob",
"dbus-next",
"dbus-next", # TODO: remove once we moved everything to jeepney
"dictdiffer",
"jeepney",
"matplotlib",
"opencv-python-headless",
"parameterized >=0.8, <0.9",
@@ -120,6 +124,7 @@ 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')",
"dearpygui>=2.1.0",
]
[project.urls]
@@ -157,7 +162,6 @@ testpaths = [
"system/camerad",
"system/hardware",
"system/loggerd",
"system/proclogd",
"system/tests",
"system/ubloxd",
"system/webrtc",
@@ -173,7 +177,7 @@ quiet-level = 3
# if you've got a short variable name that's getting flagged, add it here
ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite"
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*"
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*"
[tool.mypy]
python_version = "3.11"
@@ -247,6 +251,7 @@ exclude = [
"teleoprtc_repo",
"third_party",
"*.ipynb",
"generated",
]
lint.flake8-implicit-str-concat.allow-multiline = false

View File

@@ -14,7 +14,7 @@ def setup_argument_parser():
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
parser.add_argument('--source-branch', type=str, default='master',
help='Source branch for merging')
parser.add_argument('--target-branch', type=str, default='master-dev-c3-new-test',
parser.add_argument('--target-branch', type=str, default='master-dev-test',
help='Target branch for merging')
parser.add_argument('--squash-script-path', type=str, required=True,
help='Path to the squash_and_merge.py script')

View File

@@ -179,7 +179,7 @@ class Car:
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
self.mock_carstate = MockCarState()
self.v_cruise_helper = VCruiseHelper(self.CP)
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode")

View File

@@ -30,8 +30,8 @@ CRUISE_INTERVAL_SIGN = {
class VCruiseHelper(VCruiseHelperSP):
def __init__(self, CP):
VCruiseHelperSP.__init__(self)
def __init__(self, CP, CP_SP):
VCruiseHelperSP.__init__(self, CP, CP_SP)
self.CP = CP
self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET
@@ -46,10 +46,13 @@ class VCruiseHelper(VCruiseHelperSP):
def update_v_cruise(self, CS, enabled, is_metric):
self.v_cruise_kph_last = self.v_cruise_kph
self.get_minimum_set_speed(is_metric)
if CS.cruiseState.available:
if not self.CP.pcmCruise:
_enabled = self.update_enabled_state(CS, enabled)
if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled):
# if stock cruise is completely disabled, then we can use our own set speed logic
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
self._update_v_cruise_non_pcm(CS, _enabled, is_metric)
self.v_cruise_cluster_kph = self.v_cruise_kph
self.update_button_timers(CS, enabled)
else:
@@ -111,7 +114,7 @@ class VCruiseHelper(VCruiseHelperSP):
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), self.v_cruise_min, V_CRUISE_MAX)
def update_button_timers(self, CS, enabled):
# increment timer for buttons still pressed

View File

@@ -60,5 +60,8 @@ def convert_carControlSP(struct: capnp.lib.capnp._DynamicStructReader) -> struct
struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
struct_dataclass.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(
**remove_deprecated(struct_dict.get('intelligentCruiseButtonManagement', {}))
)
return struct_dataclass

View File

@@ -1,15 +1,12 @@
import os
import math
import hypothesis.strategies as st
from hypothesis import Phase, given, settings
from parameterized import parameterized
from cereal import car, custom
from opendbc.car import DT_CTRL
from opendbc.car.car_helpers import interfaces
from opendbc.car.structs import CarParams
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface_args
from opendbc.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.values import PLATFORMS
from openpilot.selfdrive.car.helpers import convert_carControlSP
@@ -21,11 +18,6 @@ from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
ALL_ECUS = {ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}
ALL_ECUS |= {ecu for config in FW_QUERY_CONFIGS.values() for ecu in config.extra_ecus}
ALL_REQUESTS = {tuple(r.request) for config in FW_QUERY_CONFIGS.values() for r in config.requests}
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60'))
@@ -37,43 +29,10 @@ class TestCarInterfaces:
phases=(Phase.reuse, Phase.generate, Phase.shrink))
@given(data=st.data())
def test_car_interfaces(self, car_name, data):
CarInterface = interfaces[car_name]
args = get_fuzzy_car_interface_args(data.draw)
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
alpha_long=args['alpha_long'], is_release=False, docs=False)
car_params_sp = CarInterface.get_params_sp(car_params, car_name, args['fingerprints'], args['car_fw'],
alpha_long=args['alpha_long'], docs=False)
car_params = car_params.as_reader()
car_interface = CarInterface(car_params, car_params_sp)
car_interface = get_fuzzy_car_interface(car_name, data.draw)
car_params = car_interface.CP.as_reader()
car_params_sp = car_interface.CP_SP
sunnypilot_interfaces.setup_interfaces(car_interface)
assert car_params
assert car_params_sp
assert car_interface
assert car_params.mass > 1
assert car_params.wheelbase > 0
# centerToFront is center of gravity to front wheels, assert a reasonable range
assert car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7
assert car_params.maxLateralAccel > 0
# Longitudinal sanity checks
assert len(car_params.longitudinalTuning.kpV) == len(car_params.longitudinalTuning.kpBP)
assert len(car_params.longitudinalTuning.kiV) == len(car_params.longitudinalTuning.kiBP)
# Lateral sanity checks
if car_params.steerControlType != CarParams.SteerControlType.angle:
tune = car_params.lateralTuning
if tune.which() == 'pid':
if car_name != MOCK.MOCK:
assert not math.isnan(tune.pid.kf) and tune.pid.kf > 0
assert len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)
assert len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)
elif tune.which() == 'torque':
assert not math.isnan(tune.torque.kf) and tune.torque.kf > 0
assert not math.isnan(tune.torque.friction) and tune.torque.friction > 0
cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True)
cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)

View File

@@ -5,7 +5,7 @@ import numpy as np
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 cereal import car, custom
from openpilot.common.constants import CV
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
@@ -49,7 +49,8 @@ class TestCruiseSpeed:
class TestVCruiseHelper:
def setup_method(self):
self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
self.v_cruise_helper = VCruiseHelper(self.CP)
self.CP_SP = custom.CarParamsSP()
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
self.reset_cruise_speed_state()
def reset_cruise_speed_state(self):

View File

@@ -116,7 +116,8 @@ class Controls(ControlsExt, ModelStateBase):
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
(not standstill or self.CP.steerAtStandstill)
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and \
(self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
actuators = CC.actuators
actuators.longControlState = self.LoC.long_control_state
@@ -168,7 +169,7 @@ class Controls(ControlsExt, ModelStateBase):
CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist()
CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist()
CC.cruiseControl.override = CC.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl
CC.cruiseControl.override = CC.enabled and not CC.longActive and (self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise)
CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop

View File

@@ -2,6 +2,7 @@ from cereal import log
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
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection
@@ -41,6 +42,11 @@ class DesireHelper:
self.prev_one_blinker = False
self.desire = log.Desire.none
self.alc = AutoLaneChangeController(self)
self.lane_turn_controller = LaneTurnController()
@staticmethod
def get_lane_change_direction(CS):
return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right
def update(self, carstate, lateral_active, lane_change_prob):
self.alc.update_params()
@@ -56,12 +62,13 @@ class DesireHelper:
if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
self.lane_change_state = LaneChangeState.preLaneChange
self.lane_change_ll_prob = 1.0
# Initialize lane change direction to prevent UI alert flicker
self.lane_change_direction = self.get_lane_change_direction(carstate)
# LaneChangeState.preLaneChange
elif self.lane_change_state == LaneChangeState.preLaneChange:
# Set lane change direction
self.lane_change_direction = LaneChangeDirection.left if \
carstate.leftBlinker else LaneChangeDirection.right
# Update lane change direction
self.lane_change_direction = self.get_lane_change_direction(carstate)
torque_applied = carstate.steeringPressed and \
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
@@ -108,6 +115,12 @@ class DesireHelper:
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
# Lane turn controller update
turn_desire = self.lane_turn_controller.update(carstate.leftBlindspot, carstate.rightBlindspot, carstate.leftBlinker, carstate.rightBlinker,
carstate.vEgo, carstate.steeringPressed, carstate.steeringTorque)
if turn_desire != log.Desire.none:
self.desire = turn_desire
# Send keep pulse once per second during LaneChangeStart.preLaneChange
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
self.keep_pulse_timer = 0.0

View File

@@ -56,10 +56,8 @@ class LatControlTorque(LatControl):
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
# desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
# desired_lateral_jerk = desired_curvature_rate * CS.vEgo ** 2
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
@@ -71,6 +69,8 @@ class LatControlTorque(LatControl):
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
pid_log.error = float(setpoint - measurement)
ff = gravity_adjusted_lateral_accel
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
ff -= self.torque_params.latAccelOffset
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5

View File

@@ -146,6 +146,9 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
# Get new v_cruise and a_desired from Smart Cruise Control
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
if force_slow_decel:
v_cruise = 0.0

View File

@@ -0,0 +1,70 @@
import numpy as np
from cereal import car, messaging
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
from opendbc.car import structs
from opendbc.car.lateral import get_friction, FRICTION_THRESHOLD
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS
np.random.seed(0)
LA_ERR_STD = 1.0
INPUT_NOISE_STD = 0.08
V_EGO = 30.0
WARMUP_BUCKET_POINTS = (1.5*MIN_BUCKET_POINTS).astype(int)
STRAIGHT_ROAD_LA_BOUNDS = (0.02, 0.03)
ROLL_BIAS_DEG = 2.0
ROLL_COMPENSATION_BIAS = ACCELERATION_DUE_TO_GRAVITY*float(np.sin(np.deg2rad(ROLL_BIAS_DEG)))
TORQUE_TUNE = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=0.0, friction=0.2)
TORQUE_TUNE_BIASED = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=-ROLL_COMPENSATION_BIAS, friction=0.2)
def generate_inputs(torque_tune, la_err_std, input_noise_std=None):
rng = np.random.default_rng(0)
steer_torques = np.concat([rng.uniform(bnd[0], bnd[1], pts) for bnd, pts in zip(STEER_BUCKET_BOUNDS, WARMUP_BUCKET_POINTS, strict=True)])
la_errs = rng.normal(scale=la_err_std, size=steer_torques.size)
frictions = np.array([get_friction(la_err, 0.0, FRICTION_THRESHOLD, torque_tune) for la_err in la_errs])
lat_accels = torque_tune.latAccelFactor*steer_torques + torque_tune.latAccelOffset + frictions
if input_noise_std is not None:
steer_torques += rng.normal(scale=input_noise_std, size=steer_torques.size)
lat_accels += rng.normal(scale=input_noise_std, size=steer_torques.size)
return steer_torques, lat_accels
def get_warmed_up_estimator(steer_torques, lat_accels):
est = TorqueEstimator(car.CarParams())
for steer_torque, lat_accel in zip(steer_torques, lat_accels, strict=True):
est.filtered_points.add_point(steer_torque, lat_accel)
return est
def simulate_straight_road_msgs(est):
carControl = messaging.new_message('carControl').carControl
carOutput = messaging.new_message('carOutput').carOutput
carState = messaging.new_message('carState').carState
livePose = messaging.new_message('livePose').livePose
carControl.latActive = True
carState.vEgo = V_EGO
carState.steeringPressed = False
ts = DT_MDL*np.arange(2*POINTS_PER_BUCKET)
steer_torques = np.concat((np.linspace(-0.03, -0.02, POINTS_PER_BUCKET), np.linspace(0.02, 0.03, POINTS_PER_BUCKET)))
lat_accels = TORQUE_TUNE.latAccelFactor * steer_torques
for t, steer_torque, lat_accel in zip(ts, steer_torques, lat_accels, strict=True):
carOutput.actuatorsOutput.torque = float(-steer_torque)
livePose.orientationNED.x = float(np.deg2rad(ROLL_BIAS_DEG))
livePose.angularVelocityDevice.z = float(lat_accel / V_EGO)
for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)):
est.handle_log(t, which, msg)
def test_estimated_offset():
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
msg = est.get_msg()
# TODO add lataccelfactor and friction check when we have more accurate estimates
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
def test_straight_road_roll_bias():
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
simulate_straight_road_msgs(est)
msg = est.get_msg()
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)

View File

@@ -13,12 +13,9 @@ class ModelConstants:
META_T_IDXS = [2., 4., 6., 8., 10.]
# model inputs constants
MODEL_FREQ = 20
HISTORY_FREQ = 5
HISTORY_LEN_SECONDS = 5
TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ
FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS
INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS
N_FRAMES = 2
MODEL_RUN_FREQ = 20
MODEL_CONTEXT_FREQ = 5 # "model_trained_fps"
FEATURE_LEN = 512

View File

@@ -3,6 +3,7 @@ import capnp
import numpy as np
from cereal import log
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -95,8 +96,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
# action
modelV2.action = action
# times at X_IDXS of edges and lines aren't used
LINE_T_IDXS: list[float] = []
# times at X_IDXS of edges and lines
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
# lane lines
modelV2.init('laneLines', 4)
@@ -149,7 +150,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
meta.hardBrakePredicted = hard_brake_predicted.item()
# confidence
if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0:
if vipc_frame_id % (2*ModelConstants.MODEL_RUN_FREQ) == 0:
# any disengage prob
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]

View File

@@ -80,6 +80,64 @@ 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 InputQueues:
def __init__ (self, model_fps, env_fps, n_frames_input):
assert env_fps % model_fps == 0
assert env_fps >= model_fps
self.model_fps = model_fps
self.env_fps = env_fps
self.n_frames_input = n_frames_input
self.dtypes = {}
self.shapes = {}
self.q = {}
def update_dtypes_and_shapes(self, input_dtypes, input_shapes) -> None:
self.dtypes.update(input_dtypes)
if self.env_fps == self.model_fps:
self.shapes.update(input_shapes)
else:
for k in input_shapes:
shape = list(input_shapes[k])
if 'img' in k:
n_channels = shape[1] // self.n_frames_input
shape[1] = (self.env_fps // self.model_fps + (self.n_frames_input - 1)) * n_channels
else:
shape[1] = (self.env_fps // self.model_fps) * shape[1]
self.shapes[k] = tuple(shape)
def reset(self) -> None:
self.q = {k: np.zeros(self.shapes[k], dtype=self.dtypes[k]) for k in self.dtypes.keys()}
def enqueue(self, inputs:dict[str, np.ndarray]) -> None:
for k in inputs.keys():
if inputs[k].dtype != self.dtypes[k]:
raise ValueError(f'supplied input <{k}({inputs[k].dtype})> has wrong dtype, expected {self.dtypes[k]}')
input_shape = list(self.shapes[k])
input_shape[1] = -1
single_input = inputs[k].reshape(tuple(input_shape))
sz = single_input.shape[1]
self.q[k][:,:-sz] = self.q[k][:,sz:]
self.q[k][:,-sz:] = single_input
def get(self, *names) -> dict[str, np.ndarray]:
if self.env_fps == self.model_fps:
return {k: self.q[k] for k in names}
else:
out = {}
for k in names:
shape = self.shapes[k]
if 'img' in k:
n_channels = shape[1] // (self.env_fps // self.model_fps + (self.n_frames_input - 1))
out[k] = np.concatenate([self.q[k][:, s:s+n_channels] for s in np.linspace(0, shape[1] - n_channels, self.n_frames_input, dtype=int)], axis=1)
elif 'pulse' in k:
# any pulse within interval counts
out[k] = self.q[k].reshape((shape[0], shape[1] * self.model_fps // self.env_fps, self.env_fps // self.model_fps, -1)).max(axis=2)
else:
idxs = np.arange(-1, -shape[1], -self.env_fps // self.model_fps)[::-1]
out[k] = self.q[k][:, idxs]
return out
class ModelState(ModelStateBase):
frames: dict[str, DrivingModelFrame]
inputs: dict[str, np.ndarray]
@@ -102,19 +160,15 @@ class ModelState(ModelStateBase):
self.policy_output_slices = policy_metadata['output_slices']
policy_output_size = policy_metadata['output_shapes']['outputs'][1]
self.frames = {name: DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) for name in self.vision_input_names}
self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names}
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
self.full_features_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
self.full_desire = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32)
self.temporal_idxs = slice(-1-(ModelConstants.TEMPORAL_SKIP*(ModelConstants.INPUT_HISTORY_BUFFER_LEN-1)), None, ModelConstants.TEMPORAL_SKIP)
# policy inputs
self.numpy_inputs = {
'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32),
'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
}
self.numpy_inputs = {k: np.zeros(self.policy_input_shapes[k], dtype=np.float32) for k in self.policy_input_shapes}
self.full_input_queues = InputQueues(ModelConstants.MODEL_CONTEXT_FREQ, ModelConstants.MODEL_RUN_FREQ, ModelConstants.N_FRAMES)
for k in ['desire_pulse', 'features_buffer']:
self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape})
self.full_input_queues.reset()
# img buffers are managed in openCL transform code
self.vision_inputs: dict[str, Tensor] = {}
@@ -136,15 +190,10 @@ class ModelState(ModelStateBase):
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
inputs['desire'][0] = 0
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.prev_desire[:] = inputs['desire']
inputs['desire_pulse'][0] = 0
new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0)
self.prev_desire[:] = inputs['desire_pulse']
self.full_desire[0,:-1] = self.full_desire[0,1:]
self.full_desire[0,-1] = new_desire
self.numpy_inputs['desire'][:] = self.full_desire.reshape((1,ModelConstants.INPUT_HISTORY_BUFFER_LEN,ModelConstants.TEMPORAL_SKIP,-1)).max(axis=2)
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names}
if TICI and not USBGPU:
@@ -163,9 +212,10 @@ class ModelState(ModelStateBase):
self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy()
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices))
self.full_features_buffer[0,:-1] = self.full_features_buffer[0,1:]
self.full_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :]
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire})
for k in ['desire_pulse', 'features_buffer']:
self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k]
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy()
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices))
@@ -216,14 +266,14 @@ def main(demo=False):
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
# messaging
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
publish_state = PublishState()
params = Params()
# setup filter to track dropped frames
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ)
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_RUN_FREQ)
frame_id = 0
last_vipc_frame_id = 0
run_count = 0
@@ -320,7 +370,7 @@ def main(demo=False):
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
inputs:dict[str, np.ndarray] = {
'desire': vec_desire,
'desire_pulse': vec_desire,
'traffic_convention': traffic_convention,
}
@@ -333,6 +383,7 @@ def main(demo=False):
modelv2_send = messaging.new_message('modelV2')
drivingdata_send = messaging.new_message('drivingModelData')
posenet_send = messaging.new_message('cameraOdometry')
mdv2sp_send = messaging.new_message('modelDataV2SP')
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
prev_action = action
@@ -347,6 +398,7 @@ def main(demo=False):
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_controller.turn_direction
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
@@ -354,6 +406,7 @@ def main(demo=False):
pm.send('modelV2', modelv2_send)
pm.send('drivingModelData', drivingdata_send)
pm.send('cameraOdometry', posenet_send)
pm.send('modelDataV2SP', mdv2sp_send)
last_vipc_frame_id = meta_main.frame_id

View File

@@ -84,12 +84,10 @@ 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();
bool is_supported_panda = std::find(SUPPORTED_PANDA_TYPES.begin(), SUPPORTED_PANDA_TYPES.end(), panda->hw_type) != SUPPORTED_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));
if (!is_supported_panda) {
LOGW("panda %s is not supported (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
return panda.release();
}
@@ -175,7 +173,6 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
ps.setInterruptLoad(health.interrupt_load_pkt);
ps.setFanPower(health.fan_power);
ps.setFanStallCount(health.fan_stall_count);
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
ps.setSpiErrorCount(health.spi_error_count_pkt);
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);

View File

@@ -9,13 +9,10 @@
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
static const std::vector<cereal::PandaState::PandaType> SUPPORTED_PANDA_TYPES = {
cereal::PandaState::PandaType::RED_PANDA,
cereal::PandaState::PandaType::TRES,
cereal::PandaState::PandaType::CUATRO,
};

View File

@@ -29,6 +29,12 @@ def flash_panda(panda_serial: str) -> Panda:
HARDWARE.recover_internal_panda()
raise
# skip flashing if the detected panda is not supported
supported_panda = check_panda_support(panda)
if not supported_panda:
cloudlog.warning(f"Panda {panda_serial} is not supported (hw_type: {panda.get_type()}), skipping flash...")
return panda
fw_signature = get_expected_signature(panda)
internal_panda = panda.is_internal()
@@ -36,12 +42,6 @@ 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()
@@ -67,6 +67,14 @@ def flash_panda(panda_serial: str) -> Panda:
return panda
def check_panda_support(panda) -> bool:
hw_type = panda.get_type()
if hw_type in Panda.SUPPORTED_DEVICES:
return True
return False
def main() -> None:
# signal pandad to close the relay and exit
def signal_handler(signum, frame):
@@ -91,11 +99,6 @@ def main() -> None:
cloudlog.event("pandad.flash_and_connect", count=count)
params.remove("PandaSignatures")
# TODO: remove this in the next AGNOS
# wait until USB is up before counting
if time.monotonic() < 60.:
no_internal_panda_count = 0
# Handle missing internal panda
if no_internal_panda_count > 0:
if no_internal_panda_count == 3:
@@ -145,6 +148,12 @@ def main() -> None:
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
for panda in pandas:
# skip health check if the detected panda is not supported
supported_panda = check_panda_support(panda)
if not supported_panda:
cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...")
continue
# check health for lost heartbeat
health = panda.health()
if health["heartbeat_lost"]:

View File

@@ -22,8 +22,6 @@ class TestPandad:
if len(Panda.list()) == 0:
self._run_test(60)
self.spi = HARDWARE.get_device_type() != 'tici'
def teardown_method(self):
managed_processes['pandad'].stop()
@@ -106,11 +104,9 @@ class TestPandad:
# - 0.2s pandad -> pandad
# - plus some buffer
print("startup times", ts, sum(ts) / len(ts))
assert 0.1 < (sum(ts)/len(ts)) < (0.7 if self.spi else 5.0)
assert 0.1 < (sum(ts)/len(ts)) < 0.7
def test_protocol_version_check(self):
if not self.spi:
pytest.skip("SPI test")
# flash old fw
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
self._flash_bootstub_and_test(fn, expect_mismatch=True)

View File

@@ -6,7 +6,6 @@ import random
import cereal.messaging as messaging
from cereal.services import SERVICE_LIST
from openpilot.system.hardware import HARDWARE
from openpilot.selfdrive.test.helpers import with_processes
from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages
@@ -16,8 +15,6 @@ JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ
class TestBoarddSpi:
@classmethod
def setup_class(cls):
if HARDWARE.get_device_type() == 'tici':
pytest.skip("only for spi pandas")
os.environ['STARTED'] = '1'
os.environ['SPI_ERR_PROB'] = '0.001'
if not JUNGLE_SPAM:

View File

@@ -29,10 +29,6 @@
"text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.",
"severity": 1
},
"Offroad_StorageMissing": {
"text": "NVMe drive not mounted.",
"severity": 1
},
"Offroad_CarUnrecognized": {
"text": "sunnypilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.",
"severity": 0
@@ -49,5 +45,10 @@
"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."
},
"Offroad_TiciSupport": {
"text": "<b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three.",
"severity": 1,
"_comment": "Set extra field to the current branch name."
}
}

View File

@@ -83,7 +83,7 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S
f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
"",
AlertStatus.userPrompt, AlertSize.small,
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4)
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4)
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:

View File

@@ -21,12 +21,12 @@ from openpilot.selfdrive.selfdrived.helpers import ExcessiveActuationCheck
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
from openpilot.system.hardware import HARDWARE
from openpilot.system.version import get_build_metadata
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
REPLAY = "REPLAY" in os.environ
@@ -86,7 +86,7 @@ class SelfdriveD(CruiseHelper):
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
ignore = self.sensor_packets + self.gps_packets + ['alertDebug']
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
if REPLAY:
@@ -95,7 +95,8 @@ 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', 'userBookmark', 'audioFeedback'] + \
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
'modelDataV2SP'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL))
@@ -134,13 +135,7 @@ class SelfdriveD(CruiseHelper):
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
# some comma three with NVMe experience NVMe dropouts mid-drive that
# cause loggerd to crash on write, so ignore it only on that platform
self.ignored_processes = set()
nvme_expected = os.path.exists('/dev/nvme0n1') or (not os.path.isfile("/persist/comma/living-in-the-moment"))
if HARDWARE.get_device_type() == 'tici' and nvme_expected:
self.ignored_processes = {'loggerd', }
self.ignored_processes.update({'mapd'})
self.ignored_processes = {'mapd', }
# Determine startup event
is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote
@@ -162,6 +157,7 @@ class SelfdriveD(CruiseHelper):
self.events_sp_prev = []
self.mads = ModularAssistiveDrivingSystem(self)
self.icbm = IntelligentCruiseButtonManagement(self.CP, self.CP_SP)
self.car_events_sp = CarSpecificEventsSP(self.CP, self.params)
@@ -300,6 +296,13 @@ class SelfdriveD(CruiseHelper):
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
# Handle lane turn
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
if lane_turn_direction == custom.TurnDirection.turnLeft:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
elif lane_turn_direction == custom.TurnDirection.turnRight:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
for i, pandaState in enumerate(self.sm['pandaStates']):
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
if i < len(self.CP.safetyConfigs):
@@ -441,6 +444,8 @@ class SelfdriveD(CruiseHelper):
self.events.add(EventName.personalityChanged)
self.experimental_mode_switched = False
self.icbm.run(CS, self.sm['carControl'], self.is_metric)
def data_sample(self):
_car_state = messaging.recv_one(self.car_state_sock)
CS = _car_state.carState if _car_state else self.CS_prev
@@ -545,6 +550,11 @@ class SelfdriveD(CruiseHelper):
mads.active = self.mads.active
mads.available = self.mads.enabled_toggle
icbm = ss_sp.intelligentCruiseButtonManagement
icbm.state = self.icbm.state
icbm.sendButton = self.icbm.cruise_button
icbm.vTarget = self.icbm.v_target
self.pm.send('selfdriveStateSP', ss_sp_msg)
# onroadEventsSP - logged every second or on change

View File

@@ -595,7 +595,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") ->
if len(live_calibration) > 0:
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
if len(live_parameters) > 0:
custom_params["LiveParameters"] = live_parameters[msg_index].as_builder().to_bytes()
custom_params["LiveParametersV2"] = live_parameters[msg_index].as_builder().to_bytes()
if len(live_torque_parameters) > 0:
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()

View File

@@ -1 +1 @@
6d3219bca9f66a229b38a5382d301a92b0147edb
afcab1abb62b9d5678342956cced4712f44e909e

View File

@@ -18,7 +18,6 @@ from openpilot.common.timeout import Timeout
from openpilot.common.params import Params
from openpilot.selfdrive.selfdrived.events import EVENTS, ET
from openpilot.selfdrive.test.helpers import set_params_enabled, release_only
from openpilot.system.hardware import HARDWARE
from openpilot.system.hardware.hw import Paths
from openpilot.tools.lib.logreader import LogReader
from openpilot.tools.lib.log_time_series import msgs_to_time_series
@@ -33,7 +32,7 @@ CPU usage budget
TEST_DURATION = 25
LOG_OFFSET = 8
MAX_TOTAL_CPU = 280. # total for all 8 cores
MAX_TOTAL_CPU = 300. # total for all 8 cores
PROCS = {
# Baseline CPU usage by process
"selfdrive.controls.controlsd": 16.0,
@@ -57,30 +56,20 @@ PROCS = {
"selfdrive.ui.soundd": 3.0,
"selfdrive.ui.feedback.feedbackd": 1.0,
"selfdrive.monitoring.dmonitoringd": 4.0,
"./proclogd": 2.0,
"system.proclogd": 3.0,
"system.logmessaged": 1.0,
"system.tombstoned": 0,
"./logcatd": 1.0,
"system.journald": 1.0,
"system.micd": 5.0,
"system.timed": 0,
"selfdrive.pandad.pandad": 0,
"system.statsd": 1.0,
"system.loggerd.uploader": 15.0,
"system.loggerd.deleter": 1.0,
"./pandad": 19.0,
"system.qcomgpsd.qcomgpsd": 1.0,
}
PROCS.update({
"tici": {
"./pandad": 5.0,
"./ubloxd": 1.0,
"system.ubloxd.pigeond": 6.0,
},
"tizi": {
"./pandad": 19.0,
"system.qcomgpsd.qcomgpsd": 1.0,
}
}.get(HARDWARE.get_device_type(), {}))
TIMINGS = {
# rtols: max/min, rsd
"can": [2.5, 0.35],

View File

@@ -63,14 +63,20 @@ class MainLayout(Widget):
# Don't hide sidebar from interactive timeout
if self._current_mode != MainState.ONROAD:
self._sidebar.set_visible(False)
self._current_mode = MainState.ONROAD
self._set_current_layout(MainState.ONROAD)
else:
self._current_mode = MainState.HOME
self._set_current_layout(MainState.HOME)
self._sidebar.set_visible(True)
def _set_current_layout(self, layout: MainState):
if layout != self._current_mode:
self._layouts[self._current_mode].hide_event()
self._current_mode = layout
self._layouts[self._current_mode].show_event()
def open_settings(self, panel_type: PanelType):
self._layouts[MainState.SETTINGS].set_current_panel(panel_type)
self._current_mode = MainState.SETTINGS
self._set_current_layout(MainState.SETTINGS)
self._sidebar.set_visible(False)
def _on_settings_clicked(self):

View File

@@ -1,17 +0,0 @@
import pyray as rl
from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import WifiManagerUI
class NetworkLayout(Widget):
def __init__(self):
super().__init__()
self.wifi_manager = WifiManagerWrapper()
self.wifi_ui = WifiManagerUI(self.wifi_manager)
def _render(self, rect: rl.Rectangle):
self.wifi_ui.render(rect)
def shutdown(self):
self.wifi_manager.shutdown()

View File

@@ -2,7 +2,7 @@ import pyray as rl
import time
import threading
from openpilot.common.api import Api, api_get
from openpilot.common.api import api_get
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.ui.ui_state import ui_state
@@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.wrap_text import wrap_text
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.lib.api_helpers import get_token
TITLE = "Firehose Mode"
DESCRIPTION = (
@@ -163,7 +163,7 @@ class FirehoseLayout(Widget):
dongle_id = self.params.get("DongleId")
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
return
identity_token = Api(dongle_id).get_token()
identity_token = get_token(dongle_id)
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token)
if response.status_code == 200:
data = response.json()

View File

@@ -2,7 +2,6 @@ import pyray as rl
from dataclasses import dataclass
from enum import IntEnum
from collections.abc import Callable
from openpilot.selfdrive.ui.layouts.network import NetworkLayout
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
@@ -10,7 +9,9 @@ from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.wifi_manager import WifiManager
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import WifiManagerUI
# Settings close button
SETTINGS_CLOSE_TEXT = "×"
@@ -43,7 +44,7 @@ class PanelType(IntEnum):
@dataclass
class PanelInfo:
name: str
instance: object
instance: Widget
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
@@ -53,9 +54,12 @@ class SettingsLayout(Widget):
self._current_panel = PanelType.DEVICE
# Panel configuration
wifi_manager = WifiManager()
wifi_manager.set_active(False)
self._panels = {
PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
PanelType.NETWORK: PanelInfo("Network", NetworkLayout()),
PanelType.NETWORK: PanelInfo("Network", WifiManagerUI(wifi_manager)),
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
@@ -149,8 +153,14 @@ class SettingsLayout(Widget):
def set_current_panel(self, panel_type: PanelType):
if panel_type != self._current_panel:
self._panels[self._current_panel].instance.hide_event()
self._current_panel = panel_type
self._panels[self._current_panel].instance.show_event()
def close_settings(self):
if self._close_callback:
self._close_callback()
def show_event(self):
super().show_event()
self._panels[self._current_panel].instance.show_event()
def hide_event(self):
super().hide_event()
self._panels[self._current_panel].instance.hide_event()

View File

@@ -0,0 +1,14 @@
import time
from functools import lru_cache
from openpilot.common.api import Api
TOKEN_EXPIRY_HOURS = 2
@lru_cache(maxsize=1)
def _get_token(dongle_id: str, t: int):
return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS)
def get_token(dongle_id: str):
return _get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60)))

View File

@@ -2,14 +2,12 @@ from enum import IntEnum
import os
import threading
import time
from functools import lru_cache
from openpilot.common.api import Api, api_get
from openpilot.common.api import api_get
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
TOKEN_EXPIRY_HOURS = 2
from openpilot.selfdrive.ui.lib.api_helpers import get_token
class PrimeType(IntEnum):
@@ -23,12 +21,6 @@ class PrimeType(IntEnum):
PURPLE = 5,
@lru_cache(maxsize=1)
def get_token(dongle_id: str, t: int):
print('getting token')
return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS)
class PrimeState:
FETCH_INTERVAL = 5.0 # seconds between API calls
API_TIMEOUT = 10.0 # seconds for API requests
@@ -58,15 +50,13 @@ class PrimeState:
return
try:
identity_token = get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60)))
identity_token = get_token(dongle_id)
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
if response.status_code == 200:
data = response.json()
is_paired = data.get("is_paired", False)
prime_type = data.get("prime_type", 0)
self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
elif response.status_code == 401:
get_token.cache_clear()
except Exception as e:
cloudlog.error(f"Failed to fetch prime status: {e}")

View File

@@ -4,6 +4,9 @@
#include <map>
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#endif
void OnroadAlerts::updateState(const UIState &s) {
Alert a = getAlert(*(s.sm), s.scene.started_frame);
@@ -73,6 +76,12 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
}
QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2);
#ifdef SUNNYPILOT
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
const int adjustment = dev_ui_info > 1 && alert.size != cereal::SelfdriveState::AlertSize::FULL ? 30 : 0;
r = QRect(0 + margin, height() - h + margin - adjustment, width() - margin*2, h - margin*2);
#endif
QPainter p(this);
// draw background + gradient

View File

@@ -12,6 +12,7 @@
#include "selfdrive/ui/sunnypilot/qt/onroad/model.h"
#define ExperimentalButton ExperimentalButtonSP
#define ModelRenderer ModelRendererSP
#define HudRenderer HudRendererSP
#else
#include "selfdrive/ui/qt/onroad/buttons.h"
#include "selfdrive/ui/qt/onroad/hud.h"

View File

@@ -73,6 +73,11 @@ void DriverMonitorRenderer::draw(QPainter &painter, const QRect &surface_rect) {
float y = surface_rect.height() - offset;
float opacity = is_active ? 0.65f : 0.2f;
#ifdef SUNNYPILOT
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
y -= dev_ui_info > 1 ? 50 : 0;
#endif
drawIcon(painter, QPoint(x, y), dm_img, QColor(0, 0, 0, 70), opacity);
QPointF keypoints[std::size(DEFAULT_FACE_KPTS_3D)];

View File

@@ -39,6 +39,7 @@ qt_src = [
"sunnypilot/qt/offroad/settings/visuals_panel.cc",
"sunnypilot/qt/onroad/annotated_camera.cc",
"sunnypilot/qt/onroad/buttons.cc",
"sunnypilot/qt/onroad/developer_ui/developer_ui.cc",
"sunnypilot/qt/onroad/hud.cc",
"sunnypilot/qt/onroad/model.cc",
"sunnypilot/qt/onroad/onroad_home.cc",
@@ -47,6 +48,7 @@ qt_src = [
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/lane_turn_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
]

View File

@@ -0,0 +1,86 @@
/**
* 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/lane_turn_settings.h"
LaneTurnSettings::LaneTurnSettings(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(tr("Back"));
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
ListWidgetSP *list = new ListWidgetSP(this, false);
// Lane Turn Desire Control
laneTurnDesireControl = new LaneTurnDesireControl();
laneTurnDesireControl->setUpdateOtherToggles(true);
laneTurnDesireControl->showDescription();
connect(laneTurnDesireControl, &OptionControlSP::updateLabels, laneTurnDesireControl, &LaneTurnDesireControl::refresh);
list->addItem(laneTurnDesireControl);
// Lane Turn Value control
bool is_metric = params.getBool("IsMetric");
int per_value_change = is_metric ? 62 : 100; // ~1 km/h or 1 mph
laneTurnValueControl = new OptionControlSP("LaneTurnValue", tr("Adjust Lane Turn Speed"),
tr("Set the maximum speed for lane turn desires. Default is 19 %1.").arg(is_metric ? "km/h" : "mph"),
"", {500, 2000}, per_value_change, false, nullptr, true, true);
laneTurnValueControl->showDescription();
list->addItem(laneTurnValueControl);
// show/hide value control based on desire
connect(laneTurnDesireControl, &OptionControlSP::updateLabels, this, &LaneTurnSettings::updateValueControlVisibility);
connect(laneTurnValueControl, &OptionControlSP::updateLabels, this, &LaneTurnSettings::refreshLaneTurnValueControl);
main_layout->addWidget(new ScrollViewSP(list, this));
}
void LaneTurnSettings::showEvent(QShowEvent *event) {
updateValueControlVisibility();
}
void LaneTurnSettings::updateValueControlVisibility() {
QString option = QString::fromStdString(params.get("LaneTurnDesire"));
bool visible = (option != "0");
laneTurnValueControl->setVisible(visible);
if (visible) {
refreshLaneTurnValueControl();
}
}
void LaneTurnSettings::refreshLaneTurnValueControl() {
if (!laneTurnValueControl) return;
float stored_mph = QString::fromStdString(params.get("LaneTurnValue")).toFloat();
bool is_metric = params.getBool("IsMetric");
QString unit = is_metric ? "km/h" : "mph";
float display_value = is_metric ? stored_mph * 1.609344f : stored_mph;
laneTurnValueControl->setLabel(QString::number(qRound(display_value)) + " " + unit);
}
// Lane Turn Desire Control
LaneTurnDesireControl::LaneTurnDesireControl() : OptionControlSP(
"LaneTurnDesire",
tr("Lane Turn Desires"),
tr("If you're driving at 20 mph (32 km/h) or below and have your blinker on, "
"the car will plan a turn in that direction at the nearest drivable path. "
"This prevents situations (like at red lights) where the car might plan the wrong turn direction."),
"../assets/offroad/icon_shell.png",
{0, 2}) {
refresh();
}
void LaneTurnDesireControl::refresh() {
QString option = QString::fromStdString(params.get("LaneTurnDesire"));
static const QMap<QString, QString> options = {
{"0", tr("Off")},
{"1", tr("Nudge")},
{"2", tr("Nudgeless")},
};
setLabel(options.value(option, tr("Off")));
}

View File

@@ -0,0 +1,45 @@
/**
* 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/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class LaneTurnDesireControl : public OptionControlSP {
Q_OBJECT
public:
LaneTurnDesireControl();
void refresh();
private:
Params params;
};
class LaneTurnSettings : public QWidget {
Q_OBJECT
public:
explicit LaneTurnSettings(QWidget* parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
private:
void refreshLaneTurnValueControl();
void updateValueControlVisibility();
private:
Params params;
std::map<std::string, ParamControlSP*> toggles;
LaneTurnDesireControl *laneTurnDesireControl;
OptionControlSP *laneTurnValueControl;
};

View File

@@ -59,7 +59,27 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
list->addItem(laneChangeSettingsButton);
// Lane Turn Settings
laneTurnSettingsButton = new PushButtonSP(tr("Customize Lane Turn"));
laneTurnSettingsButton->setObjectName("lane_turn_btn");
connect(laneTurnSettingsButton, &QPushButton::clicked, [=]() {
sunnypilotScroller->setLastScrollPosition();
main_layout->setCurrentWidget(laneTurnWidget);
});
laneTurnWidget = new LaneTurnSettings(this);
connect(laneTurnWidget, &LaneTurnSettings::backPress, [=]() {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
QWidget *laneButtonsWidget = new QWidget();
QHBoxLayout *laneButtonsLayout = new QHBoxLayout(laneButtonsWidget);
laneButtonsLayout->setContentsMargins(0, 0, 0, 0);
laneChangeSettingsButton->setFixedWidth(750); laneTurnSettingsButton->setFixedWidth(750);
laneButtonsLayout->addWidget(laneChangeSettingsButton); laneButtonsLayout->addWidget(laneTurnSettingsButton);
list->addItem(laneButtonsWidget);
list->addItem(vertical_space(0));
list->addItem(horizontal_line());
@@ -100,6 +120,7 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
main_layout->addWidget(sunnypilotScreen);
main_layout->addWidget(madsWidget);
main_layout->addWidget(laneChangeWidget);
main_layout->addWidget(laneTurnWidget);
setStyleSheet(R"(
#back_btn {

View File

@@ -15,6 +15,7 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_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/sunnypilot/qt/offroad/settings/lateral/lane_turn_settings.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
@@ -43,6 +44,8 @@ private:
MadsSettings *madsWidget = nullptr;
PushButtonSP *laneChangeSettingsButton;
LaneChangeSettings *laneChangeWidget = nullptr;
PushButtonSP *laneTurnSettingsButton;
LaneTurnSettings *laneTurnWidget = nullptr;
NeuralNetworkLateralControl *nnlcToggle = nullptr;
BlinkerPauseLateralSettings *blinkerPauseLateralSettings = nullptr;

View File

@@ -18,6 +18,23 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
cruisePanelScroller = new ScrollViewSP(list, this);
vlayout->addWidget(cruisePanelScroller);
intelligentCruiseButtonManagement = new ParamControlSP(
"IntelligentCruiseButtonManagement",
tr("Intelligent Cruise Button Management (ICBM) (Alpha)"),
tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."),
"",
this
);
intelligentCruiseButtonManagement->setConfirmation(true, false);
list->addItem(intelligentCruiseButtonManagement);
SmartCruiseControlVision = new ParamControl(
"SmartCruiseControlVision",
tr("Smart Cruise Control - Vision"),
tr("Use vision path predictions to estimate the appropriate speed to drive through turns ahead."),
"");
list->addItem(SmartCruiseControlVision);
customAccIncrement = new CustomAccIncrement("CustomAccIncrementsEnabled", tr("Custom ACC Speed Increments"), "", "", this);
list->addItem(customAccIncrement);
@@ -35,16 +52,22 @@ void LongitudinalPanel::showEvent(QShowEvent *event) {
void LongitudinalPanel::refresh(bool _offroad) {
auto cp_bytes = params.get("CarParamsPersistent");
if (!cp_bytes.empty()) {
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
AlignedBuffer aligned_buf;
AlignedBuffer aligned_buf_sp;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
has_longitudinal_control = hasLongitudinalControl(CP);
is_pcm_cruise = CP.getPcmCruise();
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
} else {
has_longitudinal_control = false;
is_pcm_cruise = false;
intelligent_cruise_button_management_available = false;
}
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
@@ -56,7 +79,7 @@ void LongitudinalPanel::refresh(bool _offroad) {
customAccIncrement->setDescription(onroadOnlyDescription);
customAccIncrement->showDescription();
} else {
if (has_longitudinal_control) {
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
if (is_pcm_cruise) {
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
customAccIncrement->showDescription();
@@ -68,12 +91,20 @@ void LongitudinalPanel::refresh(bool _offroad) {
customAccIncrement->toggleFlipped(false);
customAccIncrement->setDescription(accNoLongDescription);
customAccIncrement->showDescription();
params.remove("IntelligentCruiseButtonManagement");
intelligentCruiseButtonManagement->toggleFlipped(false);
}
}
bool icbm_allowed = intelligent_cruise_button_management_available && !has_longitudinal_control;
intelligentCruiseButtonManagement->setEnabled(icbm_allowed && offroad);
// enable toggle when long is available and is not PCM cruise
customAccIncrement->setEnabled(has_longitudinal_control && !is_pcm_cruise && !offroad);
bool cai_allowed = (has_longitudinal_control && !is_pcm_cruise) || icbm_allowed;
customAccIncrement->setEnabled(cai_allowed && !offroad);
customAccIncrement->refresh();
SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed);
offroad = _offroad;
}

View File

@@ -23,10 +23,13 @@ private:
Params params;
bool has_longitudinal_control = false;
bool is_pcm_cruise = false;
bool intelligent_cruise_button_management_available = false;;
bool offroad = false;
QStackedLayout *main_layout = nullptr;
ScrollViewSP *cruisePanelScroller = nullptr;
QWidget *cruisePanelScreen = nullptr;
CustomAccIncrement *customAccIncrement = nullptr;
ParamControl *SmartCruiseControlVision;
ParamControl *intelligentCruiseButtonManagement = nullptr;
};

View File

@@ -101,7 +101,6 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
QString policyType = tr("Policy Model");
policyFrame = createModelDetailFrame(this, policyType, policyProgressBar);
list->addItem(policyFrame);
list->addItem(horizontal_line());
// LiveDelay toggle
@@ -110,23 +109,10 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
list->addItem(lagd_toggle_control);
// Software delay control
int liveDelayMaxInt = 30;
std::string liveDelayBytes = params.get("LiveDelay");
if (!liveDelayBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
liveDelayBytes.size() / sizeof(capnp::word)));
auto event = msg.getRoot<cereal::Event>();
if (event.hasLiveDelay()) {
auto liveDelay = event.getLiveDelay();
float lateralDelay = liveDelay.getLateralDelay();
liveDelayMaxInt = static_cast<int>(lateralDelay * 100.0f) + 20;
}
}
delay_control = new OptionControlSP("LagdToggleDelay", tr("Adjust Software Delay"),
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
"\nThe default software delay value is 0.2"),
"", {5, liveDelayMaxInt}, 1, false, nullptr, true, true);
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
"\nThe default software delay value is 0.2"),
"", {5, 50}, 1, false, nullptr, true, true);
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
@@ -159,10 +145,6 @@ QFrame* ModelsPanel::createModelDetailFrame(QWidget *parent, QString &typeName,
return frame;
}
/**
* @brief Updates the UI with bundle download progress information
* Reads status from modelManagerSP cereal message and displays status for all models
*/
void ModelsPanel::handleBundleDownloadProgress() {
supercomboFrame->setVisible(false);
visionFrame->setVisible(false);
@@ -401,34 +383,28 @@ void ModelsPanel::updateLabels() {
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
bool lagdEnabled = params.getBool("LagdToggle");
if (lagdEnabled) {
std::string liveDelayBytes = params.get("LiveDelay");
auto liveDelayBytes = params.get("LiveDelay");
if (!liveDelayBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
liveDelayBytes.size() / sizeof(capnp::word)));
auto event = msg.getRoot<cereal::Event>();
if (event.hasLiveDelay()) {
auto liveDelay = event.getLiveDelay();
float lateralDelay = liveDelay.getLateralDelay();
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
}
auto LD = loadCerealEvent(params, "LiveDelay");
float lateralDelay = LD->getLiveDelay().getLateralDelay();
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
}
} else {
std::string carParamsBytes = params.get("CarParamsPersistent");
auto carParamsBytes = params.get("CarParamsPersistent");
if (!carParamsBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(carParamsBytes.data()),
carParamsBytes.size() / sizeof(capnp::word)));
auto carParams = msg.getRoot<cereal::CarParams>();
float steerDelay = carParams.getSteerActuatorDelay();
AlignedBuffer aligned_buf_cp;
capnp::FlatArrayMessageReader cmsg(aligned_buf_cp.align(carParamsBytes.data(), carParamsBytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
float steerDelay = CP.getSteerActuatorDelay();
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
float totalLag = steerDelay + softwareDelay;
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
"<b>%1</b> %2 s + <b>%3</b> %4 s = <b>%5</b> %6 s</span>")
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
}
}
lagd_toggle_control->setDescription(desc);

View File

@@ -9,6 +9,7 @@
#include <QProgressBar>
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
class ModelsPanel : public QWidget {
@@ -81,5 +82,4 @@ private:
Params params;
ButtonControlSP *clearModelCacheBtn;
ButtonControlSP *refreshAvailableModelsBtn;
};

View File

@@ -11,12 +11,39 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
// branch selector
QObject::disconnect(targetBranchBtn, nullptr, nullptr, nullptr);
connect(targetBranchBtn, &ButtonControlSP::clicked, [=]() {
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
if (Hardware::get_device_type() == cereal::InitData::DeviceType::TICI) {
auto current = params.get("GitBranch");
QStringList allBranches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
QStringList branches;
for (const QString &b : allBranches) {
if (b.endsWith("-tici")) {
branches.append(b);
}
}
for (QString b : {current.c_str(), "master-tici", "staging-tici", "release-tici"}) {
auto i = branches.indexOf(b);
if (i >= 0) {
branches.removeAt(i);
branches.insert(0, b);
}
}
QString cur = QString::fromStdString(params.get("UpdaterTargetBranch"));
QString selection = MultiOptionDialog::getSelection(tr("Select a branch"), branches, cur, this);
if (!selection.isEmpty()) {
params.put("UpdaterTargetBranch", selection.toStdString());
targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch")));
checkForUpdates();
}
} else {
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
d.setMinLength(0);
const int ret = d.exec();
if (ret) {
searchBranches(d.text());
}
}
});
// Disable Updates toggle

View File

@@ -35,6 +35,13 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
"../assets/offroad/icon_monitoring.png",
false,
},
{
"StandstillTimer",
tr("Enable Standstill Timer"),
tr("Show a timer on the HUD when the car is at a standstill."),
"../assets/offroad/icon_monitoring.png",
false,
},
};
// Add regular toggles first
@@ -72,6 +79,15 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
list->addItem(chevron_info_settings);
param_watcher->addParam("ChevronInfo");
// Visuals: Developer UI Info (Dev UI)
std::vector<QString> dev_ui_settings_texts{tr("Off"), tr("Right"), tr("Right &&\nBottom")};
dev_ui_settings = new ButtonParamControlSP(
"DevUIInfo", tr("Developer UI"), tr("Display real-time parameters and metrics from various sources."),
"",
dev_ui_settings_texts,
380);
list->addItem(dev_ui_settings);
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
@@ -90,4 +106,7 @@ void VisualsPanel::paramsRefresh() {
if (chevron_info_settings) {
chevron_info_settings->refresh();
}
if (dev_ui_settings) {
dev_ui_settings->refresh();
}
}

View File

@@ -28,4 +28,5 @@ protected:
std::map<std::string, ParamControlSP*> toggles;
ParamWatcher * param_watcher;
ButtonParamControlSP *chevron_info_settings;
ButtonParamControlSP *dev_ui_settings;
};

View File

@@ -14,3 +14,8 @@ AnnotatedCameraWidgetSP::AnnotatedCameraWidgetSP(VisionStreamType type, QWidget
void AnnotatedCameraWidgetSP::updateState(const UIState &s) {
AnnotatedCameraWidget::updateState(s);
}
void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) {
AnnotatedCameraWidget::showEvent(event);
ui_update_params_sp(uiState());
}

View File

@@ -15,4 +15,7 @@ class AnnotatedCameraWidgetSP : public AnnotatedCameraWidget {
public:
explicit AnnotatedCameraWidgetSP(VisionStreamType type, QWidget *parent = nullptr);
void updateState(const UIState &s) override;
protected:
void showEvent(QShowEvent *event) override;
};

View File

@@ -0,0 +1,227 @@
/**
* 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 <cmath>
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h"
// Add Relative Distance to Primary Lead Car
// Unit: Meters
UiElement DeveloperUi::getDRel(bool lead_status, float lead_d_rel) {
QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-";
QColor color = QColor(255, 255, 255, 255);
if (lead_status) {
// Orange if close, Red if very close
if (lead_d_rel < 5) {
color = QColor(255, 0, 0, 255);
} else if (lead_d_rel < 15) {
color = QColor(255, 188, 0, 255);
}
}
return UiElement(value, "REL DIST", "m", color);
}
// Add Relative Velocity vs Primary Lead Car
// Unit: kph if metric, else mph
UiElement DeveloperUi::getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit) {
QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-";
QColor color = QColor(255, 255, 255, 255);
if (lead_status) {
// Red if approaching faster than 10mph
// Orange if approaching (negative)
if (lead_v_rel < -4.4704) {
color = QColor(255, 0, 0, 255);
} else if (lead_v_rel < 0) {
color = QColor(255, 188, 0, 255);
}
}
return UiElement(value, "REL SPEED", speed_unit, color);
}
// Add Real Steering Angle
// Unit: Degrees
UiElement DeveloperUi::getSteeringAngleDeg(float angle_steers, bool lat_active, bool steer_override) {
QString value = QString("%1%2%3").arg(QString::number(angle_steers, 'f', 1)).arg("°").arg("");
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
// Red if large steering angle
// Orange if moderate steering angle
if (std::fabs(angle_steers) > 180) {
color = QColor(255, 0, 0, 255);
} else if (std::fabs(angle_steers) > 90) {
color = QColor(255, 188, 0, 255);
}
return UiElement(value, "REAL STEER", "", color);
}
// Add Actual Lateral Acceleration (roll compensated) when using Torque
// Unit: m/s²
UiElement DeveloperUi::getActualLateralAccel(float curvature, float v_ego, float roll, bool lat_active, bool steer_override) {
double actualLateralAccel = (curvature * pow(v_ego, 2)) - (roll * 9.81);
QString value = QString::number(actualLateralAccel, 'f', 2);
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
return UiElement(value, "ACTUAL L.A.", "m/s²", color);
}
// Add Desired Steering Angle when using PID
// Unit: Degrees
UiElement DeveloperUi::getSteeringAngleDesiredDeg(bool lat_active, float steer_angle_desired, float angle_steers) {
QString value = lat_active ? QString("%1%2%3").arg(QString::number(steer_angle_desired, 'f', 1)).arg("°").arg("") : "-";
QColor color = QColor(255, 255, 255, 255);
if (lat_active) {
// Red if large steering angle
// Orange if moderate steering angle
if (std::fabs(angle_steers) > 180) {
color = QColor(255, 0, 0, 255);
} else if (std::fabs(angle_steers) > 90) {
color = QColor(255, 188, 0, 255);
} else {
color = QColor(0, 255, 0, 255);
}
}
return UiElement(value, "DESIRED STEER", "", color);
}
// Add Device Memory (RAM) Usage
// Unit: Percent
UiElement DeveloperUi::getMemoryUsagePercent(int memory_usage_percent) {
QString value = QString("%1%2").arg(QString::number(memory_usage_percent, 'd', 0)).arg("%");
QColor color = (memory_usage_percent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255);
return UiElement(value, "RAM", "", color);
}
// Add Vehicle Current Acceleration
// Unit: m/s²
UiElement DeveloperUi::getAEgo(float a_ego) {
QString value = QString::number(a_ego, 'f', 1);
QColor color = QColor(255, 255, 255, 255);
return UiElement(value, "ACC.", "m/s²", color);
}
// Add Relative Velocity to Primary Lead Car
// Unit: kph if metric, else mph
UiElement DeveloperUi::getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit) {
QString value = lead_status ? QString::number((lead_v_rel + v_ego) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-";
QColor color = QColor(255, 255, 255, 255);
if (lead_status) {
// Red if approaching faster than 10mph
// Orange if approaching (negative)
if (lead_v_rel < -4.4704) {
color = QColor(255, 0, 0, 255);
} else if (lead_v_rel < 0) {
color = QColor(255, 188, 0, 255);
}
}
return UiElement(value, "L.S.", speed_unit, color);
}
// Add Friction Coefficient Raw from torqued
// Unit: None
UiElement DeveloperUi::getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid) {
QString value = QString::number(friction_coefficient_filtered, 'f', 3);
QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
return UiElement(value, "FRIC.", "", color);
}
// Add Lateral Acceleration Factor Raw from torqued
// Unit: m/s²
UiElement DeveloperUi::getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid) {
QString value = QString::number(lat_accel_factor_filtered, 'f', 3);
QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
return UiElement(value, "L.A.", "m/s²", color);
}
// Add Steering Torque from Car EPS
// Unit: Newton Meters
UiElement DeveloperUi::getSteeringTorqueEps(float steering_torque_eps) {
QString value = QString::number(std::fabs(steering_torque_eps), 'f', 1);
QColor color = QColor(255, 255, 255, 255);
return UiElement(value, "E.T.", "N·dm", color);
}
// Add Bearing Degree and Direction from Car (Compass)
// Unit: Meters
UiElement DeveloperUi::getBearingDeg(float bearing_accuracy_deg, float bearing_deg) {
QString value = (bearing_accuracy_deg != 180.00) ? QString("%1%2%3").arg(QString::number(bearing_deg, 'd', 0)).arg("°").arg("") : "-";
QColor color = QColor(255, 255, 255, 255);
QString dir_value;
if (bearing_accuracy_deg != 180.00) {
if (((bearing_deg >= 337.5) && (bearing_deg <= 360)) || ((bearing_deg >= 0) && (bearing_deg <= 22.5))) {
dir_value = "N";
} else if ((bearing_deg > 22.5) && (bearing_deg < 67.5)) {
dir_value = "NE";
} else if ((bearing_deg >= 67.5) && (bearing_deg <= 112.5)) {
dir_value = "E";
} else if ((bearing_deg > 112.5) && (bearing_deg < 157.5)) {
dir_value = "SE";
} else if ((bearing_deg >= 157.5) && (bearing_deg <= 202.5)) {
dir_value = "S";
} else if ((bearing_deg > 202.5) && (bearing_deg < 247.5)) {
dir_value = "SW";
} else if ((bearing_deg >= 247.5) && (bearing_deg <= 292.5)) {
dir_value = "W";
} else if ((bearing_deg > 292.5) && (bearing_deg < 337.5)) {
dir_value = "NW";
}
} else {
dir_value = "OFF";
}
return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color);
}
// Add Altitude of Current Location
// Unit: Meters
UiElement DeveloperUi::getAltitude(float gps_accuracy, float altitude) {
QString value = (gps_accuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-";
QColor color = QColor(255, 255, 255, 255);
return UiElement(value, "ALT.", "m", color);
}
// Add Actuators Output
// Unit: Degree (angle) or m/s² (torque)
UiElement DeveloperUi::getActuatorsOutputLateral(cereal::CarParams::SteerControlType steerControlType,
cereal::CarControl::Actuators::Reader &actuators,
float desiredCurvature, float v_ego, float roll, bool lat_active, bool steer_override) {
QString label;
QString value;
QString unit;
if (steerControlType == cereal::CarParams::SteerControlType::ANGLE) {
label = "DESIRED STEER";
value = QString("%1%2%3").arg(QString::number(actuators.getSteeringAngleDeg(), 'f', 1)).arg("°").arg("");
} else {
label = "DESIRED L.A.";
double desiredLateralAccel = (desiredCurvature * pow(v_ego, 2)) - (roll * 9.81);
value = QString::number(desiredLateralAccel, 'f', 2);
unit = "m/s²";
}
value = lat_active ? value : "-";
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
return UiElement(value, label, unit, color);
}

View File

@@ -0,0 +1,31 @@
/**
* 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/qt/onroad/developer_ui/ui_elements.h"
class DeveloperUi {
public:
static UiElement getDRel(bool lead_status, float lead_d_rel);
static UiElement getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit);
static UiElement getSteeringAngleDeg(float angle_steers, bool lat_active, bool steer_override);
static UiElement getActualLateralAccel(float curvature, float v_ego, float roll, bool lat_active, bool steer_override);
static UiElement getSteeringAngleDesiredDeg(bool lat_active, float steer_angle_desired, float angle_steers);
static UiElement getMemoryUsagePercent(int memory_usage_percent);
static UiElement getAEgo(float a_ego);
static UiElement getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit);
static UiElement getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid);
static UiElement getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid);
static UiElement getSteeringTorqueEps(float steering_torque_eps);
static UiElement getBearingDeg(float bearing_accuracy_deg, float bearing_deg);
static UiElement getAltitude(float gps_accuracy, float altitude);
static UiElement getActuatorsOutputLateral(cereal::CarParams::SteerControlType steerControlType,
cereal::CarControl::Actuators::Reader &actuators,
float desiredCurvature, float v_ego, float roll, bool lat_active, bool steer_override);
};

View File

@@ -0,0 +1,19 @@
/**
* 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 <QColor>
struct UiElement {
QString value{};
QString label{};
QString units{};
QColor color{};
explicit UiElement(const QString &value = "", const QString &label = "", const QString &units = "", const QColor &color = QColor(255, 255, 255, 255))
: value(value), label(label), units(units), color(color) {}
};

View File

@@ -4,15 +4,310 @@
* 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 <QPainterPath>
#include "selfdrive/ui/sunnypilot/qt/onroad/hud.h"
#include "selfdrive/ui/qt/util.h"
HudRendererSP::HudRendererSP() {}
void HudRendererSP::updateState(const UIState &s) {
HudRenderer::updateState(s);
const SubMaster &sm = *(s.sm);
const bool cs_alive = sm.alive("controlsState");
const auto cs = sm["controlsState"].getControlsState();
const auto car_state = sm["carState"].getCarState();
const auto car_control = sm["carControl"].getCarControl();
const auto radar_state = sm["radarState"].getRadarState();
const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1;
const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation();
const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters();
const auto car_params = sm["carParams"].getCarParams();
const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
static int reverse_delay = 0;
bool reverse_allowed = false;
if (int(car_state.getGearShifter()) != 4) {
reverse_delay = 0;
reverse_allowed = false;
} else {
reverse_delay += 50;
if (reverse_delay >= 1000) {
reverse_allowed = true;
}
}
reversing = reverse_allowed;
is_metric = s.scene.is_metric;
// Handle older routes where vEgoCluster is not set
v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0;
float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo();
speed = cs_alive ? std::max<float>(0.0, v_ego) : 0.0;
speed *= is_metric ? MS_TO_KPH : MS_TO_MPH;
latActive = car_control.getLatActive();
steerOverride = car_state.getSteeringPressed();
devUiInfo = s.scene.dev_ui_info;
speedUnit = is_metric ? tr("km/h") : tr("mph");
lead_d_rel = radar_state.getLeadOne().getDRel();
lead_v_rel = radar_state.getLeadOne().getVRel();
lead_status = radar_state.getLeadOne().getStatus();
steerControlType = car_params.getSteerControlType();
actuators = car_control.getActuators();
torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE;
angleSteers = car_state.getSteeringAngleDeg();
desiredCurvature = cs.getDesiredCurvature();
curvature = cs.getCurvature();
roll = sm["liveParameters"].getLiveParameters().getRoll();
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
altitude = gpsLocation.getAltitude();
vEgo = car_state.getVEgo();
aEgo = car_state.getAEgo();
steeringTorqueEps = car_state.getSteeringTorqueEps();
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
bearingDeg = gpsLocation.getBearingDeg();
torquedUseParams = ltp.getUseParams();
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
liveValid = ltp.getLiveValid();
standstillTimer = s.scene.standstill_timer;
isStandstill = car_state.getStandstill();
longOverride = car_control.getCruiseControl().getOverride();
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
}
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
HudRenderer::draw(p, surface_rect);
if (!reversing) {
// Smart Cruise Control
int x_offset = -260;
int y1_offset = -80;
// int y2_offset = -140; // reserved for 2 icons
bool scc_vision_active_pulse = pulseElement(smartCruiseControlVisionFrame);
if ((smartCruiseControlVisionEnabled && !smartCruiseControlVisionActive) || (smartCruiseControlVisionActive && scc_vision_active_pulse)) {
drawSmartCruiseControlOnroadIcon(p, surface_rect, x_offset, y1_offset, "SCC-V");
}
if (smartCruiseControlVisionActive) {
smartCruiseControlVisionFrame++;
} else {
smartCruiseControlVisionFrame = 0;
}
// Bottom Dev UI
if (devUiInfo == 2) {
QRect rect_bottom(surface_rect.left(), surface_rect.bottom() - 60, surface_rect.width(), 61);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 100));
p.drawRect(rect_bottom);
drawBottomDevUI(p, rect_bottom.left(), rect_bottom.center().y());
}
// Right Dev UI
if (devUiInfo != 0) {
QRect rect_right(surface_rect.right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 170);
drawRightDevUI(p, surface_rect.right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rect_right.height());
}
// Standstill Timer
if (standstillTimer) {
drawStandstillTimer(p, surface_rect.right() / 12 * 10, surface_rect.bottom() / 12 * 1.53);
}
}
}
void HudRendererSP::drawText(QPainter &p, int x, int y, const QString &text, QColor color) {
QRect real_rect = p.fontMetrics().boundingRect(text);
real_rect.moveCenter({x, y - real_rect.height() / 2});
p.setPen(color);
p.drawText(real_rect.x(), real_rect.bottom(), text);
}
bool HudRendererSP::pulseElement(int frame) {
if (frame % UI_FREQ < (UI_FREQ / 2.5)) {
return false;
}
return true;
}
void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &surface_rect, int x_offset, int y_offset, std::string name) {
int x = surface_rect.center().x();
int y = surface_rect.height() / 4;
QString text = QString::fromStdString(name);
QFont font = InterFont(36, QFont::Bold);
p.setFont(font);
QFontMetrics fm(font);
int padding_v = 5;
int box_width = 160;
int box_height = fm.height() + padding_v * 2;
QRectF bg_rect(x - (box_width / 2) + x_offset,
y - (box_height / 2) + y_offset,
box_width, box_height);
QPainterPath boxPath;
boxPath.addRoundedRect(bg_rect, 10, 10);
int text_w = fm.horizontalAdvance(text);
qreal baseline_y = bg_rect.top() + padding_v + fm.ascent();
qreal text_x = bg_rect.center().x() - (text_w / 2.0);
QPainterPath textPath;
textPath.addText(QPointF(text_x, baseline_y), font, text);
boxPath = boxPath.subtracted(textPath);
p.setPen(Qt::NoPen);
p.setBrush(longOverride ? QColor(0x91, 0x9b, 0x95, 0xf1) : QColor(0, 0xff, 0, 0xff));
p.drawPath(boxPath);
}
int HudRendererSP::drawRightDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
p.setFont(InterFont(28, QFont::Bold));
x += 92;
y += 80;
drawText(p, x, y, label);
p.setFont(InterFont(30 * 2, QFont::Bold));
y += 65;
drawText(p, x, y, value, color);
p.setFont(InterFont(28, QFont::Bold));
if (units.length() > 0) {
p.save();
x += 120;
y -= 25;
p.translate(x, y);
p.rotate(-90);
drawText(p, 0, 0, units);
p.restore();
}
return 130;
}
void HudRendererSP::drawRightDevUI(QPainter &p, int x, int y) {
int rh = 5;
int ry = y;
UiElement dRelElement = DeveloperUi::getDRel(lead_status, lead_d_rel);
rh += drawRightDevUIElement(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color);
ry = y + rh;
UiElement vRelElement = DeveloperUi::getVRel(lead_status, lead_v_rel, is_metric, speedUnit);
rh += drawRightDevUIElement(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color);
ry = y + rh;
UiElement steeringAngleDegElement = DeveloperUi::getSteeringAngleDeg(angleSteers, latActive, steerOverride);
rh += drawRightDevUIElement(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color);
ry = y + rh;
UiElement actuatorsOutputLateralElement = DeveloperUi::getActuatorsOutputLateral(steerControlType, actuators, desiredCurvature, vEgo, roll, latActive, steerOverride);
rh += drawRightDevUIElement(p, x, ry, actuatorsOutputLateralElement.value, actuatorsOutputLateralElement.label, actuatorsOutputLateralElement.units, actuatorsOutputLateralElement.color);
ry = y + rh;
UiElement actualLateralAccelElement = DeveloperUi::getActualLateralAccel(curvature, vEgo, roll, latActive, steerOverride);
rh += drawRightDevUIElement(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color);
}
int HudRendererSP::drawBottomDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
p.setFont(InterFont(38, QFont::Bold));
QFontMetrics fm(p.font());
QRect init_rect = fm.boundingRect(label + " ");
QRect real_rect = fm.boundingRect(init_rect, 0, label + " ");
real_rect.moveCenter({x, y});
QRect init_rect2 = fm.boundingRect(value);
QRect real_rect2 = fm.boundingRect(init_rect2, 0, value);
real_rect2.moveTop(real_rect.top());
real_rect2.moveLeft(real_rect.right() + 10);
QRect init_rect3 = fm.boundingRect(units);
QRect real_rect3 = fm.boundingRect(init_rect3, 0, units);
real_rect3.moveTop(real_rect.top());
real_rect3.moveLeft(real_rect2.right() + 10);
p.setPen(Qt::white);
p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, label);
p.setPen(color);
p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, value);
p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, units);
return 430;
}
void HudRendererSP::drawBottomDevUI(QPainter &p, int x, int y) {
int rw = 90;
UiElement aEgoElement = DeveloperUi::getAEgo(aEgo);
rw += drawBottomDevUIElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color);
UiElement vEgoLeadElement = DeveloperUi::getVEgoLead(lead_status, lead_v_rel, vEgo, is_metric, speedUnit);
rw += drawBottomDevUIElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color);
if (torqueLateral && torquedUseParams) {
UiElement frictionCoefficientFilteredElement = DeveloperUi::getFrictionCoefficientFiltered(frictionCoefficientFiltered, liveValid);
rw += drawBottomDevUIElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color);
UiElement latAccelFactorFilteredElement = DeveloperUi::getLatAccelFactorFiltered(latAccelFactorFiltered, liveValid);
rw += drawBottomDevUIElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color);
} else {
UiElement steeringTorqueEpsElement = DeveloperUi::getSteeringTorqueEps(steeringTorqueEps);
rw += drawBottomDevUIElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color);
UiElement bearingDegElement = DeveloperUi::getBearingDeg(bearingAccuracyDeg, bearingDeg);
rw += drawBottomDevUIElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color);
}
UiElement altitudeElement = DeveloperUi::getAltitude(gpsAccuracy, altitude);
rw += drawBottomDevUIElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color);
}
void HudRendererSP::drawStandstillTimer(QPainter &p, int x, int y) {
if (isStandstill) {
standstillElapsedTime += 1.0 / UI_FREQ;
int minute = static_cast<int>(standstillElapsedTime / 60);
int second = static_cast<int>(standstillElapsedTime - (minute * 60));
// stop sign for standstill timer
const int size = 190; // size
const float angle = M_PI / 8.0;
QPolygon octagon;
for (int i = 0; i < 8; i++) {
float curr_angle = angle + i * M_PI / 4.0;
int point_x = x + size / 2 * cos(curr_angle);
int point_y = y + size / 2 * sin(curr_angle);
octagon << QPoint(point_x, point_y);
}
p.setPen(QPen(Qt::white, 6));
p.setBrush(QColor(255, 90, 81, 200)); // red pastel
p.drawPolygon(octagon);
QString time_str = QString("%1:%2").arg(minute, 1, 10, QChar('0')).arg(second, 2, 10, QChar('0'));
p.setFont(InterFont(55, QFont::Bold));
p.setPen(Qt::white);
QRect timerTextRect = p.fontMetrics().boundingRect(QString(time_str));
timerTextRect.moveCenter({x, y});
p.drawText(timerTextRect, Qt::AlignCenter, QString(time_str));
} else {
standstillElapsedTime = 0.0;
}
}

View File

@@ -7,9 +7,8 @@
#pragma once
#include <QPainter>
#include "selfdrive/ui/qt/onroad/hud.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h"
class HudRendererSP : public HudRenderer {
Q_OBJECT
@@ -18,4 +17,50 @@ public:
HudRendererSP();
void updateState(const UIState &s) override;
void draw(QPainter &p, const QRect &surface_rect) override;
private:
Params params;
void drawText(QPainter &p, int x, int y, const QString &text, QColor color = Qt::white);
void drawRightDevUI(QPainter &p, int x, int y);
int drawRightDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
int drawBottomDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
void drawBottomDevUI(QPainter &p, int x, int y);
void drawStandstillTimer(QPainter &p, int x, int y);
bool pulseElement(int frame);
void drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &surface_rect, int x_offset, int y_offset, std::string name);
bool lead_status;
float lead_d_rel;
float lead_v_rel;
bool torqueLateral;
float angleSteers;
float desiredCurvature;
float curvature;
float roll;
int memoryUsagePercent;
int devUiInfo;
float gpsAccuracy;
float altitude;
float vEgo;
float aEgo;
float steeringTorqueEps;
float bearingAccuracyDeg;
float bearingDeg;
bool torquedUseParams;
float latAccelFactorFiltered;
float frictionCoefficientFiltered;
bool liveValid;
QString speedUnit;
bool latActive;
bool steerOverride;
bool reversing;
cereal::CarParams::SteerControlType steerControlType;
cereal::CarControl::Actuators::Reader actuators;
bool standstillTimer;
bool isStandstill;
float standstillElapsedTime;
bool longOverride;
bool smartCruiseControlVisionEnabled;
bool smartCruiseControlVisionActive;
int smartCruiseControlVisionFrame;
};

View File

@@ -110,3 +110,16 @@ QStringList searchFromList(const QString &query, const QStringList &list) {
}
return search_results;
}
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param) {
std::string bytes = params.get(_param);
try {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(bytes.data(), bytes.size()));
return cmsg.getRoot<cereal::Event>();
} catch (kj::Exception& e) {
qInfo() << "invalid " << QString::fromStdString(_param) << ":" << e.getDescription().cStr();
return std::nullopt;
}
}

View File

@@ -15,8 +15,11 @@
#include <QRegularExpression>
#include <QWidget>
#include "selfdrive/ui/sunnypilot/ui.h"
QString getUserAgent(bool sunnylink = false);
std::optional<QString> getSunnylinkDongleId();
std::optional<QString> getParamIgnoringDefault(const std::string &param_name, const std::string &default_value);
QMap<QString, QVariantMap> loadPlatformList();
QStringList searchFromList(const QString &query, const QStringList &list);
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param);

View File

@@ -18,13 +18,23 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) {
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState",
"pandaStates", "carParams", "driverMonitoringState", "carState", "driverStateV2",
"wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan",
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP"
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
"carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters",
"carStateSP", "liveParameters"
});
// update timer
timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, this, &UIStateSP::update);
timer->start(1000 / UI_FREQ);
// Param watcher for UIScene param updates
param_watcher = new ParamWatcher(this);
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
ui_update_params_sp(this);
});
param_watcher->addParam("DevUIInfo");
param_watcher->addParam("StandstillTimer");
}
// This method overrides completely the update method from the parent class intentionally.
@@ -39,6 +49,12 @@ void UIStateSP::update() {
emit uiUpdate(*this);
}
void ui_update_params_sp(UIStateSP *s) {
auto params = Params();
s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str());
s->scene.standstill_timer = params.getBool("StandstillTimer");
}
DeviceSP::DeviceSP(QObject *parent) : Device(parent) {
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &DeviceSP::update);
QObject::connect(this, &Device::displayPowerChanged, this, &DeviceSP::handleDisplayPowerChanged);

View File

@@ -13,6 +13,7 @@
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
class UIStateSP : public UIState {
Q_OBJECT
@@ -73,6 +74,7 @@ private slots:
private:
std::vector<RoleModel> sunnylinkRoles = {};
std::vector<UserModel> sunnylinkUsers = {};
ParamWatcher *param_watcher;
};
UIStateSP *uiStateSP();
@@ -92,3 +94,5 @@ private:
DeviceSP *deviceSP();
inline DeviceSP *device() { return deviceSP(); }
void ui_update_params_sp(UIStateSP *s);

View File

@@ -0,0 +1,13 @@
/**
* 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
typedef struct UISceneSP : UIScene {
int dev_ui_info = 0;
bool standstill_timer = false;
} UISceneSP;

View File

@@ -29,7 +29,7 @@ UI_DELAY = 0.5 # may be slower on CI?
TEST_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19"
STREAMS: list[tuple[VisionStreamType, CameraConfig, bytes]] = []
OFFROAD_ALERTS = ['Offroad_StorageMissing', 'Offroad_IsTakingSnapshot']
OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot', ]
DATA: dict[str, capnp.lib.capnp._DynamicStructBuilder] = dict.fromkeys(
["carParams", "deviceState", "pandaStates", "controlsState", "selfdriveState",
"liveCalibration", "modelV2", "radarState", "driverMonitoringState", "carState",

View File

@@ -55,8 +55,7 @@ void update_state(UIState *s) {
}
if (sm.updated("wideRoadCameraState")) {
auto cam_state = sm["wideRoadCameraState"].getWideRoadCameraState();
float scale = (cam_state.getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f;
scene.light_sensor = std::max(100.0f - scale * cam_state.getExposureValPercent(), 0.0f);
scene.light_sensor = std::max(100.0f - cam_state.getExposureValPercent(), 0.0f);
} else if (!sm.allAliveAndValid({"wideRoadCameraState"})) {
scene.light_sensor = -1;
}

View File

@@ -66,6 +66,11 @@ typedef struct UIScene {
uint64_t started_frame;
} UIScene;
#ifdef SUNNYPILOT
#include "sunnypilot/ui_scene.h"
#define UIScene UISceneSP
#endif
class UIState : public QObject {
Q_OBJECT

View File

@@ -105,10 +105,7 @@ class UIState:
# Handle wide road camera state updates
if self.sm.updated["wideRoadCameraState"]:
cam_state = self.sm["wideRoadCameraState"]
# Scale factor based on sensor type
scale = 6.0 if cam_state.sensor == 'ar0231' else 1.0
self.light_sensor = max(100.0 - scale * cam_state.exposureValPercent, 0.0)
self.light_sensor = max(100.0 - cam_state.exposureValPercent, 0.0)
elif not self.sm.alive["wideRoadCameraState"] or not self.sm.valid["wideRoadCameraState"]:
self.light_sensor = -1

View File

@@ -0,0 +1,7 @@
"""
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.
"""
PARAMS_UPDATE_PERIOD = 3 # seconds

View File

@@ -3,6 +3,7 @@ import capnp
import numpy as np
from cereal import log
from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
from openpilot.sunnypilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_lag_adjusted_curvature, MIN_SPEED
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -120,23 +121,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
modelV2_action.desiredCurvature = desired_curvature
# times at X_IDXS according to model plan
PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N
PLAN_T_IDXS[0] = 0.0
plan_x = net_output_data['plan'][0,:,Plan.POSITION][:,0].tolist()
for xidx in range(1, ModelConstants.IDX_N):
tidx = 0
# increment tidx until we find an element that's further away than the current xidx
while tidx < ModelConstants.IDX_N - 1 and plan_x[tidx+1] < ModelConstants.X_IDXS[xidx]:
tidx += 1
if tidx == ModelConstants.IDX_N - 1:
# if the Plan doesn't extend far enough, set plan_t to the max value (10s), then break
PLAN_T_IDXS[xidx] = ModelConstants.T_IDXS[ModelConstants.IDX_N - 1]
break
# interpolate to find `t` for the current xidx
current_x_val = plan_x[tidx]
next_x_val = plan_x[tidx+1]
p = (ModelConstants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) if abs(next_x_val - current_x_val) > 1e-9 else float('nan')
PLAN_T_IDXS[xidx] = p * ModelConstants.T_IDXS[tidx+1] + (1 - p) * ModelConstants.T_IDXS[tidx]
PLAN_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
# lane lines
modelV2.init('laneLines', 4)

View File

@@ -177,7 +177,7 @@ def main(demo=False):
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
# messaging
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
publish_state = PublishState()
@@ -304,6 +304,7 @@ def main(demo=False):
modelv2_send = messaging.new_message('modelV2')
drivingdata_send = messaging.new_message('drivingModelData')
posenet_send = messaging.new_message('cameraOdometry')
mdv2sp_send = messaging.new_message('modelDataV2SP')
action = model.get_action_from_model(model_output, prev_action, long_delay + DT_MDL)
fill_model_msg(drivingdata_send, modelv2_send, model_output, action, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen,
@@ -316,6 +317,7 @@ def main(demo=False):
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_controller.turn_direction
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
@@ -323,6 +325,7 @@ def main(demo=False):
pm.send('modelV2', modelv2_send)
pm.send('drivingModelData', drivingdata_send)
pm.send('cameraOdometry', posenet_send)
pm.send('modelDataV2SP', mdv2sp_send)
last_vipc_frame_id = meta_main.frame_id

View File

@@ -3,6 +3,7 @@ import capnp
import numpy as np
from cereal import log
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants, Plan
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
from openpilot.selfdrive.controls.lib.drive_helpers import get_curvature_from_plan
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -118,8 +119,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
# action (includes lateral planning now)
modelV2.action = action
# times at X_IDXS of edges and lines aren't used
LINE_T_IDXS: list[float] = []
# times at X_IDXS of edges and lines
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
# lane lines
modelV2.init('laneLines', 4)

View File

@@ -27,7 +27,7 @@ from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.models.helpers import get_active_bundle
from openpilot.sunnypilot.models.runners.helpers import get_model_runner
PROCESS_NAME = "selfdrive.modeld.modeld"
PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad"
class FrameMeta:
@@ -77,42 +77,47 @@ class ModelState(ModelStateBase):
self.numpy_inputs[key] = np.zeros(shape, dtype=np.float32)
# Temporal input: shape is [batch, history, features]
if len(shape) == 3 and shape[1] > 1:
buffer_history_len = max(100, (shape[1] * 4 if shape[1] < 100 else shape[1])) # Allow for higher history buffers in the future
buffer_history_len = shape[1] * 4 if shape[1] < 99 else shape[1] # Allow for higher history buffers in the future
feature_len = shape[2]
self.temporal_buffers[key] = np.zeros((1, buffer_history_len, feature_len), dtype=np.float32)
features_buffer_shape = self.model_runner.input_shapes.get('features_buffer')
if shape[1] in (24, 25) and features_buffer_shape is not None and features_buffer_shape[1] == 24: # 20Hz
buffer_history_len = (features_buffer_shape[1] + 1) * 4
step = int(-buffer_history_len / shape[1])
self.temporal_idxs_map[key] = np.arange(step, step * (shape[1] + 1), step)[::-1]
elif shape[1] == 25: # Split
skip = buffer_history_len // shape[1]
self.temporal_idxs_map[key] = np.arange(buffer_history_len)[-1 - (skip * (shape[1] - 1))::skip]
elif shape[1] == buffer_history_len: # non20hz
self.temporal_idxs_map[key] = np.arange(buffer_history_len)
elif shape[1] >= 99: # non20hz
self.temporal_idxs_map[key] = np.arange(shape[1])
self.temporal_buffers[key] = np.zeros((1, buffer_history_len, feature_len), dtype=np.float32)
@property
def mlsim(self) -> bool:
return bool(self.generation is not None and self.generation >= 11)
@property
def desire_key(self) -> str:
return next(key for key in self.numpy_inputs if key.startswith('desire'))
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
inputs['desire'][0] = 0
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.prev_desire[:] = inputs['desire']
self.temporal_buffers['desire'][0,:-1] = self.temporal_buffers['desire'][0,1:]
self.temporal_buffers['desire'][0,-1] = new_desire
inputs[self.desire_key][0] = 0
new_desire = np.where(inputs[self.desire_key] - self.prev_desire > .99, inputs[self.desire_key], 0)
self.prev_desire[:] = inputs[self.desire_key]
self.temporal_buffers[self.desire_key][0,:-1] = self.temporal_buffers[self.desire_key][0,1:]
self.temporal_buffers[self.desire_key][0,-1] = new_desire
# Roll buffer and assign based on desire.shape[1] value
if self.temporal_buffers['desire'].shape[1] > self.numpy_inputs['desire'].shape[1]:
skip = self.temporal_buffers['desire'].shape[1] // self.numpy_inputs['desire'].shape[1]
self.numpy_inputs['desire'][:] = (
self.temporal_buffers['desire'][0].reshape(self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], skip, -1).max(axis=2))
if self.temporal_buffers[self.desire_key].shape[1] > self.numpy_inputs[self.desire_key].shape[1]:
skip = self.temporal_buffers[self.desire_key].shape[1] // self.numpy_inputs[self.desire_key].shape[1]
self.numpy_inputs[self.desire_key][:] = (self.temporal_buffers[self.desire_key][0].reshape(
self.numpy_inputs[self.desire_key].shape[0], self.numpy_inputs[self.desire_key].shape[1], skip, -1).max(axis=2))
else:
self.numpy_inputs['desire'][:] = self.temporal_buffers['desire'][0, self.temporal_idxs_map['desire']]
self.numpy_inputs[self.desire_key][:] = self.temporal_buffers[self.desire_key][0, self.temporal_idxs_map[self.desire_key]]
for key in self.numpy_inputs:
if key in inputs and key not in ['desire']:
if key in inputs and key not in [self.desire_key]:
self.numpy_inputs[key][:] = inputs[key]
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.model_runner.vision_input_names}
@@ -156,10 +161,11 @@ class ModelState(ModelStateBase):
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
desired_curvature = get_curvature_from_output(model_output, v_ego, lat_action_t, self.mlsim)
if v_ego > self.MIN_LAT_CONTROL_SPEED:
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
else:
desired_curvature = prev_action.desiredCurvature
if self.generation is not None and self.generation >= 10: # smooth curvature for post FOF models
if v_ego > self.MIN_LAT_CONTROL_SPEED:
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
else:
desired_curvature = prev_action.desiredCurvature
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),desiredAcceleration=float(desired_accel), shouldStop=bool(should_stop))
@@ -202,7 +208,7 @@ def main(demo=False):
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
# messaging
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
publish_state = PublishState()
@@ -306,7 +312,7 @@ def main(demo=False):
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.model_runner.vision_input_names}
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.model_runner.vision_input_names}
inputs:dict[str, np.ndarray] = {
'desire': vec_desire,
model.desire_key: vec_desire,
'traffic_convention': traffic_convention,
}
@@ -322,6 +328,7 @@ def main(demo=False):
modelv2_send = messaging.new_message('modelV2')
drivingdata_send = messaging.new_message('drivingModelData')
posenet_send = messaging.new_message('cameraOdometry')
mdv2sp_send = messaging.new_message('modelDataV2SP')
action = model.get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
prev_action = action
@@ -336,6 +343,7 @@ def main(demo=False):
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_controller.turn_direction
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
@@ -343,6 +351,7 @@ def main(demo=False):
pm.send('modelV2', modelv2_send)
pm.send('drivingModelData', drivingdata_send)
pm.send('cameraOdometry', posenet_send)
pm.send('modelDataV2SP', mdv2sp_send)
last_vipc_frame_id = meta_main.frame_id

View File

@@ -8,12 +8,16 @@ import openpilot.sunnypilot.modeld_v2.modeld as modeld_module
ModelState = modeld_module.ModelState
# These are the shapes extracted/loaded from the model onnx
SHAPE_MODE_PARAMS = [
({'desire': (1, 25, 8), 'features_buffer': (1, 25, 512), 'prev_desired_curv': (1, 25, 1)}, 'split'),
({'desire': (1, 25, 8), 'features_buffer': (1, 24, 512), 'prev_desired_curv': (1, 25, 1)}, '20hz'),
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1)}, 'non20hz'),
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "nav_features": (1, 256), "nav_instructions": (1, 150)}, 'non20hz'), # Optimus Prime
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "lat_planner_state": (1, 4),}, 'non20hz'), # farmville
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "lateral_control_params": (1, 2), "prev_desired_curv": (1, 100, 1)}, 'non20hz'), # wd40
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1), "lateral_control_params": (1, 2),}, 'non20hz'), # NTS
({'desire': (1, 25, 8), 'features_buffer': (1, 24, 512)}, '20hz'), # NPR
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1), "lateral_control_params": (1, 2),}, 'non20hz'), # NTS
({'desire': (1, 25, 8), 'features_buffer': (1, 25, 512)}, 'split'), # Steam Powered v2
({'desire_pulse': (1, 25, 8), 'features_buffer': (1, 25, 512)}, 'split'), # desire rename
]
@@ -95,9 +99,7 @@ def get_expected_indices(shape, constants, mode, key=None):
idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
return idxs
elif mode == 'non20hz':
if key and shape[1] == constants.FULL_HISTORY_BUFFER_LEN:
return np.arange(constants.FULL_HISTORY_BUFFER_LEN)
return None
return np.arange(shape[1])
return None
@@ -108,6 +110,8 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
for key in shapes:
buf = state.temporal_buffers.get(key, None)
idxs = state.temporal_idxs_map.get(key, None)
if buf is None:
continue # not all shapes are 3D, and the non-3D ones are not buffered
# Buffer shape logic
if mode == 'split':
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
@@ -116,10 +120,7 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
expected_idxs = get_expected_indices(shapes[key], constants, '20hz', key)
elif mode == 'non20hz':
if key == 'features_buffer':
expected_shape = (1, shapes[key][1]*4, shapes[key][2])
else:
expected_shape = (1, shapes[key][1], shapes[key][2])
expected_shape = (1, shapes[key][1], shapes[key][2])
expected_idxs = get_expected_indices(shapes[key], constants, 'non20hz', key)
assert buf is not None, f"{key}: buffer not found"
@@ -130,10 +131,10 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
assert idxs is None or idxs.size == 0, f"{key}: buffer idxs should be None or empty"
def legacy_buffer_update(buf, new_val, mode, key, constants, idxs):
def legacy_buffer_update(buf, new_val, mode, key, constants, idxs, input_shape, prev_desire=None):
# This is what we compare the new dynamic logic to, to ensure it does the same thing
if mode == 'split':
if key == 'desire':
if key == 'desire' or key.startswith('desire'):
buf[0,:-1] = buf[0,1:]
buf[0,-1] = new_val
return buf.reshape((1, constants.INPUT_HISTORY_BUFFER_LEN, constants.TEMPORAL_SKIP, -1)).max(axis=2)
@@ -173,15 +174,22 @@ def legacy_buffer_update(buf, new_val, mode, key, constants, idxs):
return legacy_buf[idxs]
elif mode == 'non20hz':
if key == 'desire':
length = new_val.shape[0]
buf[0,:-1,:length] = buf[0,1:,:length]
buf[0,-1,:length] = new_val[:length]
desire_len = constants.DESIRE_LEN
if prev_desire is None:
prev_desire = np.zeros(desire_len, dtype=np.float32)
# Set first element to zero
new_val = new_val.copy()
new_val[0] = 0
# Shift buffer by desire len
buf[0][:-desire_len] = buf[0][desire_len:]
# Only insert new desire if rising edge
buf[0][-desire_len:] = np.where(new_val - prev_desire > 0.99, new_val, 0)
prev_desire[:] = new_val
return buf[0]
elif key == 'features_buffer':
feature_len = new_val.shape[0]
buf[0,:-1,:feature_len] = buf[0,1:,:feature_len]
buf[0,-1,:feature_len] = new_val[:feature_len]
return buf[0]
buf[0, :-1] = buf[0, 1:]
buf[0, -1] = new_val
return buf[0, -input_shape[1]:] # (99, 512)
elif key == 'prev_desired_curv':
length = new_val.shape[0]
buf[0,:-length,0] = buf[0,length:,0]
@@ -191,32 +199,18 @@ def legacy_buffer_update(buf, new_val, mode, key, constants, idxs):
def dynamic_buffer_update(state, key, new_val, mode):
if key == 'desire':
state.temporal_buffers['desire'][0,:-1] = state.temporal_buffers['desire'][0,1:]
state.temporal_buffers['desire'][0,-1] = new_val
if state.temporal_buffers['desire'].shape[1] > state.numpy_inputs['desire'].shape[1]:
skip = state.temporal_buffers['desire'].shape[1] // state.numpy_inputs['desire'].shape[1]
return state.temporal_buffers['desire'][0].reshape(
state.numpy_inputs['desire'].shape[0], state.numpy_inputs['desire'].shape[1], skip, -1
).max(axis=2)
else:
return state.temporal_buffers['desire'][0, state.temporal_idxs_map['desire']]
inputs = {'desire': np.zeros((1, state.constants.DESIRE_LEN), dtype=np.float32)}
for k, tb in state.temporal_buffers.items():
if k in state.temporal_idxs_map:
continue
buf_len = tb.shape[1]
if k in state.numpy_inputs:
out_len = state.numpy_inputs[k].shape[1]
if out_len <= buf_len:
state.temporal_idxs_map[k] = np.arange(buf_len)[-out_len:]
else:
state.temporal_idxs_map[k] = np.arange(buf_len)
else:
state.temporal_idxs_map[k] = np.arange(buf_len)
if key == 'desire' or key.startswith('desire'):
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
for k, v in state.model_runner.input_shapes.items() if k != key}
inputs[key] = new_val.copy()
# ModelState.run expects desire as a pulse, so we zero the first element.
inputs[key][0] = 0
state.run({}, {}, inputs, prepare_only=False)
return state.numpy_inputs[key]
if key == 'features_buffer':
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
for k, v in state.model_runner.input_shapes.items() if k != 'features_buffer'}
def run_model_stub():
return {
'hidden_state': np.asarray(new_val, dtype=np.float32).reshape(1, -1),
@@ -226,6 +220,8 @@ def dynamic_buffer_update(state, key, new_val, mode):
return state.numpy_inputs['features_buffer'][0]
if key == 'prev_desired_curv':
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
for k, v in state.model_runner.input_shapes.items() if k != 'prev_desired_curv'}
def run_model_stub():
return {
'hidden_state': np.zeros((1, state.constants.FEATURE_LEN), dtype=np.float32),
@@ -241,16 +237,27 @@ def dynamic_buffer_update(state, key, new_val, mode):
@pytest.mark.parametrize("key", ["desire", "features_buffer", "prev_desired_curv"])
def test_buffer_update_equivalence(shapes, mode, key, apply_patches):
state = ModelState(None)
if key == "desire":
desire_keys = [k for k in shapes.keys() if k.startswith('desire')]
if desire_keys:
actual_key = desire_keys[0] # Use the first (and likely only) desire key
else:
actual_key = key
if actual_key not in state.numpy_inputs:
pytest.skip()
constants = DummyModelRunner(shapes).constants
buf = state.temporal_buffers.get(key, None)
idxs = state.temporal_idxs_map.get(key, None)
input_shape = shapes[key]
buf = state.temporal_buffers.get(actual_key, None)
idxs = state.temporal_idxs_map.get(actual_key, None)
input_shape = shapes[actual_key]
prev_desire = np.zeros(constants.DESIRE_LEN, dtype=np.float32) if key == 'desire' else None
for step in range(20): # multiple steps to ensure history is built up
new_val = np.full((input_shape[2],), step, dtype=np.float32)
expected = legacy_buffer_update(buf, new_val, mode, key, constants, idxs)
actual = dynamic_buffer_update(state, key, new_val, mode)
# Model returns the reduced numpy_inputs history, compare the last n entries so the test is checking the same slices.
expected = legacy_buffer_update(buf, new_val, mode, actual_key, constants, idxs, input_shape, prev_desire)
actual = dynamic_buffer_update(state, actual_key, new_val, mode)
if expected is not None and actual is not None and expected.shape != actual.shape:
if expected.ndim == 2 and actual.ndim == 2 and expected.shape[1] == actual.shape[1]:
expected = expected[-actual.shape[0]:]
assert np.allclose(actual, expected), f"{mode} {key}: dynamic buffer update does not match legacy logic"
assert np.allclose(actual, expected), f"{mode} {actual_key}: dynamic buffer update does not match legacy logic"

View File

@@ -8,6 +8,7 @@ See the LICENSE.md file in the root directory for more details.
import time
import requests
from requests.exceptions import (SSLError, RequestException, HTTPError)
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from sunnypilot.models.helpers import is_bundle_version_compatible
@@ -122,19 +123,36 @@ class ModelFetcher:
self.model_cache = ModelCache(params)
self.model_parser = ModelParser()
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle]:
"""Fetches fresh model data from remote and updates cache"""
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle] | None:
"""Fetches fresh model data from remote and updates cache.
Returns None on transport errors. Raises on 404 and other fatal HTTP errors.
"""
try:
response = requests.get(self.MODEL_URL, timeout=10)
response.raise_for_status()
json_data = response.json()
# Explicitly handle 404 differently
if response.status_code == 404:
cloudlog.error(f"Models URL returned 404 Not Found: {self.MODEL_URL}")
raise HTTPError(f"404 Not Found: {self.MODEL_URL}", response=response)
# Raise for any other 4xx/5xx
response.raise_for_status()
json_data = response.json()
self.model_cache.set(json_data)
cloudlog.debug("Successfully updated models cache")
return self.model_parser.parse_models(json_data)
except Exception:
cloudlog.exception("Error fetching models")
raise
except ConnectionError as e:
cloudlog.warning(f"DNS/connection error while fetching models: {e}")
except SSLError as e:
cloudlog.warning(f"SSL error while fetching models: {e}")
except RequestException as e:
cloudlog.warning(f"Request transport error while fetching models: {e}")
except Exception as e:
cloudlog.exception(f"Unexpected error fetching models: {e}")
return None
def get_available_bundles(self) -> list[custom.ModelManagerSP.ModelBundle]:
"""Gets the list of available models, with smart cache handling"""
@@ -144,12 +162,12 @@ class ModelFetcher:
cloudlog.debug("Using valid cached models data")
return self.model_parser.parse_models(cached_data)
try:
return self._fetch_and_cache_models()
except Exception:
if not cached_data:
cloudlog.exception("Failed to fetch fresh data and no cache available")
raise
fetched_bundles = self._fetch_and_cache_models()
if fetched_bundles is not None:
return fetched_bundles
if not cached_data:
cloudlog.warning("Failed to fetch fresh data and no cache available")
cloudlog.warning("Failed to fetch fresh data. Using expired cache as fallback")
return self.model_parser.parse_models(cached_data)

View File

@@ -19,7 +19,7 @@ from openpilot.system.hardware.hw import Paths
from pathlib import Path
# see the README.md for more details on the model selector versioning
CURRENT_SELECTOR_VERSION = 9
CURRENT_SELECTOR_VERSION = 10
REQUIRED_MIN_SELECTOR_VERSION = 9
USE_ONNX = os.getenv('USE_ONNX', PC)
@@ -185,3 +185,27 @@ def load_meta_constants(model_metadata):
meta = MetaTombRaider
return meta
# The following method(s) are modeld helper methods
def plan_x_idxs_helper(constants, plan, model_output) -> list[float]:
# times at X_IDXS according to plan.
LINE_T_IDXS = [np.nan] * constants.IDX_N
LINE_T_IDXS[0] = 0.0
plan_x = model_output['plan'][0, :, plan.POSITION][:, 0].tolist()
for xidx in range(1, constants.IDX_N):
tidx = 0
# increment tidx until we find an element that's further away than the current xidx
while tidx < constants.IDX_N - 1 and plan_x[tidx + 1] < constants.X_IDXS[xidx]:
tidx += 1
if tidx == constants.IDX_N - 1:
# if the plan doesn't extend far enough, set plan_t to the max value (10s), then break
LINE_T_IDXS[xidx] = constants.T_IDXS[constants.IDX_N - 1]
break
# interpolate to find `t` for the current xidx
current_x_val = plan_x[tidx]
next_x_val = plan_x[tidx + 1]
p = (constants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) if abs(
next_x_val - current_x_val) > 1e-9 else float('nan')
LINE_T_IDXS[xidx] = p * constants.T_IDXS[tidx + 1] + (1 - p) * constants.T_IDXS[tidx]
return LINE_T_IDXS

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