Compare commits

...

411 Commits

Author SHA1 Message Date
Jason Wen 4c046bc3ce Merge branch 'ui-models-current-top' into hkg-angle-steering-2025-software
# Conflicts:
#	system/ui/sunnypilot/widgets/tree_dialog.py
2025-12-07 01:23:31 -05:00
Jason Wen 35b78c3d0b ui: improve TreeOptionDialog node selection and item handling 2025-12-07 01:23:07 -05:00
Jason Wen 1807b193fa ui: preserve and update current_ref in TreeOptionDialog init (#1562)
* ui: preserve and update `current_ref` in `TreeOptionDialog` init

* do it in init instead
2025-12-07 00:58:33 -05:00
Jason Wen 5188f31531 Merge branch 'ui-tree-current-fix' into hkg-angle-steering-2025-software 2025-12-07 00:56:23 -05:00
Jason Wen ae5fe50c0d do it in init instead 2025-12-07 00:50:38 -05:00
Jason Wen 883e282d0a Merge branch 'ui-tree-current-fix' into hkg-angle-steering-2025-software 2025-12-07 00:39:04 -05:00
Jason Wen 0837757107 ui: preserve and update current_ref in TreeOptionDialog init 2025-12-07 00:38:40 -05:00
Jason Wen 9e6a13a4e3 Merge branch 'software' into hkg-angle-steering-2025-software 2025-12-07 00:26:52 -05:00
Jason Wen beca4a184c need to show current branch 2025-12-07 00:26:47 -05:00
Jason Wen 2725a8bf1c Merge branch 'software' into hkg-angle-steering-2025-software 2025-12-07 00:18:27 -05:00
Jason Wen 35dc7d661e Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-12-07 00:11:54 -05:00
Jason Wen 82d875e026 add more 2025-12-07 00:11:13 -05:00
Jason Wen 9693fb458d move it! 2025-12-06 14:35:12 -05:00
Jason Wen b82d8bf175 move 2025-12-06 14:27:47 -05:00
Jason Wen d7202e68a4 easier 2025-12-05 23:12:59 -05:00
Jason Wen 9c88e294ae Merge branch 'master' into software 2025-12-05 22:59:27 -05:00
Nayan 5007437969 ui: enforce fixed dimensions for ToggleSP (#1558)
fix rect

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-05 22:58:53 -05:00
Nayan 93c1c713a9 ui: toggle should only toggle toggle - don't toggle description with toggle. Yeah, that! (#1559)
@sunnyhaibin, please!

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-05 22:52:41 -05:00
Jason Wen d382cd08e5 ui: adjust Sponsor and Pairing button behaviors in sunnylink panel (#1557)
ui: adjust sponsor and pairing button behaviors in sunnyLink panel
2025-12-05 16:49:29 -05:00
nayan 7828ee9974 nayan, NO !! 2025-12-05 08:12:20 -05:00
nayan e83da5f38b sunny, NO 2025-12-05 08:10:48 -05:00
nayan 4f886b8998 Merge remote-tracking branch 'origin/software' into software 2025-12-05 08:08:38 -05:00
nayan d418a1417c handle toggle confirmation 2025-12-05 08:05:25 -05:00
James Vecellio-Grant 57eca29970 ui: Models panel (#1495)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* sp raylib preview

* fix callback

* fix ui preview

* dialog txt

* compare vs what used to be done before InputDialog

* introducing ui_state_sp for py

* raylib: input dialog

* raylib: SP Toggles

* raylib: SP Panels

* raylib: Option Control

* init

* param to control stock vs sp ui

* better

* tree dialog, progress bar widget cool stuff

* merge origin raylib toggles

* tree dialog

* less trees for the planet

* the heck

* add ui_update callback

* save the trees we got icons

* Update process.py

* yesssssssssss

* utilize ON_COLOR constant form op system

* Revert "add ui_update callback"

This reverts commit 4da32cc009.

* # Conflicts:
#	system/ui/sunnypilot/widgets/list_view.py
#	system/ui/sunnypilot/widgets/option_control.py

* Merge remote-tracking branch 'openpilot/master' into nov-19-sync

* ui: `GuiApplicationExt`

* add to readme

* scroller_tici :)

* use gui_app.sunnypilot_ui()

* # Conflicts:
#	selfdrive/ui/layouts/main.py
#	selfdrive/ui/sunnypilot/layouts/settings/cruise.py
#	selfdrive/ui/sunnypilot/layouts/settings/display.py
#	selfdrive/ui/sunnypilot/layouts/settings/models.py
#	selfdrive/ui/sunnypilot/layouts/settings/navigation.py
#	selfdrive/ui/sunnypilot/layouts/settings/osm.py
#	selfdrive/ui/sunnypilot/layouts/settings/steering.py
#	selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py
#	selfdrive/ui/sunnypilot/layouts/settings/trips.py
#	selfdrive/ui/sunnypilot/layouts/settings/vehicle.py
#	selfdrive/ui/sunnypilot/layouts/settings/visuals.py
#	system/ui/sunnypilot/widgets/option_control.py

* init value

* Remove 'sunnypilot_ui' 

Removed 'sunnypilot_ui' parameter from params_keys.h

* Update raylib_screenshots.py

Removed the parameter setting for 'sunnypilot_ui' in the test.

* easier to see

* Update progress_bar.py

* try something

* adjust placement

* more simple

* smoothing updating components

* ui: fuzzy search helper

* ui_state_sp

* description!

* fuzzy af searching

* better tree. fully dynamic and stuff

* rm

* rearrange

* license

* idk how maybe the merge

* more indent

* more indent

* cleanup

* temporaily revert ui_state_sp

* only show if fav_param is used in the call

* conditional for mypy

* mypy

* conditional for mypy

* str concatenation to reduce line len

* level

* sunny's new x,y makes this even easier!

* refreshing half a second seems legit.

* loathing loathing, unadulterated loathing, i loathe it all

* loathing loathing, unadulterated loathing, i loathe it all

* # Conflicts:
#	system/ui/sunnypilot/lib/styles.py
#	system/ui/sunnypilot/widgets/tree_dialog.py

* Update models.py

* Change BUTTON_DISABLED_BG_COLOR to a lighter shade

* I think this is it

* Update tree_dialog.py

* Update models.py

* Update models.py

* oops, angry f string

* bool

* Update ui_state.py

* Update ui_state.py

* wtf where'd the end quote go lol

* some

* more

* quick test

* Revert "quick test"

This reverts commit fb97afa54c.

* try this out

* use sp's

* cap

* how weird?

---------

Co-authored-by: nayan <nayan8teen@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-05 03:14:53 -05:00
Jason Wen 4b00b7ee59 some 2025-12-05 01:34:44 -05:00
Jason Wen fae2950581 Merge branch 'master' into software 2025-12-05 01:03:05 -05:00
Nayan 1d9bda65fe ui: sunnylink panel (#1494)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* sp raylib preview

* fix callback

* fix ui preview

* sunnylink state

* introducing ui_state_sp for py

* poll from ui_state_sp

* cloudlog & ruff

* param to control stock vs sp ui

* better

* better padding

* this

* listitem -> listitemsp

* add show_description method

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

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* fetch only when connected to network

* sponsor & pairing qr

* init panel elements

* backup & restore

* fruit loops

* update

* enable, disable, enable, disable

* handle layout updates

* not needed

* change it up

* better

* scroller -> scroller_tici

* optimizations

* remove Params

* fix button disablement

* ui_state_sp changes

* keep enabled

* add header text

* dad jokes?

* no

* lint? Lint!

* final touches

* add sp font

* use sp font

* some

* ui: add right-aligned value display support in `ListItem` (in another pr)

* display sunnylink device id

* display sunnylink device id and sponsor tiers

* ui: add right-aligned value display support in `ListItemSP`

* lint

* styles

* lint

* ui: introduce customizable value colors for `ButtonActionSP` and `ListViewSP`

* support

* convert to str

* disable if paired

* colored sponsors

* hide and disable pairing button if paired

* texts

* ui: sunnypilot sponsor tier color mapping

* lint

* dongle id for ui preview

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2025-12-05 01:01:55 -05:00
Jason Wen b5b170b65a ui: sunnypilot sponsor tier color mapping (#1556)
* ui: sunnypilot sponsor tier color mapping

* lint
2025-12-05 00:30:48 -05:00
Jason Wen c6818bd07f ui: customizable value colors for ButtonActionSP and ListViewSP (#1555)
ui: introduce customizable value colors for `ButtonActionSP` and `ListViewSP`
2025-12-04 21:41:58 -05:00
Jason Wen d75d80b885 ui: add right-aligned value display support in ListItemSP (#1554)
* ui: add right-aligned value display support in `ListItemSP`

* lint

* styles
2025-12-04 17:53:33 -05:00
Nayan 1f967668a5 ui: add sunnypilot font (#1552)
add sp font
2025-12-04 01:51:08 -05:00
James Vecellio-Grant fad2c3c1a8 Merge branch 'master' into software 2025-12-03 06:04:46 -08:00
Jason Wen bea05d4624 ui: add pressed state and visual feedback for search button in TreeOptionDialog (#1547)
* ui: add pressed state and visual feedback for search button in `TreeDialog`

* less
2025-12-03 02:29:33 -05:00
Jason Wen b9c54e07fb Revert "ci: disable macos builds" (#1529)
Revert "ci: disable macos builds (#1514)"

This reverts commit 42b2e1534b.
2025-12-03 01:18:00 -05:00
James Vecellio-Grant 64c6944ee9 Merge branch 'master' into software 2025-12-02 22:13:43 -08:00
James Vecellio-Grant c560ac43aa ui: NetworkUISP (#1535)
* ui: network panel

* refactor adding scan button without overriding upstream layout

* trigger immediate network refresh instead of waiting

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-03 01:08:38 -05:00
David a83c64ffbd ui: fix sidebar scroll in UI screenshots (#1519)
* fix: delay between scroll clicks; add larger delay; fix cruise button

* scroll more for cruise

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-03 00:56:19 -05:00
Nayan f08dfa1c14 ui: sunnylink client-side implementation (#1488)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* sp raylib preview

* fix callback

* fix ui preview

* sunnylink state

* introducing ui_state_sp for py

* poll from ui_state_sp

* cloudlog & ruff

* param to control stock vs sp ui

* better

* better padding

* this

* listitem -> listitemsp

* add show_description method

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

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* fetch only when connected to network

* final

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-03 00:20:40 -05:00
James Vecellio-Grant 3c5841ff02 ui: vehicle brand settings (#1509)
* commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* sunnypilot: remove Qt

* cabana: revert to stock Qt

* commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9

* commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e

* commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e

* sum more

* bump opendbc

* not yet

* should've been symlink'ed

* raylib says wut

* quiet mode back

* more fixes

* no more

* too extra red diff on the side

* need to bring this back

* too extra

* let's update docs here

* Revert "let's update docs here"

This reverts commit 51fe03cd51.

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* multi-button

* Lint

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* dialog txt

* compare vs what used to be done before InputDialog

* introducing ui_state_sp for py

* param to control stock vs sp ui

* better

* add ui_update callback

* better padding

* this

* support for next line multi-button

* uhh

* disabled colors

* listitem -> listitemsp

* listitem -> listitemsp

* Revert "add ui_update callback"

This reverts commit 4da32cc009.

* add show_description method

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

* scroller -> scroller_tici

* scroller -> scroller_tici

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* lint

* no fancy toggles :(

* match them

* mici scroller - no touchy

* no

* more

* size adjustments

* fix scroller. yay

* ui vehicle panel: platform selector

* some brands

* platform selector

* bruh

* ui_state_sp

* o

* is_offroad() and format

* huh

* use in toggles panel

* ugh. no

* better & animated

* lint

* cleanup

* lint. LINT

* slight

* rebase

* no more

* rebae

* vic

* v

* # Conflicts:
#	system/ui/sunnypilot/lib/styles.py
#	system/ui/sunnypilot/widgets/helpers/fuzzy_search.py

* loathing loathing, unadulterated loathing, i loathe it all

* more changes!

* Update styles.py

* set visibility

* Update hyundai.py

* add padding

* use symlink on sp side

* use make from json and show all actual makes

* all done!

* Revert "all done!"

This reverts commit 595c45f057.

* reimpl onroad/offroad confirmation

* use global offroad directly

* ui: highlight on pressed, and less indent

* inherit MultiOptionDialog main buttons

* align top level folders to the edge properly

* lint

* lint

* handle folder presses too

* ui: overridable title and subtitle for `TreeOptionDialog`

* override TreeOptionDialog title and subtitle

* lint

* more

* ui: magnifying glass icon and new search bar style in `TreeDialog`

* cleanup

* ui: recreate search dialog with the latest query

* make model year but display as platform

* move into settings directory

* move into dir

* sync

* equality

* use singleton directly

* also use singleton directly

* inherit from base class

* include all brands

* added refresh

* always assume it's subaru

* slight

* split get brand

* hyundai changes

* tesla changes

* do not allow while offroad

* fix

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: nayan <nayan8teen@gmail.com>
2025-12-02 14:44:50 -05:00
Jason Wen 138d637bbd ui: couple Platform Selector refresh in Vehicle panel state updates (#1545) 2025-12-02 13:34:35 -05:00
Jason Wen eda189c564 ui: refine height calculations and action placement in ListViewSP (#1543)
* ui: refine height calculations and action placement in `ListViewSP`

* relative
2025-12-02 02:29:29 -05:00
James Vecellio-Grant 04504d47f3 ui: Platform Selector (#1508)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* dialog txt

* compare vs what used to be done before InputDialog

* introducing ui_state_sp for py

* param to control stock vs sp ui

* better

* add ui_update callback

* better padding

* this

* listitem -> listitemsp

* Revert "add ui_update callback"

This reverts commit 4da32cc009.

* add show_description method

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

* scroller -> scroller_tici

* scroller -> scroller_tici

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* lint

* no fancy toggles :(

* match them

* mici scroller - no touchy

* no

* more

* size adjustments

* fix scroller. yay

* ui vehicle panel: platform selector

* platform selector

* bruh

* ui_state_sp

* huh

* rebase

* rebae

* vic

* # Conflicts:
#	system/ui/sunnypilot/lib/styles.py
#	system/ui/sunnypilot/widgets/helpers/fuzzy_search.py

* loathing loathing, unadulterated loathing, i loathe it all

* more changes!

* Update styles.py

* add padding

* use symlink on sp side

* use make from json and show all actual makes

* all done!

* Revert "all done!"

This reverts commit 595c45f057.

* reimpl onroad/offroad confirmation

* use global offroad directly

* ui: highlight on pressed, and less indent

* inherit MultiOptionDialog main buttons

* align top level folders to the edge properly

* lint

* lint

* handle folder presses too

* ui: overridable title and subtitle for `TreeOptionDialog`

* override TreeOptionDialog title and subtitle

* lint

* more

* ui: magnifying glass icon and new search bar style in `TreeDialog`

* cleanup

* ui: recreate search dialog with the latest query

* make model year but display as platform

* move into settings directory

* move into dir

---------

Co-authored-by: nayan <nayan8teen@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-02 00:02:33 -05:00
Jason Wen 7ba9876fa4 ui: recreate search dialog with the latest query (#1542) 2025-12-01 23:44:01 -05:00
Jason Wen f312c011e8 ui: magnifying glass icon and new search bar style in TreeDialog (#1541)
* ui: magnifying glass icon and new search bar style in `TreeDialog`

* cleanup
2025-12-01 23:33:26 -05:00
Jason Wen 9ee965d2e0 ui: overridable title and subtitle for search query in TreeOptionDialog (#1538)
* ui: overridable title and subtitle for `TreeOptionDialog`

* lint
2025-12-01 21:35:28 -05:00
James Vecellio-Grant 859745ea86 ui: tree dialog improvements (#1537)
* ui: highlight on pressed, and less indent

* inherit MultiOptionDialog main buttons

* align top level folders to the edge properly

* lint

* handle folder presses too

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-12-01 17:31:37 -05:00
James Vecellio-Grant 79fa8803b6 ui: add padding above tree dialog buttons (#1533)
add padding
2025-12-01 09:22:49 -05:00
discountchubbs 9e752a67d7 hide on advanced controls 2025-11-29 22:39:11 -08:00
discountchubbs 2005875548 Merge remote-tracking branch 'origin/master' into software
# Conflicts:
#	system/ui/sunnypilot/lib/styles.py
#	system/ui/sunnypilot/widgets/helpers/star_icon.py
#	system/ui/sunnypilot/widgets/tree_dialog.py
2025-11-29 22:34:37 -08:00
James Vecellio-Grant c68c914444 ui: tree dialog widget (#1493)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* sp raylib preview

* fix callback

* fix ui preview

* dialog txt

* compare vs what used to be done before InputDialog

* merge origin raylib toggles

* tree dialog

* less trees for the planet

* the heck

* save the trees we got icons

* Update process.py

* Remove 'sunnypilot_ui' 

Removed 'sunnypilot_ui' parameter from params_keys.h

* Update raylib_screenshots.py

Removed the parameter setting for 'sunnypilot_ui' in the test.

* ui: fuzzy search helper

* better tree. fully dynamic and stuff

* rm

* more indent

* only show if fav_param is used in the call

* conditional for mypy

* sunny's new x,y makes this even easier!

* loathing loathing, unadulterated loathing, i loathe it all

* more changes!

* more changes!

* Update BUTTON_DISABLED_BG_COLOR to a lighter shade

* Update tree_dialog.py

* final

---------

Co-authored-by: nayan <nayan8teen@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-30 01:21:15 -05:00
Nayan 0f7498e214 ui: UIStateSP (#1489)
* introducing ui_state_sp for py

* param to control stock vs sp ui

* better

* add ui_update callback

* Revert "add ui_update callback"

This reverts commit 4da32cc009.

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* add cp_sp to ui_state_sp

* fix ui crash

* update params

* more

* slightly more

* add directly to the list

* nah

* move around

* rename

* call before param time tracker is updated

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-30 00:54:03 -05:00
discountchubbs c57146922f Merge remote-tracking branch 'origin/master' into software
# Conflicts:
#	system/ui/sunnypilot/lib/styles.py
#	system/ui/sunnypilot/widgets/helpers/fuzzy_search.py
#	system/ui/sunnypilot/widgets/input_dialog.py
2025-11-29 07:53:41 -08:00
James Vecellio-Grant ebca4fc901 ui: fuzzy search helper (#1507)
* ui: fuzzy search helper

* final

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-29 04:19:48 -05:00
DevTekVE c39b2dad94 Merge branch 'master' into hkg-angle-steering-2025 2025-11-29 10:12:31 +01:00
Jason Wen 023b842e3c controlsd_ext: use time.monotonic to check params intervals (#1481)
* controlsd_ext: use time.monotonic to check params intervals

* test

* Revert "test"

This reverts commit 151ac3bc68.
2025-11-29 03:58:58 -05:00
DevTekVE a4454721ea sunnylink: dynamic param metadata (#1522)
* feat(params): add support for parameter metadata retrieval

- Introduced `getKeyMetadata` method for accessing metadata associated with params.
- Enhanced `getParamsAllKeysV1` to include metadata parsing and optional dynamic enum generation.
- Extended unit tests to verify metadata parsing, enum mapping, and edge cases.

* Revert "feat(params): add support for parameter metadata retrieval"

This reverts commit 865b695ff9.

* update: integrate params metadata management and unit tests

- Added `update_params_metadata.py` to manage and update parameters metadata.
- Enhanced `getParamsAllKeysV1` to include metadata for params.
- Created comprehensive tests (`test_params_metadata.py`, `test_params_sync.py`) to validate metadata integrity and params consistency.

* update: improve params metadata readability and enhance enums

- Renamed params titles for clarity and consistency.
- Added enum options and mappings to selected params for better usability.

* update: enhance params metadata with improved enum structures

- Replaced plain enum lists with detailed objects (`value`, `label`) for clarity.
- Standardized parameter options for consistency across metadata.

* update: add validation constraints to params metadata

- Introduced `min`, `max`, and `step` attributes for improved parameter range validation.
- Enhances user input handling and ensures consistency in metadata.

* lint

* more lint stuff and permissions

* does this suffice?

* more lint

* update: refine params type hinting and remove unused shebang

- Adjusted type annotation in `params_dict` for better compatibility.
- Removed unnecessary shebang from `test_params_metadata.py`.

* update: expand test coverage for params metadata validation

- Added detailed test cases to ensure metadata consistency (`options`, `constraints`, `titles`).
- Validates API response alignment with `params_metadata.json`.

* the finals

* names

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-29 03:52:01 -05:00
James Vecellio-Grant 3e29a0ccfe ui: ProgressBarAction (#1492)
* raylib: progress bar

* freaking test dir

* easier to see

* smoother updating

* final

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-29 03:30:18 -05:00
James Vecellio-Grant bee820f8ed ui: InputDialogSP (#1484)
* dialog txt

* compare vs what used to be done before InputDialog

* rm

* final

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-29 03:23:03 -05:00
Nayan 99ed90d459 ui: Option Control (#1479)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* Option Control

* Need this

* better padding

* this

* listitem -> listitemsp

* add show_description method

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

* simplify

* I. SAID. SIMPLIFY.

* AAARGGGGGG.....

* option control value fix

* forgot about the setter

* scroller -> scroller_tici

* scroller -> scroller_tici

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* ugh. no

* new style. old style.

* lint

* rename

* old but gold <3

---------

Co-authored-by: discountchubbs <alexgrant990@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2025-11-29 03:10:59 -05:00
Jason Wen aa5a7ecb31 ci: no parallelism for LiveLocationKalman compile (#1531)
* ci: no parallelism for locationd compile

* just LLK

* bump to 2
2025-11-29 02:24:12 -05:00
Jason Wen 9595a6f246 ui Qt: remove leftover files (#1530) 2025-11-28 20:46:09 -05:00
DevTekVE 1d6d0fb85c ci: prevent build OOM by throttling locationd concurrency (#1527)
* ci: add conditional swap creation to prebuilt workflow

- Dynamically creates an 8GB swap file on systems with less than 8GB RAM.
- Ensures builds complete reliably on low-memory environments.
- Introduced cleanup step to remove swap after workflow execution.

* Nice save

* clean

* reduce swap size in prebuilt workflow
- Adjusted swap file size from 8GB to 4GB to optimize resource usage.

* remove swap creation from prebuilt workflow
- Simplified workflow by removing dynamic swap file creation and cleanup.
- Adjusted resource management to rely on existing system resources.

* update sunnypilot build workflow to use explicit script calls

- Replaced `op` commands with explicit `/data/openpilot/tools/op.sh` script calls for better reliability and clarity.

* update sunnypilot build workflow to use systemd for process management

- Replaced `/data/openpilot/tools/op.sh` script calls with `systemctl` commands for improved compatibility and reliability.
- Ensures consistent resource management during start and stop operations.

* Splitting a bit the build then?

* split build steps in sunnypilot prebuilt workflow

- Added separate build steps for `modeld`, `modeld_v2`, and `locationd` with descriptive messages.
- Improves logging and clarity during the build process.

* limit locationd parallel processes aiming to help with resource consumption

* typo

* adding op's location d AND bumping to 4 cores for them

* Apply suggestion from @sunnyhaibin

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-28 20:19:30 -05:00
Nayan 4914445415 ui: sunnypilot MultiButtonControl (#1478)
* commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* sunnypilot: remove Qt

* cabana: revert to stock Qt

* commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9

* commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e

* commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e

* sum more

* bump opendbc

* not yet

* should've been symlink'ed

* raylib says wut

* quiet mode back

* more fixes

* no more

* too extra red diff on the side

* need to bring this back

* too extra

* let's update docs here

* Revert "let's update docs here"

This reverts commit 51fe03cd51.

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* multi-button

* Lint

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* better padding

* this

* support for next line multi-button

* uhh

* disabled colors

* listitem -> listitemsp

* listitem -> listitemsp

* add show_description method

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

* scroller -> scroller_tici

* scroller -> scroller_tici

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* use in toggles panel

* ugh. no

* better & animated

* lint

* cleanup

* lint. LINT

* slight

* default no inline ty <3

* style change

* move stuff around

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-28 01:02:31 -05:00
James Vecellio-Grant 3e4374b907 ds 2025-11-26 20:37:16 -08:00
discountchubbs 6fffb2c3f2 search 2025-11-26 20:20:41 -08:00
discountchubbs d71127b2ed # Conflicts:
#	system/ui/sunnypilot/lib/styles.py
#	system/ui/sunnypilot/widgets/tree_dialog.py
2025-11-26 20:19:47 -08:00
discountchubbs 1dae8b2d16 loathing loathing, unadulterated loathing, i loathe it all 2025-11-26 18:16:01 -08:00
discountchubbs f54b10e3f2 loathing loathing, unadulterated loathing, i loathe it all 2025-11-26 17:53:56 -08:00
discountchubbs 38312b519d add 2025-11-26 12:41:12 -08:00
discountchubbs d1f23ad7ea rm 2025-11-26 12:40:38 -08:00
James Vecellio ebc3e5b04c software stuffs 2025-11-26 12:36:26 -08:00
James Vecellio 0c56c7d76d refreshing half a second seems legit. 2025-11-26 09:37:59 -08:00
James Vecellio fdf97f224f sunny's new x,y makes this even easier! 2025-11-26 08:02:48 -08:00
James Vecellio-Grant 2806c9c38b Merge branch 'master' into models-panel-rl 2025-11-26 07:58:54 -08:00
Jason Wen e6b769245c Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	selfdrive/ui/sunnypilot/SConscript
#	selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc
#	selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h
#	selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc
2025-11-25 18:54:02 -05:00
Jason Wen c229eb4f38 ui: reimplement sunnypilot branding with Raylib (#1517)
* ui: sunnypilot branding

* fr??
2025-11-25 18:37:09 -05:00
Jason Wen da35e60101 ui: display mouse coordinates with debug mode (#1512)
* ui: add debug to display mouse coordinates

* cleanup

* use mouse x and y directly

* even less

* try this out

* ui: increase `scroll` delay in UI Preview

* sleep for clicks too

* don't lol

* try 1 secs

* try this out

* wait a sec before doing a screenshot

* nah
2025-11-25 18:10:40 -05:00
Jason Wen 1e95e1cc01 Revert "ui: reduce sidebar scroll in UI Preview" (#1516)
Revert "ui: reduce sidebar `scroll` in UI Preview (#1513)"

This reverts commit ad88c306e9.
2025-11-25 18:04:21 -05:00
Jason Wen ad88c306e9 ui: reduce sidebar scroll in UI Preview (#1513)
* ui: increase `scroll` delay in UI Preview

* sleep for clicks too

* don't lol

* try 1 secs

* try this out

* wait a sec before doing a screenshot

* nah
2025-11-25 17:51:03 -05:00
James Vecellio-Grant 5c126396b9 Merge branch 'master' into models-panel-rl 2025-11-25 14:25:53 -08:00
discountchubbs de912b917e level 2025-11-25 14:21:56 -08:00
discountchubbs cd182c95db str concatenation to reduce line len 2025-11-25 14:20:25 -08:00
discountchubbs 3d752d1f24 conditional for mypy 2025-11-25 14:14:34 -08:00
discountchubbs a6001f2bc9 mypy 2025-11-25 14:14:19 -08:00
discountchubbs 14a22c0736 conditional for mypy 2025-11-25 14:13:17 -08:00
James Vecellio 450f0770b7 Merge remote-tracking branch 'origin/rl-tree-dialog' into models-panel-rl 2025-11-25 14:01:12 -08:00
discountchubbs 8109909e57 only show if fav_param is used in the call 2025-11-25 13:57:48 -08:00
Jason Wen 42b2e1534b ci: disable macos builds (#1514)
* ci: fix macos builds

* Revert "ci: fix macos builds"

This reverts commit 433ca0d7f6.

* disable
2025-11-25 16:56:53 -05:00
James Vecellio 483b14c4e7 temporaily revert ui_state_sp 2025-11-24 18:46:05 -08:00
discountchubbs e5bedd1c89 cleanup 2025-11-24 17:39:13 -08:00
James Vecellio-Grant 1dbd066824 Merge branch 'master' into models-panel-rl 2025-11-24 17:19:48 -08:00
discountchubbs 6b5b686fa5 more indent 2025-11-24 17:16:17 -08:00
James Vecellio-Grant 76bc538ac7 Merge branch 'master' into rl-tree-dialog 2025-11-24 17:15:48 -08:00
James Vecellio 3f6a4606c5 more indent 2025-11-24 17:15:34 -08:00
DevTekVE c53e2134e2 sunnylink: centralize key pair handling in sunnylink registration (#1510)
* refactor(api): centralize key pair handling in SunnyLink registration

- Replaced manual key reading with `BaseApi.get_key_pair` for consistency.
- Simplifies code and improves maintainability of key management.

* cleanup
2025-11-24 23:33:40 +01:00
James Vecellio-Grant 53eb821dc4 Merge branch 'master' into rl-tree-dialog 2025-11-24 11:54:55 -08:00
discountchubbs d8faa768e8 idk how maybe the merge 2025-11-24 11:54:12 -08:00
discountchubbs ebe5378dd0 license 2025-11-24 11:52:04 -08:00
discountchubbs f4530681a3 Merge remote-tracking branch 'origin/master' into models-panel-rl
# Conflicts:
#	selfdrive/ui/layouts/main.py
#	selfdrive/ui/sunnypilot/layouts/settings/cruise.py
#	selfdrive/ui/sunnypilot/layouts/settings/device.py
#	selfdrive/ui/sunnypilot/layouts/settings/display.py
#	selfdrive/ui/sunnypilot/layouts/settings/models.py
#	selfdrive/ui/sunnypilot/layouts/settings/navigation.py
#	selfdrive/ui/sunnypilot/layouts/settings/osm.py
#	selfdrive/ui/sunnypilot/layouts/settings/settings.py
#	selfdrive/ui/sunnypilot/layouts/settings/steering.py
#	selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py
#	selfdrive/ui/sunnypilot/layouts/settings/trips.py
#	selfdrive/ui/sunnypilot/layouts/settings/vehicle.py
#	selfdrive/ui/sunnypilot/layouts/settings/visuals.py
2025-11-24 11:50:41 -08:00
Nayan 844f4cbc74 ui: sunnypilot panels (#1477)
* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* better padding

* this

* listitem -> listitemsp

* add show_description method

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

* scroller -> scroller_tici

* scroller -> scroller_tici

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* lint

* no fancy toggles :(

* match them

* mici scroller - no touchy

* no

* more

* size adjustments

* fix scroller. yay

* more

* loopy loop

* update brand name

* ui preview fixes

* Revert "update brand name"

This reverts commit ad44c32fc7.

* fix sidebar scroller reset

* fix developer click

* more

* more

* try this out

* one more time

* bruh

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-24 14:43:33 -05:00
James Vecellio f64a180973 rearrange 2025-11-24 10:56:42 -08:00
discountchubbs 82e1ebe97e rm 2025-11-24 10:23:35 -08:00
discountchubbs da3ff45bb6 Merge remote-tracking branch 'origin/rl-tree-dialog' into rl-tree-dialog 2025-11-24 10:19:08 -08:00
discountchubbs 41da513fca better tree. fully dynamic and stuff 2025-11-24 10:18:43 -08:00
discountchubbs b2950149fb Merge remote-tracking branch 'origin/input-dialog' into rl-tree-dialog 2025-11-24 10:17:51 -08:00
James Vecellio-Grant a829a1b972 Merge branch 'master' into rl-tree-dialog 2025-11-24 10:16:28 -08:00
discountchubbs 4fb8e4beed Merge remote-tracking branch 'origin/fuzzy-dialog' into rl-tree-dialog 2025-11-24 10:16:20 -08:00
James Vecellio 298b36cec7 fuzzy af searching 2025-11-24 10:15:48 -08:00
Nayan 9edc36ca66 ui: fix scroll panel mouse wheel behavior (#1506)
fix scroller. yay

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-24 11:18:12 -05:00
James Vecellio 4824642838 description! 2025-11-24 07:01:44 -08:00
James Vecellio 872790782d Merge remote-tracking branch 'origin/fuzzy-dialog' into models-panel-rl 2025-11-24 06:39:54 -08:00
James Vecellio-Grant af4f0f8372 Merge branch 'master' into fuzzy-dialog 2025-11-24 06:39:29 -08:00
James Vecellio-Grant 1fcb1bb00f Merge branch 'master' into models-panel-rl 2025-11-23 16:06:48 -08:00
discountchubbs e8ff41b0e8 ui_state_sp 2025-11-23 16:06:23 -08:00
discountchubbs 0521af3f5c Merge remote-tracking branch 'origin/py-ui-state-sp' into models-panel-rl 2025-11-23 16:00:40 -08:00
James Vecellio-Grant 3cd55260d9 modeld: remove SNPE accelerator (#1476)
* commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* sunnypilot: remove Qt

* cabana: revert to stock Qt

* commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9

* commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e

* commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e

* sum more

* bump opendbc

* not yet

* should've been symlink'ed

* raylib says wut

* quiet mode back

* more fixes

* no more

* too extra red diff on the side

* need to bring this back

* too extra

* let's update docs here

* Revert "let's update docs here"

This reverts commit 51fe03cd51.

* del snpe

* reset min for safety catch as we killed snpe

* uhhh

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-23 15:22:23 -05:00
discountchubbs 1d5f0ab282 ui: fuzzy search helper 2025-11-23 11:28:59 -08:00
James Vecellio 8e378ecaf8 smoothing updating components 2025-11-22 20:08:17 -08:00
James Vecellio b0423fdb2a more simple 2025-11-22 13:30:55 -08:00
discountchubbs e1ce48b79a Merge remote-tracking branch 'origin/master' into models-panel-rl
# Conflicts:
#	selfdrive/ui/tests/test_ui/raylib_screenshots.py
#	system/ui/sunnypilot/lib/styles.py
#	system/ui/sunnypilot/widgets/list_view.py
#	system/ui/sunnypilot/widgets/toggle.py
2025-11-22 10:35:44 -08:00
James Vecellio 1bce355885 adjust placement 2025-11-22 10:30:10 -08:00
discountchubbs e9ce266638 try something 2025-11-22 10:22:07 -08:00
discountchubbs c72be372a1 Update progress_bar.py 2025-11-22 09:54:31 -08:00
discountchubbs 5f6ec0ebcf easier to see 2025-11-22 09:51:30 -08:00
James Vecellio-Grant 848290d07e Update raylib_screenshots.py
Removed the parameter setting for 'sunnypilot_ui' in the test.
2025-11-21 21:08:07 -08:00
James Vecellio-Grant 6694928a46 Remove 'sunnypilot_ui'
Removed 'sunnypilot_ui' parameter from params_keys.h
2025-11-21 21:06:57 -08:00
James Vecellio-Grant b3c90ef7b2 Merge branch 'master' into rl-tree-dialog 2025-11-21 21:06:04 -08:00
James Vecellio-Grant bf85781f16 Merge branch 'master' into models-panel-rl 2025-11-21 19:33:50 -08:00
James Vecellio-Grant 924e5a3211 Merge branch 'master' into input-dialog 2025-11-21 19:33:18 -08:00
discountchubbs e88efc82f4 init value 2025-11-21 16:42:08 -08:00
discountchubbs aa74e67330 # Conflicts:
#	selfdrive/ui/layouts/main.py
#	selfdrive/ui/sunnypilot/layouts/settings/cruise.py
#	selfdrive/ui/sunnypilot/layouts/settings/display.py
#	selfdrive/ui/sunnypilot/layouts/settings/models.py
#	selfdrive/ui/sunnypilot/layouts/settings/navigation.py
#	selfdrive/ui/sunnypilot/layouts/settings/osm.py
#	selfdrive/ui/sunnypilot/layouts/settings/steering.py
#	selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py
#	selfdrive/ui/sunnypilot/layouts/settings/trips.py
#	selfdrive/ui/sunnypilot/layouts/settings/vehicle.py
#	selfdrive/ui/sunnypilot/layouts/settings/visuals.py
#	system/ui/sunnypilot/widgets/option_control.py
2025-11-21 16:25:58 -08:00
James Vecellio-Grant a4ee4ba76d Merge branch 'master' into input-dialog 2025-11-21 16:24:31 -08:00
nayan 45c853c87a Merge remote-tracking branch 'origin/ui-gui-app-ext' into py-ui-state-sp 2025-11-21 17:57:35 -05:00
nayan e8ab9d812d use gui_app.sunnypilot_ui() 2025-11-21 17:57:06 -05:00
Jason Wen 0db8722221 Merge branch 'master' into ui-gui-app-ext 2025-11-21 17:24:14 -05:00
discountchubbs 9bd86cb2ea scroller_tici :) 2025-11-21 13:57:05 -08:00
James Vecellio-Grant 94f3771fcc Merge branch 'master' into models-panel-rl 2025-11-21 13:56:12 -08:00
James Vecellio-Grant e911de5968 Merge branch 'master' into input-dialog 2025-11-21 13:50:33 -08:00
Jason Wen a33497ed19 add to readme 2025-11-21 16:42:59 -05:00
Jason Wen 91f2bf3459 ui: GuiApplicationExt 2025-11-21 16:23:01 -05:00
nayan e74252bdf5 Merge remote-tracking branch 'origin/master' into py-ui-state-sp 2025-11-21 15:37:33 -05:00
James Vecellio-Grant c4f79b7043 Merge branch 'master' into models-panel-rl 2025-11-21 12:02:40 -08:00
James Vecellio-Grant cea6e00819 Merge branch 'master' into input-dialog 2025-11-21 12:01:45 -08:00
discountchubbs df8b02b4bf Merge remote-tracking branch 'openpilot/master' into nov-19-sync 2025-11-21 11:56:37 -08:00
discountchubbs 08e34dc293 # Conflicts:
#	system/ui/sunnypilot/widgets/list_view.py
#	system/ui/sunnypilot/widgets/option_control.py
2025-11-21 07:44:45 -08:00
nayan ffa78eabaa Revert "add ui_update callback"
This reverts commit 4da32cc009.
2025-11-20 17:58:19 -05:00
James Vecellio 723023fcfb utilize ON_COLOR constant form op system 2025-11-20 06:17:18 -08:00
James Vecellio 46222c3bb0 yesssssssssss 2025-11-19 21:25:02 -08:00
James Vecellio 335821663f Merge remote-tracking branch 'origin/rl-tree-dialog' into models-panel-rl 2025-11-19 20:40:53 -08:00
James Vecellio-Grant 0d3bc959c8 Update process.py 2025-11-19 20:29:30 -08:00
James Vecellio 4f3c19ffb5 save the trees we got icons 2025-11-19 20:28:59 -08:00
nayan 4da32cc009 add ui_update callback 2025-11-19 23:03:56 -05:00
discountchubbs ae5c44355d the heck 2025-11-19 13:38:04 -08:00
discountchubbs 066438ad10 Merge remote-tracking branch 'origin/rl-tree-dialog' into rl-tree-dialog 2025-11-19 12:18:17 -08:00
discountchubbs 9532675814 less trees for the planet 2025-11-19 12:17:52 -08:00
James Vecellio-Grant e74460f3a8 Merge branch 'rl-sp-toggles' into rl-tree-dialog 2025-11-19 09:37:44 -08:00
discountchubbs c347db376a tree dialog 2025-11-19 09:36:33 -08:00
discountchubbs c9bd67b261 merge origin raylib toggles 2025-11-19 09:34:08 -08:00
James Vecellio 5e8cf86284 tree dialog, progress bar widget cool stuff 2025-11-19 07:01:36 -08:00
nayan 4820265268 better 2025-11-18 19:09:46 -05:00
nayan 01aa6c4204 param to control stock vs sp ui 2025-11-18 18:51:52 -05:00
discountchubbs 545f323000 init 2025-11-18 15:38:20 -08:00
discountchubbs f0b255aad0 raylib: Option Control 2025-11-18 13:38:22 -08:00
discountchubbs a31cb3f543 raylib: SP Panels 2025-11-18 13:37:35 -08:00
discountchubbs 929b417f19 raylib: SP Toggles 2025-11-18 13:36:59 -08:00
discountchubbs 905304cbcc raylib: input dialog 2025-11-18 13:36:03 -08:00
nayan 21beea51ec introducing ui_state_sp for py 2025-11-18 16:26:48 -05:00
discountchubbs d7b8ce86ed compare vs what used to be done before InputDialog 2025-11-17 20:21:33 -08:00
James Vecellio-Grant 2d3d104658 Merge branch 'master' into input-dialog 2025-11-17 19:24:20 -08:00
discountchubbs ded02895f4 dialog txt 2025-11-17 19:22:01 -08:00
Jason Wen 9778a925b0 input dialog 2025-11-17 19:05:54 -08:00
nayan 423a7d2ed0 fix ui preview 2025-11-16 11:15:28 -05:00
nayan e4e10d4b87 fix callback 2025-11-16 11:15:22 -05:00
nayan 362e9ce04b sp raylib preview 2025-11-16 09:53:28 -05:00
nayan 3946e643f6 optimizations 2025-11-16 09:29:58 -05:00
nayan 0c37a38596 Lint 2025-11-16 09:29:58 -05:00
nayan 9c5acf61c0 SP Toggles 2025-11-16 09:29:58 -05:00
nayan 121b304fe0 init styles 2025-11-16 09:29:58 -05:00
nayan 47d848293b param to control stock vs sp ui 2025-11-16 09:29:58 -05:00
DevTekVE 8b94f8b2f8 Merge branch 'master' into hkg-angle-steering-2025 2025-11-06 18:31:16 +01:00
Jason Wen dec014cd17 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	sunnypilot/selfdrive/car/interfaces.py
2025-11-04 17:45:05 -05:00
DevTekVE 7b40272866 Add sunnypilot-specific stats logging and handling
- Introduced `StatLogSP` for sunnypilot-specific metrics.
- Integrated stats collection and submission pathways for sunnylink.
- Extended parameters and handlers to support additional metrics.
- Added gzip compression and base64 encoding for oversized payload handling.
2025-11-04 21:20:16 +01:00
DevTekVE 506456e7f0 Merge branch 'master' into hkg-angle-steering-2025 2025-11-01 13:31:23 +01:00
DevTekVE 4ea4b9d177 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-10-31 06:59:39 +01:00
DevTekVE 0e2313dc31 Merge branch 'master' into hkg-angle-steering-2025 2025-10-18 11:14:45 +02:00
Jason Wen 79ea7db103 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-17 23:42:01 -04:00
Jason Wen f9ae9192fa Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 21:55:00 -04:00
Jason Wen 7ec23006c6 check this 2025-10-17 21:54:44 -04:00
Jason Wen 2be9447a6c Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 21:19:09 -04:00
Jason Wen cfd926778e always init true 2025-10-17 21:19:03 -04:00
Jason Wen a97a67e3d0 need 2025-10-17 21:17:31 -04:00
Jason Wen 518b6de08d Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 21:15:59 -04:00
Jason Wen 6933e3bcdb fix cruise toggles 2025-10-17 21:15:43 -04:00
Jason Wen 05e0ca8bee some more 2025-10-17 20:46:36 -04:00
Jason Wen 410614fcf3 single location 2025-10-17 20:26:18 -04:00
Jason Wen e2bc0996ef Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 12:21:47 -04:00
Jason Wen 1be0c20cf5 oops 2025-10-17 12:21:37 -04:00
Jason Wen 839143b9ed oops 2025-10-17 12:20:23 -04:00
Jason Wen bce86637ae Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 12:15:23 -04:00
Jason Wen b833d3ee89 ui: update ICBM-related settings handling 2025-10-17 12:14:44 -04:00
Jason Wen e0441dfb4b Merge branch 'sla-event' into hkg-angle-steering-2025 2025-10-17 11:58:42 -04:00
Jason Wen 62ec40bba6 Speed Limit Assist: update active event handling 2025-10-17 11:58:19 -04:00
Jason Wen 15c6d38028 bump 2025-10-16 17:05:58 -04:00
Jason Wen 56eb9f555c Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py
2025-10-16 01:12:10 -04:00
Jason Wen 2f9951df02 Merge branch 'e2e-alert-state-machine' into hkg-angle-steering-2025 2025-10-15 23:55:40 -04:00
Jason Wen 6030bf4da3 less 2025-10-15 23:52:03 -04:00
Jason Wen 074694d660 lead depart: only arm if we have a confirmed close lead for over a second after allowing alert 2025-10-15 23:48:13 -04:00
Jason Wen df35f48f3b magic 2025-10-15 22:56:08 -04:00
Jason Wen 4fb9704540 time based 2025-10-15 22:51:40 -04:00
Jason Wen 48cbe266fc 10 frames for both 2025-10-15 22:50:42 -04:00
Jason Wen 21aa7ff367 rename 2025-10-15 22:47:08 -04:00
Jason Wen 7caf05dd51 not used 2025-10-15 22:41:00 -04:00
Jason Wen 2d779f5db9 E2E Helper: universal state machine 2025-10-15 22:38:57 -04:00
Jason Wen 18208f1da0 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-15 17:41:56 -04:00
Jason Wen 5a3c6ddf57 gate lka angle steering out of alpha long 2025-10-14 23:25:00 -04:00
Jason Wen eaa8732ab0 Merge branch 'e2e-alerts-cooldown' into hkg-angle-steering-2025 2025-10-14 14:44:00 -04:00
Jason Wen 6f0284c84f try preventing startup false trigger 2025-10-14 14:43:47 -04:00
Jason Wen 999ea03f23 try preventing startup false trigger 2025-10-14 14:42:42 -04:00
Jason Wen 0975db3ff1 Merge branch 'e2e-alerts-cooldown' into hkg-angle-steering-2025 2025-10-14 14:31:59 -04:00
Jason Wen 8d70a8b80a only when long not engaged 2025-10-14 14:31:48 -04:00
Jason Wen da93f92887 rename 2025-10-14 14:26:57 -04:00
Jason Wen 9117f6c071 Merge branch 'e2e-alerts-cooldown' into hkg-angle-steering-2025 2025-10-14 14:23:46 -04:00
Jason Wen 3567ff9691 introduce recent moving check 2025-10-14 14:22:41 -04:00
Jason Wen f44ae2ced9 too complicated 2025-10-14 14:08:40 -04:00
Jason Wen 376e0ca615 only allow one trigger per standstill session 2025-10-14 14:07:16 -04:00
Jason Wen 32b7686468 Merge branch 'master' into e2e-alerts-cooldown 2025-10-14 11:40:58 -04:00
nayan b9e0f52ea9 E2E Alert Cooldown 2025-10-14 07:43:15 -04:00
Jason Wen a3163b680f Merge branch 'sla-chimes' into hkg-angle-steering-2025 2025-10-14 02:01:05 -04:00
Jason Wen 8a927d808f Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-10-14 02:00:36 -04:00
Jason Wen bc7d5e474d Speed Limit Assist: audible alerts for certain states 2025-10-14 01:45:13 -04:00
Jason Wen 36f192b5fe Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-13 03:10:47 -04:00
Jason Wen ffd5cd4ac2 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-11 02:29:47 -04:00
Jason Wen e4a00fcd6c Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-10 17:28:16 -04:00
Jason Wen 0bdcb41103 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
#	selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc
2025-10-10 17:02:14 -04:00
DevTekVE 78051085ca Merge branch 'master' into hkg-angle-steering-2025 2025-09-23 07:54:32 +02:00
DevTekVE 4910d5809a bump opendbc 2025-09-23 07:45:39 +02:00
DevTekVE 8dd862ff28 yikes, becoming picky huh? 2025-09-14 22:50:21 +02:00
DevTekVE 1b57497da9 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-09-14 22:48:00 +02:00
DevTekVE 8d8d1ffc7a bump opendbc again 2025-09-14 22:46:20 +02:00
DevTekVE c5919d5495 wrong dbc lol 2025-09-14 22:42:19 +02:00
DevTekVE da71951c95 This is no longer in use nor needed. Bai! 2025-09-14 12:56:28 +02:00
DevTekVE 25a152cd8b Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-09-14 12:44:23 +02:00
DevTekVE 9077a1082a Sorry for C3 :( but moving you out to a last working branch before I sync it up 2025-09-14 06:52:24 +02:00
DevTekVE 7ae7000254 Rework override behavior and feeling 2025-09-14 06:38:00 +02:00
DevTekVE 7029455706 better juggle 2025-09-13 08:32:27 +02:00
DevTekVE 3a71a62215 Merge branch 'master' into hkg-angle-steering-2025 2025-09-12 10:14:52 +02:00
DevTekVE 00622e8c33 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-09-05 09:46:41 +02:00
DevTekVE 549da3ee92 Merge branch 'master' into hkg-angle-steering-2025 2025-09-04 20:20:37 +02:00
DevTekVE e038a65ef8 Merge branch 'master' into hkg-angle-steering-2025 2025-08-31 13:14:33 +02:00
DevTekVE 8d0513c657 dbc: update CHECKSUM format for multiple messages to improve data integrity 2025-08-30 19:56:21 +02:00
DevTekVE 2cea48f4cd Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-08-30 15:27:45 +02:00
DevTekVE 8855a9ab65 Improve surprise jerk by safety blocks 2025-08-26 14:47:26 +02:00
DevTekVE 32321c01cc A bit more helpful safety block investigation help 2025-08-26 10:09:11 +02:00
DevTekVE 52cd65fefb lint dont bother me 2025-08-25 08:24:24 +02:00
DevTekVE 1fcdeccd40 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
2025-08-25 08:01:51 +02:00
DevTekVE 3517c36978 Revert "Add HkgAngleDebug structure and enhance angle debugging in car controller" 2025-08-24 19:02:48 +02:00
DevTekVE fb30c3c1e8 cleanup and honour params 2025-08-23 15:42:47 +02:00
DevTekVE 1c25e568d5 Update steering pressed logic to include hands-on-wheel detection for improved safety 2025-08-23 15:11:07 +02:00
DevTekVE f172122b7c Update steering pressed logic to include hands-on-wheel detection for improved safety 2025-08-22 19:55:16 +02:00
Jason Wen 67d6cdc7cd Merge remote-tracking branch 'sunnypilot/sunnypilot/hkg-angle-steering-2025' into hkg-angle-steering-2025 2025-08-21 16:39:36 -04:00
DevTekVE 6724085cfd Refactor angle limit calculations and adjust average road roll for improved steering dynamics 2025-08-21 20:09:57 +02:00
DevTekVE 6774f34eee Refine non-linear mapping in torque reduction gain calculation for improved steering response 2025-08-21 00:21:46 +02:00
DevTekVE 6d0402896d Adjust STEER_THRESHOLD and refine non-linear mapping in torque reduction gain calculation for improved steering response 2025-08-20 23:16:04 +02:00
DevTekVE 344021a3d9 Adjust non-linear mapping in torque reduction gain calculation for improved response 2025-08-20 19:54:31 +02:00
DevTekVE a9b85ab27d Refactor HkgAngleDebug structure to include current and baseline limits for angle parameters 2025-08-20 19:54:10 +02:00
DevTekVE 17204a46e4 Add HkgAngleDebug structure and enhance angle debugging in car controller 2025-08-20 18:39:53 +02:00
DevTekVE 7c4d415462 Enhance torque reduction gain calculation with non-linear mapping and smoothing 2025-08-20 00:12:43 +02:00
DevTekVE b8985b6d72 Enhance torque reduction gain calculation with non-linear mapping and smoothing 2025-08-19 19:50:13 +02:00
DevTekVE c669473f88 Refine torque reduction parameters and update UI for angle error analysis 2025-08-19 19:35:34 +02:00
DevTekVE 8751435bf5 Improving tq redc gain and override behavior 2025-08-19 10:08:51 +02:00
DevTekVE 30ae210761 Merge branch 'master' into hkg-angle-steering-2025 2025-08-19 10:07:42 +02:00
Jason Wen 3eb693f58b Merge remote-tracking branch 'sunnypilot/sunnypilot/hkg-angle-steering-2025' into hkg-angle-steering-2025 2025-08-18 12:30:29 -04:00
DevTekVE 7b4a31c5ac bugfix 2025-08-17 16:07:45 +02:00
DevTekVE 1527c8cf88 bump opendbc 2025-08-17 15:31:31 +02:00
DevTekVE ab0a7ae666 no joystick on this branch, causing issues 2025-08-17 15:25:40 +02:00
DevTekVE 11ec2f1f21 Apply suggestions from code review 2025-08-17 15:10:17 +02:00
DevTekVE 2c6808d37e Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-08-17 15:02:28 +02:00
Jason Wen 2e96382c49 notebook init 2025-08-17 00:34:01 -04:00
DevTekVE 0239e440ca Adjust replay 2025-08-15 10:32:07 +02:00
Jason Wen 76b972daff Hyundai angle steering: STEERING_ANGLE_2 available on all cars 2025-08-14 01:56:46 -04:00
Jason Wen bc3ef3e7dd Hyundai angle steering: hugging no more - use the true steering angle signal from MDPS 2025-08-13 23:08:32 -04:00
DevTekVE 9839291dd0 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025 2025-08-13 20:13:42 +02:00
DevTekVE 17cba328d6 refactor: update torque tuning configuration for angle steering
- Adjusted torque tuning configuration to avoid reliance on torque controller for Hyundai angle steering.
- Simplified control logic by removing unnecessary checks for torque control type.
refactor: clean up code formatting and improve test structure for torque reduction gain
2025-08-13 20:03:51 +02:00
DevTekVE 86093765d8 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-08-13 07:55:46 +02:00
DevTekVE 05fa1c8ae8 Adjust default params and cleanup 2025-08-12 21:36:22 +02:00
DevTekVE e8a40d6b85 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	system/manager/process_config.py
2025-08-10 14:24:10 +02:00
DevTekVE c265e0bb85 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
#	system/manager/manager.py
2025-08-02 09:56:58 +02:00
DevTekVE c6b118788b Merge branch 'master-new' into hkg-angle-steering-2025 2025-07-31 18:25:08 +02:00
DevTekVE b004f6dbdc Merge branch 'master-new' into hkg-angle-steering-2025 2025-07-31 18:17:34 +02:00
DevTekVE cd4930b680 Bump opendbc 2025-07-26 10:56:49 +02:00
DevTekVE f861aca628 Refactor vehicle model initialization and adjust angle limits for baseline model 2025-07-26 08:12:06 +02:00
DevTekVE 1ff0d8e2ee please don't bother me anymore! 2025-07-26 08:09:51 +02:00
DevTekVE d76d70764e Update slip factor precision for Hyundai steering parameters
- Adjusted `slip_factor` in Hyundai CANFD safety modes for improved consistency and accuracy.
- Ensured proper representation of `slip_factor` output in test logs.
2025-07-25 20:17:19 +02:00
DevTekVE dc73ce0b71 save tools replay 2025-07-25 19:47:31 +02:00
DevTekVE 3f666748af bump opendbc 2025-07-25 14:16:02 +02:00
DevTekVE 8de8a8838c bumo opendbc 2025-07-25 14:08:53 +02:00
DevTekVE f563b7eb71 Refactor steering angle limit application for improved safety and model compliance 2025-07-25 12:12:42 +02:00
DevTekVE 3c18b83708 save temp 2025-07-25 09:56:30 +02:00
DevTekVE bd35f5904b Enhance steering angle rate limiting and safety enforcement logic
- Introduced explicit post-rate limiting using model-specific dynamics.
- Improved low-speed smoothing and precision of applied angles.
2025-07-25 08:12:41 +02:00
DevTekVE 474f2737f6 Refactor steering angle limit logic for conservative seleion
- Removed unused lateral accel/jerk logic for simplicity.
- Updated angle limit calculations to choose the smallest delta for safer control.
2025-07-24 21:48:24 +02:00
DevTekVE ea1ac4a212 Revert "Add configurable max lateral accel and jerk parameters for Hyundai vehicles"
This reverts commit b95f8c5929.

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

This reverts commit b53cbb2e18.

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

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

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

Refactor lateral control to combine torque and angle logic

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

Add angle-torque hybrid lateral control for Hyundai CAN FD

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

Add overriding cycles parameter for torque adjustment

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

Remove "inputs" package and update "pygame" dependency

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

Update dependencies: replace 'inputs' with 'pygame'

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

Ugly, I know, but soundd is unhappy with joystick

Allowing lat with mads

Invert steering input for joystick control

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

Refactor joystick control to use pygame for broader support

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

Revert "More improvement but still not quite"

This reverts commit ad95493c5c61b2ace7c459d2ebc151ddaa80040f.

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

This reverts commit 6f789ac1ebb66b0239b4028303573c2d7d386b39.

Revert "Refactor speed-based steering scaling logic."

This reverts commit 1d40735ab8db8d470ff3b287a6b42847beffff7d.
2025-05-25 17:31:18 +02:00
DevTekVE 10f345f956 Refactor speed-based steering scaling logic.
Updated the steering angle computation to use a clearer and more descriptive speed-scaling configuration. Replaced low-speed-specific logic with a generalized approach based on speed breakpoints and corresponding influence factors. This improves maintainability and ensures smoother steering adjustments at varying speeds.
2025-05-25 17:31:18 +02:00
DevTekVE 956d2c36d0 Adjust low-speed scaling for lateral control angle
Refined the low-speed scaling parameters by modifying speed breakpoints and factors. This improves handling at lower speeds for smoother and more predictable behavior.
2025-05-25 17:31:18 +02:00
DevTekVE 55e688b6f2 More improvement but still not quite 2025-05-25 17:31:17 +02:00
DevTekVE f017954027 Significant improvement on the jerkiness 2025-05-25 17:31:17 +02:00
DevTekVE 7e992d11b1 bump panda and opendbc 2025-05-25 17:31:15 +02:00
175 changed files with 8638 additions and 7405 deletions
+1
View File
@@ -2,5 +2,6 @@ Wen
REGIST
PullRequest
cancelled
indeces
FOF
NoO
@@ -6,10 +6,10 @@ env:
CI_DIR: ${{ github.workspace }}/release/ci
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
# Branch configurations
STAGING_SOURCE_BRANCH: 'master'
# Runtime configuration
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
@@ -75,7 +75,7 @@ jobs:
cancel="$(echo "$CONFIG" | jq -r '.cancel_publish_in_progress')";
echo "cancel_publish_in_progress=$( [ "$cancel" = "null" ] && echo "true" || echo $cancel)" >> $GITHUB_OUTPUT
echo "publish_concurrency_group=publish-${BRANCH}$( [ "$cancel" = "null" ] || [ "$cancel" = "true" ] || echo "${{ github.sha }}" )" >> $GITHUB_OUTPUT
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
@@ -85,7 +85,7 @@ jobs:
fi
echo "build=$BUILD" >> $GITHUB_OUTPUT
cat $GITHUB_OUTPUT
validate_tests:
runs-on: ubuntu-24.04
needs: [ prepare_strategy ]
@@ -119,7 +119,7 @@ jobs:
needs.prepare_strategy.result == 'success' &&
(needs.validate_tests.result == 'success' || needs.validate_tests.result == 'skipped') &&
(!contains(github.event_name, 'pull_request') ||
(github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
(github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
}}
steps:
- uses: actions/checkout@v4
@@ -134,7 +134,7 @@ jobs:
with:
path: ${{env.SCONS_CACHE_DIR}}
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
restore-keys: |
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
@@ -148,7 +148,7 @@ jobs:
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
# Set up common environment
source /etc/profile;
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
@@ -180,6 +180,15 @@ jobs:
./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
cd $BUILD_DIR
sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py
echo "Building sunnypilot's modeld..."
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld
echo "Building sunnypilot's modeld_v2..."
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2
echo "Building sunnypilot's locationd..."
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd
echo "Building openpilot's locationd..."
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd
echo "Building rest of sunnypilot"
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
touch ${BUILD_DIR}/prebuilt
if [[ "${{ runner.debug }}" == "1" ]]; then
@@ -241,8 +250,8 @@ jobs:
if: always()
run: |
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
publish:
concurrency:
# We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name.
@@ -293,7 +302,7 @@ jobs:
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
echo "3. Update as needed (JSON array with no spaces)"
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
run: |
@@ -302,7 +311,7 @@ jobs:
git push -f origin ${TAG}
notify:
needs:
needs:
- prepare_strategy
- build
- publish
@@ -331,7 +340,7 @@ jobs:
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
EOF
)
{
echo 'content<<EOFMARKER'
echo "$MESSAGE"
+7
View File
@@ -21,5 +21,12 @@
</clean>
</configuration>
</target>
<target id="f2590b2b-9b93-49f9-8510-da3f3724a2ae" name="replay" defaultType="TOOL">
<configuration id="d475264f-6f4c-4092-9b4e-6773309f38b7" name="replay" toolchainName="Default">
<build type="TOOL">
<tool actionId="Tool_External Tools_uv build tools replay" />
</build>
</configuration>
</target>
</component>
</project>
+7
View File
@@ -20,4 +20,11 @@
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
<tool name="uv build tools replay" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec>
<option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) tools/replay/&quot;" />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
</toolSet>
+1 -1
View File
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="sunnypilot" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<envs>
<env name="QT_DBL_CLICK_DIST" value="150" />
</envs>
+27
View File
@@ -0,0 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Debug Route Controls" type="PythonConfigurationType" factoryName="Python">
<module name="openpilot-special" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="FINGERPRINT" value="KIA_EV9" />
<env name="SKIP_FW_QUERY" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/selfdrive/car" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/selfdrive/car/card.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Replay for controls + ui" type="Multirun" separateTabs="false" reuseTabsWithFailures="false" startOneByOne="true" markFailedProcess="true" hideSuccessProcess="false" delayTime="0.0">
<runConfiguration name="replay for controls" type="Native Application" />
<runConfiguration name="Build Debug" type="Custom Build Application" />
<method v="2" />
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="replay for controls" type="CLionNativeAppRunConfigurationType" focusToolWindowBeforeRun="true" PROGRAM_PARAMS="&quot;$Prompt$&quot; --block &quot;sendcan,carState,carParams,carOutput,liveTracks,carParamsSP,carStateSP,bookmarkButton&quot;" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="true" WORKING_DIR="file://$ProjectFileDir$/tools/replay" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="replay" CONFIG_NAME="replay" version="1" RUN_PATH="replay">
<method v="2">
<option name="CLION.COMPOUND.BUILD" enabled="true" />
</method>
</configuration>
</component>
-10
View File
@@ -75,7 +75,6 @@ env = Environment(
"#third_party/acados/include/hpipm/include",
"#third_party/catch2/include",
"#third_party/libyuv/include",
"#third_party/snpe/include",
],
LIBPATH=[
"#common",
@@ -101,7 +100,6 @@ if arch == "larch64":
"/usr/local/lib",
"/system/vendor/lib64",
"/usr/lib/aarch64-linux-gnu",
"#third_party/snpe/larch64",
])
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"]
env.Append(CCFLAGS=arch_flags)
@@ -125,14 +123,6 @@ else:
"/usr/local/lib",
])
if arch == "x86_64":
env.Append(LIBPATH=[
f"#third_party/snpe/{arch}"
])
env.Append(RPATH=[
Dir(f"#third_party/snpe/{arch}").abspath,
])
# Sanitizers and extra CCFLAGS from CLI
if GetOption('asan'):
env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"])
+7
View File
@@ -262,4 +262,11 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
// Tuning keys
{"EnableHkgTuningAngleSmoothingFactor", {PERSISTENT | BACKUP, BOOL, "1"}},
{"HkgTuningAngleMinTorqueReductionGain", {PERSISTENT | BACKUP, INT, "10"}},
{"HkgTuningAngleMaxTorqueReductionGain", {PERSISTENT | BACKUP, INT, "100"}},
{"HkgTuningAngleActiveTorqueReductionGain", {PERSISTENT | BACKUP, INT, "100"}},
{"HkgTuningOverridingCycles", {PERSISTENT | BACKUP, INT, "17"}},
};
+1 -1
View File
@@ -13,7 +13,7 @@ cd $ROOT
FAILED=0
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md"
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md|layouts\/.*\.xml|.*\.ipynb"
IGNORED_DIRS="^third_party.*|^msgq.*|^msgq_repo.*|^opendbc.*|^opendbc_repo.*|^cereal.*|^panda.*|^rednose.*|^rednose_repo.*|^tinygrad.*|^tinygrad_repo.*|^teleoprtc.*|^teleoprtc_repo.*"
function run() {
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:434a720871336d359378beff5ebff3f9fd654d958693d272c7c6f2e271c7e41c
size 47676
+8 -28
View File
@@ -1,7 +1,5 @@
#!/usr/bin/env python3
import math
import threading
import time
from numbers import Number
from cereal import car, log
@@ -22,8 +20,6 @@ from openpilot.selfdrive.controls.lib.longcontrol import LongControl
from openpilot.selfdrive.modeld.modeld import LAT_SMOOTH_SECONDS
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.controlsd_ext import ControlsExt
State = log.SelfdriveState.OpenpilotState
@@ -33,7 +29,7 @@ LaneChangeDirection = log.LaneChangeDirection
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
class Controls(ControlsExt, ModelStateBase):
class Controls(ControlsExt):
def __init__(self) -> None:
self.params = Params()
cloudlog.info("controlsd is waiting for CarParams")
@@ -42,7 +38,6 @@ class Controls(ControlsExt, ModelStateBase):
# Initialize sunnypilot controlsd extension and base model state
ControlsExt.__init__(self, self.CP, self.params)
ModelStateBase.__init__(self)
self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP)
@@ -231,30 +226,15 @@ class Controls(ControlsExt, ModelStateBase):
cc_send.carControl = CC
self.pm.send('carControl', cc_send)
def params_thread(self, evt):
while not evt.is_set():
self.get_params_sp()
if self.CP.lateralTuning.which() == 'torque':
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
time.sleep(0.1)
def run(self):
rk = Ratekeeper(100, print_delay_threshold=None)
e = threading.Event()
t = threading.Thread(target=self.params_thread, args=(e,))
try:
t.start()
while True:
self.update()
CC, lac_log = self.state_control()
self.publish(CC, lac_log)
self.run_ext(self.sm, self.pm)
rk.monitor_time()
finally:
e.set()
t.join()
while True:
self.update()
CC, lac_log = self.state_control()
self.publish(CC, lac_log)
self.get_params_sp(self.sm)
self.run_ext(self.sm, self.pm)
rk.monitor_time()
def main():
+1 -1
View File
@@ -227,6 +227,6 @@ class HomeLayout(Widget):
self._prev_alerts_present = alerts_present
def _get_version_text(self) -> str:
brand = "openpilot"
brand = "sunnypilot"
description = self.params.get("UpdaterCurrentDescription")
return f"{brand} {description}" if description else brand
+3
View File
@@ -10,6 +10,9 @@ from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
class MainState(IntEnum):
HOME = 0
+4 -4
View File
@@ -109,8 +109,8 @@ class TermsPage(Widget):
self._on_accept = on_accept
self._on_decline = on_decline
self._title = Label(tr("Welcome to openpilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)
self._desc = Label(tr("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing."),
self._title = Label(tr("Welcome to sunnypilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)
self._desc = Label(tr("You must accept the Terms and Conditions to use sunnypilot. Read the latest terms at https://comma.ai/terms before continuing."),
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)
self._decline_btn = Button(tr("Decline"), click_callback=on_decline)
@@ -143,10 +143,10 @@ class TermsPage(Widget):
class DeclinePage(Widget):
def __init__(self, back_callback=None):
super().__init__()
self._text = Label(tr("You must accept the Terms and Conditions in order to use openpilot."),
self._text = Label(tr("You must accept the Terms and Conditions in order to use sunnypilot."),
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)
self._back_btn = Button(tr("Back"), click_callback=back_callback)
self._uninstall_btn = Button(tr("Decline, uninstall openpilot"), button_style=ButtonStyle.DANGER,
self._uninstall_btn = Button(tr("Decline, uninstall sunnypilot"), button_style=ButtonStyle.DANGER,
click_callback=self._on_uninstall_clicked)
def _on_uninstall_clicked(self):
+7 -5
View File
@@ -23,10 +23,11 @@ DESCRIPTIONS = {
"other than your own. A comma employee will NEVER ask you to add their GitHub username."
),
'alpha_longitudinal': tr_noop(
"<b>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</b><br><br>" +
"On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " +
"Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. " +
"Changing this setting will restart openpilot if the car is powered on."
"<b>WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</b><br><br>" +
"On this car, sunnypilot defaults to the car's built-in ACC instead of sunnypilot's longitudinal control. " +
"Enable this to switch to sunnypilot longitudinal control. " +
"Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. " +
"Changing this setting will restart sunnypilot if the car is powered on."
),
}
@@ -71,7 +72,7 @@ class DeveloperLayout(Widget):
)
self._alpha_long_toggle = toggle_item(
lambda: tr("openpilot Longitudinal Control (Alpha)"),
lambda: tr("sunnypilot Longitudinal Control (Alpha)"),
description=lambda: tr(DESCRIPTIONS["alpha_longitudinal"]),
initial_state=self._params.get_bool("AlphaLongitudinalEnabled"),
callback=self._on_alpha_long_enabled,
@@ -148,6 +149,7 @@ class DeveloperLayout(Widget):
self._params.put_bool("ShowDebugInfo", state)
gui_app.set_show_touches(state)
gui_app.set_show_fps(state)
gui_app.set_show_mouse_coords(state)
def _on_enable_adb(self, state: bool):
self._params.put_bool("AdbEnabled", state)
+7 -4
View File
@@ -19,12 +19,15 @@ from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_b
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.scroller_tici import Scroller
if gui_app.sunnypilot_ui():
from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item
# Description constants
DESCRIPTIONS = {
'pair_device': tr_noop("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."),
'driver_camera': tr_noop("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"),
'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."),
'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"),
'reset_calibration': tr_noop("sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."),
'review_guide': tr_noop("Review the rules, features, and limitations of sunnypilot"),
}
@@ -162,8 +165,8 @@ class DeviceLayout(Widget):
cloudlog.exception("invalid LiveTorqueParameters")
desc += "<br><br>"
desc += tr("openpilot is continuously calibrating, resetting is rarely required. " +
"Resetting calibration will restart openpilot if the car is powered on.")
desc += tr("sunnypilot is continuously calibrating, resetting is rarely required. " +
"Resetting calibration will restart sunnypilot if the car is powered on.")
self._reset_calib_btn.set_description(desc)
+1 -1
View File
@@ -17,7 +17,7 @@ from openpilot.selfdrive.ui.lib.api_helpers import get_token
TITLE = tr_noop("Firehose Mode")
DESCRIPTION = tr_noop(
"openpilot learns to drive by watching humans, like you, drive.\n\n"
"sunnypilot learns to drive by watching humans, like you, drive.\n\n"
+ "Firehose Mode allows you to maximize your training data uploads to improve "
+ "openpilot's driving models. More data means bigger models, which means better Experimental Mode."
)
@@ -11,6 +11,9 @@ from openpilot.system.ui.widgets.list_view import button_item, text_item, ListIt
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.scroller_tici import Scroller
if gui_app.sunnypilot_ui():
from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item
# TODO: remove this. updater fails to respond on startup if time is not correct
UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond
+14 -13
View File
@@ -11,26 +11,27 @@ from openpilot.selfdrive.ui.ui_state import ui_state
if gui_app.sunnypilot_ui():
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item
from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp as multiple_button_item
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
# Description constants
DESCRIPTIONS = {
"OpenpilotEnabledToggle": tr_noop(
"Use the openpilot system for adaptive cruise control and lane keep driver assistance. " +
"Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. " +
"Your attention is required at all times to use this feature."
),
"DisengageOnAccelerator": tr_noop("When enabled, pressing the accelerator pedal will disengage openpilot."),
"DisengageOnAccelerator": tr_noop("When enabled, pressing the accelerator pedal will disengage sunnypilot."),
"LongitudinalPersonality": tr_noop(
"Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " +
"In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " +
"Standard is recommended. In aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. " +
"In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " +
"your steering wheel distance button."
),
"IsLdwEnabled": tr_noop(
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " +
"without a turn signal activated while driving over 31 mph (50 km/h)."
),
"AlwaysOnDM": tr_noop("Enable driver monitoring even when openpilot is not engaged."),
"AlwaysOnDM": tr_noop("Enable driver monitoring even when sunnypilot is not engaged."),
'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
"IsMetric": tr_noop("Display speed in km/h instead of mph."),
"RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."),
@@ -46,7 +47,7 @@ class TogglesLayout(Widget):
# param, title, desc, icon, needs_restart
self._toggle_defs = {
"OpenpilotEnabledToggle": (
lambda: tr("Enable openpilot"),
lambda: tr("Enable sunnypilot"),
DESCRIPTIONS["OpenpilotEnabledToggle"],
"chffr_wheel.png",
True,
@@ -99,7 +100,7 @@ class TogglesLayout(Widget):
lambda: tr("Driving Personality"),
lambda: tr(DESCRIPTIONS["LongitudinalPersonality"]),
buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("Relaxed")],
button_width=255,
button_width=300,
callback=self._set_longitudinal_personality,
selected_index=self._params.get("LongitudinalPersonality", return_default=True),
icon="speed_limit.png"
@@ -125,7 +126,7 @@ class TogglesLayout(Widget):
# Make description callable for live translation
additional_desc = ""
if needs_restart and not locked:
additional_desc = tr("Changing this setting will restart openpilot if the car is powered on.")
additional_desc = tr("Changing this setting will restart sunnypilot if the car is powered on.")
toggle.set_description(lambda og_desc=toggle.description, add_desc=additional_desc: tr(og_desc) + (" " + tr(add_desc) if add_desc else ""))
# track for engaged state updates
@@ -158,10 +159,10 @@ class TogglesLayout(Widget):
ui_state.update_params()
e2e_description = tr(
"openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " +
"sunnypilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " +
"Experimental features are listed below:<br>" +
"<h4>End-to-End Longitudinal Control</h4><br>" +
"Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " +
"Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. " +
"Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " +
"mistakes should be expected.<br>" +
"<h4>New Driving Visualization</h4><br>" +
@@ -183,13 +184,13 @@ class TogglesLayout(Widget):
unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.")
long_desc = unavailable + " " + tr("openpilot longitudinal control may come in a future update.")
long_desc = unavailable + " " + tr("sunnypilot longitudinal control may come in a future update.")
if ui_state.CP.alphaLongitudinalAvailable:
if self._is_release:
long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with " +
long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with " +
"Experimental mode, on non-release branches.")
else:
long_desc = tr("Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode.")
long_desc = tr("Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode.")
self._toggles["ExperimentalMode"].set_description("<b>" + long_desc + "</b><br><br>" + e2e_description)
else:
+2 -2
View File
@@ -45,7 +45,7 @@ class DeviceStatus(Widget):
self._version_text = self._get_version_text()
def _get_version_text(self) -> str:
brand = "openpilot"
brand = "sunnypilot"
description = ui_state.params.get("UpdaterCurrentDescription")
return f"{brand} {description}" if description else brand
@@ -111,7 +111,7 @@ class MiciHomeLayout(Widget):
self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 55, 35)
self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 55, 35)
self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY)
self._openpilot_label = MiciLabel("sunnypilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY)
self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN)
self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN)
self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN)
+1 -1
View File
@@ -254,7 +254,7 @@ class MiciOffroadAlerts(Widget):
parts = new_desc.split(" / ")
if len(parts) > 3:
version, date = parts[0], parts[3]
update_alert_data.text = f"update available\n openpilot {version}, {date}. go to comma.ai/blog to read the release notes."
update_alert_data.text = f"update available\n sunnypilot {version}, {date}. go to comma.ai/blog to read the release notes."
update_alert_data.visible = True
active_count += 1
+4 -4
View File
@@ -182,7 +182,7 @@ class TrainingGuideAttentionNotice(SetupTermsPage):
def __init__(self, continue_callback):
super().__init__(continue_callback, continue_text="continue")
self._title_header = TermsHeader("driver assistance", gui_app.texture("icons_mici/setup/warning.png", 60, 60))
self._warning_label = UnifiedLabel("1. openpilot is a driver assistance system.\n\n" +
self._warning_label = UnifiedLabel("1. sunnypilot is a driver assistance system.\n\n" +
"2. You must pay attention at all times.\n\n" +
"3. You must be ready to take over at any time.\n\n" +
"4. You are fully responsible for driving the car.", 42,
@@ -239,12 +239,12 @@ class TrainingGuide(Widget):
class DeclinePage(Widget):
def __init__(self, back_callback=None):
super().__init__()
self._uninstall_slider = SmallSlider("uninstall openpilot", self._on_uninstall)
self._uninstall_slider = SmallSlider("uninstall sunnypilot", self._on_uninstall)
self._back_button = SmallButton("back")
self._back_button.set_click_callback(back_callback)
self._warning_header = TermsHeader("you must accept the\nterms to use openpilot",
self._warning_header = TermsHeader("you must accept the\nterms to use sunnypilot",
gui_app.texture("icons_mici/setup/red_warning.png", 66, 60))
def _on_uninstall(self):
@@ -282,7 +282,7 @@ class TermsPage(SetupTermsPage):
info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60)
self._title_header = TermsHeader("terms & conditions", info_txt)
self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use openpilot. " +
self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use sunnypilot. " +
"Read the latest terms at https://comma.ai/terms before continuing.", 36,
FontWeight.ROMAN)
+3 -3
View File
@@ -156,7 +156,7 @@ class UpdateOpenpilotBigButton(BigButton):
self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 64)
self._txt_reboot_icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 64)
self._txt_up_to_date_icon = gui_app.texture("icons_mici/settings/device/up_to_date.png", 64, 64)
super().__init__("update openpilot", "", self._txt_update_icon)
super().__init__("update sunnypilot", "", self._txt_update_icon)
self._waiting_for_updater_t: float | None = None
self._hide_value_t: float | None = None
@@ -193,7 +193,7 @@ class UpdateOpenpilotBigButton(BigButton):
if value:
self.set_text("")
else:
self.set_text("update openpilot")
self.set_text("update sunnypilot")
def _update_state(self):
if ui_state.started:
@@ -294,7 +294,7 @@ class DeviceLayoutMici(NavWidget):
reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png")
reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset"))
uninstall_openpilot_btn = BigButton("uninstall openpilot", "", "icons_mici/settings/device/uninstall.png")
uninstall_openpilot_btn = BigButton("uninstall sunnypilot", "", "icons_mici/settings/device/uninstall.png")
uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall"))
reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False)
@@ -17,7 +17,7 @@ from openpilot.system.ui.widgets import NavWidget
TITLE = tr_noop("Firehose Mode")
DESCRIPTION = tr_noop(
"openpilot learns to drive by watching humans, like you, drive.\n\n"
"sunnypilot learns to drive by watching humans, like you, drive.\n\n"
+ "Firehose Mode allows you to maximize your training data uploads to improve "
+ "openpilot's driving models. More data means bigger models, which means better Experimental Mode."
)
@@ -24,7 +24,7 @@ class TogglesLayoutMici(NavWidget):
always_on_dm_toggle = BigParamControl("always-on driver monitor", "AlwaysOnDM")
record_front = BigParamControl("record & upload driver camera", "RecordFront", toggle_callback=restart_needed_callback)
record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback)
enable_openpilot = BigParamControl("enable openpilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback)
enable_openpilot = BigParamControl("enable sunnypilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback)
self._scroller = Scroller([
self._personality_toggle,
+1 -1
View File
@@ -65,7 +65,7 @@ class Alert:
# Pre-defined alert instances
ALERT_STARTUP_PENDING = Alert(
text1="openpilot Unavailable",
text1="sunnypilot Unavailable",
text2="Waiting to start",
size=AlertSize.mid,
status=AlertStatus.normal,
@@ -153,7 +153,7 @@ class AugmentedRoadView(CameraView):
self._alert_renderer = AlertRenderer()
self._driver_state_renderer = DriverStateRenderer()
self._confidence_ball = ConfidenceBall()
self._offroad_label = UnifiedLabel("start the car to\nuse openpilot", 54, FontWeight.DISPLAY,
self._offroad_label = UnifiedLabel("start the car to\nuse sunnypilot", 54, FontWeight.DISPLAY,
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
@@ -171,7 +171,7 @@ class AugmentedRoadView(CameraView):
if ui_state.panda_type == log.PandaState.PandaType.unknown:
self._offroad_label.set_text("system booting")
else:
self._offroad_label.set_text("start the car to\nuse openpilot")
self._offroad_label.set_text("start the car to\nuse sunnypilot")
def _handle_mouse_release(self, mouse_pos: MousePos):
# Don't trigger click callback if bookmark was triggered
+1 -1
View File
@@ -48,7 +48,7 @@ class Alert:
# Pre-defined alert instances
ALERT_STARTUP_PENDING = Alert(
text1=tr("openpilot Unavailable"),
text1=tr("sunnypilot Unavailable"),
text2=tr("Waiting to start"),
size=AlertSize.mid,
status=AlertStatus.normal,
-158
View File
@@ -1,158 +0,0 @@
/**
* 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/qt/offroad/offroad_home.h"
#include "selfdrive/ui/qt/offroad/experimental_mode.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/prime.h"
// OffroadHome: the offroad home page
OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(40, 40, 40, 40);
// top header
header_layout = new QHBoxLayout();
header_layout->setContentsMargins(0, 0, 0, 0);
header_layout->setSpacing(16);
update_notif = new QPushButton(tr("UPDATE"));
update_notif->setVisible(false);
update_notif->setStyleSheet("background-color: #364DEF;");
QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
alert_notif = new QPushButton();
alert_notif->setVisible(false);
alert_notif->setStyleSheet("background-color: #E22C2C;");
QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); });
header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
version = new ElidedLabel();
header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight);
main_layout->addLayout(header_layout);
// main content
main_layout->addSpacing(25);
center_layout = new QStackedLayout();
QWidget *home_widget = new QWidget(this);
{
home_layout = new QHBoxLayout(home_widget);
home_layout->setContentsMargins(0, 0, 0, 0);
home_layout->setSpacing(30);
#ifndef SUNNYPILOT
// left: PrimeAdWidget
QStackedWidget *left_widget = new QStackedWidget(this);
QVBoxLayout *left_prime_layout = new QVBoxLayout();
left_prime_layout->setContentsMargins(0, 0, 0, 0);
QWidget *prime_user = new PrimeUserWidget();
prime_user->setStyleSheet(R"(
border-radius: 10px;
background-color: #333333;
)");
left_prime_layout->addWidget(prime_user);
left_prime_layout->addStretch();
left_widget->addWidget(new LayoutWidget(left_prime_layout));
left_widget->addWidget(new PrimeAdWidget);
left_widget->setStyleSheet("border-radius: 10px;");
connect(uiState()->prime_state, &PrimeState::changed, [left_widget]() {
left_widget->setCurrentIndex(uiState()->prime_state->isSubscribed() ? 0 : 1);
});
home_layout->addWidget(left_widget, 1);
#endif
// right: ExperimentalModeButton, SetupWidget
QWidget* right_widget = new QWidget(this);
QVBoxLayout* right_column = new QVBoxLayout(right_widget);
right_column->setContentsMargins(0, 0, 0, 0);
right_widget->setFixedWidth(750);
right_column->setSpacing(30);
ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this);
QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings);
right_column->addWidget(experimental_mode, 1);
SetupWidget *setup_widget = new SetupWidget;
QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings);
right_column->addWidget(setup_widget, 1);
home_layout->addWidget(right_widget, 1);
}
center_layout->addWidget(home_widget);
// add update & alerts widgets
update_widget = new UpdateAlert();
QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(update_widget);
alerts_widget = new OffroadAlert();
QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(alerts_widget);
main_layout->addLayout(center_layout, 1);
// set up refresh timer
timer = new QTimer(this);
timer->callOnTimeout(this, &OffroadHome::refresh);
setStyleSheet(R"(
* {
color: white;
}
OffroadHome {
background-color: black;
}
OffroadHome > QPushButton {
padding: 15px 30px;
border-radius: 5px;
font-size: 40px;
font-weight: 500;
}
OffroadHome > QLabel {
font-size: 55px;
}
)");
}
void OffroadHome::showEvent(QShowEvent *event) {
refresh();
timer->start(10 * 1000);
}
void OffroadHome::hideEvent(QHideEvent *event) {
timer->stop();
}
void OffroadHome::refresh() {
version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription")));
bool updateAvailable = update_widget->refresh();
int alerts = alerts_widget->refresh();
// pop-up new notification
int idx = center_layout->currentIndex();
if (!updateAvailable && !alerts) {
idx = 0;
} else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) {
idx = 1;
} else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) {
idx = 2;
}
center_layout->setCurrentIndex(idx);
update_notif->setVisible(updateAvailable);
alert_notif->setVisible(alerts);
if (alerts) {
alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT")));
}
}
-59
View File
@@ -1,59 +0,0 @@
/**
* 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 "common/params.h"
#include "selfdrive/ui/qt/body.h"
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h"
#include "selfdrive/ui/sunnypilot/qt/sidebar.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/prime.h"
#define OnroadWindow OnroadWindowSP
#define LayoutWidget LayoutWidgetSP
#define Sidebar SidebarSP
#define ElidedLabel ElidedLabelSP
#define SetupWidget SetupWidgetSP
#else
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#include "selfdrive/ui/qt/sidebar.h"
#include "selfdrive/ui/qt/widgets/prime.h"
#endif
class OffroadHome : public QFrame {
Q_OBJECT
public:
explicit OffroadHome(QWidget* parent = 0);
signals:
void openSettings(int index = 0, const QString &param = "");
protected:
QHBoxLayout *home_layout;
QHBoxLayout *header_layout;
void showEvent(QShowEvent *event) override;
void refresh();
private:
void hideEvent(QHideEvent *event) override;
Params params;
QTimer* timer;
ElidedLabel* version;
QStackedLayout* center_layout;
UpdateAlert *update_widget;
OffroadAlert* alerts_widget;
QPushButton* alert_notif;
QPushButton* update_notif;
};
View File
@@ -0,0 +1,30 @@
"""
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.
"""
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class CruiseLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,12 @@
"""
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.
"""
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
class DeviceLayoutSP(DeviceLayout):
def __init__(self):
DeviceLayout.__init__(self)
@@ -0,0 +1,30 @@
"""
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.
"""
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class DisplayLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,258 @@
"""
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.
"""
import os
import re
import time
import pyray as rl
from cereal import custom
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import DialogResult, Widget
from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets.toggle import ON_COLOR
from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH
from openpilot.system.ui.sunnypilot.lib.styles import style
from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP, ListItemSP, toggle_item_sp, option_item_sp
from openpilot.system.ui.sunnypilot.widgets.progress_bar import progress_item
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder
if gui_app.sunnypilot_ui():
from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item
class ModelAction(ButtonActionSP):
def get_width_hint(self):
return super().get_width_hint() + 1
class ModelsLayout(Widget):
def __init__(self):
super().__init__()
self.model_manager = None
self.download_status = None
self.prev_download_status = None
self.model_dialog = None
self.last_cache_calc_time = 0
self._initialize_items()
self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB")
for ctrl, key in [(self.lane_turn_value_control, "LaneTurnValue"), (self.delay_control, "LagdToggleDelay")]:
ctrl.action_item.set_value(int(float(ui_state.params.get(key, return_default=True)) * 100))
self._scroller = Scroller(self.items, line_separator=True, spacing=0)
def _initialize_items(self):
self.current_model_item = ListItemSP(
title=tr("Current Model"),
description="",
action_item=ModelAction(tr("SELECT")),
callback=self._handle_current_model_clicked
)
self.supercombo_label = progress_item(tr("Driving Model"))
self.vision_label = progress_item(tr("Vision Model"))
self.policy_label = progress_item(tr("Policy Model"))
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "",
lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0),
gui_app.set_modal_overlay(alert_dialog(tr("Fetching Latest Models")))))
self.clear_cache_item = ListItemSP(
title=tr("Clear Model Cache"),
description="",
action_item=ModelAction(tr("CLEAR")),
callback=self._clear_cache
)
self.cancel_download_item = button_item(tr("Cancel Download"), tr("Cancel"), "", lambda: ui_state.params.remove("ModelManager_DownloadIndex"))
self.lane_turn_value_control = option_item_sp(tr("Adjust Lane Turn Speed"), "LaneTurnValue", 500, 2000,
tr("Set the maximum speed for lane turn desires. Default is 19 mph."),
int(round(100 / CV.MPH_TO_KPH)), None, True, "", style.BUTTON_WIDTH, None, True,
lambda v: f"{int(round(v / 100 * (CV.MPH_TO_KPH if ui_state.is_metric else 1)))}" +
f" {'km/h' if ui_state.is_metric else 'mph'}")
self.lane_turn_desire_toggle = toggle_item_sp(tr("Use 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."),
param="LaneTurnDesire")
self.delay_control = option_item_sp(tr("Adjust Software Delay"), "LagdToggleDelay", 5, 50,
tr("Adjust the software delay when Live Learning Steer Delay is toggled off. The default software delay value is 0.2"),
1, None, True, "", style.BUTTON_WIDTH, None, True, lambda v: f"{v / 100:.2f}s")
self.lagd_toggle = toggle_item_sp(tr("Live Learning Steer Delay"), "", param="LagdToggle")
self.items = [self.current_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label,
self.policy_label, self.refresh_item, self.clear_cache_item, self.lane_turn_desire_toggle,
self.lane_turn_value_control, self.lagd_toggle, self.delay_control]
def _update_lagd_description(self, lagd_toggle: bool):
desc = tr("Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. " +
"Keeping this on provides the stock openpilot experience.")
if lagd_toggle:
desc += f"<br>{tr('Live Steer Delay:')} {ui_state.sm['liveDelay'].lateralDelay:.3f} s"
elif ui_state.CP:
sw = float(ui_state.params.get("LagdToggleDelay", "0.2"))
cp = ui_state.CP.steerActuatorDelay
desc += f"<br>{tr('Actuator Delay:')} {cp:.2f} s + {tr('Software Delay:')} {sw:.2f} s = {tr('Total Delay:')} {cp + sw:.2f} s"
self.lagd_toggle.set_description(desc)
def _is_downloading(self):
return (self.model_manager and self.model_manager.selectedBundle and
self.model_manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.downloading)
@staticmethod
def _calculate_cache_size():
cache_size = 0.0
if os.path.exists(CUSTOM_MODEL_PATH):
cache_size = sum(os.path.getsize(os.path.join(CUSTOM_MODEL_PATH, file)) for file in os.listdir(CUSTOM_MODEL_PATH)) / (1024**2)
return cache_size
def _clear_cache(self):
def _callback(response):
if response == DialogResult.CONFIRM:
ui_state.params.put_bool("ModelManager_ClearCache", True)
self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB")
gui_app.set_modal_overlay(ConfirmDialog(tr("This will delete ALL downloaded models from the cache except the currently active model. Are you sure?"),
tr("Clear Cache")), callback=_callback)
def _handle_bundle_download_progress(self):
labels = {custom.ModelManagerSP.Model.Type.supercombo: self.supercombo_label,
custom.ModelManagerSP.Model.Type.vision: self.vision_label,
custom.ModelManagerSP.Model.Type.policy: self.policy_label}
for label in labels.values():
label.set_visible(False)
self.cancel_download_item.set_visible(False)
if not self.model_manager or (not self.model_manager.selectedBundle and not self.model_manager.activeBundle):
return
bundle = self.model_manager.selectedBundle if self._is_downloading() or (
self.model_manager.selectedBundle and self.model_manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.failed
) else self.model_manager.activeBundle
if not bundle:
return
self.download_status = bundle.status
status_changed = self.prev_download_status != self.download_status
self.prev_download_status = self.download_status
self.cancel_download_item.set_visible(bool(self.model_manager.selectedBundle) and bool(ui_state.params.get("ModelManager_DownloadIndex")))
if (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5:
self.last_cache_calc_time = current_time
self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB")
if self.download_status == custom.ModelManagerSP.DownloadStatus.downloading:
device.reset_interactive_timeout()
for model in bundle.models:
if label := labels.get(getattr(model.type, 'raw', model.type)):
label.set_visible(True)
p = model.artifact.downloadProgress
text, show, color = f"pending - {bundle.displayName}", False, rl.GRAY
if p.status == custom.ModelManagerSP.DownloadStatus.downloading:
text, show = f"{int(p.progress)}% - {bundle.displayName}", True
elif p.status in (custom.ModelManagerSP.DownloadStatus.downloaded, custom.ModelManagerSP.DownloadStatus.cached):
status_text = tr("from cache" if p.status == custom.ModelManagerSP.DownloadStatus.cached else "downloaded")
text, color = f"{bundle.displayName} - {status_text if status_changed else tr('ready')}", ON_COLOR
elif p.status == custom.ModelManagerSP.DownloadStatus.failed:
text, color = f"download failed - {bundle.displayName}", rl.RED
label.action_item.update(p.progress, text, show, color)
@staticmethod
def _show_reset_params_dialog():
def _callback(response):
if response == DialogResult.CONFIRM:
ui_state.params.remove("CalibrationParams")
ui_state.params.remove("LiveTorqueParameters")
msg = tr("Model download has started in the background. We suggest resetting calibration. Would you like to do that now?")
gui_app.set_modal_overlay(ConfirmDialog(msg, tr("Reset Calibration")), callback=_callback)
def _on_model_selected(self, result):
if result != DialogResult.CONFIRM:
return
selected_ref = self.model_dialog.selection_ref
if selected_ref == "Default":
ui_state.params.remove("ModelManager_ActiveBundle")
self._show_reset_params_dialog()
elif selected_bundle := next((bundle for bundle in self.model_manager.availableBundles if bundle.ref == selected_ref), None):
ui_state.params.put("ModelManager_DownloadIndex", selected_bundle.index)
if self.model_manager.activeBundle and selected_bundle.generation != self.model_manager.activeBundle.generation:
self._show_reset_params_dialog()
self.model_dialog = None
@staticmethod
def _bundle_to_node(bundle):
return TreeNode(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName})
def _get_folders(self, favorites):
bundles = self.model_manager.availableBundles
folders = {}
for bundle in bundles:
folders.setdefault(next((ov_ride.value for ov_ride in bundle.overrides if ov_ride.key == "folder"), ""), []).append(bundle)
folders_list = [TreeFolder("", [TreeNode("Default", {'display_name': tr("Default Model"), 'short_name': "Default"})])]
for folder, folder_bundles in sorted(folders.items(), key=lambda x: max((bundle.index for bundle in x[1]), default=-1), reverse=True):
folder_bundles.sort(key=lambda bundle: bundle.index, reverse=True)
name = folder + (f" - (Updated: {m.group(1)})" if folder_bundles and (m := re.search(r'\(([^)]*)\)[^(]*$', folder_bundles[0].displayName)) else "")
folders_list.append(TreeFolder(name, [self._bundle_to_node(bundle) for bundle in folder_bundles]))
if favorites and (fav_bundles := [bundle for bundle in bundles if bundle.ref in favorites]):
folders_list.insert(1, TreeFolder("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles]))
return folders_list
def _handle_current_model_clicked(self):
favs = ui_state.params.get("ModelManager_Favs")
favorites = set(favs.split(';')) if favs else set()
folders_list = self._get_folders(favorites)
active_ref = self.model_manager.activeBundle.ref if self.model_manager.activeBundle else "Default"
self.model_dialog = TreeOptionDialog(tr("Select a Model"), folders_list, active_ref, "ModelManager_Favs",
get_folders_fn=self._get_folders, on_exit=self._on_model_selected)
gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected)
def _update_state(self):
advanced_controls: bool = ui_state.params.get_bool("ShowAdvancedControls")
turn_desire: bool = ui_state.params.get_bool("LaneTurnDesire")
live_delay: bool = ui_state.params.get_bool("LagdToggle")
self.lane_turn_desire_toggle.action_item.set_state(turn_desire)
self.lane_turn_value_control.set_visible(turn_desire and advanced_controls)
self.lagd_toggle.action_item.set_state(live_delay)
self.delay_control.set_visible(not live_delay and advanced_controls)
new_step = int(round(100 / CV.MPH_TO_KPH)) if ui_state.is_metric else 100
if self.lane_turn_value_control.action_item.value_change_step != new_step:
self.lane_turn_value_control.action_item.value_change_step = new_step
self._update_lagd_description(live_delay)
self.model_manager = ui_state.sm["modelManagerSP"]
self._handle_bundle_download_progress()
active_name = self.model_manager.activeBundle.internalName if self.model_manager and self.model_manager.activeBundle.ref else tr("Default Model")
self.current_model_item.action_item.set_value(active_name)
if not ui_state.is_offroad():
self.current_model_item.action_item.set_enabled(False)
self.current_model_item.set_description(tr("Only available when vehicle is off, or always offroad mode is on"))
else:
self.current_model_item.action_item.set_enabled(True)
self.current_model_item.set_description("")
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,30 @@
"""
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.
"""
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class NavigationLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,46 @@
"""
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.
"""
import threading
import time
import pyray as rl
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets.button import Button, ButtonStyle
from openpilot.system.ui.widgets.network import NetworkUI, PanelType
class NetworkUISP(NetworkUI):
def __init__(self, wifi_manager):
super().__init__(wifi_manager)
self.scan_button = Button(tr("Scan"), self._scan_clicked, button_style=ButtonStyle.NORMAL, font_size=60, border_radius=30)
self.scan_button.set_rect(rl.Rectangle(0, 0, 400, 100))
self._scanning = False
self._wifi_manager.add_callbacks(networks_updated=self._on_networks_updated)
def _scan_clicked(self):
self._scanning = True
self.scan_button.set_text(tr("Scanning..."))
self.scan_button.set_enabled(False)
threading.Thread(target=self._wifi_manager._update_networks, daemon=True).start()
self._wifi_manager._request_scan()
self._wifi_manager._last_network_update = time.monotonic()
def _on_networks_updated(self, networks):
if self._scanning:
self._scanning = False
self.scan_button.set_text(tr("Scan"))
self.scan_button.set_enabled(True)
def _render(self, rect: rl.Rectangle):
super()._render(rect)
if self._current_panel == PanelType.WIFI:
self.scan_button.set_position(self._rect.x, self._rect.y + 20)
self.scan_button.render()
@@ -0,0 +1,30 @@
"""
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.
"""
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class OSMLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,202 @@
"""
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.
"""
from dataclasses import dataclass
from enum import IntEnum
import pyray as rl
from openpilot.selfdrive.ui.layouts.settings import settings as OP
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.ui.lib.application import gui_app, MousePos
from openpilot.system.ui.lib.multilang import tr_noop
from openpilot.system.ui.sunnypilot.lib.styles import style
from openpilot.system.ui.widgets.scroller_tici import Scroller
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.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.network import NetworkUISP
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.trips import TripsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle import VehicleLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout
# from openpilot.selfdrive.ui.sunnypilot.layouts.settings.navigation import NavigationLayout
OP.PANEL_COLOR = rl.Color(10, 10, 10, 255)
ICON_SIZE = 70
OP.PanelType = IntEnum( # type: ignore
"PanelType",
[es.name for es in OP.PanelType] + [
"SUNNYLINK",
"MODELS",
"STEERING",
"CRUISE",
"VISUALS",
"DISPLAY",
"OSM",
"NAVIGATION",
"TRIPS",
"VEHICLE",
],
start=0,
)
@dataclass
class PanelInfo(OP.PanelInfo):
icon: str = ""
class NavButton(Widget):
def __init__(self, parent, p_type, p_info):
super().__init__()
self.parent = parent
self.panel_type = p_type
self.panel_info = p_info
def _render(self, rect):
is_selected = self.panel_type == self.parent._current_panel
text_color = OP.TEXT_SELECTED if is_selected else OP.TEXT_NORMAL
content_x = rect.x + 90
text_size = measure_text_cached(self.parent._font_medium, self.panel_info.name, 65)
# Draw background if selected
if is_selected:
self.container_rect = rl.Rectangle(
content_x - 50, rect.y, OP.SIDEBAR_WIDTH - 50, OP.NAV_BTN_HEIGHT
)
rl.draw_rectangle_rounded(self.container_rect, 0.2, 5, OP.CLOSE_BTN_COLOR)
if self.panel_info.icon:
icon_texture = gui_app.texture(self.panel_info.icon, ICON_SIZE, ICON_SIZE, keep_aspect_ratio=True)
rl.draw_texture(icon_texture, int(content_x), int(rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2),
rl.WHITE)
content_x += ICON_SIZE + 20
# Draw button text (right-aligned)
text_pos = rl.Vector2(
content_x,
rect.y + (OP.NAV_BTN_HEIGHT - text_size.y) / 2
)
rl.draw_text_ex(self.parent._font_medium, self.panel_info.name, text_pos, 55, 0, text_color)
# Store button rect for click detection
self.panel_info.button_rect = rect
class SettingsLayoutSP(OP.SettingsLayout):
def __init__(self):
OP.SettingsLayout.__init__(self)
self._nav_items: list[Widget] = []
# Create sidebar scroller
self._sidebar_scroller = Scroller([], spacing=0, line_separator=False, pad_end=False)
# Panel configuration
wifi_manager = WifiManager()
wifi_manager.set_active(False)
self._panels = {
OP.PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_home.png"),
OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUISP(wifi_manager), icon="icons/network.png"),
OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/wifi_strength_full.png"),
OP.PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
OP.PanelType.MODELS: PanelInfo(tr_noop("Models"), ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"),
OP.PanelType.STEERING: PanelInfo(tr_noop("Steering"), SteeringLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"),
OP.PanelType.CRUISE: PanelInfo(tr_noop("Cruise"), CruiseLayout(), icon="icons/speed_limit.png"),
OP.PanelType.VISUALS: PanelInfo(tr_noop("Visuals"), VisualsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"),
OP.PanelType.DISPLAY: PanelInfo(tr_noop("Display"), DisplayLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_display.png"),
OP.PanelType.OSM: PanelInfo(tr_noop("OSM"), OSMLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"),
# OP.PanelType.NAVIGATION: PanelInfo(tr_noop("Navigation"), NavigationLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"),
OP.PanelType.TRIPS: PanelInfo(tr_noop("Trips"), TripsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
OP.PanelType.VEHICLE: PanelInfo(tr_noop("Vehicle"), VehicleLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
OP.PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_firehose.png"),
OP.PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout(), icon="icons/shell.png"),
}
def _draw_sidebar(self, rect: rl.Rectangle):
rl.draw_rectangle_rec(rect, OP.SIDEBAR_COLOR)
# Close button
close_btn_rect = rl.Rectangle(
rect.x + style.ITEM_PADDING * 3, rect.y + style.ITEM_PADDING * 2, style.CLOSE_BTN_SIZE, style.CLOSE_BTN_SIZE
)
pressed = (rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and
rl.check_collision_point_rec(rl.get_mouse_position(), close_btn_rect))
close_color = OP.CLOSE_BTN_PRESSED if pressed else OP.CLOSE_BTN_COLOR
rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color)
icon_color = rl.Color(255, 255, 255, 255) if not pressed else rl.Color(220, 220, 220, 255)
icon_dest = rl.Rectangle(
close_btn_rect.x + (close_btn_rect.width - self._close_icon.width) / 2,
close_btn_rect.y + (close_btn_rect.height - self._close_icon.height) / 2,
self._close_icon.width,
self._close_icon.height,
)
rl.draw_texture_pro(
self._close_icon,
rl.Rectangle(0, 0, self._close_icon.width, self._close_icon.height),
icon_dest,
rl.Vector2(0, 0),
0,
icon_color,
)
# Store close button rect for click detection
self._close_btn_rect = close_btn_rect
# Navigation buttons with scroller
if not self._nav_items:
for panel_type, panel_info in self._panels.items():
nav_button = NavButton(self, panel_type, panel_info)
nav_button.rect.width = rect.width - 100 # Full width minus padding
nav_button.rect.height = OP.NAV_BTN_HEIGHT
self._nav_items.append(nav_button)
self._sidebar_scroller.add_widget(nav_button)
# Draw navigation section with scroller
nav_rect = rl.Rectangle(
rect.x,
self._close_btn_rect.height + style.ITEM_PADDING * 4, # Starting Y position for nav items
rect.width,
rect.height - 300 # Remaining height after close button
)
if self._nav_items:
self._sidebar_scroller.render(nav_rect)
return
def _handle_mouse_release(self, mouse_pos: MousePos) -> bool:
# Check close button
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
if self._close_callback:
self._close_callback()
return True
# Check navigation buttons
for panel_type, panel_info in self._panels.items():
if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect) and self._sidebar_scroller.scroll_panel.is_touch_valid():
self.set_current_panel(panel_type)
return True
return False
def show_event(self):
super().show_event()
self._panels[self._current_panel].instance.show_event()
self._sidebar_scroller.show_event()
@@ -0,0 +1,96 @@
"""
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.
"""
import os
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder
DESCRIPTIONS = {
'disable_updates_offroad': tr_noop(
"When enabled, automatic software updates will be off.<br><b>This requires a reboot to take effect.</b>"
),
'disable_updates_onroad': tr_noop(
"Please enable \"Always Offroad\" mode or turn off the vehicle to adjust these toggles."
)
}
class SoftwareLayoutSP(SoftwareLayout):
def __init__(self):
super().__init__()
self.disable_updates_toggle = toggle_item_sp(
lambda: tr("Disable Updates"),
description="",
initial_state=ui_state.params.get_bool("DisableUpdates"),
callback=self._on_disable_updates_toggled,
)
self._scroller.add_widget(self.disable_updates_toggle)
def _handle_reboot(self, result):
if result == DialogResult.CONFIRM:
ui_state.params.put_bool("DisableUpdates", self.disable_updates_toggle.action_item.get_state())
ui_state.params.put_bool("DoReboot", True)
else:
self.disable_updates_toggle.action_item.set_state(ui_state.params.get_bool("DisableUpdates"))
def _on_disable_updates_toggled(self, enabled):
dialog = ConfirmDialog(tr("System reboot required for changes to take effect. Reboot now?"), tr("Reboot"))
gui_app.set_modal_overlay(dialog, callback=self._handle_reboot)
def _on_select_branch(self):
current_git_branch = ui_state.params.get("GitBranch") or ""
branches_str = ui_state.params.get("UpdaterAvailableBranches") or ""
branches = [b for b in branches_str.split(",") if b]
current_target = ui_state.params.get("UpdaterTargetBranch") or ""
top_level_branches = [current_git_branch, "release-mici", "release-tizi", "staging", "dev", "master"]
if HARDWARE.get_device_type() == "tici":
top_level_branches = ["release-tici", "staging-tici"]
branches = [b for b in branches if b.endswith("-tici")]
top_level_nodes = [TreeNode(b, {'display_name': b}) for b in top_level_branches if b in branches]
remaining_branches = [b for b in branches if b not in top_level_branches]
prebuilt_nodes = [TreeNode(b, {'display_name': b}) for b in remaining_branches if b.endswith("-prebuilt")]
non_prebuilt_nodes = [TreeNode(b, {'display_name': b}) for b in remaining_branches if not b.endswith("-prebuilt")]
folders = [
TreeFolder("", top_level_nodes),
TreeFolder("Prebuilt Branches", prebuilt_nodes),
TreeFolder("Non-Prebuilt Branches", non_prebuilt_nodes),
]
def _on_branch_selected(result):
if result == DialogResult.CONFIRM and self._branch_dialog is not None:
selection = self._branch_dialog.selection_ref
if selection:
ui_state.params.put("UpdaterTargetBranch", selection)
self._branch_btn.action_item.set_value(selection)
os.system("pkill -SIGUSR1 -f system.updated.updated")
self._branch_dialog = None
self._branch_dialog = TreeOptionDialog(tr("Select a branch"), folders, current_target, "",
on_exit=_on_branch_selected)
gui_app.set_modal_overlay(self._branch_dialog, callback=_on_branch_selected)
def _update_state(self):
super()._update_state()
show_advanced = ui_state.params.get_bool("ShowAdvancedControls")
self.disable_updates_toggle.action_item.set_enabled(ui_state.is_offroad())
self.disable_updates_toggle.set_visible(show_advanced)
disable_updates_desc = tr(DESCRIPTIONS["disable_updates_offroad"] if ui_state.is_offroad() else DESCRIPTIONS["disable_updates_onroad"])
self.disable_updates_toggle.set_description(disable_updates_desc)
@@ -0,0 +1,30 @@
"""
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.
"""
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class SteeringLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,343 @@
"""
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.
"""
from cereal import custom
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.sunnylink_pairing_dialog import SunnylinkPairingDialog
from openpilot.system.ui.widgets.button import ButtonStyle, Button
from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.list_view import button_item, dual_button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller, LineSeparator
from openpilot.system.ui.widgets import Widget, DialogResult
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
import pyray as rl
if gui_app.sunnypilot_ui():
from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item
class SunnylinkHeader(Widget):
def __init__(self):
super().__init__()
self._title = UnifiedLabel(
text="🚀 sunnylink 🚀",
font_size=90,
font_weight=FontWeight.AUDIOWIDE,
text_color=rl.WHITE,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
wrap_text=False,
elide=False
)
self._description = UnifiedLabel(
text=tr("For secure backup, restore, and remote configuration"),
font_size=40,
font_weight=FontWeight.LIGHT,
text_color=rl.Color(0, 255, 0, 255), # Green
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
wrap_text=True,
elide=False
)
self._sponsor_msg = UnifiedLabel(
text=tr("Sponsorship isn't required for basic backup/restore") + "\n" +
tr("Click the Sponsor button for more details"),
font_size=35,
font_weight=FontWeight.LIGHT,
text_color=rl.Color(255, 165, 0, 255), # Orange
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
wrap_text=True,
elide=False
)
self._padding = 20
self._spacing = 10
def set_parent_rect(self, parent_rect: rl.Rectangle) -> None:
super().set_parent_rect(parent_rect)
content_width = int(parent_rect.width - (self._padding * 2))
title_height = self._title.get_content_height(content_width)
desc_height = self._description.get_content_height(content_width)
sponsor_height = self._sponsor_msg.get_content_height(content_width)
total_height = (self._padding + title_height + self._spacing +
desc_height + self._spacing + sponsor_height + self._padding)
self._rect.width = parent_rect.width
self._rect.height = total_height
def _render(self, rect: rl.Rectangle):
content_width = rect.width - (self._padding * 2)
current_y = rect.y + self._padding
# Render title
title_height = self._title.get_content_height(int(content_width))
title_rect = rl.Rectangle(rect.x + self._padding, current_y, content_width, title_height)
self._title.render(title_rect)
current_y += title_height + self._spacing
# Render description
desc_height = self._description.get_content_height(int(content_width))
desc_rect = rl.Rectangle(rect.x + self._padding, current_y, content_width, desc_height)
self._description.render(desc_rect)
current_y += desc_height + self._spacing
# Render sponsor message
sponsor_height = self._sponsor_msg.get_content_height(int(content_width))
sponsor_rect = rl.Rectangle(rect.x + self._padding, current_y, content_width, sponsor_height)
self._sponsor_msg.render(sponsor_rect)
class SunnylinkDescriptionItem(Widget):
def __init__(self):
super().__init__()
self._description = UnifiedLabel(
text="",
font_size=40,
font_weight=FontWeight.LIGHT,
text_color=rl.WHITE,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
wrap_text=True,
elide=False,
)
self._padding = 20
def set_parent_rect(self, parent_rect: rl.Rectangle) -> None:
super().set_parent_rect(parent_rect)
desc_height = self._description.get_content_height(int(parent_rect.width)) + self._padding * 2
self._rect.width = parent_rect.width
self._rect.height = desc_height
def set_text(self, text: str):
self._description.set_text(text)
def set_color(self, color: rl.Color):
self._description.set_text_color(color)
def _render(self, rect: rl.Rectangle):
content_width = rect.width - (self._padding * 2)
desc_height = self._description.get_content_height(int(content_width))
desc_rect = rl.Rectangle(rect.x + self._padding, rect.y, content_width, desc_height)
self._description.render(desc_rect)
class SunnylinkLayout(Widget):
def __init__(self):
super().__init__()
self._sunnylink_pairing_dialog: SunnylinkPairingDialog | None = None
self._restore_in_progress = False
self._backup_in_progress = False
self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled")
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=False, spacing=0)
def _initialize_items(self):
self._sunnylink_toggle = toggle_item_sp(
title=tr("Enable sunnylink"),
description=tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that."),
param="SunnylinkEnabled",
callback=self._sunnylink_toggle_callback
)
self._sunnylink_description = SunnylinkDescriptionItem()
self._sunnylink_description.set_visible(False)
self._sponsor_btn = button_item(
title=tr("Sponsor Status"),
button_text=tr("SPONSOR"),
description=tr(
"Become a sponsor of sunnypilot to get early access to sunnylink features when they become available."),
callback=lambda: self._handle_pair_btn(False)
)
self._pair_btn = button_item(
title=tr("Pair GitHub Account"),
button_text=tr("Not Paired"),
description=tr(
"Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink."),
callback=lambda: self._handle_pair_btn(True)
)
self._sunnylink_uploader_toggle = toggle_item_sp(
title=tr("Enable sunnylink uploader (infrastructure test)"),
description=tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. ") +
tr("(Only for highest tiers, and does NOT bring ANY benefit to you yet. We are just testing data volume.)"),
param="EnableSunnylinkUploader"
)
self._sunnylink_backup_restore_buttons = dual_button_item(
description="",
left_text=tr("Backup Settings"),
right_text=tr("Restore Settings"),
left_callback=self._handle_backup_btn,
right_callback=self._handle_restore_btn
)
self._backup_btn: Button = self._sunnylink_backup_restore_buttons.action_item.left_button # store for easy individual access
self._restore_btn: Button = self._sunnylink_backup_restore_buttons.action_item.right_button
self._backup_btn.set_button_style(ButtonStyle.NORMAL)
self._restore_btn.set_button_style(ButtonStyle.PRIMARY)
items = [
SunnylinkHeader(),
LineSeparator(),
self._sunnylink_toggle,
self._sunnylink_description,
LineSeparator(),
self._sponsor_btn,
LineSeparator(),
self._pair_btn,
LineSeparator(),
self._sunnylink_uploader_toggle,
LineSeparator(),
self._sunnylink_backup_restore_buttons
]
return items
@staticmethod
def _get_sunnylink_dongle_id() -> str | None:
return str(ui_state.params.get("SunnylinkDongleId") or (lambda: tr("N/A")))
def _handle_pair_btn(self, sponsor_pairing: bool = False):
sunnylink_dongle_id = self._get_sunnylink_dongle_id()
if sunnylink_dongle_id == UNREGISTERED_SUNNYLINK_DONGLE_ID:
gui_app.set_modal_overlay(alert_dialog(message=tr("sunnylink Dongle ID not found. ") +
tr("This may be due to weak internet connection or sunnylink registration issue. ") +
tr("Please reboot and try again.")))
elif not self._sunnylink_pairing_dialog:
self._sunnylink_pairing_dialog = SunnylinkPairingDialog(sponsor_pairing)
gui_app.set_modal_overlay(self._sunnylink_pairing_dialog, callback=lambda result: setattr(self, '_sunnylink_pairing_dialog', None))
def _handle_backup_btn(self):
backup_dialog = ConfirmDialog(text=tr("Are you sure you want to backup your current sunnypilot settings?"), confirm_text="Backup")
gui_app.set_modal_overlay(backup_dialog, callback=self._backup_handler)
def _handle_restore_btn(self):
self._restore_btn.set_enabled(False)
restore_dialog = ConfirmDialog(text=tr("Are you sure you want to restore the last backed up sunnypilot settings?"), confirm_text="Restore")
gui_app.set_modal_overlay(restore_dialog, callback=self._restore_handler)
def _backup_handler(self, dialog_result: int):
if dialog_result == DialogResult.CONFIRM:
self._backup_in_progress = True
self._backup_btn.set_enabled(False)
ui_state.params.put_bool("BackupManager_CreateBackup", True)
def _restore_handler(self, dialog_result: int):
if dialog_result == DialogResult.CONFIRM:
self._restore_in_progress = True
self._restore_btn.set_enabled(False)
ui_state.params.put("BackupManager_RestoreVersion", "latest")
def handle_backup_restore_progress(self):
sunnylink_backup_manager = ui_state.sm["backupManagerSP"]
backup_status = sunnylink_backup_manager.backupStatus
restore_status = sunnylink_backup_manager.restoreStatus
backup_progress = sunnylink_backup_manager.backupProgress
restore_progress = sunnylink_backup_manager.restoreProgress
if self._backup_in_progress:
self._restore_btn.set_enabled(False)
self._backup_btn.set_enabled(False)
if backup_status == custom.BackupManagerSP.Status.inProgress:
self._backup_in_progress = True
text = tr(f"Backing up {backup_progress}%")
self._backup_btn.set_text(text)
elif backup_status == custom.BackupManagerSP.Status.failed:
self._backup_in_progress = False
self._backup_btn.set_enabled(not ui_state.is_onroad())
self._backup_btn.set_text(tr("Backup Failed"))
elif (backup_status == custom.BackupManagerSP.Status.completed or
(backup_status == custom.BackupManagerSP.Status.idle and backup_progress == 100.0)):
self._backup_in_progress = False
dialog = alert_dialog(tr("Settings backup completed."))
gui_app.set_modal_overlay(dialog)
self._backup_btn.set_enabled(not ui_state.is_onroad())
elif self._restore_in_progress:
self._restore_btn.set_enabled(False)
self._backup_btn.set_enabled(False)
if restore_status == custom.BackupManagerSP.Status.inProgress:
self._restore_in_progress = True
text = tr(f"Restoring {restore_progress}%")
self._restore_btn.set_text(text)
elif restore_status == custom.BackupManagerSP.Status.failed:
self._restore_in_progress = False
self._restore_btn.set_enabled(not ui_state.is_onroad())
self._restore_btn.set_text(tr("Restore Failed"))
dialog = alert_dialog(tr("Unable to restore the settings, try again later."))
gui_app.set_modal_overlay(dialog)
elif (restore_status == custom.BackupManagerSP.Status.completed or
(restore_status == custom.BackupManagerSP.Status.idle and restore_progress == 100.0)):
self._restore_in_progress = False
dialog = alert_dialog(tr("Settings restored. Confirm to restart the interface."))
gui_app.set_modal_overlay(dialog, callback=lambda: gui_app.request_close())
else:
can_enable = self._sunnylink_enabled and not ui_state.is_onroad()
self._backup_btn.set_enabled(can_enable)
self._backup_btn.set_text(tr("Backup Settings"))
self._restore_btn.set_enabled(can_enable)
self._restore_btn.set_text(tr("Restore Settings"))
def _sunnylink_toggle_callback(self, state: bool):
if state:
description = tr(
"Welcome back!! We're excited to see you've enabled sunnylink again!")
color = rl.Color(0, 255, 0, 255) # Green
else:
description = ("😢 " + tr("Not going to lie, it's sad to see you disabled sunnylink") +
tr(", but we'll be here when you're ready to come back."))
color = rl.Color(255, 165, 0, 255) # Orange
self._sunnylink_description.set_text(description)
self._sunnylink_description.set_color(color)
self._sunnylink_description.set_visible(True)
self._sunnylink_toggle.show_description(False)
def _update_state(self):
super()._update_state()
self._sunnylink_enabled = ui_state.params.get_bool("SunnylinkEnabled")
self._sunnylink_toggle.set_right_value(tr("Dongle ID") + ": " + self._get_sunnylink_dongle_id())
self._sunnylink_toggle.action_item.set_enabled(not ui_state.is_onroad())
self._sunnylink_toggle.action_item.set_state(self._sunnylink_enabled)
self._sunnylink_uploader_toggle.action_item.set_enabled(self._sunnylink_enabled)
self.handle_backup_restore_progress()
sponsor_btn_text = tr("THANKS ♥") if ui_state.sunnylink_state.is_sponsor() else tr("SPONSOR")
tier_name = ui_state.sunnylink_state.get_sponsor_tier().name.capitalize() or tr("Not Sponsor")
self._sponsor_btn.action_item.set_text(sponsor_btn_text)
self._sponsor_btn.action_item.set_value(tier_name, ui_state.sunnylink_state.get_sponsor_tier_color())
self._sponsor_btn.action_item.set_enabled(self._sunnylink_enabled)
pair_btn_text = tr("Paired") if ui_state.sunnylink_state.is_paired() else tr("Not Paired")
self._pair_btn.action_item.set_text(pair_btn_text)
self._pair_btn.action_item.set_enabled(self._sunnylink_enabled)
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
super().show_event()
self._scroller.show_event()
self._sunnylink_description.set_visible(False)
@@ -0,0 +1,30 @@
"""
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.
"""
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class TripsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,67 @@
"""
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.
"""
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.list_view import ButtonAction
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.factory import BrandSettingsFactory
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.platform_selector import PlatformSelector, LegendWidget
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP
class VehicleLayout(Widget):
def __init__(self):
super().__init__()
self._brand_settings = None
self._brand_items = []
self._current_brand = None
self._platform_selector = PlatformSelector(self._update_brand_settings)
self._vehicle_item = ListItemSP(title=self._platform_selector.text, action_item=ButtonAction(text=tr("Select")),
callback=self._platform_selector._on_clicked)
self._vehicle_item.title_color = self._platform_selector.color
self._legend_widget = LegendWidget(self._platform_selector)
self.items = [self._vehicle_item, self._legend_widget]
self._scroller = Scroller(self.items, line_separator=True, spacing=0)
@staticmethod
def get_brand():
if bundle := ui_state.params.get("CarPlatformBundle"):
return bundle.get("brand", "")
elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK":
return ui_state.CP.brand
return ""
def _update_brand_settings(self):
self._vehicle_item._title = self._platform_selector.text
self._vehicle_item.title_color = self._platform_selector.color
vehicle_text = tr("Remove") if ui_state.params.get("CarPlatformBundle") else tr("Select")
self._vehicle_item.action_item.set_text(vehicle_text)
brand = self.get_brand()
if brand != self._current_brand:
self._current_brand = brand
self._brand_settings = BrandSettingsFactory.create_brand_settings(brand)
self._brand_items = self._brand_settings.items if self._brand_settings else []
self.items = [self._vehicle_item, self._legend_widget] + self._brand_items
self._scroller = Scroller(self.items, line_separator=True, spacing=0)
def _update_state(self):
self._update_brand_settings()
if self._brand_settings:
self._brand_settings.update_settings()
self._platform_selector.refresh()
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,16 @@
"""
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.
"""
import abc
class BrandSettings(abc.ABC):
def __init__(self):
self.items = []
@abc.abstractmethod
def update_settings(self) -> None:
"""Update the settings based on the current vehicle brand."""
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class BodySettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class ChryslerSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.body import BodySettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.chrysler import ChryslerSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.ford import FordSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.gm import GMSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.honda import HondaSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.hyundai import HyundaiSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.mazda import MazdaSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.nissan import NissanSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.psa import PSASettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.rivian import RivianSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.subaru import SubaruSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.tesla import TeslaSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.toyota import ToyotaSettings
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.volkswagen import VolkswagenSettings
class BrandSettingsFactory:
_BRAND_MAP: dict[str, type[BrandSettings]] = {
"body": BodySettings,
"chrysler": ChryslerSettings,
"ford": FordSettings,
"gm": GMSettings,
"honda": HondaSettings,
"hyundai": HyundaiSettings,
"mazda": MazdaSettings,
"nissan": NissanSettings,
"psa": PSASettings,
"rivian": RivianSettings,
"subaru": SubaruSettings,
"tesla": TeslaSettings,
"toyota": ToyotaSettings,
"volkswagen": VolkswagenSettings,
}
@staticmethod
def create_brand_settings(brand: str) -> BrandSettings | None:
cls = BrandSettingsFactory._BRAND_MAP.get(brand)
return cls() if cls is not None else None
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class FordSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class GMSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class HondaSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,59 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp
from opendbc.car.hyundai.values import CAR, CANFD_UNSUPPORTED_LONGITUDINAL_CAR, UNSUPPORTED_LONGITUDINAL_CAR
class HyundaiSettings(BrandSettings):
def __init__(self):
super().__init__()
self.alpha_long_available = False
tuning_texts = [tr("Off"), tr("Dynamic"), tr("Predictive")]
self.longitudinal_tuning_item = multiple_button_item_sp(tr("Custom Longitudinal Tuning"), "", tuning_texts,
button_width=300, callback=self._on_tuning_selected,
param="HyundaiLongitudinalTuning", inline=False)
self.items = [self.longitudinal_tuning_item]
@staticmethod
def _on_tuning_selected(index):
ui_state.params.put("HyundaiLongitudinalTuning", index)
def update_settings(self):
self.alpha_long_available = False
bundle = ui_state.params.get("CarPlatformBundle")
if bundle:
platform = bundle.get("platform")
self.alpha_long_available = CAR[platform] not in (UNSUPPORTED_LONGITUDINAL_CAR | CANFD_UNSUPPORTED_LONGITUDINAL_CAR)
elif ui_state.CP:
self.alpha_long_available = ui_state.CP.alphaLongitudinalAvailable
tuning_param = int(ui_state.params.get("HyundaiLongitudinalTuning") or "0")
long_enabled = ui_state.has_longitudinal_control
long_tuning_descs = [
tr("Your vehicle will use the Default longitudinal tuning."),
tr("Your vehicle will use the Dynamic longitudinal tuning."),
tr("Your vehicle will use the Predictive longitudinal tuning."),
]
long_tuning_desc = long_tuning_descs[tuning_param] if tuning_param < len(long_tuning_descs) else long_tuning_descs[0]
longitudinal_tuning_disabled = not ui_state.is_offroad() or not long_enabled
if longitudinal_tuning_disabled:
if not ui_state.is_offroad():
long_tuning_desc = tr("This feature is unavailable while the car is onroad.")
elif not long_enabled:
long_tuning_desc = tr("This feature is unavailable because sunnypilot Longitudinal Control (Alpha) is not enabled.")
self.longitudinal_tuning_item.action_item.set_enabled(not longitudinal_tuning_disabled)
self.longitudinal_tuning_item.set_description(long_tuning_desc)
self.longitudinal_tuning_item.show_description(True)
self.longitudinal_tuning_item.action_item.set_selected_button(tuning_param)
self.longitudinal_tuning_item.set_visible(self.alpha_long_available)
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class MazdaSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class NissanSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class PSASettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class RivianSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,54 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
from opendbc.car.subaru.values import CAR, SubaruFlags
class SubaruSettings(BrandSettings):
def __init__(self):
super().__init__()
self.has_stop_and_go = False
self.stop_and_go_toggle = toggle_item_sp(tr("Stop and Go (Beta)"), "", param="SubaruStopAndGo", callback=self._on_toggle_changed)
self.stop_and_go_manual_parking_brake_toggle = toggle_item_sp(tr("Stop and Go for Manual Parking Brake (Beta)"), "",
param="SubaruStopAndGoManualParkingBrake", callback=self._on_toggle_changed)
self.items = [self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle]
def _on_toggle_changed(self, _):
self.update_settings()
def stop_and_go_disabled_msg(self):
if not self.has_stop_and_go:
return tr("This feature is currently not available on this platform.")
elif not ui_state.is_offroad():
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.")
return ""
def update_settings(self):
bundle = ui_state.params.get("CarPlatformBundle")
if bundle:
platform = bundle.get("platform")
config = CAR[platform].config
self.has_stop_and_go = not (config.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
elif ui_state.CP:
self.has_stop_and_go = not (ui_state.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
disabled_msg = self.stop_and_go_disabled_msg()
descriptions = [
tr("Experimental feature to enable auto-resume during stop-and-go for certain supported Subaru platforms."),
tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. " +
"Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!")
]
for toggle, desc in zip([self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle], descriptions, strict=True):
toggle.action_item.set_enabled(self.has_stop_and_go and ui_state.is_offroad())
toggle.set_description(f"<b>{disabled_msg}</b><br><br>{desc}" if disabled_msg else desc)
@@ -0,0 +1,43 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
COOP_STEERING_MIN_KMH = 23
OEM_STEERING_MIN_KMH = 48
KM_TO_MILE = 0.621371
class TeslaSettings(BrandSettings):
def __init__(self):
super().__init__()
self.coop_steering_toggle = toggle_item_sp(tr("Cooperative Steering (Beta)"), "", param="TeslaCoopSteering")
self.items = [self.coop_steering_toggle]
def update_settings(self):
is_metric = ui_state.is_metric
unit = "km/h" if is_metric else "mph"
display_value_coop = COOP_STEERING_MIN_KMH if is_metric else round(COOP_STEERING_MIN_KMH * KM_TO_MILE)
display_value_oem = OEM_STEERING_MIN_KMH if is_metric else round(OEM_STEERING_MIN_KMH * KM_TO_MILE)
coop_steering_disabled_msg = tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.")
coop_steering_warning = tr(f"Warning: May experience steering oscillations below {display_value_oem} {unit} during turns, " +
"recommend disabling this feature if you experience these.")
coop_steering_desc = (
f"<b>{coop_steering_warning}</b><br><br>" +
f"{tr('Allows the driver to provide limited steering input while openpilot is engaged.')}<br>" +
f"{tr(f'Only works above {display_value_coop} {unit}.')}"
)
if not ui_state.is_offroad():
coop_steering_desc = f"<b>{coop_steering_disabled_msg}</b><br><br>{coop_steering_desc}"
self.coop_steering_toggle.set_description(coop_steering_desc)
self.coop_steering_toggle.action_item.set_enabled(ui_state.is_offroad())
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class ToyotaSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,15 @@
"""
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.
"""
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
class VolkswagenSettings(BrandSettings):
def __init__(self):
super().__init__()
def update_settings(self):
pass
@@ -0,0 +1,138 @@
"""
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.
"""
import json
import os
import pyray as rl
from collections.abc import Callable
from functools import partial
from openpilot.common.basedir import BASEDIR
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import DialogResult, Widget
from openpilot.system.ui.widgets.button import Button, ButtonStyle
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.sunnypilot.lib.styles import style
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder
from openpilot.selfdrive.ui.ui_state import ui_state
CAR_LIST_JSON_OUT = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "car", "car_list.json")
class LegendWidget(Widget):
def __init__(self, platform_selector):
super().__init__()
self.set_rect(rl.Rectangle(0, 0, 0, 350))
self._platform_selector = platform_selector
self._font = gui_app.font(FontWeight.NORMAL)
self._bold_font = gui_app.font(FontWeight.BOLD)
def _render(self, rect):
x = rect.x + 20
y = rect.y + 20
rl.draw_text_ex(self._font, tr("Select vehicle to force fingerprint manually."), rl.Vector2(x, y), 40, 0, style.ITEM_DESC_TEXT_COLOR)
y += 80
rl.draw_text_ex(self._font, tr("Colors represent vehicle fingerprint status:"), rl.Vector2(x, y), 40, 0, style.ITEM_DESC_TEXT_COLOR)
y += 80
items = [
(style.GREEN, tr("Fingerprinted automatically")),
(style.BLUE, tr("Manually selected fingerprint")),
(style.YELLOW, tr("Not fingerprinted or manually selected")),
]
for color, text in items:
p_color = self._platform_selector.color
is_active = p_color.r == color.r and p_color.g == color.g and p_color.b == color.b and p_color.a == color.a
rl.draw_rectangle(int(x), int(y + 5), 30, 30, color)
font = self._bold_font if is_active else self._font
text_color = rl.WHITE if is_active else style.ITEM_DESC_TEXT_COLOR
rl.draw_text_ex(font, f"- {text}", rl.Vector2(x + 50, y - 7), 40, 0, text_color)
y += 50
class PlatformSelector(Button):
def __init__(self, on_platform_change: Callable[[], None] | None = None):
super().__init__(tr("Vehicle"), self._on_clicked, button_style=ButtonStyle.NORMAL)
self.set_rect(rl.Rectangle(0, 0, 0, 120))
with open(CAR_LIST_JSON_OUT) as car_list_json:
self._platforms = json.load(car_list_json)
self._on_platform_change = on_platform_change
self.refresh()
@property
def text(self):
return self._label._text
def set_parent_rect(self, parent_rect):
super().set_parent_rect(parent_rect)
self._rect.width = parent_rect.width
def _on_clicked(self):
if ui_state.params.get("CarPlatformBundle"):
ui_state.params.remove("CarPlatformBundle")
self.refresh()
if self._on_platform_change:
self._on_platform_change()
else:
self._show_platform_dialog()
def _set_platform(self, platform_name):
if data := self._platforms.get(platform_name):
ui_state.params.put("CarPlatformBundle", {**data, "name": platform_name})
self.refresh()
if self._on_platform_change:
self._on_platform_change()
def _on_platform_selected(self, dialog, res):
if res == DialogResult.CONFIRM and dialog.selection_ref:
offroad_msg = tr("This setting will take effect immediately.") if ui_state.is_offroad else \
tr("This setting will take effect once the device enters offroad state.")
confirm_dialog = ConfirmDialog(offroad_msg, tr("Confirm"))
callback = partial(self._confirm_platform, dialog.selection_ref)
gui_app.set_modal_overlay(confirm_dialog, callback=callback)
def _confirm_platform(self, platform_name, res):
if res == DialogResult.CONFIRM:
self._set_platform(platform_name)
def _show_platform_dialog(self):
platforms = sorted(self._platforms.keys())
makes = sorted({self._platforms[p].get('make') for p in platforms})
folders = [TreeFolder(make, [TreeNode(p, {
'display_name': p,
'search_tags': f"{p} {self._platforms[p].get('make')} {' '.join(map(str, self._platforms[p].get('year', [])))} {self._platforms[p].get('model', p)}"
}) for p in platforms if self._platforms[p].get('make') == make]) for make in makes]
dialog = TreeOptionDialog(
tr("Select a vehicle"),
folders,
search_title=tr("Search your vehicle"),
search_subtitle=tr("Enter model year (e.g., 2021) and model (Toyota Corolla):"),
search_funcs=[lambda node: node.data.get('display_name', ''), lambda node: node.data.get('search_tags', '')]
)
callback = partial(self._on_platform_selected, dialog)
dialog.on_exit = callback
gui_app.set_modal_overlay(dialog, callback=callback)
def refresh(self):
self.color = style.YELLOW
self._platform = tr("Unrecognized Vehicle")
self.set_text(tr("No vehicle selected"))
if bundle := ui_state.params.get("CarPlatformBundle"):
self._platform = bundle.get("name", "")
self.set_text(self._platform)
self.color = style.BLUE
elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK":
self._platform = ui_state.CP.carFingerprint
self.set_text(self._platform)
self.color = style.GREEN
self.set_enabled(True)
@@ -0,0 +1,30 @@
"""
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.
"""
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class VisualsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/angle_tuning_settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
AngleTunningSettings::AngleTunningSettings(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
main_layout->setSpacing(20);
// Back button
PanelBackButton *back = new PanelBackButton();
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
auto *list = new ListWidgetSP(this, false);
main_layout->addWidget(new QWidget());
enableHkgAngleSmoothingFactor = new ExpandableToggleRow("EnableHkgTuningAngleSmoothingFactor", tr("HKG Angle Smoothing Factor"), tr("Applies EMA (Exponential Moving Average) to the desired angle steering and avoid overcorrections."), "../assets/offroad/icon_blank.png");
list->addItem(enableHkgAngleSmoothingFactor);
auto first_row = new QHBoxLayout();
hkgTuningOverridingCycles = new OptionControlSP("HkgTuningOverridingCycles", tr("Override Ramp-Down Cycles"), tr("Number of cycles to ramp down the current amount of torque on the steering wheel.<br/>A smaller value means a faster override by the user (less effort)"), "../assets/offroad/icon_blank.png", {10, 30}, 1);
connect(hkgTuningOverridingCycles, &OptionControlSP::updateLabels, hkgTuningOverridingCycles, [=]() {
this->updateToggles(offroad);
});
first_row->addWidget(hkgTuningOverridingCycles);
hkgAngleMinTorque = new OptionControlSP("HkgTuningAngleMinTorqueReductionGain", tr("Override Steering Effort"), tr("Sets the steering effort percentage used when the driver is overriding lateral control.<br/>Higher values increase resistance and make the wheel feel stiffer."), "../assets/offroad/icon_blank.png", {5, 60}, 1);
connect(hkgAngleMinTorque, &OptionControlSP::updateLabels, hkgAngleMinTorque, [=]() {
this->updateToggles(offroad);
});
first_row->addWidget(hkgAngleMinTorque);
list->addItem(first_row);
auto second_row = new QHBoxLayout();
hkgAngleActiveTorque = new OptionControlSP("HkgTuningAngleActiveTorqueReductionGain", tr("Min Active Torque"), tr("Torque applied when lateral control is active but the vehicle is not turning.<br/>Used to maintain lane centering on straight paths when no user input is detected."), "../assets/offroad/icon_blank.png", {10, 100}, 1);
connect(hkgAngleActiveTorque, &OptionControlSP::updateLabels, hkgAngleActiveTorque, [=]() {
this->updateToggles(offroad);
});
second_row->addWidget(hkgAngleActiveTorque);
hkgAngleMaxTorque = new OptionControlSP("HkgTuningAngleMaxTorqueReductionGain", tr("Max Torque Allowance"), tr("Sets the maximum torque reduction percentage the controller can apply during normal lateral control.<br/>"), "../assets/offroad/icon_blank.png", {10, 100}, 1);
connect(hkgAngleMaxTorque, &OptionControlSP::updateLabels, hkgAngleMaxTorque, [=]() {
this->updateToggles(offroad);
});
second_row->addWidget(hkgAngleMaxTorque);
list->addItem(second_row);
QObject::connect(uiState(), &UIState::offroadTransition, this, &AngleTunningSettings::updateToggles);
main_layout->addWidget(new ScrollViewSP(list, this));
auto *warning = new QLabel(tr("Reboot required for settings to apply; Tap on each setting to see more details."));
warning->setStyleSheet("font-size: 30px; font-weight: 500; font-family: 'Noto Color Emoji'; color: orange;");
main_layout->addWidget(warning, 0, Qt::AlignCenter);
}
void AngleTunningSettings::showEvent(QShowEvent *event) {
updateToggles(offroad);
}
void AngleTunningSettings::updateToggles(bool _offroad) {
auto HkgAngleSmoothingFactorValue = params.getBool("EnableHkgTuningAngleSmoothingFactor");
enableHkgAngleSmoothingFactor->toggleFlipped(HkgAngleSmoothingFactorValue);
auto HkgAngleMinTorqueValue = QString::fromStdString(params.get("HkgTuningAngleMinTorqueReductionGain")).toInt();
hkgAngleMinTorque->setLabel(QString::number(HkgAngleMinTorqueValue)+"%");
auto HkgAngleActiveTorqueValue = QString::fromStdString(params.get("HkgTuningAngleActiveTorqueReductionGain")).toInt();
hkgAngleActiveTorque->setLabel(QString::number(HkgAngleActiveTorqueValue)+"%");
auto HkgAngleMaxTorqueValue = QString::fromStdString(params.get("HkgTuningAngleMaxTorqueReductionGain")).toInt();
hkgAngleMaxTorque->setLabel(QString::number(HkgAngleMaxTorqueValue)+"%");
auto HkgTuningOverridingCyclesValue = QString::fromStdString(params.get("HkgTuningOverridingCycles")).toInt();
hkgTuningOverridingCycles->setLabel(QString::number(HkgTuningOverridingCyclesValue));
offroad = _offroad;
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
class AngleTunningSettings : public QWidget {
Q_OBJECT
public:
explicit AngleTunningSettings(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles(bool _offroad);
private:
Params params;
bool offroad;
ExpandableToggleRow* enableHkgAngleSmoothingFactor;
OptionControlSP* hkgAngleMinTorque;
OptionControlSP* hkgAngleActiveTorque;
OptionControlSP* hkgAngleMaxTorque;
OptionControlSP* hkgTuningOverridingCycles;
};
+29
View File
@@ -0,0 +1,29 @@
"""
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.
"""
from cereal import messaging, custom
from openpilot.common.params import Params
from openpilot.sunnypilot.sunnylink.sunnylink_state import SunnylinkState
class UIStateSP:
def __init__(self):
self.params = Params()
self.sm_services_ext = [
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
"gpsLocation", "liveTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP", "liveDelay"
]
self.sunnylink_state = SunnylinkState()
def update(self) -> None:
self.sunnylink_state.start()
def update_params(self) -> None:
CP_SP_bytes = self.params.get("CarParamsSPPersistent")
if CP_SP_bytes is not None:
self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP)
self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled")
+113 -37
View File
@@ -41,47 +41,47 @@ def put_update_params(params: Params):
params.put("UpdaterTargetBranch", BRANCH_NAME)
def setup_homescreen(click, pm: PubMaster):
def setup_homescreen(click, pm: PubMaster, scroll=None):
pass
def setup_homescreen_update_available(click, pm: PubMaster):
def setup_homescreen_update_available(click, pm: PubMaster, scroll=None):
params = Params()
params.put_bool("UpdateAvailable", True)
put_update_params(params)
setup_offroad_alert(click, pm)
def setup_settings(click, pm: PubMaster):
def setup_settings(click, pm: PubMaster, scroll=None):
click(100, 100)
def close_settings(click, pm: PubMaster):
click(240, 216)
def close_settings(click, pm: PubMaster, scroll=None):
click(140, 120)
def setup_settings_network(click, pm: PubMaster):
def setup_settings_network(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
click(278, 450)
def setup_settings_network_advanced(click, pm: PubMaster):
setup_settings_network(click, pm)
def setup_settings_network_advanced(click, pm: PubMaster, scroll=None):
setup_settings_network(click, pm, scroll=scroll)
click(1880, 100)
def setup_settings_toggles(click, pm: PubMaster):
def setup_settings_toggles(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
click(278, 600)
click(278, 620)
def setup_settings_software(click, pm: PubMaster):
def setup_settings_software(click, pm: PubMaster, scroll=None):
put_update_params(Params())
setup_settings(click, pm)
click(278, 720)
click(278, 730)
def setup_settings_software_download(click, pm: PubMaster):
def setup_settings_software_download(click, pm: PubMaster, scroll=None):
params = Params()
# setup_settings_software but with "DOWNLOAD" button to test long text
params.put("UpdaterState", "idle")
@@ -89,13 +89,13 @@ def setup_settings_software_download(click, pm: PubMaster):
setup_settings_software(click, pm)
def setup_settings_software_release_notes(click, pm: PubMaster):
setup_settings_software(click, pm)
def setup_settings_software_release_notes(click, pm: PubMaster, scroll=None):
setup_settings_software(click, pm, scroll=scroll)
click(588, 110) # expand description for current version
def setup_settings_software_branch_switcher(click, pm: PubMaster):
setup_settings_software(click, pm)
def setup_settings_software_branch_switcher(click, pm: PubMaster, scroll=None):
setup_settings_software(click, pm, scroll=scroll)
params = Params()
params.put("UpdaterAvailableBranches", f"master,nightly,release,{BRANCH_NAME}")
params.put("GitBranch", BRANCH_NAME) # should be on top
@@ -103,30 +103,32 @@ def setup_settings_software_branch_switcher(click, pm: PubMaster):
click(1984, 449)
def setup_settings_firehose(click, pm: PubMaster):
def setup_settings_firehose(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
click(278, 845)
scroll(-20, 278, 950)
click(278, 850)
def setup_settings_developer(click, pm: PubMaster):
def setup_settings_developer(click, pm: PubMaster, scroll=None):
CP = car.CarParams()
CP.alphaLongitudinalAvailable = True # show alpha long control toggle
Params().put("CarParamsPersistent", CP.to_bytes())
setup_settings(click, pm)
scroll(-20, 278, 950)
click(278, 950)
def setup_keyboard(click, pm: PubMaster):
setup_settings_developer(click, pm)
def setup_keyboard(click, pm: PubMaster, scroll=None):
setup_settings_developer(click, pm, scroll=scroll)
click(1930, 470)
def setup_pair_device(click, pm: PubMaster):
def setup_pair_device(click, pm: PubMaster, scroll=None):
click(1950, 800)
def setup_offroad_alert(click, pm: PubMaster):
def setup_offroad_alert(click, pm: PubMaster, scroll=None):
put_update_params(Params())
set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C')
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal')
@@ -137,22 +139,73 @@ def setup_offroad_alert(click, pm: PubMaster):
close_settings(click, pm)
def setup_confirmation_dialog(click, pm: PubMaster):
def setup_confirmation_dialog(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
click(1985, 791) # reset calibration
def setup_experimental_mode_description(click, pm: PubMaster):
def setup_experimental_mode_description(click, pm: PubMaster, scroll=None):
setup_settings_toggles(click, pm)
click(1200, 280) # expand description for experimental mode
def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster):
setup_settings_developer(click, pm)
click(650, 960) # toggle openpilot longitudinal control
def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster, scroll=None):
setup_settings_developer(click, pm, scroll=scroll)
click(650, 960) # toggle sunnypilot longitudinal control
def setup_onroad(click, pm: PubMaster):
def setup_settings_sunnylink(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
click(278, 510)
def setup_settings_models(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
click(278, 840)
def setup_settings_steering(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
click(278, 950)
def setup_settings_cruise(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
scroll(-4, 278, 950)
click(278, 860)
def setup_settings_visuals(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
scroll(-20, 278, 950)
click(278, 330)
def setup_settings_display(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
scroll(-20, 278, 950)
click(278, 420)
def setup_settings_osm(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
scroll(-20, 278, 950)
click(278, 520)
def setup_settings_trips(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
scroll(-20, 278, 950)
click(278, 630)
def setup_settings_vehicle(click, pm: PubMaster, scroll=None):
setup_settings(click, pm)
scroll(-20, 278, 950)
click(278, 750)
def setup_onroad(click, pm: PubMaster, scroll=None):
ds = messaging.new_message('deviceState')
ds.deviceState.started = True
@@ -173,7 +226,7 @@ def setup_onroad(click, pm: PubMaster):
time.sleep(0.05)
def setup_onroad_sidebar(click, pm: PubMaster):
def setup_onroad_sidebar(click, pm: PubMaster, scroll=None):
setup_onroad(click, pm)
click(100, 100) # open sidebar
@@ -192,23 +245,23 @@ def setup_onroad_alert(click, pm: PubMaster, size: log.SelfdriveState.AlertSize,
time.sleep(0.05)
def setup_onroad_small_alert(click, pm: PubMaster):
def setup_onroad_small_alert(click, pm: PubMaster, scroll=None):
setup_onroad_alert(click, pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)
def setup_onroad_medium_alert(click, pm: PubMaster):
def setup_onroad_medium_alert(click, pm: PubMaster, scroll=None):
setup_onroad_alert(click, pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)
def setup_onroad_full_alert(click, pm: PubMaster):
def setup_onroad_full_alert(click, pm: PubMaster, scroll=None):
setup_onroad_alert(click, pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)
def setup_onroad_full_alert_multiline(click, pm: PubMaster):
def setup_onroad_full_alert_multiline(click, pm: PubMaster, scroll=None):
setup_onroad_alert(click, pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)
def setup_onroad_full_alert_long_text(click, pm: PubMaster):
def setup_onroad_full_alert_long_text(click, pm: PubMaster, scroll=None):
setup_onroad_alert(click, pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)
@@ -243,6 +296,19 @@ CASES = {
"onroad_full_alert_long_text": setup_onroad_full_alert_long_text,
}
# sunnypilot cases
CASES.update({
"settings_sunnylink": setup_settings_sunnylink,
"settings_models": setup_settings_models,
"settings_steering": setup_settings_steering,
"settings_cruise": setup_settings_cruise,
"settings_visuals": setup_settings_visuals,
"settings_display": setup_settings_display,
"settings_osm": setup_settings_osm,
"settings_trips": setup_settings_trips,
"settings_vehicle": setup_settings_vehicle,
})
class TestUI:
def __init__(self):
@@ -276,11 +342,20 @@ class TestUI:
time.sleep(0.01)
pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs)
def scroll(self, clicks: int, x, y, *args, **kwargs):
if clicks == 0:
return
click = -1 if clicks < 0 else 1 # -1 = down, 1 = up
for _ in range(abs(clicks)):
pyautogui.scroll(click, self.ui.left + x, self.ui.top + y, *args, **kwargs) # scroll for individual clicks since we need to delay between clicks
time.sleep(0.01) # small delay between scroll clicks to work properly
time.sleep(2) # wait for scroll to fully settle
@with_processes(["ui"])
def test_ui(self, name, setup_case):
self.setup()
time.sleep(UI_DELAY) # wait for UI to start
setup_case(self.click, self.pm)
setup_case(self.click, self.pm, self.scroll)
self.screenshot(name)
@@ -294,6 +369,7 @@ def create_screenshots():
with OpenpilotPrefix():
params = Params()
params.put("DongleId", "123456789012345")
params.put("SunnylinkDongleId", "123456789012345")
# Set branch name
params.put("UpdaterCurrentDescription", VERSION)
+7 -2
View File
@@ -12,6 +12,8 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeState
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.hardware import HARDWARE, PC
from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP
BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50
@@ -21,7 +23,7 @@ class UIStatus(Enum):
OVERRIDE = "override"
class UIState:
class UIState(UIStateSP):
_instance: 'UIState | None' = None
def __new__(cls):
@@ -31,6 +33,7 @@ class UIState:
return cls._instance
def _initialize(self):
UIStateSP.__init__(self)
self.params = Params()
self.sm = messaging.SubMaster(
[
@@ -55,7 +58,7 @@ class UIState:
"carControl",
"liveParameters",
"rawAudioData",
]
] + self.sm_services_ext
)
self.prime_state = PrimeState()
@@ -111,6 +114,7 @@ class UIState:
if time.monotonic() - self._param_update_time > 5.0:
self.update_params()
device.update()
UIStateSP.update(self)
def _update_state(self) -> None:
# Handle panda states updates
@@ -180,6 +184,7 @@ class UIState:
self.has_longitudinal_control = self.params.get_bool("AlphaLongitudinalEnabled")
else:
self.has_longitudinal_control = self.CP.openpilotLongitudinalControl
UIStateSP.update_params(self)
self._param_update_time = time.monotonic()
+1 -16
View File
@@ -1,5 +1,3 @@
import glob
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations')
lenv = env.Clone()
lenvCython = envCython.Clone()
@@ -23,12 +21,6 @@ thneed_src_qcom = thneed_src_common + ["thneed/thneed_qcom2.cc"]
thneed_src_pc = thneed_src_common + ["thneed/thneed_pc.cc"]
thneed_src = thneed_src_qcom if arch == "larch64" else thneed_src_pc
# SNPE except on Mac and ARM Linux
snpe_lib = []
if arch != "Darwin" and arch != "aarch64":
common_src += ['runners/snpemodel.cc']
snpe_lib += ['SNPE']
# OpenCL is a framework on Mac
if arch == "Darwin":
frameworks += ['OpenCL']
@@ -40,20 +32,13 @@ for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transfor
for xenv in (lenv, lenvCython):
xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"')
# Compile cython
snpe_rpath_qcom = "/data/pythonpath/third_party/snpe/larch64"
snpe_rpath_pc = f"{Dir('#').abspath}/third_party/snpe/x86_64-linux-clang"
snpe_rpath = lenvCython['RPATH'] + [snpe_rpath_qcom if arch == "larch64" else snpe_rpath_pc]
cython_libs = envCython["LIBS"] + libs
snpemodel_lib = lenv.Library('snpemodel', ['runners/snpemodel.cc'])
commonmodel_lib = lenv.Library('commonmodel', common_src)
lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=cython_libs, FRAMEWORKS=frameworks)
lenvCython.Program('runners/snpemodel_pyx.so', 'runners/snpemodel_pyx.pyx', LIBS=[snpemodel_lib, snpe_lib, *cython_libs], FRAMEWORKS=frameworks, RPATH=snpe_rpath)
lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks)
if arch == "larch64":
thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[common, 'OpenCL', 'dl'])
thneedmodel_lib = env.Library('thneedmodel', ['runners/thneedmodel.cc'])
lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, common, 'dl', 'OpenCL'])
lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, common, 'dl', 'OpenCL'])
-5
View File
@@ -4,20 +4,15 @@ from openpilot.sunnypilot.modeld.runners.runmodel_pyx import RunModel, Runtime
assert Runtime
USE_THNEED = int(os.getenv('USE_THNEED', str(int(TICI))))
USE_SNPE = int(os.getenv('USE_SNPE', str(int(TICI))))
class ModelRunner(RunModel):
THNEED = 'THNEED'
SNPE = 'SNPE'
ONNX = 'ONNX'
def __new__(cls, paths, *args, **kwargs):
if ModelRunner.THNEED in paths and USE_THNEED:
from openpilot.sunnypilot.modeld.runners.thneedmodel_pyx import ThneedModel as Runner
runner_type = ModelRunner.THNEED
elif ModelRunner.SNPE in paths and USE_SNPE:
from openpilot.sunnypilot.modeld.runners.snpemodel_pyx import SNPEModel as Runner
runner_type = ModelRunner.SNPE
elif ModelRunner.ONNX in paths:
from openpilot.sunnypilot.modeld.runners.onnxmodel import ONNXModel as Runner
runner_type = ModelRunner.ONNX
-1
View File
@@ -1,4 +1,3 @@
#pragma once
#include "sunnypilot/modeld/runners/runmodel.h"
#include "sunnypilot/modeld/runners/snpemodel.h"
-116
View File
@@ -1,116 +0,0 @@
#pragma clang diagnostic ignored "-Wexceptions"
#include "sunnypilot/modeld/runners/snpemodel.h"
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "common/util.h"
#include "common/timing.h"
void PrintErrorStringAndExit() {
std::cerr << zdl::DlSystem::getLastErrorString() << std::endl;
std::exit(EXIT_FAILURE);
}
SNPEModel::SNPEModel(const std::string path, float *_output, size_t _output_size, int runtime, bool _use_tf8, cl_context context) {
output = _output;
output_size = _output_size;
use_tf8 = _use_tf8;
#ifdef QCOM2
if (runtime == USE_GPU_RUNTIME) {
snpe_runtime = zdl::DlSystem::Runtime_t::GPU;
} else if (runtime == USE_DSP_RUNTIME) {
snpe_runtime = zdl::DlSystem::Runtime_t::DSP;
} else {
snpe_runtime = zdl::DlSystem::Runtime_t::CPU;
}
assert(zdl::SNPE::SNPEFactory::isRuntimeAvailable(snpe_runtime));
#endif
model_data = util::read_file(path);
assert(model_data.size() > 0);
// load model
std::unique_ptr<zdl::DlContainer::IDlContainer> container = zdl::DlContainer::IDlContainer::open((uint8_t*)model_data.data(), model_data.size());
if (!container) { PrintErrorStringAndExit(); }
LOGW("loaded model with size: %lu", model_data.size());
// create model runner
zdl::SNPE::SNPEBuilder snpe_builder(container.get());
while (!snpe) {
#ifdef QCOM2
snpe = snpe_builder.setOutputLayers({})
.setRuntimeProcessor(snpe_runtime)
.setUseUserSuppliedBuffers(true)
.setPerformanceProfile(zdl::DlSystem::PerformanceProfile_t::HIGH_PERFORMANCE)
.build();
#else
snpe = snpe_builder.setOutputLayers({})
.setUseUserSuppliedBuffers(true)
.setPerformanceProfile(zdl::DlSystem::PerformanceProfile_t::HIGH_PERFORMANCE)
.build();
#endif
if (!snpe) std::cerr << zdl::DlSystem::getLastErrorString() << std::endl;
}
// create output buffer
zdl::DlSystem::UserBufferEncodingFloat ub_encoding_float;
zdl::DlSystem::IUserBufferFactory &ub_factory = zdl::SNPE::SNPEFactory::getUserBufferFactory();
const auto &output_tensor_names_opt = snpe->getOutputTensorNames();
if (!output_tensor_names_opt) throw std::runtime_error("Error obtaining output tensor names");
const auto &output_tensor_names = *output_tensor_names_opt;
assert(output_tensor_names.size() == 1);
const char *output_tensor_name = output_tensor_names.at(0);
const zdl::DlSystem::TensorShape &buffer_shape = snpe->getInputOutputBufferAttributes(output_tensor_name)->getDims();
if (output_size != 0) {
assert(output_size == buffer_shape[1]);
} else {
output_size = buffer_shape[1];
}
std::vector<size_t> output_strides = {output_size * sizeof(float), sizeof(float)};
output_buffer = ub_factory.createUserBuffer(output, output_size * sizeof(float), output_strides, &ub_encoding_float);
output_map.add(output_tensor_name, output_buffer.get());
}
void SNPEModel::addInput(const std::string name, float *buffer, int size) {
const int idx = inputs.size();
const auto &input_tensor_names_opt = snpe->getInputTensorNames();
if (!input_tensor_names_opt) throw std::runtime_error("Error obtaining input tensor names");
const auto &input_tensor_names = *input_tensor_names_opt;
const char *input_tensor_name = input_tensor_names.at(idx);
const bool input_tf8 = use_tf8 && strcmp(input_tensor_name, "input_img") == 0; // TODO: This is a terrible hack, get rid of this name check both here and in onnx_runner.py
LOGW("adding index %d: %s", idx, input_tensor_name);
zdl::DlSystem::UserBufferEncodingFloat ub_encoding_float;
zdl::DlSystem::UserBufferEncodingTf8 ub_encoding_tf8(0, 1./255); // network takes 0-1
zdl::DlSystem::IUserBufferFactory &ub_factory = zdl::SNPE::SNPEFactory::getUserBufferFactory();
zdl::DlSystem::UserBufferEncoding *input_encoding = input_tf8 ? (zdl::DlSystem::UserBufferEncoding*)&ub_encoding_tf8 : (zdl::DlSystem::UserBufferEncoding*)&ub_encoding_float;
const auto &buffer_shape_opt = snpe->getInputDimensions(input_tensor_name);
const zdl::DlSystem::TensorShape &buffer_shape = *buffer_shape_opt;
size_t size_of_input = input_tf8 ? sizeof(uint8_t) : sizeof(float);
std::vector<size_t> strides(buffer_shape.rank());
strides[strides.size() - 1] = size_of_input;
size_t product = 1;
for (size_t i = 0; i < buffer_shape.rank(); i++) product *= buffer_shape[i];
size_t stride = strides[strides.size() - 1];
for (size_t i = buffer_shape.rank() - 1; i > 0; i--) {
stride *= buffer_shape[i];
strides[i-1] = stride;
}
auto input_buffer = ub_factory.createUserBuffer(buffer, product*size_of_input, strides, input_encoding);
input_map.add(input_tensor_name, input_buffer.get());
inputs.push_back(std::unique_ptr<SNPEModelInput>(new SNPEModelInput(name, buffer, size, std::move(input_buffer))));
}
void SNPEModel::execute() {
if (!snpe->execute(input_map, output_map)) {
PrintErrorStringAndExit();
}
}
-52
View File
@@ -1,52 +0,0 @@
#pragma once
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include <memory>
#include <string>
#include <utility>
#include <DlContainer/IDlContainer.hpp>
#include <DlSystem/DlError.hpp>
#include <DlSystem/ITensor.hpp>
#include <DlSystem/ITensorFactory.hpp>
#include <DlSystem/IUserBuffer.hpp>
#include <DlSystem/IUserBufferFactory.hpp>
#include <SNPE/SNPE.hpp>
#include <SNPE/SNPEBuilder.hpp>
#include <SNPE/SNPEFactory.hpp>
#include "sunnypilot/modeld/runners/runmodel.h"
struct SNPEModelInput : public ModelInput {
std::unique_ptr<zdl::DlSystem::IUserBuffer> snpe_buffer;
SNPEModelInput(const std::string _name, float *_buffer, int _size, std::unique_ptr<zdl::DlSystem::IUserBuffer> _snpe_buffer) : ModelInput(_name, _buffer, _size), snpe_buffer(std::move(_snpe_buffer)) {}
void setBuffer(float *_buffer, int _size) {
ModelInput::setBuffer(_buffer, _size);
assert(snpe_buffer->setBufferAddress(_buffer) == true);
}
};
class SNPEModel : public RunModel {
public:
SNPEModel(const std::string path, float *_output, size_t _output_size, int runtime, bool use_tf8 = false, cl_context context = NULL);
void addInput(const std::string name, float *buffer, int size);
void execute();
private:
std::string model_data;
#ifdef QCOM2
zdl::DlSystem::Runtime_t snpe_runtime;
#endif
// snpe model stuff
std::unique_ptr<zdl::SNPE::SNPE> snpe;
zdl::DlSystem::UserBufferMap input_map;
zdl::DlSystem::UserBufferMap output_map;
std::unique_ptr<zdl::DlSystem::IUserBuffer> output_buffer;
bool use_tf8;
float *output;
size_t output_size;
};
-9
View File
@@ -1,9 +0,0 @@
# distutils: language = c++
from libcpp.string cimport string
from msgq.visionipc.visionipc cimport cl_context
cdef extern from "sunnypilot/modeld/runners/snpemodel.h":
cdef cppclass SNPEModel:
SNPEModel(string, float*, size_t, int, bool, cl_context)
@@ -1,17 +0,0 @@
# distutils: language = c++
# cython: c_string_encoding=ascii, language_level=3
import os
from libcpp cimport bool
from libcpp.string cimport string
from .snpemodel cimport SNPEModel as cppSNPEModel
from openpilot.sunnypilot.modeld.models.commonmodel_pyx cimport CLContext
from openpilot.sunnypilot.modeld.runners.runmodel_pyx cimport RunModel
from openpilot.sunnypilot.modeld.runners.runmodel cimport RunModel as cppRunModel
os.environ['ADSP_LIBRARY_PATH'] = "/data/pythonpath/third_party/snpe/dsp/"
cdef class SNPEModel(RunModel):
def __cinit__(self, string path, float[:] output, int runtime, bool use_tf8, CLContext context):
self.model = <cppRunModel *> new cppSNPEModel(path, &output[0], len(output), runtime, use_tf8, context.context)
+1 -1
View File
@@ -116,7 +116,7 @@ class ModelCache:
class ModelFetcher:
"""Handles fetching and caching of model data from remote source"""
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v9.json"
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v10.json"
def __init__(self, params: Params):
self.params = params
+2 -2
View File
@@ -19,8 +19,8 @@ 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 = 11
REQUIRED_MIN_SELECTOR_VERSION = 11
CURRENT_SELECTOR_VERSION = 12
REQUIRED_MIN_SELECTOR_VERSION = 12
USE_ONNX = os.getenv('USE_ONNX', PC)
+4
View File
@@ -63,6 +63,9 @@ class ModelManagerSP:
f.write(chunk)
bytes_downloaded += len(chunk)
if not self.params.get("ModelManager_DownloadIndex"):
raise Exception("Download cancelled")
if total_size > 0:
progress = (bytes_downloaded / total_size) * 100
model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloading
@@ -176,6 +179,7 @@ class ModelManagerSP:
cloudlog.exception(e)
finally:
self.params.remove("ModelManager_DownloadIndex")
self.selected_bundle = None
if self.params.get("ModelManager_ClearCache"):
self.clear_model_cache()
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a258a022f0ab90368327d899ed4fb85b47dd0e35c93508e35851d9c3184528c0
size 36382
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8623dbc5c7dd043a91d98777cb423cfd116014ed6390af6d7d00c1f8dea3c6e8
size 1181
+16 -4
View File
@@ -4,21 +4,27 @@ 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.
"""
import time
import cereal.messaging as messaging
from cereal import log, custom
from opendbc.car import structs
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral
class ControlsExt:
class ControlsExt(ModelStateBase):
def __init__(self, CP: structs.CarParams, params: Params):
ModelStateBase.__init__(self)
self.CP = CP
self.params = params
self._param_update_time: float = 0.0
self.blinker_pause_lateral = BlinkerPauseLateral()
self.get_params_sp()
cloudlog.info("controlsd_ext is waiting for CarParamsSP")
self.CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP)
@@ -27,8 +33,14 @@ class ControlsExt:
self.sm_services_ext = ['radarState', 'selfdriveStateSP']
self.pm_services_ext = ['carControlSP']
def get_params_sp(self) -> None:
self.blinker_pause_lateral.get_params()
def get_params_sp(self, sm: messaging.SubMaster) -> None:
if time.monotonic() - self._param_update_time > PARAMS_UPDATE_PERIOD:
self.blinker_pause_lateral.get_params()
if self.CP.lateralTuning.which() == 'torque':
self.lat_delay = get_lat_delay(self.params, sm["liveDelay"].lateralDelay)
self._param_update_time = time.monotonic()
def get_lat_active(self, sm: messaging.SubMaster) -> bool:
if self.blinker_pause_lateral.update(sm['carState']):
+3 -8
View File
@@ -3,7 +3,6 @@ import os
import random
import time
from datetime import datetime, timedelta
from pathlib import Path
import jwt
from openpilot.common.api.base import BaseApi
@@ -81,23 +80,19 @@ class SunnylinkApi(BaseApi):
if sunnylink_dongle_id not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID):
return sunnylink_dongle_id
privkey_path = Path(f"{Paths.persist_root()}/comma/id_rsa")
pubkey_path = Path(f"{Paths.persist_root()}/comma/id_rsa.pub")
jwt_algo, private_key, public_key = BaseApi.get_key_pair()
start_time = time.monotonic()
successful_registration = False
if not pubkey_path.is_file():
if not public_key:
sunnylink_dongle_id = UNREGISTERED_SUNNYLINK_DONGLE_ID
self._status_update("Public key not found, setting dongle ID to unregistered.")
else:
Params().put("LastSunnylinkPingTime", 0) # Reset the last ping time to 0 if we are trying to register
with pubkey_path.open() as f1, privkey_path.open() as f2:
public_key = f1.read()
private_key = f2.read()
backoff = 1
while True:
register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm='RS256')
register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm=jwt_algo)
try:
if verbose or time.monotonic() - start_time < timeout / 2:
self._status_update("Registering device to sunnylink...")
+25 -7
View File
@@ -1,5 +1,10 @@
#!/usr/bin/env python3
"""
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.
"""
from __future__ import annotations
import base64
@@ -32,9 +37,11 @@ LOCAL_PORT_WHITELIST = {8022}
SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload"
SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require a change on sidebar.cc
DISALLOW_LOG_UPLOAD = threading.Event()
METADATA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "params_metadata.json")
params = Params()
def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None:
cloudlog.info("sunnylinkd.handle_long_poll started")
sm = messaging.SubMaster(['deviceState'])
@@ -180,16 +187,30 @@ def getParamsAllKeys() -> list[str]:
@dispatcher.add_method
def getParamsAllKeysV1() -> dict[str, str]:
try:
with open(METADATA_PATH) as f:
metadata = json.load(f)
except Exception:
cloudlog.exception("sunnylinkd.getParamsAllKeysV1.exception")
metadata = {}
available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()]
params_dict: dict[str, list[dict[str, str | bool | int | None]]] = {"params": []}
params_dict: dict[str, list[dict[str, str | bool | int | object | dict | None]]] = {"params": []}
for key in available_keys:
value = get_param_as_byte(key, get_default=True)
params_dict["params"].append({
param_entry = {
"key": key,
"type": int(params.get_type(key).value),
"default_value": base64.b64encode(value).decode('utf-8') if value else None,
})
}
if key in metadata:
meta_copy = metadata[key].copy()
param_entry["_extra"] = meta_copy
params_dict["params"].append(param_entry)
return {"keys": json.dumps(params_dict.get("params", []))}
@@ -238,10 +259,7 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local
cloudlog.debug("athena.startLocalProxy.starting")
ws = create_connection(
remote_ws_uri,
header={"Authorization": f"Bearer {sunnylink_api.get_token()}"},
enable_multithread=True,
sslopt={"cert_reqs": ssl.CERT_NONE}
remote_ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True, sslopt={"cert_reqs": ssl.CERT_NONE}
)
return start_local_proxy_shim(global_end_event, local_port, ws)
File diff suppressed because it is too large Load Diff
+221
View File
@@ -0,0 +1,221 @@
"""
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.
"""
from enum import IntEnum
import threading
import time
import json
import pyray as rl
from cereal import messaging
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID, SunnylinkApi
from openpilot.system.ui.sunnypilot.lib.styles import style
class RoleType(IntEnum):
READONLY = 0
SPONSOR = 1
ADMIN = 2
class SponsorTier(IntEnum):
FREE = 0
NOVICE = 1
SUPPORTER = 2
CONTRIBUTOR = 3
BENEFACTOR = 4
GUARDIAN = 5
class User:
device_id: str
user_id: str
created_at: int
updated_at: int
token_hash: str
def __init__(self, json_data):
self.device_id = json_data.get("device_id")
self.user_id = json_data.get("user_id")
self.created_at = json_data.get("created_at")
self.updated_at = json_data.get("updated_at")
self.token_hash = json_data.get("token_hash")
class Role:
role_type: str
role_tier: str
def __init__(self, json_data):
self.role_type = json_data.get("role_type")
self.role_tier = json_data.get("role_tier")
def _parse_roles(roles: str) -> list[Role]:
lst_roles = []
try:
roles_list = json.loads(roles)
for r in roles_list:
try:
role = Role(r)
lst_roles.append(role)
except Exception as e:
cloudlog.exception(f"Failed to parse role {r}: {e}")
return lst_roles
except Exception as e:
cloudlog.exception(f"Error parsing roles: {e}")
return []
def _parse_users(users: str) -> list[User]:
lst_users = []
try:
users_list = json.loads(users)
for u in users_list:
try:
user = User(u)
lst_users.append(user)
except Exception as e:
cloudlog.exception(f"Failed to parse user {u}: {e}")
return lst_users
except Exception as e:
cloudlog.exception(f"Error parsing users: {e}")
return []
class SunnylinkState:
FETCH_INTERVAL = 5.0 # seconds between API calls
API_TIMEOUT = 10.0 # seconds for API requests
SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread
NOT_PAIRED_USERNAMES = ["unregisteredsponsor", "temporarysponsor"]
def __init__(self):
self._params = Params()
self._lock = threading.Lock()
self._running = False
self._thread = None
self._sm = messaging.SubMaster(['deviceState'])
self._roles: list[Role] = []
self._users: list[User] = []
self.sponsor_tier: SponsorTier = SponsorTier.FREE
self.sunnylink_dongle_id = self._params.get("SunnylinkDongleId")
self._api = SunnylinkApi(self.sunnylink_dongle_id)
self._load_initial_state()
def _load_initial_state(self) -> None:
roles_cache = self._params.get("SunnylinkCache_Roles")
users_cache = self._params.get("SunnylinkCache_Users")
if roles_cache is not None:
self._roles = _parse_roles(roles_cache)
self.sponsor_tier = self._get_highest_tier()
if users_cache is not None:
self._users = _parse_users(users_cache)
def _get_highest_tier(self) -> SponsorTier:
role_tier = SponsorTier.FREE
for role in self._roles:
try:
if RoleType[role.role_type.upper()] == RoleType.SPONSOR:
role_tier = max(role_tier, SponsorTier[role.role_tier.upper()])
except Exception as e:
cloudlog.exception(f"Error parsing role {role}: {e} for dongle id {self.sunnylink_dongle_id}")
return role_tier
def _fetch_roles(self) -> None:
if not self.sunnylink_dongle_id or self.sunnylink_dongle_id == UNREGISTERED_SUNNYLINK_DONGLE_ID:
return
try:
token = self._api.get_token()
response = self._api.api_get(f"device/{self.sunnylink_dongle_id}/roles", method='GET', access_token=token)
if response.status_code == 200:
self._roles = _parse_roles(response.text)
self._params.put("SunnylinkCache_Roles", response.text)
sponsor_tier = self._get_highest_tier()
with self._lock:
if sponsor_tier != self.sponsor_tier:
self.sponsor_tier = sponsor_tier
cloudlog.info(f"Sunnylink sponsor tier updated to {sponsor_tier.name}")
except Exception as e:
cloudlog.exception(f"Failed to fetch sunnylink roles: {e} for dongle id {self.sunnylink_dongle_id}")
def _fetch_users(self) -> None:
if not self.sunnylink_dongle_id or self.sunnylink_dongle_id == UNREGISTERED_SUNNYLINK_DONGLE_ID:
return
try:
token = self._api.get_token()
response = self._api.api_get(f"device/{self.sunnylink_dongle_id}/users", method='GET', access_token=token)
if response.status_code == 200:
users = response.text
self._params.put("SunnylinkCache_Users", users)
with self._lock:
_parse_users(users)
except Exception as e:
cloudlog.exception(f"Failed to fetch sunnylink users: {e} for dongle id {self.sunnylink_dongle_id}")
def _worker_thread(self) -> None:
while self._running:
if self.is_connected():
self._fetch_roles()
self._fetch_users()
for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)):
if not self._running:
break
time.sleep(self.SLEEP_INTERVAL)
def start(self) -> None:
if self._thread and self._thread.is_alive():
return
self._running = True
self._thread = threading.Thread(target=self._worker_thread, daemon=True)
self._thread.start()
def stop(self) -> None:
self._running = False
if self._thread and self._thread.is_alive():
self._thread.join(timeout=1.0)
def get_sponsor_tier(self) -> SponsorTier:
with self._lock:
return self.sponsor_tier
def is_sponsor(self) -> bool:
with self._lock:
is_sponsor = any(role.role_type.upper() == RoleType.SPONSOR.name and role.role_tier.upper() != SponsorTier.FREE.name
for role in self._roles)
return is_sponsor
def is_paired(self) -> bool:
with self._lock:
is_paired = any(user.user_id not in self.NOT_PAIRED_USERNAMES for user in self._users)
return is_paired
def is_connected(self) -> bool:
network_type = self._sm["deviceState"].networkType
return bool(network_type != 0)
def get_sponsor_tier_color(self) -> rl.Color:
tier = self.get_sponsor_tier()
if tier == SponsorTier.GUARDIAN:
return rl.Color(255, 215, 0, 255)
elif tier == SponsorTier.BENEFACTOR:
return rl.Color(60, 179, 113, 255)
elif tier == SponsorTier.CONTRIBUTOR:
return rl.Color(70, 130, 180, 255)
elif tier == SponsorTier.SUPPORTER:
return rl.Color(147, 112, 219, 255)
else:
return style.ITEM_TEXT_VALUE_COLOR
def __del__(self):
self.stop()
@@ -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.
"""
import json
from openpilot.sunnypilot.sunnylink.athena.sunnylinkd import getParamsAllKeysV1, METADATA_PATH
def test_get_params_all_keys_v1():
"""
Test the getParamsAllKeysV1 API endpoint.
Why:
This endpoint is used by the UI (and potentially external tools) to fetch the list of
available parameters along with their metadata (titles, descriptions, options, constraints).
We need to ensure it returns the correct structure and that the metadata from
params_metadata.json is correctly merged into the response.
Expected:
- The response should contain a "keys" field which is a JSON string of a list of parameters.
- Each parameter object should have "key", "type", "default_value", and optionally "_extra".
- The "_extra" field should contain the rich metadata (title, options, min/max, etc.) matching
the source of truth (params_metadata.json).
"""
response = getParamsAllKeysV1()
assert "keys" in response
keys_json = response["keys"]
params_list = json.loads(keys_json)
assert isinstance(params_list, list)
assert len(params_list) > 0
# Check structure of first item
first_param = params_list[0]
assert "key" in first_param
assert "type" in first_param
assert "default_value" in first_param
if "_extra" in first_param:
assert isinstance(first_param["_extra"], dict)
assert "default" not in first_param["_extra"]
assert "type" not in first_param["_extra"]
# Load the source of truth
with open(METADATA_PATH) as f:
metadata = json.load(f)
# Verify that the API response matches the metadata file for a few sample keys
# This ensures the plumbing is working without being brittle to content changes
# 1. Check a key that should have metadata
keys_with_metadata = [k for k in params_list if k["key"] in metadata]
assert len(keys_with_metadata) > 0, "No parameters found that match metadata keys"
for param in keys_with_metadata[:5]: # Check first 5 matches
key = param["key"]
expected_meta = metadata[key]
assert "_extra" in param, f"Parameter {key} should have _extra field"
actual_meta = param["_extra"]
# Verify all fields in JSON are present in the API response
for meta_key, meta_val in expected_meta.items():
assert meta_key in actual_meta, f"Missing {meta_key} in API response for {key}"
assert actual_meta[meta_key] == meta_val, f"Mismatch for {key}.{meta_key}: expected {meta_val}, got {actual_meta[meta_key]}"
# 2. Check that we are correctly serving options if they exist
params_with_options = [k for k in keys_with_metadata if "options" in k.get("_extra", {})]
if params_with_options:
param = params_with_options[0]
key = param["key"]
assert isinstance(param["_extra"]["options"], list), f"Options for {key} should be a list"
assert param["_extra"]["options"] == metadata[key]["options"]
# 3. Check that we are correctly serving numeric constraints if they exist
params_with_constraints = [k for k in keys_with_metadata if "min" in k.get("_extra", {})]
if params_with_constraints:
param = params_with_constraints[0]
key = param["key"]
assert param["_extra"]["min"] == metadata[key]["min"]
assert param["_extra"]["max"] == metadata[key]["max"]
assert param["_extra"]["step"] == metadata[key]["step"]
@@ -0,0 +1,202 @@
"""
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.
"""
import json
import os
import pytest
from openpilot.common.params import Params
from openpilot.sunnypilot.sunnylink.athena.sunnylinkd import METADATA_PATH
def test_metadata_json_exists():
"""
Test that the params_metadata.json file exists at the expected path.
Why:
The metadata file is the source of truth for parameter descriptions, options, and constraints.
If it's missing, the UI will not be able to display rich information for parameters.
Expected:
The file should exist at sunnypilot/sunnylink/params_metadata.json.
"""
assert os.path.exists(METADATA_PATH), f"Metadata file not found at {METADATA_PATH}"
def test_metadata_json_valid():
"""
Test that the params_metadata.json file contains valid JSON.
Why:
Invalid JSON will cause the metadata loading to fail, potentially crashing the UI or
resulting in missing metadata.
Expected:
The file content should be parseable as a JSON object (dictionary).
"""
with open(METADATA_PATH) as f:
try:
data = json.load(f)
except json.JSONDecodeError:
pytest.fail("Metadata file is not valid JSON")
assert isinstance(data, dict), "Metadata root must be a dictionary"
def test_all_params_have_metadata():
"""
Test that every parameter in the codebase has a corresponding entry in params_metadata.json.
Why:
We want to ensure 100% coverage of parameter metadata. Any parameter added to the codebase
should also be documented in the metadata file.
Expected:
There should be no parameters in Params() that are missing from the metadata file.
If this fails, run 'python3 sunnypilot/sunnylink/tools/update_params_metadata.py'.
"""
params = Params()
all_keys = [k.decode('utf-8') for k in params.all_keys()]
with open(METADATA_PATH) as f:
metadata = json.load(f)
missing_keys = [key for key in all_keys if key not in metadata]
if missing_keys:
pytest.fail(
f"The following parameters are missing from metadata: {missing_keys}. "
+ "Please run 'python3 sunnypilot/sunnylink/tools/update_params_metadata.py' to update."
)
def test_metadata_keys_exist_in_params():
"""
Test that all keys in params_metadata.json actually exist in the codebase.
Why:
We want to avoid stale metadata for parameters that have been removed or renamed.
This keeps the metadata file clean and relevant.
Expected:
There should be no keys in the metadata file that are not present in Params().
This prints a warning rather than failing, as it's less critical than missing metadata.
"""
params = Params()
all_keys = {k.decode('utf-8') for k in params.all_keys()}
with open(METADATA_PATH) as f:
metadata = json.load(f)
extra_keys = [key for key in metadata.keys() if key not in all_keys]
if extra_keys:
print(f"Warning: The following keys in metadata do not exist in Params: {extra_keys}")
def test_no_default_titles():
"""
Test that no parameter has a title that is identical to its key.
Why:
The default behavior of the update script is to set the title equal to the key.
We want to force developers to provide human-readable, descriptive titles for all parameters.
Expected:
No parameter metadata should have 'title' == 'key'.
"""
with open(METADATA_PATH) as f:
metadata = json.load(f)
default_title_keys = [key for key, meta in metadata.items() if meta.get("title") == key]
if default_title_keys:
pytest.fail(
f"The following parameters have default titles (title == key): {default_title_keys}. "
+ "Please update 'params_metadata.json' with descriptive titles."
)
def test_options_structure():
"""
Test that the 'options' field in metadata follows the correct structure.
Why:
The UI expects 'options' to be a list of objects with 'value' and 'label' keys.
Incorrect structure will break the UI rendering for dropdowns/toggles.
Expected:
If 'options' is present, it must be a list of dicts, and each dict must have 'value' and 'label'.
"""
with open(METADATA_PATH) as f:
metadata = json.load(f)
for key, meta in metadata.items():
if "options" in meta:
options = meta["options"]
assert isinstance(options, list), f"Options for {key} must be a list"
for option in options:
assert isinstance(option, dict), f"Option in {key} must be a dictionary"
assert "value" in option, f"Option in {key} must have a 'value' key"
assert "label" in option, f"Option in {key} must have a 'label' key"
def test_numeric_constraints():
"""
Test that numeric parameters have valid 'min', 'max', and 'step' constraints.
Why:
The UI uses these constraints to validate user input and render sliders/steppers.
Missing or invalid constraints can lead to UI bugs or invalid parameter values.
Expected:
If any of min/max/step is present, ALL of them must be present.
They must be numbers (int/float), and min must be less than max.
"""
with open(METADATA_PATH) as f:
metadata = json.load(f)
for key, meta in metadata.items():
if "min" in meta or "max" in meta or "step" in meta:
assert "min" in meta, f"Numeric param {key} must have 'min'"
assert "max" in meta, f"Numeric param {key} must have 'max'"
assert "step" in meta, f"Numeric param {key} must have 'step'"
assert isinstance(meta["min"], (int, float)), f"Min for {key} must be number"
assert isinstance(meta["max"], (int, float)), f"Max for {key} must be number"
assert isinstance(meta["step"], (int, float)), f"Step for {key} must be number"
assert meta["min"] < meta["max"], f"Min must be less than max for {key}"
def test_known_params_metadata():
"""
Test specific known parameters to ensure they have the expected rich metadata.
Why:
This acts as a spot check to ensure that our rich metadata population logic is working correctly
and that critical parameters (like LongitudinalPersonality) have their options and constraints preserved.
Expected:
'LongitudinalPersonality' should have 3 options (Aggressive, Standard, Relaxed).
'CustomAccLongPressIncrement' should have min=1, max=10, step=1.
"""
with open(METADATA_PATH) as f:
metadata = json.load(f)
# Check an enum-like param
lp = metadata.get("LongitudinalPersonality")
assert lp is not None
assert "options" in lp
assert len(lp["options"]) == 3
assert lp["options"][0]["label"] == "Aggressive"
assert lp["options"][0]["value"] == 0
# Check a numeric param
acc_long = metadata.get("CustomAccLongPressIncrement")
assert acc_long is not None
assert acc_long["min"] == 1
assert acc_long["max"] == 10
assert acc_long["step"] == 1
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
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.
"""
import json
import os
from openpilot.common.params import Params
METADATA_PATH = os.path.join(os.path.dirname(__file__), "../params_metadata.json")
def main():
params = Params()
all_keys = params.all_keys()
if os.path.exists(METADATA_PATH):
with open(METADATA_PATH) as f:
try:
data = json.load(f)
except json.JSONDecodeError:
data = {}
else:
data = {}
# Add new keys
for key in all_keys:
key_str = key.decode("utf-8")
if key_str not in data:
print(f"Adding new key: {key_str}")
data[key_str] = {
"title": key_str,
"description": "",
}
# Remove deleted keys
# keys_to_remove = [k for k in data.keys() if k.encode("utf-8") not in all_keys]
# for k in keys_to_remove:
# print(f"Removing deleted key: {k}")
# del data[k]
# Sort keys
sorted_data = dict(sorted(data.items()))
with open(METADATA_PATH, "w") as f:
json.dump(sorted_data, f, indent=2)
f.write("\n")
print(f"Updated {METADATA_PATH}")
if __name__ == "__main__":
main()
+6
View File
@@ -94,6 +94,7 @@ class FontWeight(StrEnum):
BOLD = "Inter-Bold.fnt"
SEMI_BOLD = "Inter-SemiBold.fnt"
UNIFONT = "unifont.fnt"
AUDIOWIDE = "Audiowide-Regular.fnt"
# Small UI fonts
DISPLAY_REGULAR = "Inter-Regular.fnt"
@@ -227,6 +228,8 @@ class GuiApplication(GuiApplicationExt):
self._render_profiler = None
self._render_profile_start_time = None
GuiApplicationExt.__init__(self)
@property
def frame(self):
return self._frame
@@ -467,6 +470,9 @@ class GuiApplication(GuiApplicationExt):
if self._show_touches:
self._draw_touch_points()
if self._show_mouse_coords:
self._draw_mouse_coordinates(gui_app.font(FontWeight.SEMI_BOLD))
if self._grid_size > 0:
self._draw_grid()
+6 -2
View File
@@ -41,8 +41,12 @@ class GuiScrollPanel:
if DEBUG:
rl.draw_rectangle_lines(0, 0, abs(int(self._velocity_filter_y.x)), 10, rl.RED)
# Handle mouse wheel
self._offset_filter_y.x += rl.get_mouse_wheel_move() * MOUSE_WHEEL_SCROLL_SPEED
# Handle mouse wheel only when the mouse cursor is over this panel
mouse_wheel = rl.get_mouse_wheel_move()
if mouse_wheel != 0:
mouse_pos = rl.get_mouse_position()
if rl.check_collision_point_rec(mouse_pos, bounds):
self._offset_filter_y.x += mouse_wheel * MOUSE_WHEEL_SCROLL_SPEED
max_scroll_distance = max(0, content.height - bounds.height)
if self._scroll_state == ScrollState.IDLE:
+1 -1
View File
@@ -383,7 +383,7 @@ class WifiManager:
'connection': {
'type': ('s', '802-11-wireless'),
'uuid': ('s', str(uuid.uuid4())),
'id': ('s', f'openpilot connection {ssid}'),
'id': ('s', f'sunnypilot connection {ssid}'),
'autoconnect-retries': ('i', 0),
},
'802-11-wireless': {
+1 -1
View File
@@ -701,7 +701,7 @@ class Setup(Widget):
except urllib.error.HTTPError as e:
if e.code == 409:
error_msg = "Incompatible openpilot version"
error_msg = "Incompatible sunnypilot version"
self.download_failed(self.download_url, error_msg)
except Exception:
error_msg = "Invalid URL"
+25
View File
@@ -6,10 +6,35 @@ See the LICENSE.md file in the root directory for more details.
"""
import os
import pyray as rl
SHOW_MOUSE_COORDS = os.getenv("SHOW_MOUSE_COORDS") == "1"
SUNNYPILOT_UI = os.getenv("SUNNYPILOT_UI", "1") == "1"
class GuiApplicationExt:
def __init__(self):
self._show_mouse_coords = SHOW_MOUSE_COORDS
@staticmethod
def sunnypilot_ui() -> bool:
return SUNNYPILOT_UI
def _draw_mouse_coordinates(self, font):
coords_text = f"X:{int(rl.get_mouse_x())}, Y:{int(rl.get_mouse_y())}"
green_color = rl.Color(0, 159, 47, 255) # Match the green color of FPS counter
# Calculate text width to position it at the right edge; estimate width based on text length
# Each character is approximately 10-12 pixels wide at font size 20
estimated_text_width = len(coords_text) * 11
# Position text at the top right corner, 10px from the top
screen_width = self._scaled_width if self._scale != 1.0 else self._width
text_pos = rl.Vector2(screen_width - estimated_text_width - 10, 6)
# Draw the text
rl.draw_text_ex(font, coords_text, text_pos, 20, 0, green_color)
def set_show_mouse_coords(self, show: bool):
self._show_mouse_coords = show
+36 -1
View File
@@ -17,19 +17,27 @@ class Base:
ITEM_TEXT_FONT_SIZE = 50
ITEM_DESC_FONT_SIZE = 40
ITEM_DESC_V_OFFSET = 150
ITEM_TEXT_VALUE_COLOR = rl.Color(170, 170, 170, 255)
CLOSE_BTN_SIZE = 160
TEXT_PADDING = 20
# Toggle Control
TOGGLE_HEIGHT = 120
TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75)
TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20
# Button Control
BUTTON_WIDTH = 300
BUTTON_HEIGHT = 120
@dataclass
class DefaultStyleSP(Base):
# Base Colors
BASE_BG_COLOR = rl.Color(57, 57, 57, 255) # Grey
ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue
OFF_BG_COLOR = rl.Color(70, 70, 70, 255) # Lighter Grey
OFF_BG_COLOR = BASE_BG_COLOR
ON_HOVER_BG_COLOR = rl.Color(17, 78, 150, 255) # Dark Blue
OFF_HOVER_BG_COLOR = rl.Color(21, 21, 21, 255) # Dark gray
DISABLED_ON_BG_COLOR = rl.Color(37, 70, 107, 255) # Dull Blue
@@ -46,5 +54,32 @@ class DefaultStyleSP(Base):
TOGGLE_DISABLED_OFF_COLOR = DISABLED_OFF_BG_COLOR
TOGGLE_DISABLED_KNOB_COLOR = rl.Color(88, 88, 88, 255) # Lighter Grey
# Multi Button Control
MBC_TRANSPARENT = rl.Color(255, 255, 255, 0)
MBC_BG_CHECKED_ENABLED = rl.Color(0x69, 0x68, 0x68, 0xFF)
MBC_DISABLED = rl.Color(0xFF, 0xFF, 0xFF, 0x33)
# Option Control
OPTION_CONTROL_CONTAINER_BG = OFF_BG_COLOR
OPTION_CONTROL_BTN_ENABLED = rl.Color(88, 88, 88, 255)
OPTION_CONTROL_BTN_PRESSED = rl.Color(0x69, 0x68, 0x68, 0xFF)
OPTION_CONTROL_BTN_DISABLED = DISABLED_OFF_BG_COLOR
OPTION_CONTROL_TEXT_ENABLED = rl.WHITE
OPTION_CONTROL_TEXT_PRESSED = rl.WHITE
OPTION_CONTROL_TEXT_DISABLED = ITEM_DISABLED_TEXT_COLOR
# Tree Button Colors
BUTTON_PRIMARY_COLOR = rl.Color(70, 91, 234, 255) # Royal Blue
BUTTON_NEUTRAL_GRAY = rl.Color(51, 51, 51, 255)
BUTTON_DISABLED_BG_COLOR = rl.Color(30, 30, 30, 255) # Very Dark Grey
TREE_DIALOG_TRANSPARENT = rl.Color(0, 0, 0, 0)
TREE_DIALOG_SEARCH_BUTTON_PRESSED = rl.Color(0x69, 0x68, 0x68, 0xFF)
TREE_DIALOG_SEARCH_BUTTON_BORDER = rl.Color(150, 150, 150, 200)
# Vehicle Description Colors
GREEN = rl.Color(0, 241, 0, 255)
BLUE = rl.Color(0, 134, 233, 255)
YELLOW = rl.Color(255, 213, 0, 255)
style = DefaultStyleSP

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