Compare commits

..

149 Commits

Author SHA1 Message Date
Kumar
f275604cb2 Update accel_controller.py 2025-11-16 16:11:47 -07:00
Kumar
495bacc576 Update dynamic_follow.py 2025-11-16 16:10:22 -07:00
Kumar
45a9e4c14c Update follow distance profiles and breakpoints 2025-11-16 07:12:10 -07:00
Kumar
67e23a408c Adjust eco acceleration and breakpoint values 2025-11-16 07:11:27 -07:00
rav4kumar
a31c5cf227 ref 2025-11-15 06:06:48 -07:00
rav4kumar
5164951ffa ref 2025-11-14 07:05:04 -07:00
rav4kumar
db845cdaab ff 2025-11-13 07:04:20 -07:00
rav4kumar
006052c2f3 origin/update-tinygrad 2025-11-12 22:16:06 -07:00
Kumar
32c48b5ffc Update dynamic_follow.py 2025-11-12 18:29:06 -07:00
Kumar
85503e6054 Update accel_controller.py 2025-11-12 18:28:04 -07:00
rav4kumar
2a4bb20f9d ref 2025-11-11 21:19:31 -07:00
rav4kumar
295dac06f1 bai bai llk 2025-11-11 20:53:12 -07:00
rav4kumar
eb11de561b a7 2025-11-11 20:51:19 -07:00
rav4kumar
1dd06cc174 a6 2025-11-10 21:54:45 -07:00
rav4kumar
21601660c5 mapdout 2025-11-10 11:25:16 -07:00
rav4kumar
923318b6bb a5 2025-11-10 11:06:21 -07:00
rav4kumar
6e5fb2b9e9 consistency 2025-11-10 10:09:18 -07:00
rav4kumar
d413310cbb what if 2025-11-09 19:42:28 -07:00
rav4kumar
86751559cd mapd 2025-11-09 16:13:09 -07:00
rav4kumar
5093b0d1b3 origin/nav-commacon' 2025-11-08 21:09:36 -07:00
rav4kumar
e0fd11c184 off 2025-11-08 21:02:02 -07:00
rav4kumar
204eaebf55 what if 2025-11-08 21:02:02 -07:00
Kumar
9fcb4c9ca8 Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar
da561e8285 Update dynamic_follow.py 2025-11-08 21:02:01 -07:00
rav4kumar
691eb4ee9f what if 2025-11-08 21:02:01 -07:00
rav4kumar
c69a340599 just try 2025-11-08 21:02:01 -07:00
rav4kumar
5ef2f5d1fb tune 2025-11-08 21:02:01 -07:00
rav4kumar
3ced0d6115 df exponential smoothing 2025-11-08 21:02:01 -07:00
rav4kumar
cdcc2392a8 exponential smoothing 2025-11-08 21:02:01 -07:00
Kumar
22aff8d2eb Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar
2c8cb003f9 Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar
3ae81d10a3 Update dynamic_follow.py 2025-11-08 21:02:01 -07:00
Kumar
c90289c949 Update accel_controller.py 2025-11-08 21:02:01 -07:00
rav4kumar
b32c64130c tune 2025-11-08 21:02:01 -07:00
Kumar
9cafeefaf1 Update long_mpc.py 2025-11-08 21:02:01 -07:00
Kumar
2484851edd Update longitudinal_planner.py 2025-11-08 21:02:01 -07:00
rav4kumar
870d5c3a74 tt 2025-11-08 21:02:01 -07:00
Kumar
8e841cac89 Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar
51081e8464 Update custom.capnp 2025-11-08 21:02:01 -07:00
rav4kumar
5f8875b16e conflic 2025-11-08 21:02:01 -07:00
rav4kumar
c47223ec59 ff 2025-11-08 21:02:01 -07:00
rav4kumar
3e84bbc9aa ref 2025-11-08 21:02:01 -07:00
rav4kumar
8587e08908 merge conflict 2025-11-08 21:02:01 -07:00
rav4kumar
4dadfbad1f Concept by Aleksei Voronov: At SunnyPilot, we turn concepts into reality. 2025-11-08 21:02:01 -07:00
rav4kumar
4a0ba000d3 rebase fix 2025-11-08 21:02:00 -07:00
rav4kumar
1d7165786a point the submodule 2025-11-08 21:02:00 -07:00
rav4kumar
c750a631ef misc. toggles. 2025-11-08 21:02:00 -07:00
rav4kumar
0f6006c87b feat/relc 2025-11-08 21:02:00 -07:00
rav4kumar
19eca5e035 drive mode btn support 2025-11-08 21:02:00 -07:00
rav4kumar
5010d752f6 rip vibecontroller in to two halfs 2025-11-08 21:01:59 -07:00
rav4kumar
07efcb4abb abh, bsm 2025-11-08 21:01:57 -07:00
Jason Wen
c1d3ae427b version: bump to 2025.003.000 2025-11-06 23:12:41 -05:00
Jason Wen
2ab45b552d Update CHANGELOG.md 2025-11-06 23:10:03 -05:00
github-actions[bot]
8c1d59fecd [bot] Update Python packages (#1434)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-11-06 22:47:55 -05:00
DevTekVE
cde88fd8ed bug: Fix initial registration for sunnylink (#1457)
refactor(sunnylink): defer `SunnylinkApi` initialization to function scope

- Moved `SunnylinkApi` object creation into individual functions as needed.
- Prevents unnecessary initialization when the object isn't used.
2025-11-06 12:13:15 +01:00
DevTekVE
4b5de0eddb stats: sunnylink integration (#1454)
* sunnylink: add statsd process and related telemetry logging infrastructure

- Introduced `statsd_sp` process for handling Sunnylink-specific stats.
- Enhanced metrics logging with improved directory structure and data handling.

* sunnylink: re-enable and refine stat_handler for telemetry processing

- Reactivated `stat_handler` thread with improved path handling.
- Made `stat_handler` more flexible by allowing directory injection.

* statsd: fix formatting issue in telemetry string generation

- Corrected missing comma between `sunnylink_dongle_id` and `comma_dongle_id`.

* update statsd_sp process configuration for enhanced readiness logic

- Modified `statsd_sp` initialization to include `always_run` alongside `sunnylink_ready_shim`.
- Ensures robust process activation conditions.

* refactor(statsd): enhance and unify StatLogSP implementation

- Replaced custom `StatLogSP` in sunnylink with centralized implementation from `system.statsd`.
- Ensures consistent logic for StatLogSP handling across modules.

* fix

* refactor(statsd): add intercept parameter to StatLogSP for configurable logging

- Introduced optional `intercept` parameter to `StatLogSP` to manage `comma_statlog` initialization.
- Updated usage in `sunnylink` to disable interception where unnecessary.

* Dont complain

* feat(statsd): add raw metric type and SunnyPilot-specific stats collection

- Introduced `METRIC_TYPE.RAW` for base64-encoded raw data metrics.
- Added `sp_stats` thread to export SunnyPilot params as raw metrics.
- Enhanced telemetry handling with decoding and serialization updates.

* refactor(statsd): improve `sp_stats` error handling and param processing

- Enhanced exception handling for `params.get` to prevent crashes.
- Added support for nested dict values to be included in stats.

* refactor(statsd): adjust imports and minor code formatting updates

- Updated `Ratekeeper` import path for consistency with the `openpilot` module structure.
- Fixed minor formatting for improved readability.

* refactor(statsd): update typings and remove unused NoReturn annotation

- Removed unnecessary `NoReturn` typing for `stats_main` to simplify function definition.
- Adjusted `get_influxdb_line_raw` to refine typing for `value` parameter.

* cleanup

* init

* init

* slightly more

* staticmethod

* handle them all

* get them models

* log with route

* more

* car

* Revert "car"

This reverts commit fe1c90cf4d.

* handle capnp

* Revert "handle capnp"

This reverts commit c5aea68803.

* 1 more time

* Revert "1 more time"

This reverts commit a364474fa5.

* Cleaning to expose wider

* feat(interfaces, statsd): log car params to stats system

- Added `STATSLOGSP` import and logging to capture `carFingerprint` in metrics.
- Improved error handling in `get_influxdb_line_raw` for robust metric generation.

* refactor(interfaces): streamline car params logging to stats

- Simplified logging by directly converting `CP` to a dictionary.
- Removed legacy stats aggregation for clarity.

* feat(sunnylink): enable compression for stats in SunnyLink

- Added optional compression for stats payload to support large data.
- Updated `stat_handler` to handle compression and base64 encoding.

* fix(statsd): filter complex types in `get_influxdb_line_raw`

- Skips unsupported types (dict, list, bytes) to prevent formatting errors.
- Simplifies type annotation for `value` parameter.

* fix(statsd): use `json.dumps` for string conversion in `get_influxdb_line_raw`

- Ensures proper handling of special characters in values.
- Prevents potential formatting issues with raw `str()` conversion.

* refactor(interfaces, statsd): update parameter keys for stats logging

- Renamed logged keys for better clarity (`sunnypilot_params` → `sunnypilot.car_params`, `device_params`).
- Ensures consistency across data logs.

* bet

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-04 16:53:31 -05:00
Matt Purnell
071147baaf docs: Update README installation branches and discord links (#1453)
* Use sunnypilot CARS.md, update number of supported cars, add comma

* Update device reference

* Update discord links to forum links

* Update references to -c3-new branches and release

* Update broken link to branches table

* Update README.md

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-11-03 06:52:17 +01:00
DevTekVE
18af4d6ad6 ui: Fix spacing in sunnylink panel (#1450)
Fix spacing
2025-11-02 20:26:17 +01:00
DevTekVE
b81d5bca3c ui: update discord references and add forum widget (#1440)
* sunnylink: introduce community popup with QR code embedding

- Added `SunnylinkCommunityPopup` widget to promote the sunnypilot Community Forum.
- Integrated a QR code generator and display for quick access.
- Updated `WiFiPromptWidget` to include a "Learn More" button triggering the community popup.

* sunnylink: adjust community popup styling for better layout

- Reduced font size of description text slightly for consistency.
- Decreased QR code dimensions to improve visual balance.

* Making more space out of thin air

* sunnylink: update community references to use forum links

- Replaced Discord links with Community Forum URLs for better alignment.
- Improved clarity in sponsorship instructions.
2025-11-02 06:50:41 +01:00
Amy Jeanes
682d738ffa Tesla: Coop Steering (#1283)
* Tesla: Coop Steering

* bump

* bump

* sync with opendbc/master

* resolve comment

* add oscillation warning and add confirmation

* styling desc

* beta

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-01 22:47:30 -04:00
DevTekVE
f60c2b6a83 sunnylink: update uploader button logic to support novice tier and above (#1438)
* sunnylink: update uploader button logic to support novice tier and above

- Adjusted the enable condition to include SponsorTier::Novice and above.

* sunnylink: improve uploader button visibility and accessibility logic

- Made uploader button conditionally visible based on user tier and settings.
- Clarified button label to specify testing purposes only.
2025-11-01 12:14:57 +01:00
DevTekVE
f833819143 ci: update trigger for prebuilt (#1439)
Updated workflow `if` conditions to use `vars.PREBUILT_PR_LABEL`.
2025-10-31 17:54:39 +01:00
THERoenPR
707e2aedae controlsd: add CP_SP to get_pid_accel_limits (#1410)
* Add CP_SP to get_pid_accel_limits() call in controlsd

Match input parameters of CP_SP commit

* bump

* bump

---------

Co-authored-by: roenthomas <43324106+roenthomas@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-28 22:13:39 -04:00
DevTekVE
55147d8a55 ci: use environment variable for PR label in query (#1436)
* ci: use environment variable for PR label in query

- Replaced static `PR_LABEL` references with `${{ env.PR_LABEL }}` for consistency.
- Ensures flexibility and reduces hardcoded values in the workflow.

* does this work better?

* fuck this

* aight
2025-10-28 18:58:04 +01:00
DevTekVE
de7acc5466 ci: integrate Discourse notifications and refactor notification logic (#1435)
* ci: integrate Discourse notifications and refactor notification logic

- Replaced Discord webhook notifications with Discourse topic updates.
- Introduced reusable `post-to-discourse` composite action.
- Added `test-discourse.yaml` workflow for debugging and verification.

* ci: adjust notification dependencies and prepare_strategy reference

- Updated `notify` step to depend on `prepare_strategy` instead of `build`.
- Adjusted variable references to use `prepare_strategy` outputs.

* Forcing debug

* ci: update environment variable references and add commit information

- Switched `PUBLIC_REPO_URL` source to environment variable for consistency.
- Added commit SHA variables to enhance template generation logic.

* more tweaks!

* more tweaks!

* bad bot lmao

* Test?

* i mean....

* i mean....

* getting there

* testing the if

* testing the if

* ci: re-enable notify steps for prebuilt workflow

- Uncommented `build` and `publish` dependencies.
- Restored conditional logic to trigger only for relevant events.

* ci: enhance Discourse action to support new topic creation

- Added support for creating new topics with `category-id` and `title`.
- Improved input validation and response handling for flexibility.

* ci: improve conditions for prebuilt workflow notifications

- Refined `if` clause to ensure branches in `DEV_FEEDBACK_NOTIFICATION_BRANCHES` are targeted.
- Adjusted logic for accurate topic ID mapping in Discourse integration.

* forgot to rename
2025-10-28 16:01:21 +01:00
Nayan
e4aada10a4 Bug: Model UI Crash Fix (#1431)
Model UI Crash Fix
2025-10-26 21:43:30 -04:00
James Vecellio-Grant
b460d5804c LiveLocationKalman: skip tests on unsupported msgq (#1407)
* locationd llk: skip tests on unsupported msgq

* Update sunnypilot/selfdrive/locationd/tests/test_locationd.py

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

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-25 23:47:37 -04:00
James Vecellio-Grant
eecb8e5c19 models: bump model json to v8 (#1430)
models: bump model json to v8 post release

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-25 22:55:26 -04:00
Jason Wen
1a4ea66987 version: bump to 2025.002.000 2025-10-25 22:45:17 -04:00
Jason Wen
c1e15e5544 changelog: add new contributor entry 2025-10-25 01:00:46 -04:00
MuskratGG
3a45fff1b9 ui: openpilot Longitudinal Control → sunnypilot Longitudinal Control (#1422)
* Update developer_panel.cc

Changed mentions of "openpilot Longitudinal Control" to "sunnypilot Longitudinal Control" to align with other UI elements pointing users towards enabling "sunnypilot Longitudinal Control"

* Update warning message for longitudinal control

* more

* a bit more

* slightly more

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-24 21:09:01 -04:00
github-actions[bot]
ae9bd39883 [bot] Update Python packages (#1428)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-10-24 17:04:27 -04:00
Jason Wen
43e7d87176 version: more release branches (#1427) 2025-10-24 15:34:50 -04:00
github-actions[bot]
432c6050ed [bot] Update Python packages (#1338)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-10-24 15:28:08 -04:00
Jason Wen
4e3b1f1f6b interface: add is_release flag to get_params_sp (#1426)
* interface: add `is_release` flag to `get_params_sp`

* split and rename

* debump
2025-10-24 14:56:24 -04:00
Jason Wen
5d47ffdb8a Speed Limit Assist: Disable for Rivian (#1421)
* Speed Limit Assist: Disable for Tesla in release

* add test

* unused

* use constant

* eh

* flip

* universal it

* check release state and align in tests

* use this

* eh

* update changelog

* Speed Limit Assist: Disable for Rivian

* desc

* changelog
2025-10-23 03:48:04 -04:00
Jason Wen
1c89e2b885 Speed Limit Assist: Disable for Tesla in release (#1418)
* Speed Limit Assist: Disable for Tesla in release

* add test

* unused

* use constant

* eh

* flip

* universal it

* check release state and align in tests

* use this

* eh

* update changelog
2025-10-23 03:26:32 -04:00
Jason Wen
c552567ada ui: increase minimum button width in ButtonParamControlSP (#1419) 2025-10-23 01:08:19 -04:00
Jason Wen
7097e69aa3 Speed Limit Assist: generalize availability helper (#1416)
* init infra to disable sla in certain conditions

* a bit more

* in another PR

* in another PR

* since when?

* start here
2025-10-22 11:17:02 -04:00
Jason Wen
657ff0f8ec ui: refine ICBM description handling and availability logic (#1414)
* ui: refine ICBM description handling and availability logic

* car -> platform

* retain
2025-10-22 00:09:10 -04:00
Jason Wen
641af6d7e7 changelog: more new contributors! (#1413) 2025-10-21 17:55:27 -04:00
Jason Wen
f57de1c5b2 version: a new beginning (#1411)
* version: a new beginning

* changelog

* singular

* show ours

* actual

* readjust

* updated

* more

* official spelling

* more

* sync

* fix

* send it

* push

* we never had this lol

* syncs
2025-10-21 17:12:57 -04:00
Jason Wen
cb5d120136 FCA: update minEnableSpeed and LKAS control logic (#1386)
* FCA: update minEnableSpeed and LKAS control logic

* bump
2025-10-21 14:22:37 -04:00
Jason Wen
c85b6a0d1c branches: track sunnypilot release branches separately (#1409)
* branches: track sunnypilot release branches separately

* more remotes for legacy support

* bruh

* revert
2025-10-21 00:53:16 -04:00
Jason Wen
025a930ce8 ui: update longitudinal-related settings handling (#1401)
* ui: update ICBM-related settings handling

* oops

* oops

* single location

* some more

* fix cruise toggles

* always init true

* check this

* nah

* should be this
2025-10-18 04:04:48 -04:00
Jason Wen
523c92c6fe Speed Limit Assist: lower preActive timer for Non PCM Longitudinal and ICBM cars (#1403)
5 seconds preActive for non pcm long now
2025-10-17 23:41:33 -04:00
Jason Wen
72282f2d2e Speed Limit Assist: update events handling (#1400)
* Speed Limit Assist: update active event handling

* ok no more for non pcm long it was annoying

* 5 seconds preActive for non pcm long now

* Revert "5 seconds preActive for non pcm long now"

This reverts commit dfcc601035.

* dynamic alert size

* do the same here

* lint
2025-10-17 23:30:06 -04:00
Jason Wen
2825c00fcc controlsd: update lateral delay param in a separate thread (#1402) 2025-10-17 22:53:31 -04:00
Nayan
063aa994d2 ui: Resize E2E Alerts (#1396)
because people be enabling ALL THE UI

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-16 01:03:45 -04:00
Jason Wen
50462a1d01 E2E Alert: universal state machine (#1395)
* E2E Helper: universal state machine

* not used

* rename

* 10 frames for both

* time based

* magic

* lead depart: only arm if we have a confirmed close lead for over a second after allowing alert

* less

* shorter trigger

* lol

* always update
2025-10-16 00:55:17 -04:00
Jason Wen
437726b348 Speed Limit Mode: only cleanup param if Assist was selected (#1393)
Speed Limit Mode: only cleanup param if it was Assist
2025-10-15 18:05:50 -04:00
Nayan
9e6af5ba74 ui: Adjust UI Elements to account for Sidebar & Dev UI (#1390)
* resize & reposition

* Apply suggestion from @sunnyhaibin

* sir, this is Wendy's

* this is still a Wendy's

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-15 17:40:52 -04:00
Nayan
99bd9075d5 ui: Fix Onroad Screen-Off default param (#1389)
Change defaults

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-15 17:18:31 -04:00
Jason Wen
c438aeb5a5 ui: check for updated message before updating states in HUD (#1392) 2025-10-15 16:47:00 -04:00
Jason Wen
f1ca81debf ui: chevron should always be on top of driving path (#1391) 2025-10-15 16:20:31 -04:00
Jason Wen
d7e1c42c2b ui: move Dynamic Experimental Control (DEC) toggle to Longitudinal panel (#1388)
- Implemented a new toggle for enabling Dynamic Experimental Control (DEC) in longitudinal settings.
- Removed previous implementation for DEC from general settings.
- Updated accessibility based on longitudinal control status.
2025-10-15 12:25:23 -04:00
Jason Wen
6d51d64285 interfaces: clean up unsupported params during initialization (#1385)
* interfaces: clean up unsupported params during initialization

* fix

* logging and no DEC when no long

* ui

* ui
2025-10-15 09:46:53 -04:00
Jason Wen
e0ccc175e4 liveMapDataSP: improve speed limit validation logic (#1383) 2025-10-15 00:59:40 -04:00
James Vecellio-Grant
734151f59b Reapply "capnp: consolidate TurnDirection enum" (#1376) (#1382)
* Reapply "capnp: consolidate TurnDirection enum" (#1376)

This reverts commit 339bc0b8b3.

* cache it

* format

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-14 23:32:10 -04:00
Nayan
9a14baac4d Green Light and Lead Departure alerts improvements (#1381) 2025-10-14 20:01:42 -04:00
Jason Wen
d3e3628a95 ui: only draw ahead speed limit if it's parsed from OSM (#1380) 2025-10-14 11:40:42 -04:00
Nayan
fec6382b96 UI: Fix Speed Limit Assist (SLA) Translations (#1379)
Fix SLA Translations
2025-10-14 11:29:27 -04:00
Jason Wen
4bd020e92b Speed Limit Assist: audible alerts for certain states (#1378) 2025-10-14 09:19:26 -04:00
Jason Wen
7f5342f378 soundd: custom audible alerts (#1377)
* Revert "capnp: consolidate TurnDirection enum (#1370)"

This reverts commit 7229c7541e.

* soundd: custom audible alerts

* comment
2025-10-14 01:13:20 -04:00
Jason Wen
339bc0b8b3 Revert "capnp: consolidate TurnDirection enum" (#1376)
Revert "capnp: consolidate TurnDirection enum (#1370)"

This reverts commit 7229c7541e.
2025-10-14 00:19:21 -04:00
Jason Wen
59c64acc29 Subaru: Stop and Go support (beta) (#1375)
* Subaru: Stop and Go auto-resume support

* bump

* bump

* fix

* bump

* fix init

* wat

* use just standstill for now

* Revert "use just standstill for now"

This reverts commit f72cce6892.

* bump

* bump

* fix it

* only send at 10

* bump

* fix type

* forget about planner resume, it sucks

* try to send off_accel

* still need it

* always send

* disable safety checks for now

* same

* more

* all the time for both

* don't need i guess

* bump

* try 15 frames per try

* all should have it

* try 3 for all

* use throttle for all preglobal?

* bump

* bump

* separate thresholds between preglobal and global

* longer wait before sending

* shorter time but immediately resend

* quick

* new timeout

* about to cry

* same thing but another try

* no need

* round 3

* try 1.4

* lower!

* 1.2

* last try

* beta asf

* bump
2025-10-13 22:26:47 -04:00
James Vecellio-Grant
7229c7541e capnp: consolidate TurnDirection enum (#1370)
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-13 22:02:53 -04:00
Kumar
39e73cc46e ui: add ModelRendererSP::draw (#1372)
* ModelRendererSP::draw

* match

* less

* huh?

* unused

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-13 21:45:59 -04:00
Jason Wen
285fd97606 ui: only draw speedCluster speed over "MAX" when ICBM is enabled (#1374) 2025-10-13 20:36:24 -04:00
Jason Wen
e5f1f86ac2 params: helper to clamp out-of-range int params (#1373)
* params: helpers to clamp out-of-range values

* lint

* inline

* fix access

* actually fix the param

* inherit them

* more lint
2025-10-13 19:37:58 -04:00
Jason Wen
7e03277962 ui: bigger cluster set speed fonts when ICBM is active (#1369) 2025-10-13 03:06:39 -04:00
Jason Wen
bd9bb74d03 custom cereal: fix formatting (#1367) 2025-10-13 01:40:22 -04:00
Kirito3481
7d54b58b8d ui: Update Korean translations (#1359)
Update ko-kr translations

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-13 00:19:24 -04:00
Jason Wen
68d059fd5d ui: ensure Cruise panel widget is reset when hideEvent (#1366) 2025-10-12 23:48:58 -04:00
Nayan
728108f97f ui: Optimize Param Read for Onroad UI (#1365)
* Move params to uiscene

* more

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 23:36:58 -04:00
Kumar
cb6fa622ee Visuals: Move custom chevron info to sunnypilot/qt (#1066)
* refactor: move to sp ui

* pr suggestion

* no need to check pcm just oplong

* no color, big front, long check

* Fix Rainbow Mode & Y Positioning

* Move param to uiscene

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: nayan <nayan8teen@gmail.com>
2025-10-12 23:11:23 -04:00
Nayan
5b29fd0f2c UI: Onroad Screen Timeout Fixes (#1364)
* Screen Timeout Fixes

* rename

* auto type

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 12:12:52 -04:00
Nayan
6f42bbab18 Reapply "UI: Make Always Offroad more accessible" (#1327) (#1361)
* UI: Make Always Offroad more accessible

* conditional - based on offroad

* no need to delete

* account for always offroad

* fix offroad transition

* do this inside updateState on every invoke

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 01:59:03 -04:00
Nayan
b89393a5a2 UI: Blinker Size & State Fix (#1363)
Blinker Size & State Fix

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-12 00:34:15 -04:00
Nayan
1e5758e712 ui: Better E2E Alert UI Positioning (#1355)
Better E2E Alert UI Positioning
2025-10-12 00:21:38 -04:00
James Vecellio-Grant
974a7a3f7d ui: improve toggle states in Developer panel (#1352)
ui: quickboot toggle race condition

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-11 02:47:45 -04:00
Jason Wen
fe6edda23a Revert "UI: Make Always Offroad more accessible (#1327)" (#1358)
Reverts #1327
2025-10-11 01:52:33 -04:00
Jason Wen
17e25f78b4 ui: dynamic ICBM status (#1357)
* ui: dynamic ICBM status

* straight up pls
2025-10-10 17:00:44 -04:00
Nayan
9b92cdd2cc ui: fix standstill timer reset when offroad (#1351)
Standstill timer fix
2025-10-09 23:57:09 -04:00
Jason Wen
d6317ffd20 mapd: script to update version and hash (#1349)
* init and hash for 1.11.0

* update version and example for 1.12.0
2025-10-09 19:04:43 -04:00
Nayan
3ba52bc6fe ui: New? Old? Different? Standstill Timer UI (#1347)
New Standstill Timer UI

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-09 17:30:59 -04:00
Nayan
d7fd78050b OSM: Search!! (#1341)
* OSM Search

* cleanup

* wat

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-09 16:29:55 -04:00
HazZelnutz
8864b79a6e Visuals: Turn signals on screen when blinker is used (#1291)
* get blinker state from car_state

* Draw turn signals when blinker is active

* reloacted and resized turning signals

* add turn signal display setting

* Lowered Blink Frequency

* Moved x and y Offsets to drawing Function

Co-authored-by: Nayan <nayan8teen@gmail.com>

* Grouped Settings

* Moved blinking Logic to pulseElement Function

Co-authored-by: Nayan <nayan8teen@gmail.com>

* Improved Blinker Size and Position

* Get laneChangeBlocked Event from onroadEvents

* Draw Blinker red if laneChangeBlocked event is set

* Revert "Get laneChangeBlocked Event from onroadEvents"

This reverts commit 4310931b6cd59aed22119c2b4b558f09d3ad9c90.

* Get left and right Blindspot

* Replaced laneChangeBlocked by Blindspot checks

* slight optimization

* more refinement

---------

Co-authored-by: Nayan <nayan8teen@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-09 15:59:28 -04:00
Nayan
e9f054b7ee UI: Adjust Alert display with Developer UI (#1299)
* Adjust Alert Rect for Dev UI

* split out into SP classes

* fix includes

* formatting

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-09 15:26:14 -04:00
Nayan
f429f3191f ui: Bug fix Search with special chars (#1344)
Fix Search with special chars

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-09 14:53:47 -04:00
Jason Wen
3df1b53fab ui: update description of Speedometer: Hide from Onroad Screen (#1346) 2025-10-09 14:33:14 -04:00
Jason Wen
6bb87174b9 Revert "CarControlSP: live params (#943)" (#1345)
* Revert "`CarControlSP`: live params (#943)"

This reverts commit b5104377

* bump

* bump
2025-10-09 14:14:12 -04:00
Jason Wen
6d356d520e ui: HUD drawing optimizations and fixes (#1342)
* no duplicated speed drawing

* reuse polygon
2025-10-09 10:28:56 -04:00
Jason Wen
73123aa400 torqued_ext: adjust RELAXED_MIN_BUCKET_POINTS boundary values (#1340) 2025-10-09 07:48:35 -04:00
github-actions[bot]
7dfe03b7a3 [bot] Update translations (#1200)
Update translations

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-10-09 02:21:09 -04:00
DevTekVE
12a4b1b561 sunnylink: enhance fault handling and parameter checks (#1323)
* enhance sunnylink fault handling and parameter checks

- Added `SunnylinkTempFault` to handle temporary registration issues.
- Updated methods to account for new fault state, improving robustness.
- Improved uploader logic by considering metered network restrictions.

* maybe it was overkill

* Lol, kinda important

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-08 23:37:21 -04:00
Jason Wen
7aac14e6fc ui: Speedometer: Hide from onroad screen (#1336)
* ui: Speedometer: Display True Speed

* update

* ui: Speedometer: Hide from onroad screen

* wrong

* use stock one
2025-10-08 21:54:24 -04:00
Jason Wen
ae21d40a19 ui: Speedometer: Always display true speed (#1335)
* ui: Speedometer: Display True Speed

* update

* use stock one
2025-10-08 20:33:19 -04:00
Nayan
41abede7f6 UI: Better layout for onroad screen-off settings (#1334)
* OCD FIX

* MORE FIX.. BECAUSE WE ALL HAVE OCD

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-08 17:55:56 -04:00
Kumar
f653566803 mapd: Bump version v1.11.0 -> v1.12.0 (#1288)
v1.12.0

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-08 17:39:14 -04:00
DevTekVE
e8a39c4a74 Reapply "sunnylink: enhanced param keys fetch with data type" (#1317)
Reapply "sunnylink: enhanced param keys fetch with data type" (#1316)"

This reverts commit 282a8b093d.

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-08 15:01:33 -04:00
Nayan
517020ffb6 UI: Move Brightness & Interactivity Timeout to Display Panel (#1326)
* UI: Move Brightness & Interactivity Timeout to Display Panel

* why Qt.. WHYY

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-08 13:51:31 -04:00
Jason Wen
a85f3ce11c Device: Onroad Uploads button (#1332) 2025-10-08 13:03:15 -04:00
Nayan
014baf8e90 UI: Make Always Offroad more accessible (#1327)
* UI: Make Always Offroad more accessible

* conditional - based on offroad

* no need to delete

* account for always offroad

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-08 12:05:06 -04:00
Nayan
8050c56a43 UI: OptionControlSP - Fix Inline Spacer (#1325)
OptionControlSP - Fix Inline Spacer issue!!

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-07 23:24:18 -04:00
Nayan
0b826002e9 UI: Expandable Row - Fix margins (#1324)
Expandable Row - Fix margins

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-07 22:44:27 -04:00
Jason Wen
408d52d72a NNLC: ensure PID is always initialized properly (#1304) 2025-10-07 21:45:33 -04:00
DevTekVE
aeaac22274 ci: refine PR search query to avoid querying whole github (#1330)
* ci: refine PR search query to improve label handling in GitHub Action workflow

- Replaced `label` with `search_query` for better flexibility.
- Added support for multiple labels (`PR_LABEL` and `${PR_LABEL}-c3`).

* ci: fix PR search query for proper label matching in workflow

- Adjusted `label` query syntax to correctly handle multiple labels.

* Update .github/workflows/sunnypilot-master-dev-prep.yaml
2025-10-07 07:45:04 +02:00
github-actions[bot]
f28cea759d [bot] Update Python packages (#1250)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-10-06 02:52:02 -04:00
280 changed files with 12392 additions and 1332 deletions

View File

@@ -3,3 +3,4 @@ REGIST
PullRequest
cancelled
FOF
NoO

View File

@@ -74,7 +74,7 @@ jobs:
env:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
git checkout main
git sparse-checkout set --no-cone models/
@@ -191,7 +191,7 @@ jobs:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
echo "Cloning GitLab"
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
echo "checkout models/${RECOMPILED_DIR}"
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}

View File

@@ -109,7 +109,7 @@ jobs:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
echo "Cloning GitLab"
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
echo "checkout models/${RECOMPILED_DIR}"
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}

View File

@@ -0,0 +1,105 @@
name: 'Post to Discourse'
description: 'Posts a message to a Discourse topic (existing or new)'
inputs:
discourse-url:
description: 'Discourse instance URL (e.g., https://discourse.example.com)'
required: true
api-key:
description: 'Discourse API key'
required: true
api-username:
description: 'Discourse API username'
required: true
topic-id:
description: 'Discourse topic ID to post to (use this OR category-id + title)'
required: false
category-id:
description: 'Category ID for new topic (required if topic-id not provided)'
required: false
title:
description: 'Title for new topic (required if topic-id not provided)'
required: false
message:
description: 'Message content (markdown supported)'
required: true
outputs:
post-number:
description: 'The post number in the topic'
value: ${{ steps.post.outputs.post_number }}
post-url:
description: 'Direct URL to the post'
value: ${{ steps.post.outputs.post_url }}
topic-id:
description: 'The topic ID (useful when creating a new topic)'
value: ${{ steps.post.outputs.topic_id }}
runs:
using: "composite"
steps:
- name: Post to Discourse
id: post
shell: bash
run: |
# Validate inputs
if [ -z "${{ inputs.topic-id }}" ] && ([ -z "${{ inputs.category-id }}" ] || [ -z "${{ inputs.title }}" ]); then
echo "❌ Error: Must provide either topic-id OR both category-id and title"
exit 1
fi
if [ -n "${{ inputs.topic-id }}" ] && ([ -n "${{ inputs.category-id }}" ] || [ -n "${{ inputs.title }}" ]); then
echo "⚠️ Warning: Both topic-id and category-id/title provided. Will post to existing topic."
fi
# Determine if creating new topic or posting to existing
if [ -n "${{ inputs.topic-id }}" ]; then
echo "📝 Posting to existing topic ID: ${{ inputs.topic-id }}"
# Create JSON payload for posting to existing topic
PAYLOAD=$(jq -n \
--arg content '${{ inputs.message }}' \
--arg topic_id "${{ inputs.topic-id }}" \
'{topic_id: $topic_id, raw: $content}')
else
echo "✨ Creating new topic: ${{ inputs.title }}"
# Create JSON payload for new topic
PAYLOAD=$(jq -n \
--arg content '${{ inputs.message }}' \
--arg title "${{ inputs.title }}" \
--arg category "${{ inputs.category-id }}" \
'{title: $title, category: ($category | tonumber), raw: $content}')
fi
# Post to Discourse
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "${{ inputs.discourse-url }}/posts.json" \
-H "Content-Type: application/json" \
-H "Api-Key: ${{ inputs.api-key }}" \
-H "Api-Username: ${{ inputs.api-username }}" \
-d "$PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Successfully posted to Discourse!"
POST_NUMBER=$(echo "$BODY" | jq -r '.post_number // "unknown"')
TOPIC_ID=$(echo "$BODY" | jq -r '.topic_id // "${{ inputs.topic-id }}"')
POST_URL="${{ inputs.discourse-url }}/t/${TOPIC_ID}/${POST_NUMBER}"
echo "post_number=${POST_NUMBER}" >> $GITHUB_OUTPUT
echo "post_url=${POST_URL}" >> $GITHUB_OUTPUT
echo "topic_id=${TOPIC_ID}" >> $GITHUB_OUTPUT
echo "Topic ID: ${TOPIC_ID}"
echo "Post number: ${POST_NUMBER}"
echo "URL: ${POST_URL}"
else
echo "❌ Failed to post to Discourse"
echo "HTTP Code: ${HTTP_CODE}"
echo "Response: ${BODY}"
exit 1
fi

View File

@@ -21,11 +21,12 @@ env:
PYTHONWARNINGS: error
BASE_IMAGE: sunnypilot-base
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
MAPBOX_TOKEN_CI: ${{ secrets.MAPBOX_TOKEN_CI }}
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: release/ci/docker_build_sp.sh base
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e MAPBOX_TOKEN_CI=$MAPBOX_TOKEN_CI -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical

View File

@@ -156,6 +156,8 @@ jobs:
with:
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
path: ${{ github.workspace }}/selfdrive/modeld/models
- run: |
rm -f ${{ github.workspace }}/selfdrive/modeld/models/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx
- name: Build Model
run: |

View File

@@ -79,7 +79,7 @@ jobs:
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
stable_version=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g');
stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
fi
@@ -302,36 +302,51 @@ jobs:
git push -f origin ${TAG}
notify:
needs: [ build, publish ]
needs:
- prepare_strategy
- build
- publish
runs-on: ubuntu-24.04
if: ${{ (always() && !cancelled() && !failure()) && needs.publish.result == 'success' && !failure() && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
if: ${{ (always() && !cancelled() && !failure())
&& needs.publish.result == 'success'
&& (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
&& (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }}
steps:
- uses: actions/checkout@v4
- name: Setup Alpine Linux environment
uses: jirutka/setup-alpine@v1.2.0
with:
packages: 'jq gettext curl'
- name: Send Discord Notification
env:
DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), env.SOURCE_BRANCH) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }}
- name: Prepare notification message
id: message
run: |
TEMPLATE='${{ vars.DISCORD_GENERAL_UPDATE_NOTICE }}'
export EXTRA_VERSION_IDENTIFIER="${{ needs.build.outputs.extra_version_identifier }}"
export VERSION="${{ needs.build.outputs.version }}"
export branch_name=${{ env.SOURCE_BRANCH }}
export new_branch=${{ needs.build.outputs.new_branch }}
export extra_version_identifier=${{ needs.build.outputs.extra_version_identifier || github.run_number}}
echo ${TEMPLATE} | envsubst | jq -c '.' | tee payload.json
curl -X POST -H "Content-Type: application/json" -d @payload.json $DISCORD_WEBHOOK
TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}'
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
export branch_name="${{ env.SOURCE_BRANCH }}"
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
export commit_sha="${{ github.sha }}"
export commit_short_sha="${{ github.sha }}"
export commit_short_sha="${commit_short_sha:0:7}"
export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}"
export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}"
echo ""
echo "---- To update the list of branches that notify to dev-feedback -----"
echo ""
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/DEV_FEEDBACK_NOTIFICATION_BRANCHES"
echo "2. Current value: ${{ vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES }}"
echo "3. Update as needed (JSON array with no spaces)"
shell: alpine.sh {0}
MESSAGE=$(cat << 'EOF' | envsubst
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
EOF
)
{
echo 'content<<EOFMARKER'
echo "$MESSAGE"
echo 'EOFMARKER'
} >> $GITHUB_OUTPUT
shell: bash
- name: Post to Discourse
uses: ./.github/workflows/post-to-discourse
with:
discourse-url: ${{ vars.DISCOURSE_URL }}
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: "system"
topic-id: ${{ fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name].topic_id }}
message: ${{ steps.message.outputs.content }}
manage-pr-labels:
name: Remove prebuilt label

View File

@@ -3,7 +3,6 @@ name: Build dev
env:
DEFAULT_SOURCE_BRANCH: "master"
DEFAULT_TARGET_BRANCH: "master-dev"
PR_LABEL: "dev"
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
@@ -43,7 +42,7 @@ jobs:
if: (
(github.event_name == 'workflow_dispatch')
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL))))
)
steps:
- uses: actions/checkout@v4
@@ -55,7 +54,7 @@ jobs:
uses: ./.github/workflows/wait-for-action # Path to where you place the action
if: (
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL))))
)
with:
workflow: selfdrive_tests.yaml # The workflow file to monitor
@@ -118,8 +117,8 @@ jobs:
run: |
# Use GitHub API to get PRs with specific label, ordered by creation date
PR_LIST=$(gh api graphql -f query='
query($label:String!) {
search(query: $label, type:ISSUE, first:100) {
query($search_query:String!) {
search(query: $search_query, type:ISSUE, first:40) {
nodes {
... on PullRequest {
number
@@ -149,7 +148,7 @@ jobs:
}
}
}
}' -F label="is:pr is:open label:${PR_LABEL} draft:false sort:created-asc")
}' -F search_query="repo:${{ github.repository }} is:pr is:open label:${{ vars.PREBUILT_PR_LABEL }},${{ vars.PREBUILT_PR_LABEL }}-c3 draft:false sort:created-asc")
PR_LIST=${PR_LIST//\'/}
echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,78 @@
name: Debug Discourse Posting
on:
push:
jobs:
test-discourse-post:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Post test message to Discourse
uses: ./.github/workflows/post-to-discourse
with:
discourse-url: ${{ vars.DISCOURSE_URL }}
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
message: |
## 🧪 Test Post from GitHub Actions
**This is a test post to verify Discourse integration**
- **Workflow**: ${{ github.workflow }}
- **Run Number**: #${{ github.run_number }}
- **Branch**: `${{ github.ref_name }}`
- **Commit**: ${{ github.sha }}
- **Actor**: @${{ github.actor }}
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
---
### Fake Build Info (for testing)
- **Version**: 0.9.8-test
- **Build**: #42
- **Branch**: release-test
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
*This is an automated test message. Drive safe! 🚗💨*
- name: Create topic on Discourse
uses: ./.github/workflows/post-to-discourse
with:
discourse-url: ${{ vars.DISCOURSE_URL }}
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
#topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
category-id: 4
title: "This is a test of a new topic instead of a reply"
message: |
## 🧪 Test Post from GitHub Actions
**This is a test post to verify Discourse integration**
- **Workflow**: ${{ github.workflow }}
- **Run Number**: #${{ github.run_number }}
- **Branch**: `${{ github.ref_name }}`
- **Commit**: ${{ github.sha }}
- **Actor**: @${{ github.actor }}
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
---
### Fake Build Info (for testing)
- **Version**: 0.9.8-test
- **Build**: #42
- **Branch**: release-test
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
*This is an automated test message. Drive safe! 🚗💨*
- name: Display results
if: always()
run: |
echo "::notice::Discourse post test completed"
echo "Check your Discourse topic to verify the post appeared correctly"

1
.gitmodules vendored
View File

@@ -4,6 +4,7 @@
[submodule "opendbc"]
path = opendbc_repo
url = https://github.com/sunnypilot/opendbc.git
branch = tn
[submodule "msgq"]
path = msgq_repo
url = https://github.com/sunnypilot/msgq.git

1104
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,9 @@
## 🌞 What is sunnypilot?
[sunnypilot](https://github.com/sunnyhaibin/sunnypilot) is a fork of comma.ai's openpilot, an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 300+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible.
## 💭 Join our Discord
Join the official sunnypilot Discord server to stay up to date with all the latest features and be a part of shaping the future of sunnypilot!
* https://discord.gg/sunnypilot
![](https://dcbadge.vercel.app/api/server/wRW3meAgtx?style=flat) ![Discord Shield](https://discordapp.com/api/guilds/880416502577266699/widget.png?style=shield)
## 💭 Join our Community Forum
Join the official sunnypilot community forum to stay up to date with all the latest features and be a part of shaping the future of sunnypilot!
* https://community.sunnypilot.ai/
## Documentation
https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot
@@ -16,13 +14,13 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t
* A supported device to run this software
* a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x)
* This software
* One of [the 300+ supported cars](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
## Installation
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging-c3-new` branch.
Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch.
### If you want to use our newest branches (our rewrite)
> [!TIP]
@@ -31,28 +29,28 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
* sunnypilot not installed or you installed a version before 0.8.17?
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```https://staging-c3-new.sunnypilot.ai```.
3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```.
4. Complete the rest of the installation following the onscreen instructions.
* sunnypilot already installed and you installed a version after 0.8.17?
1. On the comma three, go to `Settings` ▶️ `Software`.
1. On the comma three/3X, go to `Settings` ▶️ `Software`.
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging-c3-new`
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging`
| Branch | Installation URL |
|:----------------:|:---------------------------------------------:|
| `staging-c3-new` | `https://staging-c3-new.sunnypilot.ai` |
| `dev-c3-new` | `https://dev-c3-new.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
| `release-c3-new` | **Not yet available**. |
### Recommended Branches
| Branch | Installation URL |
|:---------------:|:---------------------------------------------:|
| `release` | `https://release.sunnypilot.ai` |
| `staging` | `https://staging.sunnypilot.ai` |
| `dev` | `https://dev.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
> [!TIP]
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'.
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'.
> [!NOTE]
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel.
<details>

View File

@@ -69,6 +69,48 @@ struct LeadData {
struct SelfdriveStateSP @0x81c2f05a394cf4af {
mads @0 :ModularAssistiveDrivingSystem;
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
enum AudibleAlert {
none @0;
engage @1;
disengage @2;
refuse @3;
warningSoft @4;
warningImmediate @5;
prompt @6;
promptRepeat @7;
promptDistracted @8;
# unused, these are reserved for upstream events so we don't collide
reserved9 @9;
reserved10 @10;
reserved11 @11;
reserved12 @12;
reserved13 @13;
reserved14 @14;
reserved15 @15;
reserved16 @16;
reserved17 @17;
reserved18 @18;
reserved19 @19;
reserved20 @20;
reserved21 @21;
reserved22 @22;
reserved23 @23;
reserved24 @24;
reserved25 @25;
reserved26 @26;
reserved27 @27;
reserved28 @28;
reserved29 @29;
reserved30 @30;
promptSingleLow @31;
promptSingleHigh @32;
}
}
struct ModelManagerSP @0xaedffd8f31e7b55d {
@@ -150,6 +192,7 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
aTarget @5 :Float32;
events @6 :List(OnroadEventSP.Event);
e2eAlerts @7 :E2eAlerts;
accelPersonality @8 :AccelerationPersonality;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
@@ -161,7 +204,11 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
blended @1;
}
}
enum AccelerationPersonality {
sport @0;
normal @1;
eco @2;
}
struct SmartCruiseControl {
vision @0 :Vision;
map @1 :Map;
@@ -298,6 +345,7 @@ struct OnroadEventSP @0xda96579883444c35 {
speedLimitChanged @21;
speedLimitPending @22;
e2eChime @23;
laneChangeRoadEdge @24;
}
}
@@ -404,15 +452,30 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
struct ModelDataV2SP @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
}
leftLaneChangeEdgeBlock @1 :Bool;
rightLaneChangeEdgeBlock @2 :Bool;
enum TurnDirection {
none @0;
turnLeft @1;
turnRight @2;
}
}
struct CustomReserved10 @0xcb9fd56c7057593a {
struct Navigationd @0xcb9fd56c7057593a {
upcomingTurn @0 :Text;
currentSpeedLimit @1 :UInt64;
bannerInstructions @2 :Text;
distanceFromRoute @3 :Float64;
allManeuvers @4 :List(Maneuver);
valid @5 :Bool;
struct Maneuver {
distance @0 :Float64;
type @1 :Text;
modifier @2 :Text;
instruction @3 :Text;
}
}
struct CustomReserved11 @0xc2243c65e0340384 {
@@ -433,11 +496,84 @@ struct CustomReserved15 @0xbd443b539493bc68 {
struct CustomReserved16 @0xfc6241ed8877b611 {
}
struct CustomReserved17 @0xa30662f84033036c {
enum MapdExtendedOutType {
paths @0;
settings @1;
}
struct CustomReserved18 @0xc86a3d38d13eb3ef {
struct MapdExtendedOut @0xa30662f84033036c {
type @0 :MapdExtendedOutType;
json @1 :Text;
}
struct CustomReserved19 @0xa4f1eb3323f5f582 {
enum MapdInputType {
download @0;
setTargetLateralAccel @1;
setSpeedLimitOffset @2;
setSpeedLimitControl @3;
setCurveSpeedControl @4;
setVisionCurveSpeedControl @5;
setLogLevel @6;
setVisionCurveTargetLatA @7;
setVisionCurveMinTargetV @8;
reloadSettings @9;
saveSettings @10;
setEnableSpeed @11;
setVisionCurveUseEnableSpeed @12;
setCurveUseEnableSpeed @13;
setSpeedLimitUseEnableSpeed @14;
setHoldLastSeenSpeedLimit @15;
setCurveTargetJerk @16;
setCurveTargetAccel @17;
setCurveTargetOffset @18;
setDefaultLaneWidth @19;
setCurveTargetLatA @20;
loadDefaultSettings @21;
loadRecommendedSettings @22;
setSlowDownForNextSpeedLimit @23;
setSpeedUpForNextSpeedLimit @24;
setHoldSpeedLimitWhileChangingSetSpeed @25;
}
enum SpeedLimitOffsetType {
static @0;
percent @1;
}
struct MapdIn @0xc86a3d38d13eb3ef {
type @0 :MapdInputType;
float @1 :Float32;
str @2 :Text;
bool @3 :Bool;
}
enum RoadContext {
freeway @0;
city @1;
unknown @2;
}
struct MapdOut @0xa4f1eb3323f5f582 {
wayName @0 :Text;
wayRef @1 :Text;
roadName @2 :Text;
speedLimit @3 :Float32;
nextSpeedLimit @4 :Float32;
nextSpeedLimitDistance @5 :Float32;
hazard @6 :Text;
nextHazard @7 :Text;
nextHazardDistance @8 :Float32;
advisorySpeed @9 :Float32;
nextAdvisorySpeed @10 :Float32;
nextAdvisorySpeedDistance @11 :Float32;
oneWay @12 :Bool;
lanes @13 :UInt8;
tileLoaded @14 :Bool;
speedLimitOffset @15 :Float32;
suggestedSpeed @16 :Float32;
estimatedRoadWidth @17 :Float32;
roadContext @18 :RoadContext;
distanceFromWayCenter @19 :Float32;
visionCurveSpeed @20 :Float32;
curveSpeed @21 :Float32;
}

View File

@@ -2632,16 +2632,16 @@ struct Event {
carStateSP @114 :Custom.CarStateSP;
liveMapDataSP @115 :Custom.LiveMapDataSP;
modelDataV2SP @116 :Custom.ModelDataV2SP;
customReserved10 @136 :Custom.CustomReserved10;
navigationd @136 :Custom.Navigationd;
customReserved11 @137 :Custom.CustomReserved11;
customReserved12 @138 :Custom.CustomReserved12;
customReserved13 @139 :Custom.CustomReserved13;
customReserved14 @140 :Custom.CustomReserved14;
customReserved15 @141 :Custom.CustomReserved15;
customReserved16 @142 :Custom.CustomReserved16;
customReserved17 @143 :Custom.CustomReserved17;
customReserved18 @144 :Custom.CustomReserved18;
customReserved19 @145 :Custom.CustomReserved19;
mapdExtendedOut @143 :Custom.MapdExtendedOut;
mapdIn @144 :Custom.MapdIn;
mapdOut @145 :Custom.MapdOut;
# *********** legacy + deprecated ***********
model @9 :Legacy.ModelData; # TODO: rename modelV2 and mark this as deprecated

View File

@@ -89,7 +89,9 @@ _services: dict[str, tuple] = {
"carStateSP": (True, 100., 10),
"liveMapDataSP": (True, 1., 1),
"modelDataV2SP": (True, 20.),
"navigationd": (True, 3.),
"liveLocationKalman": (True, 20.),
"mapdOut": (True, 20., 20),
# debug
"uiDebug": (True, 0., 1),

View File

@@ -130,6 +130,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Version", {PERSISTENT, STRING}},
// --- sunnypilot params --- //
{"AccelPersonality", {PERSISTENT | BACKUP, INT, std::to_string(static_cast<int>(cereal::LongitudinalPlanSP::AccelerationPersonality::NORMAL))}},
{"AccelPersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
@@ -146,27 +148,41 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
{"DynamicFollow", {PERSISTENT | BACKUP, BOOL, "0"}},
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
{"HideVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"IsReleaseSpBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"LastGPSPositionLLK", {PERSISTENT, STRING}},
{"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "100"}},
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "0"}},
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RoadEdgeLaneChangeEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
// toyota specific params
{"ToyotaAutoHold", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaEnhancedBsm", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaTSS2Long", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaDriveMode", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
@@ -182,6 +198,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
// Navigation params
{"AllowNavigation", {PERSISTENT | BACKUP, BOOL, "0"}},
{"MapboxToken", {PERSISTENT | BACKUP, STRING}},
{"MapboxSettings", {CLEAR_ON_MANAGER_START, JSON}},
{"MapboxRoute", {PERSISTENT, STRING}},
{"MapboxRecompute", {PERSISTENT | BACKUP, BOOL, "0"}},
{"NavDesiresAllowed", {PERSISTENT | BACKUP, BOOL, "0"}},
// Neural Network Lateral Control
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -193,6 +217,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SunnylinkDongleId", {PERSISTENT, STRING}},
{"SunnylinkdPid", {PERSISTENT, INT}},
{"SunnylinkEnabled", {PERSISTENT, BOOL, "1"}},
{"SunnylinkTempFault", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0"}},
// Backup Manager params
{"BackupManager_CreateBackup", {PERSISTENT, BOOL}},
@@ -200,6 +225,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// sunnypilot car specific params
{"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}},
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -211,6 +239,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
// mapd v020
{"MapdSettings", {PERSISTENT | BACKUP, JSON}},
// mapd
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
{"MapdVersion", {PERSISTENT, STRING}},

View File

@@ -15,6 +15,8 @@
#include "common/version.h"
#include "system/hardware/hw.h"
#include "sunnypilot/common/version.h"
class SwaglogState {
public:
SwaglogState() {
@@ -56,7 +58,7 @@ public:
if (char* daemon_name = getenv("MANAGER_DAEMON")) {
ctx_j["daemon"] = daemon_name;
}
ctx_j["version"] = COMMA_VERSION;
ctx_j["version"] = SUNNYPILOT_VERSION;
ctx_j["dirty"] = !getenv("CLEAN");
ctx_j["device"] = Hardware::get_name();
}

View File

@@ -6,7 +6,7 @@ from openpilot.common.markdown import parse_markdown
class TestMarkdown:
def test_all_release_notes(self):
with open(os.path.join(BASEDIR, "RELEASES.md")) as f:
with open(os.path.join(BASEDIR, "CHANGELOG.md")) as f:
release_notes = f.read().split("\n\n")
assert len(release_notes) > 10

View File

@@ -9,6 +9,8 @@
#include "system/hardware/hw.h"
#include "third_party/json11/json11.hpp"
#include "sunnypilot/common/version.h"
std::string daemon_name = "testy";
std::string dongle_id = "test_dongle_id";
int LINE_NO = 0;
@@ -53,7 +55,7 @@ void recv_log(int thread_cnt, int thread_msg_cnt) {
REQUIRE(ctx["dongle_id"].string_value() == dongle_id);
REQUIRE(ctx["dirty"].bool_value() == true);
REQUIRE(ctx["version"].string_value() == COMMA_VERSION);
REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION);
std::string device = Hardware::get_name();
REQUIRE(ctx["device"].string_value() == device);

View File

@@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 334 Supported Cars
# 339 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -21,7 +21,10 @@ A supported vehicle is one that just works when you install a comma device. All
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2017">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2018-21">Buy Here</a></sub></details>|||
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Malibu Non-ACC 2016-23">Buy Here</a></sub></details>|||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Silverado 1500 2020-21">Buy Here</a></sub></details>|||
|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Trailblazer 2021-22">Buy Here</a></sub></details>|||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>|||
@@ -83,7 +86,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector + Honda Clarity Proxy Board<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://shop.retropilot.org/product/honda-clarity-proxy-board-kit">Buy Here</a></sub></details>|||
@@ -99,7 +102,9 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|HR-V 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|Insight 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|Honda|Inspire 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|Honda|N-Box 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|Honda|Odyssey 2021-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-25">Buy Here</a></sub></details>|||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
@@ -163,6 +168,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|EV6 (without HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte 2019-21">Buy Here</a></sub></details>|||
|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte 2022-23">Buy Here</a></sub></details>|||
|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2019">Buy Here</a></sub></details>|||
|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K5 2021-24">Buy Here</a></sub></details>|||
|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K5 Hybrid 2020-22">Buy Here</a></sub></details>|||
|Kia|K8 Hybrid (with HDA II) 2023[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K8 Hybrid (with HDA II) 2023">Buy Here</a></sub></details>|||
@@ -233,20 +239,20 @@ A supported vehicle is one that just works when you install a comma device. All
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Škoda|Fabia 2022-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Škoda|Kamiq 2021-23[<sup>13,15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Škoda|Karoq 2019-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
@@ -259,7 +265,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW4) 2024[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard 2019-20">Buy Here</a></sub></details>|||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Avalon 2016">Buy Here</a></sub></details>|||
@@ -305,7 +311,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Wildlander PHEV 2021|All|openpilot|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Wildlander PHEV 2021">Buy Here</a></sub></details>|||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||

View File

@@ -39,7 +39,7 @@ cd $BUILD_DIR
rm -f panda/board/obj/panda.bin.signed
rm -f panda/board/obj/panda_h7.bin.signed
VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}')
VERSION=$(cat sunnypilot/common/version.h | awk -F[\"-] '{print $2}')
echo "[-] committing version $VERSION T=$SECONDS"
git add -f .
git commit -a -m "openpilot v$VERSION release"

View File

@@ -49,7 +49,7 @@ rm -f panda/board/obj/panda.bin.signed
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD)
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}')
VERSION=$(cat $SOURCE_DIR/sunnypilot/common/version.h | awk -F\" '{print $2}')
echo -n "$GIT_HASH" > git_src_commit
echo -n "$GIT_COMMIT_DATE" > git_src_commit_date

View File

@@ -30,7 +30,7 @@ if [ -z "$GIT_ORIGIN" ]; then
fi
# "Tagging"
echo "#define COMMA_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/common/version.h
echo "#define SUNNYPILOT_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/sunnypilot/common/version.h
## set git identity
#source $DIR/identity.sh
@@ -55,7 +55,7 @@ git add -f .
# include source commit hash and build date in commit
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/common/version.h)
SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/sunnypilot/common/version.h)
# Commit with detailed message
git commit -a -m "sunnypilot v$VERSION

Binary file not shown.

Binary file not shown.

View File

@@ -10,7 +10,7 @@ from cereal import car, log, custom
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.common.swaglog import cloudlog, ForwardingHandler
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from opendbc.car import DT_CTRL, structs
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
from opendbc.car.carlog import carlog
@@ -88,6 +88,7 @@ class Car:
self.can_callbacks = can_comm_callbacks(self.can_sock, self.pm.sock['sendcan'])
is_release = self.params.get_bool("IsReleaseBranch")
is_release_sp = self.params.get_bool("IsReleaseSpBranch")
if CI is None:
# wait for one pandaState and one CAN packet
@@ -110,7 +111,7 @@ class Car:
init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params)
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params,
fixed_fingerprint, init_params_list_sp)
fixed_fingerprint, init_params_list_sp, is_release_sp)
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
self.CP = self.CI.CP
@@ -122,7 +123,13 @@ class Car:
self.CI, self.CP, self.CP_SP = CI, CI.CP, CI.CP_SP
self.RI = RI
# set alternative experiences from parameters
sp_toyota_auto_brake_hold = self.params.get_bool("ToyotaAutoHold")
self.CP.alternativeExperience = 0
if sp_toyota_auto_brake_hold:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
# mads
set_alternative_experience(self.CP, self.CP_SP, self.params)
set_car_specific_params(self.CP, self.CP_SP, self.params)

View File

@@ -57,7 +57,7 @@ def convert_carControlSP(struct: capnp.lib.capnp._DynamicStructReader) -> struct
struct_dataclass = structs.CarControlSP(**remove_deprecated({k: v for k, v in struct_dict.items() if not isinstance(k, dict)}))
struct_dataclass.mads = structs.ModularAssistiveDrivingSystem(**remove_deprecated(struct_dict.get('mads', {})))
struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
# struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
struct_dataclass.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(

View File

@@ -151,7 +151,7 @@ class TestCarModelBase(unittest.TestCase):
cls.CarInterface = interfaces[cls.platform]
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False)
cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, docs=False)
cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False)
assert cls.CP
assert cls.CP_SP
assert cls.CP.carFingerprint == cls.platform

View File

@@ -99,7 +99,6 @@ class Controls(ControlsExt, ModelStateBase):
self.LaC.extension.update_model_v2(self.sm['modelV2'])
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
self.LaC.extension.update_lateral_lag(self.lat_delay)
long_plan = self.sm['longitudinalPlan']
@@ -133,7 +132,7 @@ class Controls(ControlsExt, ModelStateBase):
self.LoC.reset()
# accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, self.CP_SP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits))
# Steering PID loop and lateral MPC
@@ -234,6 +233,9 @@ class Controls(ControlsExt, ModelStateBase):
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):

View File

@@ -3,9 +3,11 @@ from openpilot.common.constants import CV
from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
from openpilot.sunnypilot.navd.navigation_desires.navigation_desires import NavigationDesires
LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection
TurnDirection = custom.ModelDataV2SP.TurnDirection
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
LANE_CHANGE_TIME_MAX = 10.
@@ -32,9 +34,9 @@ DESIRES = {
}
TURN_DESIRES = {
custom.TurnDirection.none: log.Desire.none,
custom.TurnDirection.turnLeft: log.Desire.turnLeft,
custom.TurnDirection.turnRight: log.Desire.turnRight,
TurnDirection.none: log.Desire.none,
TurnDirection.turnLeft: log.Desire.turnLeft,
TurnDirection.turnRight: log.Desire.turnRight,
}
@@ -49,13 +51,14 @@ class DesireHelper:
self.desire = log.Desire.none
self.alc = AutoLaneChangeController(self)
self.lane_turn_controller = LaneTurnController(self)
self.lane_turn_direction = custom.TurnDirection.none
self.lane_turn_direction = TurnDirection.none
self.navigation_desires = NavigationDesires()
@staticmethod
def get_lane_change_direction(CS):
return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right
def update(self, carstate, lateral_active, lane_change_prob):
def update(self, carstate, lateral_active, lane_change_prob, left_edge_detected, right_edge_detected):
self.alc.update_params()
self.lane_turn_controller.update_params()
v_ego = carstate.vEgo
@@ -87,8 +90,8 @@ class DesireHelper:
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
(carstate.steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))
blindspot_detected = ((carstate.leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
(carstate.rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))
blindspot_detected = (((carstate.leftBlindspot or left_edge_detected) and self.lane_change_direction == LaneChangeDirection.left) or
((carstate.rightBlindspot or right_edge_detected) and self.lane_change_direction == LaneChangeDirection.right))
self.alc.update_lane_change(blindspot_detected, carstate.brakePressed)
@@ -126,7 +129,7 @@ class DesireHelper:
self.prev_one_blinker = one_blinker
if self.lane_turn_direction != custom.TurnDirection.none:
if self.lane_turn_direction != TurnDirection.none:
self.desire = TURN_DESIRES[self.lane_turn_direction]
else:
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
@@ -142,3 +145,7 @@ class DesireHelper:
self.desire = log.Desire.none
self.alc.update_state()
nav_desire = self.navigation_desires.update(carstate, lateral_active)
if nav_desire != log.Desire.none and (self.desire == log.Desire.none or self.desire in (log.Desire.turnLeft, log.Desire.turnRight)):
self.desire = nav_desire

View File

@@ -10,6 +10,8 @@ from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.modeld.constants import index_function
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
from openpilot.sunnypilot.selfdrive.controls.lib.accel_personality.accel_controller import AccelPersonalityController
from openpilot.sunnypilot.selfdrive.controls.lib.dynamic_personality.dynamic_follow import FollowDistanceController
if __name__ == '__main__': # generating code
from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
else:
@@ -228,6 +230,8 @@ class LongitudinalMpc:
self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
self.reset()
self.source = SOURCES[2]
self.accel_controller = AccelPersonalityController()
self.dynamic_follow = FollowDistanceController()
def reset(self):
# self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
@@ -328,10 +332,27 @@ class LongitudinalMpc:
return lead_xv
def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard):
t_follow = get_T_FOLLOW(personality)
v_ego = self.x0[1]
if self.dynamic_follow.is_enabled():
t_follow = self.dynamic_follow.get_follow_distance_multiplier(v_ego)
#print(f"DEBUG: dynamic_follow enabled, t_follow={t_follow:.3f}, v_ego={v_ego:.2f}, v_cruise={v_cruise:.2f}")
else:
t_follow = get_T_FOLLOW(personality)
#print(f"DEBUG: dynamic_follow disabled, using personality t_follow={t_follow:.3f}, personality={personality}")
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
# Get acceleration limits
if self.accel_controller.is_enabled():
min_accel = self.accel_controller.get_min_accel(v_ego)
#print(f"DEBUG: accel_enabled=True, min_accel={min_accel:.3f}")
else:
min_accel = CRUISE_MIN_ACCEL
#print(f"DEBUG: accel_enabled=False, using stock min_accel={min_accel}")
a_cruise_min = min_accel
lead_xv_0 = self.process_lead(radarstate.leadOne)
lead_xv_1 = self.process_lead(radarstate.leadTwo)
@@ -350,7 +371,7 @@ class LongitudinalMpc:
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05)
v_lower = v_ego + (T_IDXS * a_cruise_min * 1.05)
# TODO does this make sense when max_a is negative?
v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05)
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),

View File

@@ -51,12 +51,12 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
class LongitudinalPlanner(LongitudinalPlannerSP):
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
def __init__(self, CP, CP_SP, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP
self.mpc = LongitudinalMpc(dt=dt)
# TODO remove mpc modes when TR released
self.mpc.mode = 'acc'
LongitudinalPlannerSP.__init__(self, self.CP, self.mpc)
LongitudinalPlannerSP.__init__(self, self.CP, CP_SP, self.mpc)
self.fcw = False
self.dt = dt
self.allow_throttle = True
@@ -124,7 +124,13 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if mode == 'acc':
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
if self.accel_controller.is_enabled():
max_accel = self.accel_controller.get_max_accel(v_ego)
#print(f"Vibe personality active - max accel: {max_accel:.3f}")
accel_clip = [ACCEL_MIN, max_accel]
else:
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
else:
@@ -149,6 +155,10 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
# Get new v_cruise and a_desired from Smart Cruise Control and Speed Limit Assist
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
if sm.valid['mapdOut']:
if sm['mapdOut'].suggestedSpeed > 0 and v_cruise > sm['mapdOut'].suggestedSpeed:
v_cruise = sm['mapdOut'].suggestedSpeed
if force_slow_decel:
v_cruise = 0.0

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
from cereal import car
from cereal import car, custom
from openpilot.common.gps import get_gps_location_service
from openpilot.common.params import Params
from openpilot.common.realtime import Priority, config_realtime_process
@@ -17,18 +17,22 @@ def main():
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("plannerd got CarParams: %s", CP.brand)
cloudlog.info("plannerd is waiting for CarParamsSP")
CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP)
cloudlog.info("plannerd got CarParamsSP")
gps_location_service = get_gps_location_service(params)
ldw = LaneDepartureWarning()
longitudinal_planner = LongitudinalPlanner(CP)
longitudinal_planner = LongitudinalPlanner(CP, CP_SP)
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
'liveMapDataSP', 'carStateSP', gps_location_service],
'liveMapDataSP', 'carStateSP', 'mapdOut', gps_location_service],
poll='carState')
while True:
sm.update()
longitudinal_planner.sla.update_car_state(sm['carState'])
#longitudinal_planner.sla.update_car_state(sm['carState'])
if sm.updated['modelV2']:
longitudinal_planner.update(sm)
longitudinal_planner.publish(sm, pm)

BIN
selfdrive/mapd Executable file

Binary file not shown.

View File

@@ -51,8 +51,8 @@ def tg_compile(flags, model_name):
for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
flags = {
'larch64': 'DEV=QCOM',
'Darwin': 'DEV=CPU IMAGE=0',
}.get(arch, 'DEV=LLVM IMAGE=0')
'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env
}.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0')
tg_compile(flags, model_name)
# Compile BIG model if USB GPU is available

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import os
from openpilot.system.hardware import TICI
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
from tinygrad.tensor import Tensor
from tinygrad.dtype import dtypes
import math

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import os
from openpilot.system.hardware import TICI
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
USBGPU = "USBGPU" in os.environ
if USBGPU:
os.environ['DEV'] = 'AMD'
@@ -33,7 +33,7 @@ from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
PROCESS_NAME = "selfdrive.modeld.modeld"
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -298,6 +298,7 @@ def main(demo=False):
prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
RELC = RoadEdgeLaneChangeController(params.get_bool("RoadEdgeLaneChangeEnabled"))
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
@@ -395,7 +396,10 @@ def main(demo=False):
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
lane_change_prob = l_lane_change_prob + r_lane_change_prob
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
RELC.update(modelv2_send.modelV2.roadEdgeStds, modelv2_send.modelV2.laneLineProbs)
mdv2sp_send.modelDataV2SP.leftLaneChangeEdgeBlock = RELC.left_edge_detected
mdv2sp_send.modelDataV2SP.rightLaneChangeEdgeBlock = RELC.right_edge_detected
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, RELC.left_edge_detected, RELC.right_edge_detected)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction

View File

@@ -24,6 +24,7 @@ from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroa
from openpilot.system.version import get_build_metadata
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
from openpilot.sunnypilot import get_sanitize_int_param
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
@@ -43,6 +44,7 @@ LaneChangeDirection = log.LaneChangeDirection
EventName = log.OnroadEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
TurnDirection = custom.ModelDataV2SP.TurnDirection
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
@@ -86,7 +88,7 @@ class SelfdriveD(CruiseHelper):
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP'] + ['navigationd']
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
if REPLAY:
@@ -96,8 +98,8 @@ class SelfdriveD(CruiseHelper):
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
'modelDataV2SP', 'longitudinalPlanSP'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
'modelDataV2SP', 'longitudinalPlanSP', 'navigationd'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL))
@@ -130,7 +132,12 @@ class SelfdriveD(CruiseHelper):
self.logged_comm_issue = None
self.not_running_prev = None
self.experimental_mode = False
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
self.personality = get_sanitize_int_param(
"LongitudinalPersonality",
min(log.LongitudinalPersonality.schema.enumerants.values()),
max(log.LongitudinalPersonality.schema.enumerants.values()),
self.params
)
self.recalibrating_seen = False
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
@@ -223,8 +230,8 @@ class SelfdriveD(CruiseHelper):
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)
# Create events for temperature, disk space, and memory
@@ -285,23 +292,29 @@ class SelfdriveD(CruiseHelper):
# Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
direction = self.sm['modelV2'].meta.laneChangeDirection
mdv2sp = self.sm['modelDataV2SP']
if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \
(CS.rightBlindspot and direction == LaneChangeDirection.right):
(CS.rightBlindspot and direction == LaneChangeDirection.right):
self.events.add(EventName.laneChangeBlocked)
elif mdv2sp.leftLaneChangeEdgeBlock or mdv2sp.rightLaneChangeEdgeBlock:
self.events_sp.add(custom.OnroadEventSP.EventName.laneChangeRoadEdge)
else:
if direction == LaneChangeDirection.left:
self.events.add(EventName.preLaneChangeLeft)
else:
self.events.add(EventName.preLaneChangeRight)
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
LaneChangeState.laneChangeFinishing):
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
# Handle lane turn
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
if lane_turn_direction == custom.TurnDirection.turnLeft:
if lane_turn_direction == TurnDirection.turnLeft:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
elif lane_turn_direction == custom.TurnDirection.turnRight:
elif lane_turn_direction == TurnDirection.turnRight:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
for i, pandaState in enumerate(self.sm['pandaStates']):
@@ -489,7 +502,7 @@ class SelfdriveD(CruiseHelper):
# All pandas not in silent mode must have controlsAllowed when openpilot is enabled
if self.enabled and any(not ps.controlsAllowed for ps in self.sm['pandaStates']
if ps.safetyModel not in IGNORED_SAFETY_MODES):
if ps.safetyModel not in IGNORED_SAFETY_MODES):
self.mismatch_counter += 1
return CS

View File

@@ -51,7 +51,9 @@ class Plant:
from opendbc.car.honda.values import CAR
from opendbc.car.honda.interface import CarInterface
self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.HONDA_CIVIC), init_v=self.speed)
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
CP_SP = CarInterface.get_non_essential_params_sp(CP, CAR.HONDA_CIVIC)
self.planner = LongitudinalPlanner(CP, CP_SP, init_v=self.speed)
@property
def current_time(self):

View File

@@ -32,11 +32,11 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
experimentalLongitudinalToggle = new ParamControl(
"AlphaLongitudinalEnabled",
tr("openpilot Longitudinal Control (Alpha)"),
tr("sunnypilot Longitudinal Control (Alpha)"),
QString("<b>%1</b><br><br>%2")
.arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
.arg(tr("On this car, sunnypilot 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.")),
.arg(tr("WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
.arg(tr("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.")),
""
);
experimentalLongitudinalToggle->setConfirmation(true, false);

View File

@@ -12,7 +12,7 @@ public:
explicit DeveloperPanel(SettingsWindow *parent);
void showEvent(QShowEvent *event) override;
private:
protected:
Params params;
ParamControl* adbToggle;
ParamControl* joystickToggle;

View File

@@ -34,9 +34,38 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
false,
},
{
"DynamicExperimentalControl",
tr("Enable Dynamic Experimental Control"),
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
"ToyotaDriveMode",
tr("Enable drive mode btn link"),
tr("Links cars drive mode btn with accel personalities based on personality (i.e., relaxed, standard, sport)"),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaAutoHold",
tr("Toyota: Auto Brake Hold FOR TSS2 HYBRID CARS"),
tr("As you may auto brake hold currently supported by openpilot, this feature will allow sunnypilot to automatically hold the vehicle at a stop when the lead car is stopped. (TSS2 Hybird only)"),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaEnhancedBsm",
tr("Toyota: Prius TSS2 BSM and some tssp"),
tr("Add support for BSM."),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaTSS2Long",
tr("Toyota: custom tune"),
tr("idk something gas and brake"),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaStockLongitudinal",
tr("Toyota: Stock Toyota Longitudinal"),
tr("This feature will allow sunnypilot to use the stock Toyota longitudinal control instead of the sunnypilot longitudinal control. "
""),
"../assets/offroad/icon_blank.png",
false,
},
@@ -92,7 +121,15 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"your steering wheel distance button."),
"../assets/icons/speed_limit.png",
longi_button_texts);
// accel controller
std::vector<QString> accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco")};
accel_personality_setting = new ButtonParamControlSP("AccelPersonality", tr("Acceleration Personality"),
tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. "
"In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these "
"acceleration personality within Onroad Settings on the driving screen."),
"",
accel_personality_texts);
accel_personality_setting->showDescription();
// set up uiState update for personality setting
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
@@ -120,6 +157,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// insert longitudinal personality after NDOG toggle
if (param == "DisengageOnAccelerator") {
addItem(long_personality_setting);
addItem(accel_personality_setting);
}
}
@@ -140,6 +178,13 @@ void TogglesPanel::updateState(const UIState &s) {
}
uiState()->scene.personality = personality;
}
if (sm.updated("longitudinalPlanSP")) {
auto accel_personality = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getAccelPersonality();
if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) {
accel_personality_setting->setCheckedButton(static_cast<int>(accel_personality));
}
uiState()->scene.accel_personality = accel_personality;
}
}
void TogglesPanel::expandToggleDescription(const QString &param) {
@@ -186,16 +231,18 @@ void TogglesPanel::updateToggles() {
experimental_mode_toggle->setEnabled(true);
experimental_mode_toggle->setDescription(e2e_description);
long_personality_setting->setEnabled(true);
accel_personality_setting->setEnabled(true);
} else {
// no long for now
experimental_mode_toggle->setEnabled(false);
long_personality_setting->setEnabled(false);
accel_personality_setting->setEnabled(true);
params.remove("ExperimentalMode");
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
QString long_desc = unavailable + " " + \
tr("openpilot longitudinal control may come in a future update.");
tr("sunnypilot longitudinal control may come in a future update.");
if (CP.getAlphaLongitudinalAvailable()) {
if (is_release) {
long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches.");

View File

@@ -88,6 +88,7 @@ protected:
Params params;
std::map<std::string, ParamControl*> toggles;
ButtonParamControl *long_personality_setting;
ButtonParamControl *accel_personality_setting;
virtual void updateToggles();
};

View File

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

View File

@@ -51,8 +51,8 @@ void HudRenderer::draw(QPainter &p, const QRect &surface_rect) {
if (is_cruise_available) {
drawSetSpeed(p, surface_rect);
}
#endif
drawCurrentSpeed(p, surface_rect);
#endif
p.restore();
}

View File

@@ -22,7 +22,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
update_model(model, lead_one);
drawLaneLines(painter);
drawPath(painter, model, surface_rect);
drawPath(painter, model, surface_rect.height(), surface_rect.width());
if (longitudinal_control && sm.alive("radarState")) {
update_leads(radar_state, model.getPosition());
@@ -34,7 +34,6 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
}
}
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
painter.restore();
}
@@ -93,7 +92,7 @@ void ModelRenderer::drawLaneLines(QPainter &painter) {
}
}
void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height) {
void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height, int width) {
QLinearGradient bg(0, height, 0, 0);
if (experimental_mode) {
// The first half of track_vertices are the points for the right side of the path
@@ -128,6 +127,9 @@ void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reade
painter.setBrush(bg);
painter.drawPolygon(track_vertices);
//LongFuel(painter,height, width);
//LateralFuel(painter, height, width);
}
void ModelRenderer::updatePathGradient(QLinearGradient &bg) {
@@ -174,173 +176,195 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
(1 - t) * start.alphaF() + t * end.alphaF());
}
void ModelRenderer::drawGaugeBackground(QPainter &painter, qreal centerX, qreal centerY) {
const qreal backgroundSize = GAUGE_SIZE * BACKGROUND_SIZE_MULTIPLIER;
void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) {
auto *s = uiState();
auto &sm = *(s->sm);
// Draw circular background
painter.setPen(Qt::NoPen);
painter.setBrush(BACKGROUND_COLOR);
painter.drawEllipse(QPointF(centerX, centerY), backgroundSize / 2, backgroundSize / 2);
if (!sm.alive("radarState")) return;
// Draw border
QPen borderPen(BORDER_COLOR);
borderPen.setWidth(BORDER_PEN_WIDTH);
painter.setPen(borderPen);
painter.drawEllipse(QPointF(centerX, centerY), backgroundSize / 2 + 1, backgroundSize / 2 + 1);
const auto &radar_state = sm["radarState"].getRadarState();
const auto &lead_one = radar_state.getLeadOne();
const auto &lead_two = radar_state.getLeadTwo();
// Draw background semicircle
QPen semicirclePen(GAUGE_BACKGROUND_COLOR);
semicirclePen.setWidth(GAUGE_PEN_WIDTH);
semicirclePen.setCapStyle(Qt::RoundCap);
painter.setPen(semicirclePen);
painter.drawArc(QRectF(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE), 0, SEMICIRCLE_SPAN);
}
// Check if we have any active leads
bool has_lead_one = lead_one.getStatus();
bool has_lead_two = lead_two.getStatus();
if (!has_lead_one && !has_lead_two) {
// Fade out status display
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
if (lead_status_alpha <= 0.0f) return;
QColor ModelRenderer::getIndicatorColor(float absoluteValue, float lowThreshold, float highThreshold) {
if (absoluteValue < lowThreshold) {
return LOW_INDICATOR_COLOR;
} else if (absoluteValue < highThreshold) {
return MODERATE_INDICATOR_COLOR;
} else {
// Fade in status display
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
}
// Draw status for each lead vehicle under its chevron
if (true) {
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
}
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
return HIGH_INDICATOR_COLOR;
}
}
void ModelRenderer::drawLeadStatusAtPosition(QPainter &painter,
const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos,
int height, int width,
const QString &label) {
int ModelRenderer::calculateSpanAngle(float absoluteValue, float maxValue) {
const int spanAngle = static_cast<int>(QUARTER_CIRCLE_SPAN * (absoluteValue / maxValue));
return std::clamp(spanAngle, 0, QUARTER_CIRCLE_SPAN);
}
float d_rel = lead_data.getDRel();
float v_rel = lead_data.getVRel();
auto *s = uiState();
auto &sm = *(s->sm);
float v_ego = sm["carState"].getCarState().getVEgo();
void ModelRenderer::drawGaugeArc(QPainter &painter, qreal centerX, qreal centerY,
float value, bool isPositive, const QString &label) {
const float absoluteValue = std::abs(value);
int chevron_data = std::atoi(Params().get("ChevronInfo").c_str());
// Calculate chevron size (same logic as drawLead)
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
QFont content_font = painter.font();
content_font.setPixelSize(35);
content_font.setBold(true);
painter.setFont(content_font);
QFontMetrics fm(content_font);
bool is_metric = s->scene.is_metric;
QStringList text_lines;
const int chevron_types = 3;
const int chevron_all = chevron_types + 1; // All metrics (value 4)
QStringList chevron_text[chevron_types];
int position;
float val;
// Distance display (chevron_data == 1 or all)
if (chevron_data == 1 || chevron_data == chevron_all) {
position = 0;
val = std::max(0.0f, d_rel);
QString distance_unit = is_metric ? "m" : "ft";
if (!is_metric) {
val *= 3.28084f; // Convert meters to feet
}
chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit);
if (absoluteValue <= MIN_THRESHOLD) {
return; // Skip drawing if value is too small
}
// Absolute velocity display (chevron_data == 2 or all)
if (chevron_data == 2 || chevron_data == chevron_all) {
position = (chevron_data == 2) ? 0 : 1;
val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH)));
chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph"));
// Set up the arc rectangle
const QRectF arcRect(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE);
// Configure pen for the indicator arc
QPen indicatorPen;
indicatorPen.setWidth(GAUGE_PEN_WIDTH);
indicatorPen.setCapStyle(Qt::RoundCap);
painter.setPen(indicatorPen);
// Draw the arc based on direction
const int spanAngle = calculateSpanAngle(absoluteValue, 1.0f); // Adjust max value as needed
if (isPositive) {
painter.drawArc(arcRect, STARTING_ANGLE, spanAngle);
} else {
painter.drawArc(arcRect, STARTING_ANGLE, -spanAngle);
}
// Time-to-contact display (chevron_data == 3 or all)
if (chevron_data == 3 || chevron_data == chevron_all) {
position = (chevron_data == 3) ? 0 : 2;
val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
chevron_text[position].append(ttc_str);
// Draw center label
painter.setPen(Qt::white);
QFont font = painter.font();
font.setPixelSize(20);
font.setBold(true);
painter.setFont(font);
painter.drawText(QRectF(centerX - 50, centerY + 10, 100, 20), Qt::AlignCenter, label);
}
void ModelRenderer::LongFuel(QPainter &painter, int height, int width) {
const qreal rectWidth = static_cast<qreal>(width);
const qreal rectHeight = static_cast<qreal>(height);
UIState *s = uiState();
if (!s || !s->sm) {
return; // Safety check
}
// Collect all non-empty text lines
for (int i = 0; i < chevron_types; ++i) {
if (!chevron_text[i].isEmpty()) {
text_lines.append(chevron_text[i]);
}
}
// Get current acceleration
const float currentAcceleration = (*s->sm)["carControl"].getCarControl().getActuators().getAccel();
const float absoluteAcceleration = std::abs(currentAcceleration);
// If no text to display, return early
if (text_lines.isEmpty()) {
// Calculate gauge position
const qreal centerX = rectWidth / 17;
const qreal centerY = rectHeight / 2 + 120;
// Draw gauge background
drawGaugeBackground(painter, centerX, centerY);
// Skip drawing arc if acceleration is too small
if (absoluteAcceleration <= MIN_THRESHOLD) {
drawGaugeArc(painter, centerX, centerY, 0.0f, true, "LONG");
return;
}
// Text box dimensions
float str_w = 150; // Width of text area
float str_h = 45; // Height per line
// Determine indicator color based on acceleration magnitude
const QColor indicatorColor = getIndicatorColor(absoluteAcceleration, 0.3f, 0.6f);
// Position text below chevron, centered horizontally
float text_x = chevron_pos.x() - str_w / 2;
float text_y = chevron_pos.y() + sz + 15;
// Calculate span angle (scale for better visibility)
const int spanAngle = static_cast<int>(QUARTER_CIRCLE_SPAN * absoluteAcceleration);
const int clampedSpanAngle = std::clamp(spanAngle, 0, QUARTER_CIRCLE_SPAN);
// Clamp to screen bounds
text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10);
// Draw the acceleration arc
QPen indicatorPen(indicatorColor);
indicatorPen.setWidth(GAUGE_PEN_WIDTH);
indicatorPen.setCapStyle(Qt::RoundCap);
painter.setPen(indicatorPen);
// Shadow offset
QPoint shadow_offset(2, 2);
const QRectF arcRect(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE);
// Draw each line of text with shadow
for (int i = 0; i < text_lines.size(); ++i) {
if (!text_lines[i].isEmpty()) {
QRect textRect(text_x, text_y + (i * str_h), str_w, str_h);
// Draw shadow
painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha)));
painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()),
Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
// Determine text color based on content and danger level
QColor text_color;
// Check if this is a distance line (contains 'm' or 'ft')
if (text_lines[i].contains("m") || text_lines[i].contains("ft")) {
if (d_rel < 20.0f) {
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger
} else if (d_rel < 40.0f) {
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
} else {
text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe
}
}
// Enhanced color coding for time-to-contact
else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) {
float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat();
if (ttc_val < 3.0f) {
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent
} else if (ttc_val < 6.0f) {
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
} else {
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe
}
}
else {
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines
}
// Draw main text
painter.setPen(text_color);
painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
}
// Draw arc based on acceleration direction
if (currentAcceleration > 0) {
painter.drawArc(arcRect, STARTING_ANGLE, -clampedSpanAngle); // Left side for positive
} else {
painter.drawArc(arcRect, STARTING_ANGLE, clampedSpanAngle); // Right side for negative
}
// Reset pen
painter.setPen(Qt::NoPen);
// Draw center label
painter.setPen(Qt::white);
QFont font = painter.font();
font.setPixelSize(20);
font.setBold(true);
painter.setFont(font);
painter.drawText(QRectF(centerX - 50, centerY + 10, 100, 20), Qt::AlignCenter, "LONG");
}
void ModelRenderer::LateralFuel(QPainter &painter, int height, int width) {
const qreal rectWidth = static_cast<qreal>(width);
const qreal rectHeight = static_cast<qreal>(height);
UIState *s = uiState();
if (!s || !s->sm) {
return; // Safety check
}
// Get current steering angle
const float currentLateral = (*s->sm)["carState"].getCarState().getSteeringAngleDeg();
const float absoluteLateral = std::abs(currentLateral);
// Calculate gauge position
const qreal centerX = rectWidth / 17;
const qreal centerY = rectHeight / 2 - 120;
// Draw gauge background
drawGaugeBackground(painter, centerX, centerY);
// Skip drawing arc if lateral force is too small
if (absoluteLateral <= 0.1f) {
drawGaugeArc(painter, centerX, centerY, 0.0f, true, "LAT");
return;
}
// Determine indicator color based on lateral force magnitude
const QColor indicatorColor = getIndicatorColor(absoluteLateral, 5.0f, 15.0f);
// Calculate span angle (normalized to max expected steering angle)
const float maxSteeringAngle = 15.0f; // Adjust based on your vehicle's characteristics
const int spanAngle = static_cast<int>(QUARTER_CIRCLE_SPAN * (absoluteLateral / maxSteeringAngle));
const int clampedSpanAngle = std::clamp(spanAngle, 0, QUARTER_CIRCLE_SPAN);
// Draw the lateral arc
QPen indicatorPen(indicatorColor);
indicatorPen.setWidth(GAUGE_PEN_WIDTH);
indicatorPen.setCapStyle(Qt::RoundCap);
painter.setPen(indicatorPen);
const QRectF arcRect(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE);
// Draw arc based on steering direction
if (currentLateral < 0) {
painter.drawArc(arcRect, STARTING_ANGLE, -clampedSpanAngle); // Left turn
} else {
painter.drawArc(arcRect, STARTING_ANGLE, clampedSpanAngle); // Right turn
}
// Draw center label
painter.setPen(Qt::white);
QFont font = painter.font();
font.setPixelSize(20);
font.setBold(true);
painter.setFont(font);
painter.drawText(QRectF(centerX - 50, centerY + 10, 100, 20), Qt::AlignCenter, "LAT");
}
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &vd, const QRect &surface_rect) {
const float speedBuff = 10.;

View File

@@ -29,25 +29,27 @@ public:
ModelRenderer() {}
void setTransform(const Eigen::Matrix3f &transform) { car_space_transform = transform; }
void draw(QPainter &painter, const QRect &surface_rect);
void LongFuel(QPainter &p, int height, int width);
void LateralFuel(QPainter &p, int height, int width);
protected:
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out);
void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
QPolygonF *pvd, int max_idx, bool allow_invert = true);
void drawLeadStatus(QPainter &painter, int height, int width);
void drawLeadStatusAtPosition(QPainter &painter,
const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos,
int height, int width,
const QString &label);
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect);
void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line);
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
void drawLaneLines(QPainter &painter);
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height);
virtual void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {;
drawPath(painter, model, surface_rect.height());
}
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height, int width);
// Gauge helper methods
void drawGaugeBackground(QPainter &painter, qreal centerX, qreal centerY);
void drawGaugeArc(QPainter &painter, qreal centerX, qreal centerY,
float value, bool isPositive, const QString &label);
QColor getIndicatorColor(float absoluteValue, float lowThreshold, float highThreshold);
int calculateSpanAngle(float absoluteValue, float maxValue);
void updatePathGradient(QLinearGradient &bg);
QColor blendColors(const QColor &start, const QColor &end, float t);
@@ -65,8 +67,21 @@ protected:
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
QRectF clip_region;
float lead_status_alpha = 0.0f;
QPointF lead_status_pos;
QString lead_status_text;
QColor lead_status_color;
// Gauge configuration constants
static constexpr qreal GAUGE_SIZE = 140.0;
static constexpr qreal BACKGROUND_SIZE_MULTIPLIER = 1.4;
static constexpr qreal GAUGE_PEN_WIDTH = 30.0;
static constexpr qreal BORDER_PEN_WIDTH = 2.0;
static constexpr int SEMICIRCLE_SPAN = 180 * 16;
static constexpr int QUARTER_CIRCLE_SPAN = 90 * 16;
static constexpr int STARTING_ANGLE = 90 * 16;
static constexpr qreal MIN_THRESHOLD = 0.01;
// Color constants - Note: QColor cannot be constexpr, use inline static const instead
inline static const QColor BACKGROUND_COLOR = QColor(0, 0, 0, 80);
inline static const QColor BORDER_COLOR = QColor(0, 0, 0, 100);
inline static const QColor GAUGE_BACKGROUND_COLOR = QColor(50, 50, 50);
inline static const QColor LOW_INDICATOR_COLOR = QColor(23, 241, 66, 200);
inline static const QColor MODERATE_INDICATOR_COLOR = QColor(255, 166, 0, 200);
inline static const QColor HIGH_INDICATOR_COLOR = QColor(245, 0, 0, 200);
};

View File

@@ -4,8 +4,10 @@
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/alerts.h"
#define UIState UIStateSP
#define AnnotatedCameraWidget AnnotatedCameraWidgetSP
#define OnroadAlerts OnroadAlertsSP
#else
#include "selfdrive/ui/qt/onroad/annotated_camera.h"
#endif

View File

@@ -11,17 +11,18 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) {
main_layout->setContentsMargins(56, 40, 56, 40);
main_layout->setSpacing(42);
QLabel *title = new QLabel(tr("<span style='font-family: \"Noto Color Emoji\";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span>"));
title->setStyleSheet("font-size: 64px; font-weight: 500;");
community_popup = new SunnylinkCommunityPopup(this);
QLabel *title = new QLabel(tr("sunnypilot Community"));
title->setStyleSheet("font-size: 56px; font-weight: 500;");
main_layout->addWidget(title);
QLabel *desc = new QLabel(tr("Maximize your training data uploads to improve openpilot's driving models."));
QLabel *desc = new QLabel(tr("Need help or have ideas?<br><b>Join</b> our community now!"));
desc->setStyleSheet("font-size: 40px; font-weight: 400;");
desc->setWordWrap(true);
main_layout->addWidget(desc);
QPushButton *settings_btn = new QPushButton(tr("Open"));
connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(1, "FirehosePanel"); });
QPushButton *settings_btn = new QPushButton(tr("Learn More"));
connect(settings_btn, &QPushButton::clicked, [=]() { community_popup->exec(); });
settings_btn->setStyleSheet(R"(
QPushButton {
font-size: 48px;

View File

@@ -3,12 +3,17 @@
#include <QFrame>
#include <QWidget>
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h"
class WiFiPromptWidget : public QFrame {
Q_OBJECT
public:
explicit WiFiPromptWidget(QWidget* parent = 0);
private:
SunnylinkCommunityPopup *community_popup;
signals:
void openSettings(int index = 0, const QString &param = "");
};

View File

@@ -92,6 +92,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
// ignore events when device is awakened by resetInteractiveTimeout
ignore = !device()->isAwake();
device()->resetInteractiveTimeout();
#ifdef SUNNYPILOT
auto *s_sp = uiStateSP();
bool onroadScreenControl = s_sp->scene.onroadScreenOffControl;
bool started = s_sp->scene.started;
bool timerExpired = (s_sp->scene.onroadScreenOffTimer == 0);
ignore |= (onroadScreenControl and started and timerExpired);
s_sp->reset_onroad_sleep_timer();
#endif
break;
}
default:

View File

@@ -7,6 +7,10 @@
#include "selfdrive/ui/qt/offroad/onboarding.h"
#include "selfdrive/ui/qt/offroad/settings.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#endif
class MainWindow : public QWidget {
Q_OBJECT

View File

@@ -4,7 +4,7 @@ import time
import wave
from cereal import car, messaging
from cereal import car, messaging, custom
from openpilot.common.basedir import BASEDIR
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.realtime import Ratekeeper
@@ -26,8 +26,15 @@ AMBIENT_DB = 30 # DB where MIN_VOLUME is applied
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert
sound_list_sp: dict[int, tuple[str, int | None, float]] = {
# AudibleAlertSP, file name, play count (none for infinite)
AudibleAlertSP.promptSingleLow: ("prompt_single_low.wav", 1, MAX_VOLUME),
AudibleAlertSP.promptSingleHigh: ("prompt_single_high.wav", 1, MAX_VOLUME),
}
sound_list: dict[int, tuple[str, int | None, float]] = {
# AudibleAlert, file name, play count (none for infinite)
AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME),
@@ -40,6 +47,8 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
**sound_list_sp,
}
def check_selfdrive_timeout_alert(sm):

View File

@@ -30,14 +30,17 @@ qt_src = [
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
"sunnypilot/qt/offroad/settings/brightness.cc",
"sunnypilot/qt/offroad/settings/models_panel.cc",
"sunnypilot/qt/offroad/settings/navigation_panel.cc",
"sunnypilot/qt/offroad/settings/osm_panel.cc",
"sunnypilot/qt/offroad/settings/settings.cc",
"sunnypilot/qt/offroad/settings/software_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc",
"sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc",
"sunnypilot/qt/offroad/settings/trips_panel.cc",
"sunnypilot/qt/offroad/settings/vehicle_panel.cc",
"sunnypilot/qt/offroad/settings/visuals_panel.cc",
"sunnypilot/qt/onroad/alerts.cc",
"sunnypilot/qt/onroad/annotated_camera.cc",
"sunnypilot/qt/onroad/buttons.cc",
"sunnypilot/qt/onroad/developer_ui/developer_ui.cc",

View File

@@ -25,8 +25,8 @@ const QMap<QString, QString> Brightness::brightness_options = {
Brightness::Brightness() : OptionControlSP(
"Brightness",
tr("Brightness"),
tr("Overrides the brightness of the device."),
tr("Global Brightness"),
tr("Overrides the brightness of the device. This applies to both onroad and offroad screens. "),
"../assets/offroad/icon_blank.png",
{0, 11}, 1, true, &brightness_options) {

View File

@@ -56,14 +56,13 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare
addItem(errorLogBtn);
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
is_release = params.getBool("IsReleaseBranch");
is_tested = params.getBool("IsTestedBranch");
is_development = params.getBool("IsDevelopmentBranch");
}
void DeveloperPanelSP::updateToggles(bool offroad) {
bool disable_updates = params.getBool("DisableUpdates");
bool is_release = params.getBool("IsReleaseBranch") || params.getBool("IsReleaseSpBranch");
bool is_tested = params.getBool("IsTestedBranch");
bool is_development = params.getBool("IsDevelopmentBranch");
prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
prebuiltToggle->setEnabled(disable_updates);
@@ -80,6 +79,9 @@ void DeveloperPanelSP::updateToggles(bool offroad) {
enableGithubRunner->setVisible(!is_release);
errorLogBtn->setVisible(!is_release);
showAdvancedControls->setEnabled(true);
joystickToggle->setVisible(!is_release);
longManeuverToggle->setVisible(!is_release);
}
void DeveloperPanelSP::showEvent(QShowEvent *event) {

View File

@@ -22,9 +22,6 @@ private:
ParamControlSP *prebuiltToggle;
Params params;
ParamControlSP *showAdvancedControls;
bool is_development;
bool is_release;
bool is_tested;
private slots:
void updateToggles(bool offroad);

View File

@@ -23,6 +23,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
{"regulatoryBtn", tr("Regulatory"), ""},
{"translateBtn", tr("Language"), ""},
{"resetParams", tr("Reset Settings"), ""},
{"onroadUploadsBtn", tr("Onroad Uploads"), "OnroadUploads"}
};
int row = 0, col = 0;
@@ -75,35 +76,21 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
connect(buttons["resetParams"], &PushButtonSP::clicked, this, &DevicePanelSP::resetSettings);
connect(buttons["onroadUploadsBtn"], &PushButtonSP::clicked, buttons["onroadUploadsBtn"], &PushButtonSP::updateButton);
// Max Time Offroad
maxTimeOffroad = new MaxTimeOffroad();
connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh);
addItem(maxTimeOffroad);
toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true);
toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true);
addItem(toggleDeviceBootMode);
connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) {
params.put("DeviceBootMode", QString::number(index).toStdString());
updateState();
updateState(offroad);
});
interactivityTimeout = new OptionControlSP("InteractivityTimeout", tr("Interactivity Timeout"),
tr("Apply a custom timeout for settings UI."
"\nThis is the time after which settings UI closes automatically if user is not interacting with the screen."),
"", {0, 120}, 10, true, nullptr, false);
connect(interactivityTimeout, &OptionControlSP::updateLabels, [=]() {
updateState();
});
addItem(interactivityTimeout);
// Brightness
brightness = new Brightness();
connect(brightness, &OptionControlSP::updateLabels, brightness, &Brightness::refresh);
addItem(brightness);
addItem(device_grid_layout);
// offroad mode and power buttons
@@ -129,29 +116,21 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
offroadBtn->setFixedWidth(power_layout->sizeHint().width());
QObject::connect(offroadBtn, &PushButtonSP::clicked, this, &DevicePanelSP::setOffroadMode);
QVBoxLayout *power_group_layout = new QVBoxLayout();
power_group_layout = new QVBoxLayout();
power_group_layout->setSpacing(25);
power_group_layout->addWidget(offroadBtn, 0, Qt::AlignHCenter);
power_group_layout->addLayout(power_layout);
addItem(power_group_layout);
std::vector always_enabled_btns = {
always_enabled_btns = {
rebootBtn,
poweroffBtn,
offroadBtn,
buttons["quietModeBtn"],
buttons["onroadUploadsBtn"],
};
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
for (auto btn : findChildren<PushButtonSP*>()) {
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
if (!always_enabled) {
btn->setEnabled(offroad);
}
}
});
QObject::connect(uiState(), &UIState::offroadTransition, this, &DevicePanelSP::updateState);
}
void DevicePanelSP::setOffroadMode() {
@@ -175,7 +154,7 @@ void DevicePanelSP::setOffroadMode() {
ConfirmationDialog::alert(tr("Disengage to Enter Always Offroad Mode"), this);
}
updateState();
updateState(offroad);
}
void DevicePanelSP::resetSettings() {
@@ -192,16 +171,20 @@ void DevicePanelSP::resetSettings() {
}
void DevicePanelSP::showEvent(QShowEvent *event) {
updateState();
updateState(offroad);
}
void DevicePanelSP::updateState() {
if (!isVisible()) {
return;
void DevicePanelSP::updateState(bool _offroad) {
for (auto btn : findChildren<PushButtonSP*>()) {
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
if (!always_enabled) {
btn->setEnabled(_offroad);
}
}
bool offroad_mode_param = params.getBool("OffroadMode");
offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Always Offroad"));
offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Enable Always Offroad"));
offroadBtn->setStyleSheet(offroad_mode_param ? alwaysOffroadStyle : autoOffroadStyle);
DeviceSleepModeStatus currStatus = DeviceSleepModeStatus::DEFAULT;
@@ -210,10 +193,11 @@ void DevicePanelSP::updateState() {
}
toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus));
QString timeoutValue = QString::fromStdString(params.get("InteractivityTimeout"));
if (timeoutValue == "0" || timeoutValue.isEmpty()) {
interactivityTimeout->setLabel("Default");
if (offroad and not offroad_mode_param) {
power_group_layout->insertWidget(0, offroadBtn, 0, Qt::AlignHCenter);
} else {
interactivityTimeout->setLabel(timeoutValue + "s");
AddWidgetAt(0, offroadBtn);
}
offroad = _offroad;
}

View File

@@ -8,7 +8,6 @@
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/max_time_offroad.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
@@ -24,7 +23,7 @@ public:
explicit DevicePanelSP(SettingsWindowSP *parent = 0);
void showEvent(QShowEvent *event) override;
void setOffroadMode();
void updateState();
void updateState(bool _offroad);
void resetSettings();
private:
@@ -32,8 +31,10 @@ private:
PushButtonSP *offroadBtn;
MaxTimeOffroad *maxTimeOffroad;
ButtonParamControlSP *toggleDeviceBootMode;
Brightness *brightness;
OptionControlSP *interactivityTimeout;
QVBoxLayout *power_group_layout;
bool offroad;
std::vector<PushButtonSP*> always_enabled_btns = {};
const QString alwaysOffroadStyle = R"(
PushButtonSP {

View File

@@ -12,30 +12,29 @@ OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString &para
QWidget *parent)
: ExpandableToggleRow(param, title, description, icon, parent) {
auto *mainFrame = new QFrame(this);
auto *mainFrameLayout = new QGridLayout();
auto *mainFrameLayout = new QVBoxLayout();
mainFrame->setLayout(mainFrameLayout);
mainFrameLayout->setSpacing(0);
mainFrameLayout->setSpacing(30);
mainFrameLayout->setContentsMargins(0, 0, 0, 0);
onroadScreenOffTimer = new OptionControlSP(
"OnroadScreenOffTimer",
"",
"Onroad Brightness Delay",
"",
"",
{0, 11}, 1, true, &onroadScreenOffTimerOptions);
onroadScreenBrightness = new OptionControlSP(
"OnroadScreenOffBrightness",
"Onroad Brightness",
"",
"",
"",
{0, 100}, 10, true, nullptr, false);
{0, 90}, 10, true);
connect(onroadScreenOffTimer, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
connect(onroadScreenBrightness, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
onroadScreenOffTimer->setFixedWidth(280);
onroadScreenBrightness->setFixedWidth(280);
mainFrameLayout->addWidget(onroadScreenOffTimer, 0, 0, Qt::AlignLeft);
mainFrameLayout->addWidget(onroadScreenBrightness, 0, 1, Qt::AlignRight);
mainFrameLayout->addWidget(onroadScreenBrightness);
mainFrameLayout->addWidget(onroadScreenOffTimer);
addItem(mainFrame);
@@ -45,19 +44,11 @@ OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString &para
void OnroadScreenBrightnessControl::refresh() {
// Driving Screen Off Timer
int valTimer = std::atoi(params.get("OnroadScreenOffTimer").c_str());
std::string labelTimer = "<span style='font-size: 45px; font-weight: 450; color: #FFFFFF;'>";
labelTimer += "Delay";
labelTimer += " <br><span style='font-size: 40px; font-weight: 450; color:rgb(174, 255, 195);'>";
labelTimer += (valTimer < 60 ? std::to_string(valTimer) + "s" : std::to_string(valTimer / 60) + "m");
labelTimer += "</span></span>";
std::string labelTimer = (valTimer < 60 ? std::to_string(valTimer) + "s" : std::to_string(valTimer / 60) + "m");
onroadScreenOffTimer->setLabel(QString::fromStdString(labelTimer));
// Driving Screen Off Brightness
std::string valBrightness = params.get("OnroadScreenOffBrightness");
std::string labelBrightness = "<span style='font-size: 45px; font-weight: 450; color: #FFFFFF;'>";
labelBrightness += "Brightness";
labelBrightness += " <br><span style='font-size: 40px; font-weight: 450; color:rgb(174, 255, 195);'>";
labelBrightness += (valBrightness == "0" ? " Screen Off" : valBrightness + "%");
labelBrightness += "</span></span>";
std::string labelBrightness = (valBrightness == "0" ? " Screen Off" : valBrightness + "%");
onroadScreenBrightness->setLabel(QString::fromStdString(labelBrightness));
}

View File

@@ -18,12 +18,30 @@ DisplayPanel::DisplayPanel(QWidget *parent) : QWidget(parent) {
// Onroad Screen Off/Brightness
onroadScreenBrightnessControl = new OnroadScreenBrightnessControl(
"OnroadScreenOffControl",
tr("Driving Screen Off: Non-Critical Events"),
tr("Onroad Screen: Reduced Brightness"),
tr("Turn off device screen or reduce brightness after driving starts. "
"It automatically brightens again when screen is touched or a critical event occurs."),
"It automatically brightens again when screen is touched or a visible alert is displayed."),
"",
this);
list->addItem(onroadScreenBrightnessControl);
list->addItem(horizontal_line());
// Global Brightness
brightness = new Brightness();
connect(brightness, &OptionControlSP::updateLabels, brightness, &Brightness::refresh);
list->addItem(brightness);
list->addItem(horizontal_line());
// Interactivity Timeout
interactivityTimeout = new OptionControlSP("InteractivityTimeout", tr("Interactivity Timeout"),
tr("Apply a custom timeout for settings UI."
"\nThis is the time after which settings UI closes automatically if user is not interacting with the screen."),
"", {0, 120}, 10, true, nullptr, false);
connect(interactivityTimeout, &OptionControlSP::updateLabels, [=]() {
refresh();
});
list->addItem(interactivityTimeout);
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
@@ -37,4 +55,11 @@ void DisplayPanel::showEvent(QShowEvent *event) {
void DisplayPanel::refresh() {
onroadScreenBrightnessControl->refresh();
QString timeoutValue = QString::fromStdString(params.get("InteractivityTimeout"));
if (timeoutValue == "0" || timeoutValue.isEmpty()) {
interactivityTimeout->setLabel("Default");
} else {
interactivityTimeout->setLabel(timeoutValue + "s");
}
}

View File

@@ -7,6 +7,7 @@
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
@@ -25,4 +26,6 @@ private:
ScrollViewSP *sunnypilotScroller = nullptr;
Params params;
OnroadScreenBrightnessControl *onroadScreenBrightnessControl = nullptr;
Brightness *brightness;
OptionControlSP *interactivityTimeout;
};

View File

@@ -33,6 +33,12 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) {
tr("Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering."),
"../assets/offroad/icon_blank.png",
},
{
"RoadEdgeLaneChangeEnabled",
tr("Block Lane Change: Road Edge Detection"),
tr("Enable this toggle to block lane change when road edge is detected on the stalk actuated side."),
"../assets/offroad/icon_blank.png",
}
};
// Controls: Auto Lane Change Timer

View File

@@ -13,9 +13,9 @@ enum class SpeedLimitOffsetType {
};
inline const QString SpeedLimitOffsetTypeTexts[]{
QObject::tr("None"),
QObject::tr("Fixed"),
QObject::tr("Percent"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "None"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Fixed"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Percent"),
};
enum class SpeedLimitSourcePolicy {
@@ -27,11 +27,11 @@ enum class SpeedLimitSourcePolicy {
};
inline const QString SpeedLimitSourcePolicyTexts[]{
QObject::tr("Car\nOnly"),
QObject::tr("Map\nOnly"),
QObject::tr("Car\nFirst"),
QObject::tr("Map\nFirst"),
QObject::tr("Combined\nData")
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nOnly"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nOnly"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nFirst"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nFirst"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Combined\nData")
};
enum class SpeedLimitMode {
@@ -42,8 +42,8 @@ enum class SpeedLimitMode {
};
inline const QString SpeedLimitModeTexts[]{
QObject::tr("Off"),
QObject::tr("Information"),
QObject::tr("Warning"),
QObject::tr("Assist"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Off"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Information"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Warning"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Assist"),
};

View File

@@ -23,11 +23,11 @@ SpeedLimitPolicy::SpeedLimitPolicy(QWidget *parent) : QWidget(parent) {
ListWidgetSP *list = new ListWidgetSP(this);
std::vector<QString> speed_limit_policy_texts{
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)],
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)]
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)].toStdString().c_str())
};
speed_limit_policy = new ButtonParamControlSP(
"SpeedLimitPolicy",

View File

@@ -7,6 +7,8 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) {
subPanelFrame = new QFrame();
QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame);
@@ -25,10 +27,10 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
speedLimitPolicyScreen = new SpeedLimitPolicy(this);
std::vector<QString> speed_limit_mode_texts{
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)],
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)],
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)],
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)],
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)].toStdString().c_str())
};
speed_limit_mode_settings = new ButtonParamControlSP(
"SpeedLimitMode",
@@ -64,9 +66,9 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame);
std::vector<QString> speed_limit_offset_texts{
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)],
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)],
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)]
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)].toStdString().c_str()),
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)].toStdString().c_str()),
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)].toStdString().c_str())
};
speed_limit_offset_settings = new ButtonParamControlSP(
"SpeedLimitOffsetType",
@@ -103,13 +105,13 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
}
void SpeedLimitSettings::refresh() {
bool is_release = params.getBool("IsReleaseSpBranch");
bool is_metric_param = params.getBool("IsMetric");
SpeedLimitMode speed_limit_mode_param = static_cast<SpeedLimitMode>(std::atoi(params.get("SpeedLimitMode").c_str()));
SpeedLimitOffsetType offset_type_param = static_cast<SpeedLimitOffsetType>(std::atoi(params.get("SpeedLimitOffsetType").c_str()));
QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset"));
bool has_longitudinal_control;
bool intelligent_cruise_button_management_available;
bool sla_available;
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
@@ -120,11 +122,24 @@ void SpeedLimitSettings::refresh() {
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
has_longitudinal_control = hasLongitudinalControl(CP);
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
bool has_longitudinal_control = hasLongitudinalControl(CP);
bool has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
/*
* Speed Limit Assist is available when:
* - has_longitudinal_control or has_icbm, and
* - is not a release branch or not a disallowed brand, and
* - is not always disallowed
*/
bool sla_disallow_in_release = CP.getBrand() == "tesla" && is_release;
bool sla_always_disallow = CP.getBrand() == "rivian";
sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release && !sla_always_disallow;
if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) {
params.put("SpeedLimitMode", std::to_string(static_cast<int>(SpeedLimitMode::WARNING)));
}
} else {
has_longitudinal_control = false;
intelligent_cruise_button_management_available = false;
sla_available = false;
}
speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param));
@@ -144,13 +159,14 @@ void SpeedLimitSettings::refresh() {
speed_limit_offset->showDescription();
}
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
if (sla_available) {
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues()));
} else {
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(
{SpeedLimitMode::OFF,SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
{SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
}
speed_limit_mode_settings->refresh();
speed_limit_mode_settings->showDescription();
speed_limit_offset->showDescription();
}
@@ -158,3 +174,7 @@ void SpeedLimitSettings::refresh() {
void SpeedLimitSettings::showEvent(QShowEvent *event) {
refresh();
}
void SpeedLimitSettings::hideEvent(QHideEvent *event) {
setCurrentWidget(subPanelFrame);
}

View File

@@ -21,6 +21,7 @@ public:
SpeedLimitSettings(QWidget *parent = nullptr);
void refresh();
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
signals:
void backPress();
@@ -34,6 +35,7 @@ private:
SpeedLimitPolicy *speedLimitPolicyScreen;
ButtonParamControlSP *speed_limit_offset_settings;
OptionControlSP *speed_limit_offset;
bool icbm_available = false;
static QString offsetDescription(SpeedLimitOffsetType type = SpeedLimitOffsetType::NONE) {
QString none_str = tr("⦿ None: No Offset");

View File

@@ -7,6 +7,8 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
setStyleSheet(R"(
#back_btn {
@@ -36,13 +38,24 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
intelligentCruiseButtonManagement = new ParamControlSP(
"IntelligentCruiseButtonManagement",
tr("Intelligent Cruise Button Management (ICBM) (Alpha)"),
tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."),
"",
"",
this
);
intelligentCruiseButtonManagement->setConfirmation(true, false);
QObject::connect(intelligentCruiseButtonManagement, &ParamControlSP::toggleFlipped, this, [=](bool) {
refresh(offroad);
});
list->addItem(intelligentCruiseButtonManagement);
dynamicExperimentalControl = new ParamControlSP(
"DynamicExperimentalControl",
tr("Dynamic Experimental Control (DEC)"),
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
"",
this
);
list->addItem(dynamicExperimentalControl);
SmartCruiseControlVision = new ParamControl(
"SmartCruiseControlVision",
tr("Smart Cruise Control - Vision"),
@@ -62,6 +75,22 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
// Acceleration Personality
AccelPersonalityControl = new ParamControlSP("AccelPersonalityEnabled",
tr("Acceleration Personality"),
tr("Controls acceleration behavior: Eco (efficient), Normal (balanced), Sport (responsive). "
"Adjust how aggressively the vehicle accelerates while maintaining smooth operation."),
"../assets/offroad/icon_shell.png");
list->addItem(AccelPersonalityControl);
// Dynamic Personality
DynamicPersonalityControl = new ParamControlSP("DynamicFollow",
tr("Following Distance Personality"),
tr("Controls following distance and braking behavior: Relaxed (longer distance, gentler braking), Standard (balanced), Aggressive (shorter distance, firmer braking). "
"Fine-tune your comfort level in traffic situations."),
"../assets/offroad/icon_shell.png");
list->addItem(DynamicPersonalityControl);
speedLimitSettings = new PushButtonSP(tr("Speed Limit"), 750, this);
connect(speedLimitSettings, &QPushButton::clicked, [&]() {
cruisePanelScroller->setLastScrollPosition();
@@ -86,7 +115,13 @@ void LongitudinalPanel::showEvent(QShowEvent *event) {
refresh(offroad);
}
void LongitudinalPanel::hideEvent(QHideEvent *event) {
main_layout->setCurrentWidget(cruisePanelScreen);
}
void LongitudinalPanel::refresh(bool _offroad) {
const QString icbm_description = tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control.");
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
@@ -99,15 +134,65 @@ void LongitudinalPanel::refresh(bool _offroad) {
has_longitudinal_control = hasLongitudinalControl(CP);
is_pcm_cruise = CP.getPcmCruise();
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) {
intelligentCruiseButtonManagement->setEnabled(offroad);
intelligentCruiseButtonManagement->setDescription(icbm_description);
} else {
params.remove("IntelligentCruiseButtonManagement");
intelligentCruiseButtonManagement->setEnabled(false);
const QString icbm_unavaialble = tr("Intelligent Cruise Button Management is currently unavailable on this platform.");
QString long_desc = icbm_unavaialble;
if (has_longitudinal_control) {
if (CP.getAlphaLongitudinalAvailable()) {
long_desc = icbm_unavaialble + " " + tr("Disable the sunnypilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management.");
} else {
long_desc = icbm_unavaialble + " " + tr("sunnypilot Longitudinal Control is the default longitudinal control for this platform.");
}
}
intelligentCruiseButtonManagement->setDescription("<b>" + long_desc + "</b><br><br>" + icbm_description);
intelligentCruiseButtonManagement->showDescription();
}
if (has_longitudinal_control || has_icbm) {
// enable Custom ACC Increments when long is available and is not PCM cruise
customAccIncrement->setEnabled(((has_longitudinal_control && !is_pcm_cruise) || has_icbm) && offroad);
dynamicExperimentalControl->setEnabled(has_longitudinal_control);
SmartCruiseControlVision->setEnabled(true);
SmartCruiseControlMap->setEnabled(true);
} else {
params.remove("CustomAccIncrementsEnabled");
params.remove("DynamicExperimentalControl");
params.remove("SmartCruiseControlVision");
params.remove("SmartCruiseControlMap");
customAccIncrement->setEnabled(false);
dynamicExperimentalControl->setEnabled(false);
SmartCruiseControlVision->setEnabled(false);
SmartCruiseControlMap->setEnabled(false);
}
intelligentCruiseButtonManagement->refresh();
customAccIncrement->refresh();
dynamicExperimentalControl->refresh();
SmartCruiseControlVision->refresh();
SmartCruiseControlMap->refresh();
AccelPersonalityControl->setEnabled(true);
DynamicPersonalityControl->setEnabled(true);
AccelPersonalityControl->refresh();
DynamicPersonalityControl->refresh();
} else {
has_longitudinal_control = false;
is_pcm_cruise = false;
intelligent_cruise_button_management_available = false;
has_icbm = false;
intelligentCruiseButtonManagement->setDescription("<b>" + tr("Start the vehicle to check vehicle compatibility.") + "</br><b><b>" + icbm_description);
}
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
QString accNoLongDescription = tr("This feature can only be used with openpilot longitudinal control enabled.");
QString accNoLongDescription = tr("This feature can only be used with sunnypilot longitudinal control enabled.");
QString accPcmCruiseDisabledDescription = tr("This feature is not supported on this platform due to vehicle limitations.");
QString onroadOnlyDescription = tr("Start the vehicle to check vehicle compatibility.");
@@ -115,33 +200,19 @@ void LongitudinalPanel::refresh(bool _offroad) {
customAccIncrement->setDescription(onroadOnlyDescription);
customAccIncrement->showDescription();
} else {
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
if (is_pcm_cruise) {
if (has_longitudinal_control || has_icbm) {
if (has_longitudinal_control && is_pcm_cruise) {
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
customAccIncrement->showDescription();
} else {
customAccIncrement->setDescription(accEnabledDescription);
}
} else {
params.remove("CustomAccIncrementsEnabled");
customAccIncrement->toggleFlipped(false);
customAccIncrement->setDescription(accNoLongDescription);
customAccIncrement->showDescription();
params.remove("IntelligentCruiseButtonManagement");
intelligentCruiseButtonManagement->toggleFlipped(false);
}
}
bool icbm_allowed = intelligent_cruise_button_management_available && !has_longitudinal_control;
intelligentCruiseButtonManagement->setEnabled(icbm_allowed && offroad);
// enable toggle when long is available and is not PCM cruise
bool cai_allowed = (has_longitudinal_control && !is_pcm_cruise) || icbm_allowed;
customAccIncrement->setEnabled(cai_allowed && !offroad);
customAccIncrement->refresh();
SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed);
SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed);
offroad = _offroad;
}

View File

@@ -18,13 +18,14 @@ class LongitudinalPanel : public QWidget {
public:
explicit LongitudinalPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void refresh(bool _offroad);
private:
Params params;
bool has_longitudinal_control = false;
bool is_pcm_cruise = false;
bool intelligent_cruise_button_management_available = false;;
bool has_icbm = false;
bool offroad = false;
QStackedLayout *main_layout = nullptr;
@@ -34,6 +35,10 @@ private:
ParamControl *SmartCruiseControlVision;
ParamControl *SmartCruiseControlMap;
ParamControl *intelligentCruiseButtonManagement = nullptr;
ParamControl *dynamicExperimentalControl = nullptr;
ParamControlSP *AccelPersonalityControl;
ParamControlSP *DynamicPersonalityControl;
SpeedLimitSettings *speedLimitScreen;
PushButtonSP *speedLimitSettings;
};

View File

@@ -310,9 +310,8 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
QList<TreeNode> sortedModels;
QSet<QString> modelFolders;
QRegularExpression re("\\(([^)]*)\\)[^(]*$");
const auto bundles = model_manager.getAvailableBundles();
for (const auto &bundle : bundles) {
for (const auto &bundle : model_manager.getAvailableBundles()) {
auto overrides = bundle.getOverrides();
QString folder;
for (const auto &override : overrides) {
@@ -392,7 +391,7 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
showResetParamsDialog();
} else {
// Find selected bundle and initiate download
for (const auto &bundle: bundles) {
for (const auto &bundle: model_manager.getAvailableBundles()) {
if (QString::fromStdString(bundle.getRef()) == selectedBundleRef) {
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {

View File

@@ -0,0 +1,79 @@
/**
* 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/navigation_panel.h"
NavigationPanel::NavigationPanel(QWidget* parent) : QWidget(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 25, 50, 25);
list = new ListWidget(this, false);
scroller = new ScrollViewSP(list, this);
main_layout->addWidget(scroller);
// Mapbox Token
mapbox_token = new ButtonControl(tr("Mapbox Token"), tr("Edit"), tr("Enter your Mapbox API token"));
QObject::connect(mapbox_token, &ButtonControl::clicked, [=]() {
QString current = QString::fromStdString(params.get("MapboxToken"));
QString token = InputDialog::getText(tr("Enter Mapbox Token"), this, "", false, -1, current);
if (!token.isEmpty()) {
params.put("MapboxToken", token.toStdString());
refresh();
}
});
list->addItem(mapbox_token);
// Mapbox Route
mapbox_route = new ButtonControl(tr("Mapbox Route"), tr("Edit"), tr("Enter Mapbox route data"));
QObject::connect(mapbox_route, &ButtonControl::clicked, [=]() {
QString current = QString::fromStdString(params.get("MapboxRoute"));
QString route = InputDialog::getText(tr("Enter Mapbox Route"), this, "", false, -1, current);
if (!route.isEmpty()) {
params.put("MapboxRoute", route.toStdString());
refresh();
}
});
list->addItem(mapbox_route);
// Allow Navigation
allow_navigation = new ParamControlSP("AllowNavigation", tr("Allow Navigation"), tr("Enable navigation features and start navigationd"), "", this);
QObject::connect(allow_navigation, &ParamControlSP::toggleFlipped, this, &NavigationPanel::updateNavigationVisibility);
list->addItem(allow_navigation);
// Mapbox Recompute
mapbox_recompute = new ParamControlSP("MapboxRecompute", tr("Mapbox Recompute"), tr("Enable automatic route recomputation"), "", this);
list->addItem(mapbox_recompute);
// Nav Allowed
nav_allowed = new ParamControlSP("NavDesiresAllowed", tr("Navigation Allowed"), tr("Allow navigation to automatically take turns"), "", this);
list->addItem(nav_allowed);
}
void NavigationPanel::updateNavigationVisibility(bool state) {
mapbox_recompute->setVisible(state);
nav_allowed->setVisible(state);
}
void NavigationPanel::showEvent(QShowEvent *event) {
refresh();
}
void NavigationPanel::refresh() {
allow_navigation->refresh();
bool nav_enabled = allow_navigation->isToggled();
updateNavigationVisibility(nav_enabled);
QString token = QString::fromStdString(params.get("MapboxToken"));
mapbox_token->setValue(token.isEmpty() ? tr("Not set") : token);
QString route = QString::fromStdString(params.get("MapboxRoute"));
mapbox_route->setValue(route.isEmpty() ? tr("Not set") : route);
mapbox_recompute->refresh();
nav_allowed->refresh();
}

View File

@@ -0,0 +1,38 @@
/**
* 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/offroad/settings.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
class NavigationPanel : public QWidget {
Q_OBJECT
public:
explicit NavigationPanel(QWidget* parent = nullptr);
void showEvent(QShowEvent *event) override;
void refresh();
public slots:
void updateNavigationVisibility(bool state);
private:
Params params;
ListWidget* list;
ScrollViewSP* scroller;
ParamControlSP* allow_navigation;
ButtonControl* mapbox_token;
ButtonControl* mapbox_route;
ParamControlSP* mapbox_recompute;
ParamControlSP* nav_allowed;
};

View File

@@ -12,6 +12,7 @@
#include <string>
#include "common/swaglog.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) {
@@ -91,24 +92,30 @@ ButtonControlSP *OsmPanel::setupOsmDownloadButton(QWidget *parent) {
locationTitles.push_back(std::get<0>(loc));
}
const QString selection = MultiOptionDialog::getSelection(tr("Country"), locationTitles, currentTitle, this);
if (!selection.isEmpty()) {
params.put("OsmLocal", "1");
params.put("OsmLocationTitle", selection.toStdString());
for (auto &loc: locations) {
if (std::get<0>(loc) == selection) {
params.put("OsmLocationName", std::get<1>(loc).toStdString());
break;
InputDialog d(tr("Search Country"), this, tr("Enter search keywords, or leave blank to list all countries."), false);
d.setMinLength(0);
const int ret = d.exec();
if (ret) {
const QString selection = search(d.text(), locationTitles, tr("Select Country"));
if (!selection.isEmpty()) {
params.put("OsmLocal", "1");
params.put("OsmLocationTitle", selection.toStdString());
for (auto &loc: locations) {
if (std::get<0>(loc) == selection) {
params.put("OsmLocationName", std::get<1>(loc).toStdString());
break;
}
}
}
if (params.get("OsmLocationName") == "US") {
usStatesBtn->click();
return;
} else if (selection != "== None ==") {
if (showConfirmationDialog(parent)) {
osm_download_in_progress = true;
params.putBool("OsmDbUpdatesCheck", true);
updateLabels();
if (params.get("OsmLocationName") == "US") {
usStatesBtn->click();
return;
}
if (selection != "== None ==") {
if (showConfirmationDialog(parent)) {
osm_download_in_progress = true;
params.putBool("OsmDbUpdatesCheck", true);
updateLabels();
}
}
}
}
@@ -135,20 +142,25 @@ ButtonControlSP *OsmPanel::setupUsStatesButton(QWidget *parent) {
locationTitles.push_back(std::get<0>(loc));
}
const QString selection = MultiOptionDialog::getSelection(tr("State"), locationTitles, currentTitle, this);
if (!selection.isEmpty()) {
params.put("OsmStateTitle", selection.toStdString());
for (auto &loc: locations) {
if (std::get<0>(loc) == selection) {
params.put("OsmStateName", std::get<1>(loc).toStdString());
break;
InputDialog d(tr("Search State"), this, tr("Enter search keywords, or leave blank to list all states."), false);
d.setMinLength(0);
const int ret = d.exec();
if (ret) {
const QString selection = search(d.text(), locationTitles, tr("Select State"));
if (!selection.isEmpty()) {
params.put("OsmStateTitle", selection.toStdString());
for (auto &loc: locations) {
if (std::get<0>(loc) == selection) {
params.put("OsmStateName", std::get<1>(loc).toStdString());
break;
}
}
usStatesBtn->setValue(selection);
if (showConfirmationDialog(parent)) {
osm_download_in_progress = true;
params.putBool("OsmDbUpdatesCheck", true);
updateLabels();
}
}
usStatesBtn->setValue(selection);
if (showConfirmationDialog(parent)) {
osm_download_in_progress = true;
params.putBool("OsmDbUpdatesCheck", true);
updateLabels();
}
}
updateLabels();
@@ -281,3 +293,15 @@ void OsmPanel::updateMapSize() {
mapSizeFuture = QtConcurrent::run(getDirSize, MAP_PATH);
}
}
QString OsmPanel::search(const QString &query, const QStringList &list, const QString &prompt_text) {
QStringList lst_results = searchFromList(query, list);
QString selection;
if (lst_results.isEmpty()) {
ConfirmationDialog::alert(tr("No results found for keywords: %1").arg(query), this);
return selection;
}
selection = MultiOptionDialog::getSelection(prompt_text, lst_results, "", this);
return selection;
}

View File

@@ -76,6 +76,7 @@ private:
void updateDownloadProgress();
static int extractIntFromJson(const QJsonObject &json, const QString &key);
QString processUpdateStatus(bool pending_update_check, int total_files, int downloaded_files, const QJsonObject &json, bool failed_state);
QString search(const QString &query, const QStringList &list, const QString &prompt_text);
ConfirmationDialog *confirmationDialog;
LabelControlSP *mapdVersion;

View File

@@ -15,6 +15,7 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/navigation_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h"
@@ -85,6 +86,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
PanelInfo(" " + tr("Toggles"), toggles, "../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
PanelInfo(" " + tr("Models"), new ModelsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_models.png"),
PanelInfo(" " + tr("Navigation"), new NavigationPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_map.png"),
PanelInfo(" " + tr("Steering"), new LateralPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"),
PanelInfo(" " + tr("Cruise"), new LongitudinalPanel(this), "../assets/icons/speed_limit.png"),
PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"),

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2025-, sunnypilot 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/sunnylink/community_widget.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
using qrcodegen::QrCode;
// --- SunnylinkCommunityQRWidget ---
SunnylinkCommunityQRWidget::SunnylinkCommunityQRWidget(QWidget* parent)
: QWidget(parent) {}
void SunnylinkCommunityQRWidget::showEvent(QShowEvent *event) {
updateQrCode(SUNNYLINK_COMMUNITY_URL);
update();
}
void SunnylinkCommunityQRWidget::updateQrCode(const QString &text) {
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
qint32 sz = qr.getSize();
QImage im(sz, sz, QImage::Format_RGB32);
QRgb black = qRgb(0, 0, 0);
QRgb white = qRgb(255, 255, 255);
for (int y = 0; y < sz; y++) {
for (int x = 0; x < sz; x++) {
im.setPixel(x, y, qr.getModule(x, y) ? black : white);
}
}
int final_sz = ((width() / sz) - 1) * sz;
img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
}
void SunnylinkCommunityQRWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(rect(), Qt::white);
if (!img.isNull()) {
QSize s = (size() - img.size()) / 2;
p.drawPixmap(s.width(), s.height(), img);
}
}
// --- SunnylinkCommunityPopup ---
QStringList SunnylinkCommunityPopup::getInstructions() {
QStringList instructions;
instructions << tr("Scan the QR code and join us!");
return instructions;
}
SunnylinkCommunityPopup::SunnylinkCommunityPopup(QWidget* parent)
: DialogBase(parent) {
auto *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Solarized Light base3 background
setStyleSheet("SunnylinkCommunityPopup { background-color: #FDF6E3; }");
// Header spanning full width
auto headerWidget = new QWidget(this);
auto headerLayout = new QHBoxLayout(headerWidget);
headerLayout->setContentsMargins(85, 50, 85, 30);
headerLayout->setSpacing(30);
auto close = new QPushButton(QIcon(":/icons/close.svg"), "", this);
close->setIconSize(QSize(80, 80));
close->setStyleSheet("border: none;");
connect(close, &QPushButton::clicked, this, &QDialog::reject);
headerLayout->addWidget(close, 0, Qt::AlignLeft | Qt::AlignVCenter);
const auto title = new QLabel(tr("Join the sunnypilot Community Forum"), this);
// Solarized base02 for text
title->setStyleSheet("font-size: 65px; color: #073642;");
title->setWordWrap(false);
title->setAlignment(Qt::AlignCenter);
headerLayout->addWidget(title, 1);
// Spacer to balance the close button on the right
auto spacer = new QWidget(this);
spacer->setFixedSize(80, 80);
headerLayout->addWidget(spacer, 0);
mainLayout->addWidget(headerWidget);
// Two-column content layout
auto contentLayout = new QHBoxLayout();
contentLayout->setContentsMargins(0, 0, 0, 0);
contentLayout->setSpacing(0);
mainLayout->addLayout(contentLayout, 66);
// Left side: description
auto leftLayout = new QVBoxLayout();
leftLayout->setContentsMargins(85, 40, 50, 70);
leftLayout->setSpacing(35);
contentLayout->addLayout(leftLayout, 40);
// Hype / intro paragraph
const auto desc = new QLabel(tr(
"We're excited to announce our <b>sunnypilot Community Forum</b><br><br>"
"Over the years, Discord just hasn't scaled well for our growing community.<br>"
"It's noisy, unsearchable, and great discussions disappear too easily.<br>"
"Our new community forum aims to fix that by making it easier to <b>find answers, share ideas, track feedback, report bugs, help newcomers</b> and more!<br><br>"
"<b>Here's what's waiting for you:</b><br>"
"• Fully <b>indexable</b> and discoverable through search engines 🔎<br>"
"• <b>AI-powered</b>🤖 topic and chat summaries, spam detection, and more<br>"
"• A <b>trust-level system</b>✅ that rewards meaningful contributions<br>"
"• Designed to work <b>on your own time</b>.🧘<br><br>"
"Scan the QR code on the right and join the discussion!"
), this);
// Solarized base01 for body text
desc->setStyleSheet("font-size: 40px; color: #586E75;");
desc->setWordWrap(true);
leftLayout->addWidget(desc);
leftLayout->addStretch();
// Right side: QR code and instructions
auto rightLayout = new QVBoxLayout();
rightLayout->setContentsMargins(50, 40, 85, 70);
rightLayout->setSpacing(40);
contentLayout->addLayout(rightLayout, 1);
// QR code (smaller, fixed size)
auto *qr = new SunnylinkCommunityQRWidget(this);
qr->setFixedSize(500, 500);
rightLayout->addStretch();
rightLayout->addWidget(qr, 0, Qt::AlignCenter);
rightLayout->addStretch();
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2025-, sunnypilot 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 <QrCode.hpp>
#include <QtCore/qjsonobject.h>
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
const QString SUNNYLINK_COMMUNITY_URL = "https://community.sunnypilot.ai/sp-qr";
class SunnylinkCommunityQRWidget : public QWidget {
Q_OBJECT
public:
explicit SunnylinkCommunityQRWidget(QWidget* parent = nullptr);
void paintEvent(QPaintEvent*) override;
private:
QPixmap img;
void updateQrCode(const QString &text);
void showEvent(QShowEvent *event) override;
};
// Popup widget
class SunnylinkCommunityPopup : public DialogBase {
Q_OBJECT
public:
explicit SunnylinkCommunityPopup(QWidget* parent = nullptr);
private:
static QStringList getInstructions();
};

View File

@@ -79,11 +79,11 @@ QStringList SunnylinkSponsorPopup::getInstructions(bool sponsor_pair) {
instructions << tr("Scan the QR code to login to your GitHub account")
<< tr("Follow the prompts to complete the pairing process")
<< tr("Re-enter the \"sunnylink\" panel to verify sponsorship status")
<< tr("If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot");
<< tr("If sponsorship status was not updated, please contact a moderator on our forum at https://community.sunnypilot.ai");
} else {
instructions << tr("Scan the QR code to visit sunnyhaibin's GitHub Sponsors page")
<< tr("Choose your sponsorship tier and confirm your support")
<< tr("Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status");
<< tr("Join our Community Forum at https://community.sunnypilot.ai and reach out to a moderator if you have issues");
}
return instructions;
}

View File

@@ -21,7 +21,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
paramsRefresh(param_name, param_value);
});
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
is_sunnylink_enabled = params.getBool("SunnylinkEnabled");
connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updatePanel);
connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updatePanel);
connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) {
@@ -90,7 +90,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)");
sunnylinkUploaderEnabledBtn = new ParamControlSP(
"EnableSunnylinkUploader",
tr("[Don't use] Enable sunnylink uploader"),
tr("Enable sunnylink uploader (infrastructure test)"),
sunnylinkUploaderDesc,
"", nullptr, true);
list->addItem(sunnylinkUploaderEnabledBtn);
@@ -272,7 +272,7 @@ void SunnylinkPanel::updatePanel() {
const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A"));
sunnylinkEnabledBtn->setEnabled(!is_onroad);
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
is_sunnylink_enabled = params.getBool("SunnylinkEnabled");
bool is_sub = uiStateSP()->isSunnylinkSponsor() && is_sunnylink_enabled;
auto max_current_sponsor_rule = uiStateSP()->sunnylinkSponsorRole();
auto role_name = max_current_sponsor_rule.getSponsorTierString();
@@ -290,7 +290,10 @@ void SunnylinkPanel::updatePanel() {
pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled);
pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired"));
sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier == SponsorTier::Guardian && is_sunnylink_enabled);
bool can_do_uploads = max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled;
sunnylinkUploaderEnabledBtn->setVisible(can_do_uploads);
sunnylinkUploaderEnabledBtn->setEnabled(can_do_uploads);
if (!is_sunnylink_enabled) {
sunnylinkEnabledBtn->setValue("");

View File

@@ -33,7 +33,7 @@ private:
static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) {
if (!_has_longitudinal_control) {
return tr("This feature can only be used with openpilot longitudinal control enabled.");
return tr("This feature can only be used with sunnypilot longitudinal control enabled.");
}
if (!_offroad) {
@@ -57,7 +57,7 @@ private:
}
return QString("%1<br><br>%2<br>%3<br>%4<br>")
.arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with openpilot longitudinal control."))
.arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with sunnypilot longitudinal control."))
.arg(off_str)
.arg(dynamic_str)
.arg(predictive_str);

View File

@@ -8,7 +8,52 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h"
SubaruSettings::SubaruSettings(QWidget *parent) : BrandSettingsInterface(parent) {
stopAndGoToggle = new ParamControl("SubaruStopAndGo", tr("Stop and Go (Beta)"), "", "");
stopAndGoToggle->setConfirmation(true, false);
list->addItem(stopAndGoToggle);
stopAndGoManualParkingBrakeToggle = new ParamControl(
"SubaruStopAndGoManualParkingBrake",
tr("Stop and Go for Manual Parking Brake (Beta)"),
"",
""
);
stopAndGoManualParkingBrakeToggle->setConfirmation(true, false);
list->addItem(stopAndGoManualParkingBrakeToggle);
}
void SubaruSettings::updateSettings() {
auto cp_bytes = params.get("CarParamsPersistent");
if (!cp_bytes.empty()) {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
is_subaru = CP.getBrand() == "subaru";
if (is_subaru) {
if (!(CP.getFlags() & (SUBARU_FLAG_GLOBAL_GEN2 | SUBARU_FLAG_HYBRID))) {
has_stop_and_go = true;
}
}
} else {
is_subaru = false;
has_stop_and_go = false;
}
bool stop_and_go_disabled = !offroad || !has_stop_and_go;
QString stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc);
QString stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc);
if (stop_and_go_disabled) {
stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc, stopAndGoDisabledMsg());
stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc, stopAndGoDisabledMsg());
}
stopAndGoToggle->setEnabled(has_stop_and_go);
stopAndGoToggle->setDescription(stop_and_go_desc);
stopAndGoToggle->showDescription();
stopAndGoManualParkingBrakeToggle->setEnabled(has_stop_and_go);
stopAndGoManualParkingBrakeToggle->setDescription(stop_and_go_manual_parking_brake_desc);
stopAndGoManualParkingBrakeToggle->showDescription();
}

View File

@@ -14,6 +14,9 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
const int SUBARU_FLAG_GLOBAL_GEN2 = 4;
const int SUBARU_FLAG_HYBRID = 32;
class SubaruSettings : public BrandSettingsInterface {
Q_OBJECT
@@ -23,4 +26,32 @@ public:
private:
bool offroad = false;
bool is_subaru;
bool has_stop_and_go;
ParamControl* stopAndGoToggle;
ParamControl* stopAndGoManualParkingBrakeToggle;
QString stopAndGoDesc = tr("Experimental feature to enable auto-resume during stop-and-go for certain supported Subaru platforms.");
QString stopAndGoManualParkingBrakeDesc = 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!");
QString stopAndGoDisabledMsg() const {
if (is_subaru && !has_stop_and_go) {
return tr("This feature is currently not available on this platform.");
}
if (!is_subaru) {
return tr("Start the car to check car compatibility.");
}
if (!offroad) {
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.");
}
return QString();
}
static QString stopAndGoDescriptionBuilder(const QString &base_description, const QString &custom_description = "") {
return "<b>" + custom_description + "</b><br><br>" + base_description;
}
};

View File

@@ -8,7 +8,41 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h"
TeslaSettings::TeslaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
constexpr int coopSteeringMinKmh = 23; // minimum speed for cooperative steering (enforced by Tesla firmware)
constexpr int oemSteeringMinKmh = 48; // minimum speed for OEM lane departure avoidance (enforced by Tesla firmware)
bool is_metric = params.getBool("IsMetric");
QString unit = is_metric ? "km/h" : "mph";
int display_value_coop;
int display_value_oem;
if (is_metric) {
display_value_coop = coopSteeringMinKmh;
display_value_oem = oemSteeringMinKmh;
} else {
display_value_coop = static_cast<int>(std::round(coopSteeringMinKmh * KM_TO_MILE));
display_value_oem = static_cast<int>(std::round(oemSteeringMinKmh * KM_TO_MILE));
}
const QString coop_desc = QString("<b>%1</b><br><br>"
"%2<br>"
"%3<br>")
.arg(tr("Warning: May experience steering oscillations below %5 %6 during turns, recommend disabling this feature if you experience these."))
.arg(tr("Allows the driver to provide limited steering input while openpilot is engaged."))
.arg(tr("Only works above %4 %6."))
.arg(display_value_coop)
.arg(display_value_oem)
.arg(unit);
coopSteeringToggle = new ParamControlSP(
"TeslaCoopSteering",
tr("Cooperative Steering (Beta)"),
coop_desc,
"",
this
);
list->addItem(coopSteeringToggle);
coopSteeringToggle->showDescription();
coopSteeringToggle->setConfirmation(true, false);
}
void TeslaSettings::updateSettings() {
coopSteeringToggle->setEnabled(offroad);
}

View File

@@ -22,5 +22,5 @@ public:
void updateSettings() override;
private:
bool offroad = false;
ParamControlSP *coopSteeringToggle = nullptr;
};

View File

@@ -69,6 +69,27 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
"",
false,
},
{
"TrueVEgoUI",
tr("Speedometer: Always Display True Speed"),
tr("Always display the true vehicle current speed from wheel speed sensors."),
"",
false,
},
{
"HideVEgoUI",
tr("Speedometer: Hide from Onroad Screen"),
tr("When enabled, the speedometer on the onroad screen is not displayed."),
"",
false,
},
{
"ShowTurnSignals",
tr("Display Turn Signals"),
tr("When enabled, visual turn indicators are drawn on the HUD."),
"",
false,
},
};
// Add regular toggles first
@@ -98,7 +119,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
// Visuals: Display Metrics below Chevron
std::vector<QString> chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")};
chevron_info_settings = new ButtonParamControlSP(
"ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."),
"ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control)."),
"",
chevron_info_settings_texts,
200);
@@ -119,6 +140,40 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen);
QObject::connect(uiState(), &UIState::offroadTransition, this, &VisualsPanel::refreshLongitudinalStatus);
refreshLongitudinalStatus();
}
void VisualsPanel::refreshLongitudinalStatus() {
auto cp_bytes = params.get("CarParamsPersistent");
if (!cp_bytes.empty()) {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
has_longitudinal_control = hasLongitudinalControl(CP);
} else {
has_longitudinal_control = false;
}
if (chevron_info_settings) {
QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control).");
QString chevronNoLongDescription = tr("This feature requires sunnypilot longitudinal control to be available.");
if (has_longitudinal_control) {
chevron_info_settings->setDescription(chevronEnabledDescription);
} else {
// Reset to "Off" when longitudinal not available
params.put("ChevronInfo", "0");
chevron_info_settings->setDescription(chevronNoLongDescription);
}
// Enable only when longitudinal is available
chevron_info_settings->setEnabled(has_longitudinal_control);
chevron_info_settings->refresh();
}
}
void VisualsPanel::paramsRefresh() {

View File

@@ -19,6 +19,7 @@ public:
explicit VisualsPanel(QWidget *parent = nullptr);
void paramsRefresh();
void refreshLongitudinalStatus();
protected:
QStackedLayout* main_layout = nullptr;
@@ -29,4 +30,6 @@ protected:
ParamWatcher * param_watcher;
ButtonParamControlSP *chevron_info_settings;
ButtonParamControlSP *dev_ui_settings;
bool has_longitudinal_control = false;
};

View File

@@ -0,0 +1,102 @@
/**
* 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/onroad/alerts.h"
#include <QPainter>
#include <map>
#include <QRect>
#include <QFont>
OnroadAlerts::Alert OnroadAlertsSP::getAlert(const SubMaster &sm, uint64_t started_frame) {
OnroadAlerts::Alert alert = OnroadAlerts::getAlert(sm, started_frame);
alert.text1.replace("openpilot", "sunnypilot");
alert.text2.replace("openpilot", "sunnypilot");
return alert;
}
void OnroadAlertsSP::paintEvent(QPaintEvent *event) {
if (alert.size == cereal::SelfdriveState::AlertSize::NONE) {
return;
} else if (alert.size == cereal::SelfdriveState::AlertSize::FULL) {
OnroadAlerts::paintEvent(event);
return;
}
static std::map<cereal::SelfdriveState::AlertSize, const int> alert_heights = {
{cereal::SelfdriveState::AlertSize::SMALL, 271},
{cereal::SelfdriveState::AlertSize::MID, 420}
};
int h = alert_heights[alert.size];
QPainter p(this);
QFont topFont;
QFont bottomFont;
QRect topTextBoundingRect;
QRect bottomTextBoundingRect;
QRect rect;
int margin = 40;
int radius = 30;
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
const int v_adjustment = dev_ui_info > 1 && alert.size != cereal::SelfdriveState::AlertSize::FULL ? 40 : 0;
const int h_adjustment = dev_ui_info > 0 && alert.size != cereal::SelfdriveState::AlertSize::FULL ? 230 : 0;
if (alert.size == cereal::SelfdriveState::AlertSize::SMALL) {
topFont = InterFont(74, QFont::DemiBold);
QFontMetrics fmTop(topFont);
topTextBoundingRect = fmTop.boundingRect(
QRect(0 + margin, height() - h + margin - v_adjustment, width() - margin * 2 - h_adjustment, 0), Qt::TextWordWrap,
alert.text1);
h = topTextBoundingRect.height();
rect = QRect(0 + margin, height() - h - margin * 2 - v_adjustment, width() - margin * 2 - h_adjustment, h + margin);
} else if (alert.size == cereal::SelfdriveState::AlertSize::MID) {
topFont = InterFont(88, QFont::Bold);
bottomFont = InterFont(66);
QFontMetrics fmTop(topFont);
QFontMetrics fmBotton(bottomFont);
topTextBoundingRect = fmTop.boundingRect(
QRect(0 + margin, height() - h + margin - v_adjustment, width() - margin * 2 - h_adjustment, 0), Qt::TextWordWrap,
alert.text1);
bottomTextBoundingRect = fmBotton.boundingRect(
QRect(0 + margin, height() - h + margin - v_adjustment + topTextBoundingRect.height(),
width() - margin * 2 - h_adjustment, 0), Qt::TextWordWrap, alert.text2);
h = topTextBoundingRect.height() + bottomTextBoundingRect.height() + margin * 2;
rect = QRect(0 + margin, height() - h - margin * 2 - v_adjustment, width() - margin * 2 - h_adjustment, h + margin);
}
// draw background + gradient
// draw background + gradient
p.setPen(Qt::NoPen);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setBrush(QBrush(alert_colors[alert.status]));
p.drawRoundedRect(rect, radius, radius);
QLinearGradient g(0, rect.y(), 0, rect.bottom());
g.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.05));
g.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0.35));
p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
p.setBrush(QBrush(g));
p.drawRoundedRect(rect, radius, radius);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
// text
p.setPen(QColor(0xff, 0xff, 0xff));
p.setRenderHint(QPainter::TextAntialiasing);
p.setFont(topFont);
if (alert.size == cereal::SelfdriveState::AlertSize::SMALL) {
p.drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, alert.text1);
} else if (alert.size == cereal::SelfdriveState::AlertSize::MID) {
QRect topText = QRect(rect.x(), rect.top() + margin, rect.width(), topTextBoundingRect.height());
p.drawText(topText, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap | Qt::AlignCenter, alert.text1);
p.setFont(bottomFont);
p.drawText(QRect(rect.x(), topText.bottom() + margin, rect.width(), bottomTextBoundingRect.height()),
Qt::AlignHCenter | Qt::TextWordWrap | Qt::AlignCenter, alert.text2);
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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/onroad/alerts.h"
#include "selfdrive/ui/sunnypilot/ui.h"
class OnroadAlertsSP : public OnroadAlerts {
Q_OBJECT
public:
OnroadAlertsSP(QWidget *parent = 0) : OnroadAlerts(parent) {}
protected:
void paintEvent(QPaintEvent *) override;
Alert getAlert(const SubMaster &sm, uint64_t started_frame);
};

View File

@@ -18,4 +18,10 @@ void AnnotatedCameraWidgetSP::updateState(const UIState &s) {
void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) {
AnnotatedCameraWidget::showEvent(event);
ui_update_params_sp(uiState());
uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::RESUME);
}
void AnnotatedCameraWidgetSP::hideEvent(QHideEvent *event) {
AnnotatedCameraWidget::hideEvent(event);
uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::PAUSE);
}

View File

@@ -18,4 +18,5 @@ public:
protected:
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent* event) override;
};

View File

@@ -5,6 +5,7 @@
* See the LICENSE.md file in the root directory for more details.
*/
#include <QPainterPath>
#include <cmath>
#include "selfdrive/ui/sunnypilot/qt/onroad/hud.h"
@@ -12,59 +13,122 @@
HudRendererSP::HudRendererSP() {
plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {105, 105});
minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {105, 105});
plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {90, 90});
minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {90, 90});
int small_max = e2e_alert_small * 2 - 40;
int large_max = e2e_alert_large * 2 - 40;
green_light_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {small_max, small_max});
green_light_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {large_max, large_max});
lead_depart_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {small_max, small_max});
lead_depart_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {large_max, large_max});
int size = e2e_alert_size * 2 - 40;
green_light_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {size, size});
lead_depart_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {size, size});
}
void HudRendererSP::updateState(const UIState &s) {
HudRenderer::updateState(s);
float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH;
devUiInfo = s.scene.dev_ui_info;
roadName = s.scene.road_name;
showTurnSignals = s.scene.turn_signals;
speedLimitMode = static_cast<SpeedLimitMode>(s.scene.speed_limit_mode);
speedUnit = is_metric ? tr("km/h") : tr("mph");
standstillTimer = s.scene.standstill_timer;
const SubMaster &sm = *(s.sm);
const auto cs = sm["controlsState"].getControlsState();
const auto car_state = sm["carState"].getCarState();
const auto car_control = sm["carControl"].getCarControl();
const auto radar_state = sm["radarState"].getRadarState();
const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1;
const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation();
const char *gps_source = is_gps_location_external ? "gpsLocationExternal" : "gpsLocation";
const auto gpsLocation = is_gps_location_external ? sm[gps_source].getGpsLocationExternal() : sm[gps_source].getGpsLocation();
const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters();
const auto car_params = sm["carParams"].getCarParams();
const auto car_params_sp = sm["carParamsSP"].getCarParamsSP();
const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP();
if (sm.updated("carParams")) {
steerControlType = car_params.getSteerControlType();
}
if (sm.updated("carParamsSP")) {
pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed();
}
if (sm.alive("mapdOut") && sm.rcv_frame("mapdOut") > 0) {
const auto mapd = sm["mapdOut"].getMapdOut();
// Road name can come from wayName, wayRef, or roadName
wayName = QString::fromStdString(mapd.getWayName());
wayRef = QString::fromStdString(mapd.getWayRef());
QString mapdRoadName = QString::fromStdString(mapd.getRoadName());
if (!mapdRoadName.isEmpty()) {
roadNameStr = mapdRoadName;
} else if (!wayRef.isEmpty() && !wayName.isEmpty()) {
roadNameStr = wayRef + " - " + wayName;
} else if (!wayName.isEmpty()) {
roadNameStr = wayName;
} else if (!wayRef.isEmpty()) {
roadNameStr = wayRef;
} else {
roadNameStr = "";
}
greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert();
leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert();
tileLoaded = mapd.getTileLoaded();
float mapdSpeedLimitRaw = mapd.getSpeedLimit();
float mapdOffsetRaw = mapd.getSpeedLimitOffset();
mapdSpeedLimit = mapdSpeedLimitRaw * speedConv;
speedLimit = mapdSpeedLimit;
speedLimitLast = mapdSpeedLimit;
speedLimitOffset = mapdOffsetRaw * speedConv;
speedLimitValid = tileLoaded && mapdSpeedLimitRaw > 0;
speedLimitLastValid = speedLimitValid;
speedLimitFinalLast = mapdSpeedLimit + speedLimitOffset;
if (tileLoaded) {
speedLimitSource = 1; // MAP
} else {
speedLimitSource = 0; // NONE
}
float nextSpeedLimitRaw = mapd.getNextSpeedLimit();
speedLimitAheadValid = nextSpeedLimitRaw > 0 && tileLoaded;
speedLimitAhead = nextSpeedLimitRaw * speedConv;
speedLimitAheadDistance = mapd.getNextSpeedLimitDistance();
float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH;
speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv;
speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv;
speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv;
speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid();
speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid();
speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv;
speedLimitMode = static_cast<SpeedLimitMode>(s.scene.speed_limit_mode);
speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState();
speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive();
roadName = s.scene.road_name;
if (sm.updated("liveMapDataSP")) {
roadNameStr = QString::fromStdString(lmd.getRoadName());
speedLimitAheadValid = lmd.getSpeedLimitAheadValid();
speedLimitAhead = lmd.getSpeedLimitAhead() * speedConv;
speedLimitAheadDistance = lmd.getSpeedLimitAheadDistance();
if (speedLimitAheadDistance < speedLimitAheadDistancePrev && speedLimitAheadValidFrame < SPEED_LIMIT_AHEAD_VALID_FRAME_THRESHOLD) {
speedLimitAheadValidFrame++;
} else if (speedLimitAheadDistance > speedLimitAheadDistancePrev && speedLimitAheadValidFrame > 0) {
speedLimitAheadValidFrame--;
}
// SCC data from mapd
suggestedSpeed = mapd.getSuggestedSpeed() * speedConv;
visionCurveSpeed = mapd.getVisionCurveSpeed() * speedConv;
curveSpeed = mapd.getCurveSpeed() * speedConv;
smartCruiseControlVisionEnabled = visionCurveSpeed > 0;
smartCruiseControlVisionActive = visionCurveSpeed > 0 && visionCurveSpeed < speedLimit;
smartCruiseControlMapEnabled = curveSpeed > 0;
smartCruiseControlMapActive = curveSpeed > 0 && curveSpeed < speedLimit;
advisorySpeed = mapd.getAdvisorySpeed() * speedConv;
nextAdvisorySpeed = mapd.getNextAdvisorySpeed() * speedConv;
nextAdvisorySpeedDistance = mapd.getNextAdvisorySpeedDistance();
}
speedLimitAheadDistancePrev = speedLimitAheadDistance;
speedLimitAssistState = 0;
speedLimitAssistActive = false;
static int reverse_delay = 0;
bool reverse_allowed = false;
if (int(car_state.getGearShifter()) != 4) {
if (car_state.getGearShifter() != cereal::CarState::GearShifter::REVERSE) {
reverse_delay = 0;
reverse_allowed = false;
} else {
@@ -76,45 +140,119 @@ void HudRendererSP::updateState(const UIState &s) {
reversing = reverse_allowed;
if (sm.updated("liveParameters")) {
roll = sm["liveParameters"].getLiveParameters().getRoll();
}
if (sm.updated("deviceState")) {
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
}
if (sm.updated(gps_source)) {
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0;
altitude = gpsLocation.getAltitude();
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
bearingDeg = gpsLocation.getBearingDeg();
}
if (sm.updated("liveTorqueParameters")) {
torquedUseParams = ltp.getUseParams();
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
liveValid = ltp.getLiveValid();
}
latActive = car_control.getLatActive();
actuators = car_control.getActuators();
longOverride = car_control.getCruiseControl().getOverride();
carControlEnabled = car_control.getEnabled();
steerOverride = car_state.getSteeringPressed();
devUiInfo = s.scene.dev_ui_info;
speedUnit = is_metric ? tr("km/h") : tr("mph");
lead_d_rel = radar_state.getLeadOne().getDRel();
lead_v_rel = radar_state.getLeadOne().getVRel();
lead_status = radar_state.getLeadOne().getStatus();
steerControlType = car_params.getSteerControlType();
actuators = car_control.getActuators();
torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE;
angleSteers = car_state.getSteeringAngleDeg();
desiredCurvature = cs.getDesiredCurvature();
curvature = cs.getCurvature();
roll = sm["liveParameters"].getLiveParameters().getRoll();
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
altitude = gpsLocation.getAltitude();
vEgo = car_state.getVEgo();
aEgo = car_state.getAEgo();
steeringTorqueEps = car_state.getSteeringTorqueEps();
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
bearingDeg = gpsLocation.getBearingDeg();
torquedUseParams = ltp.getUseParams();
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
liveValid = ltp.getLiveValid();
standstillTimer = s.scene.standstill_timer;
isStandstill = car_state.getStandstill();
longOverride = car_control.getCruiseControl().getOverride();
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled();
smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive();
if (!s.scene.started) standstillElapsedTime = 0.0;
greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert();
leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert();
// override stock current speed values
float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo();
speed = std::max<float>(0.0f, v_ego * (is_metric ? MS_TO_KPH : MS_TO_MPH));
hideVEgoUI = s.scene.hideVEgoUI;
leftBlinkerOn = car_state.getLeftBlinker();
rightBlinkerOn = car_state.getRightBlinker();
leftBlindspot = car_state.getLeftBlindspot();
rightBlindspot = car_state.getRightBlindspot();
speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv;
allow_e2e_alerts = sm["selfdriveState"].getSelfdriveState().getAlertSize() == cereal::SelfdriveState::AlertSize::NONE &&
sm.rcv_frame("driverStateV2") > s.scene.started_frame && !reversing;
// Navigationd
if (sm.updated("navigationd")) {
auto nav = sm["navigationd"].getNavigationd();
navigationValid = nav.getValid();
if (navigationValid && nav.getAllManeuvers().size() > 0) {
int currManeuverIdx = nav.getAllManeuvers().size() > 1 ? 1 : 0;
auto maneuver = nav.getAllManeuvers()[currManeuverIdx];
navigationModifier = QString::fromStdString(maneuver.getModifier());
navigationManeuverType = QString::fromStdString(maneuver.getType());
float dist = maneuver.getDistance();
if (is_metric) {
if (dist < 1000) {
if (dist < 500) {
navigationDistance = QString::number(std::round(dist / 25.0) * 25) + " m";
}
else {
navigationDistance = QString::number(std::round(dist / 50.0) * 50) + " m";
}
} else {
navigationDistance = QString::number(dist / 1000, 'f', 1) + " km";
}
} else {
float dist_ft = dist * 3.28084f;
if (dist_ft < 1000) {
if (dist_ft <= 100){
navigationDistance = QString::number((std::round(dist_ft / 10.0) * 10)) + " ft";
}
else {
navigationDistance = QString::number((std::round(dist_ft / 50.0) * 50)) + " ft";
}
} else {
navigationDistance = QString::number(dist_ft / 5280, 'f', 1) + " mi";
}
}
QString instruction = QString::fromStdString(maneuver.getInstruction());
QStringList parts = instruction.split(" onto ");
if (parts.size() > 1) {
navigationStreet = parts[1].trimmed();
} else {
navigationStreet = instruction;
}
navigationStreet = navigationStreet.replace(".", "");
// Get next maneuver if available
if (nav.getAllManeuvers().size() > 2) {
auto nextManeuver = nav.getAllManeuvers()[2];
navigationNextModifier = QString::fromStdString(nextManeuver.getModifier());
navigationNextManeuverType = QString::fromStdString(nextManeuver.getType());
navigationHasNext = true;
} else {
navigationHasNext = false;
}
}
}
}
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
@@ -128,6 +266,10 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
drawSetSpeedSP(p, surface_rect);
}
if (!hideVEgoUI) {
drawCurrentSpeedSP(p, surface_rect);
}
if (!reversing) {
// Smart Cruise Control
int x_offset = -260;
@@ -170,11 +312,6 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
drawRightDevUI(p, surface_rect.right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rect_right.height());
}
// Standstill Timer
if (standstillTimer) {
drawStandstillTimer(p, surface_rect.right() / 12 * 10, surface_rect.bottom() / 12 * 1.53);
}
// Speed Limit
bool showSpeedLimit;
bool speed_limit_assist_pre_active_pulse = pulseElement(speedLimitAssistFrame);
@@ -186,7 +323,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
const int sign_height = 204;
QRect sign_rect(sign_x, sign_y, sign_width, sign_height);
if (speedLimitAssistState == cereal::LongitudinalPlanSP::SpeedLimit::AssistState::PRE_ACTIVE) {
if (speedLimitAssistState == 1) {
speedLimitAssistFrame++;
showSpeedLimit = speed_limit_assist_pre_active_pulse;
drawSpeedLimitPreActiveArrow(p, sign_rect);
@@ -199,7 +336,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
drawSpeedLimitSigns(p, sign_rect);
// do not show during SLA's preActive state
if (speedLimitAssistState != cereal::LongitudinalPlanSP::SpeedLimit::AssistState::PRE_ACTIVE) {
if (speedLimitAssistState != 1) {
drawUpcomingSpeedLimit(p);
}
}
@@ -208,7 +345,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
drawRoadName(p, surface_rect);
// Green Light & Lead Depart Alerts
if (greenLightAlert or leadDepartAlert) {
if (greenLightAlert || leadDepartAlert) {
e2eAlertDisplayTimer = 3 * UI_FREQ;
// reset onroad sleep timer for e2e alerts
uiStateSP()->reset_onroad_sleep_timer();
@@ -218,18 +355,39 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
e2eAlertFrame++;
if (greenLightAlert) {
alert_text = tr("GREEN\nLIGHT");
alert_img = devUiInfo > 0 ? green_light_alert_small_img : green_light_alert_large_img;
alert_img = green_light_alert_img;
}
else if (leadDepartAlert) {
alert_text = tr("LEAD VEHICLE\nDEPARTING");
alert_img = devUiInfo > 0 ? lead_depart_alert_small_img : lead_depart_alert_large_img;
alert_img = lead_depart_alert_img;
}
drawE2eAlert(p, surface_rect);
} else {
}
// Standstill Timer
else if (standstillTimer && isStandstill) {
alert_img = QPixmap();
standstillElapsedTime += 1.0 / UI_FREQ;
int minute = static_cast<int>(standstillElapsedTime / 60);
int second = static_cast<int>(standstillElapsedTime - (minute * 60));
alert_text = QString("%1:%2").arg(minute, 1, 10, QChar('0')).arg(second, 2, 10, QChar('0'));
drawE2eAlert(p, surface_rect, tr("STOPPED"));
e2eAlertFrame++;
}
// No Alerts displayed
else {
e2eAlertFrame = 0;
if (!isStandstill) standstillElapsedTime = 0.0;
}
// Blinker
if (showTurnSignals) {
drawBlinker(p, surface_rect);
}
}
drawNavigationHUD(p, surface_rect);
p.restore();
}
@@ -249,9 +407,14 @@ bool HudRendererSP::pulseElement(int frame) {
}
void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &surface_rect, int x_offset, int y_offset, std::string name) {
int x = surface_rect.center().x();
int base_x = surface_rect.center().x();
int y = surface_rect.height() / 4;
if (navigationValid) {
base_x = 618;
y = 420;
}
QString text = QString::fromStdString(name);
QFont font = InterFont(36, QFont::Bold);
p.setFont(font);
@@ -262,7 +425,7 @@ void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &s
int box_width = 160;
int box_height = fm.height() + padding_v * 2;
QRectF bg_rect(x - (box_width / 2) + x_offset,
QRectF bg_rect(base_x - (box_width / 2) + x_offset,
y - (box_height / 2) + y_offset,
box_width, box_height);
@@ -385,40 +548,6 @@ void HudRendererSP::drawBottomDevUI(QPainter &p, int x, int y) {
rw += drawBottomDevUIElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color);
}
void HudRendererSP::drawStandstillTimer(QPainter &p, int x, int y) {
if (isStandstill) {
standstillElapsedTime += 1.0 / UI_FREQ;
int minute = static_cast<int>(standstillElapsedTime / 60);
int second = static_cast<int>(standstillElapsedTime - (minute * 60));
// stop sign for standstill timer
const int size = 190; // size
const float angle = M_PI / 8.0;
QPolygon octagon;
for (int i = 0; i < 8; i++) {
float curr_angle = angle + i * M_PI / 4.0;
int point_x = x + size / 2 * cos(curr_angle);
int point_y = y + size / 2 * sin(curr_angle);
octagon << QPoint(point_x, point_y);
}
p.setPen(QPen(Qt::white, 6));
p.setBrush(QColor(255, 90, 81, 200)); // red pastel
p.drawPolygon(octagon);
QString time_str = QString("%1:%2").arg(minute, 1, 10, QChar('0')).arg(second, 2, 10, QChar('0'));
p.setFont(InterFont(55, QFont::Bold));
p.setPen(Qt::white);
QRect timerTextRect = p.fontMetrics().boundingRect(QString(time_str));
timerTextRect.moveCenter({x, y});
p.drawText(timerTextRect, Qt::AlignCenter, QString(time_str));
} else {
standstillElapsedTime = 0.0;
}
}
void HudRendererSP::drawSpeedLimitSigns(QPainter &p, QRect &sign_rect) {
bool speedLimitWarningEnabled = speedLimitMode >= SpeedLimitMode::WARNING; // TODO-SP: update to include SpeedLimitMode::ASSIST
bool hasSpeedLimit = speedLimitValid || speedLimitLastValid;
@@ -546,7 +675,8 @@ void HudRendererSP::drawSpeedLimitSigns(QPainter &p, QRect &sign_rect) {
}
void HudRendererSP::drawUpcomingSpeedLimit(QPainter &p) {
bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0;
bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0 &&
tileLoaded;
if (!speed_limit_ahead) {
return;
}
@@ -640,7 +770,7 @@ void HudRendererSP::drawRoadName(QPainter &p, const QRect &surface_rect) {
void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect) {
const int sign_margin = 12;
const int arrow_spacing = sign_margin * 3;
const int arrow_spacing = sign_margin * 1.4;
int arrow_x = sign_rect.right() + arrow_spacing;
int _set_speed = std::nearbyint(set_speed);
@@ -660,15 +790,16 @@ void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect)
}
void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
// Draw outer box + border to contain set speed
const QSize default_size = {172, 204};
QSize set_speed_size = is_metric ? QSize(200, 204) : default_size;
QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size);
// Draw set speed box
p.setPen(QPen(QColor(255, 255, 255, 75), 6));
p.setBrush(QColor(0, 0, 0, 166));
p.drawRoundedRect(set_speed_rect, 32, 32);
// ICBM counter logic
if (!pcmCruiseSpeed && carControlEnabled) {
if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) {
icbm_active_counter = 3 * UI_FREQ;
} else if (icbm_active_counter > 0) {
icbm_active_counter--;
}
} else {
icbm_active_counter = 0;
}
// Colors based on status
QColor max_color = QColor(0xa6, 0xa6, 0xa6, 0xff);
@@ -689,45 +820,414 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
}
}
// Draw "MAX" text
p.setFont(InterFont(40, QFont::DemiBold));
p.setPen(max_color);
p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX"));
// Draw set speed
QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX");
QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "";
p.setFont(InterFont(90, QFont::Bold));
p.setPen(set_speed_color);
p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr);
if (!navigationValid) {
// Original positions when navigation is not valid
const QSize default_size = {172, 204};
QSize set_speed_size = is_metric ? QSize(200, 204) : default_size;
QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size);
// Draw set speed box
p.setPen(QPen(QColor(255, 255, 255, 75), 6));
p.setBrush(QColor(0, 0, 0, 166));
p.drawRoundedRect(set_speed_rect, 32, 32);
// Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text
int max_str_size = (icbm_active_counter != 0) ? 60 : 40;
int max_str_y = (icbm_active_counter != 0) ? 15 : 27;
p.setFont(InterFont(max_str_size, QFont::DemiBold));
p.setPen(max_color);
p.drawText(set_speed_rect.adjusted(0, max_str_y, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str);
// Draw set speed
p.setFont(InterFont(90, QFont::Bold));
p.setPen(set_speed_color);
p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr);
} else {
// Modified positions when navigation is valid
const int container_width = 200;
const int container_height = 320;
const int container_x = 40;
const int container_y = 45;
QRect speed_container(container_x, container_y, container_width, container_height);
// Draw outer rounded rectangle container
p.setPen(QPen(QColor(255, 255, 255, 75), 6));
p.setBrush(QColor(0, 0, 0, 166));
p.drawRoundedRect(speed_container, 24, 24);
int divider_y = container_y + 190;
p.setPen(QPen(QColor(255, 255, 255, 50), 2));
p.drawLine(container_x + 20, divider_y, container_x + container_width - 20, divider_y);
// max label
QRect max_label_rect(container_x, container_y + 200, container_width, 35);
p.setFont(InterFont(32, QFont::Normal));
p.setPen(max_color);
p.drawText(max_label_rect, Qt::AlignCenter, max_str);
// Set speed value
QRect set_speed_rect(container_x, container_y + 240, container_width, 70);
p.setFont(InterFont(68, QFont::Bold));
p.setPen(set_speed_color);
p.drawText(set_speed_rect, Qt::AlignCenter, setSpeedStr);
}
}
void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect) {
int size = devUiInfo > 0 ? e2e_alert_small : e2e_alert_large;
int x = surface_rect.center().x() + surface_rect.width() / 4;
int y = surface_rect.center().y() + 40;
x += devUiInfo > 0 ? 0 : 50;
y += devUiInfo > 0 ? 0 : 80;
QRect alertRect(x - size, y - size, size * 2, size * 2);
void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) {
if (!allow_e2e_alerts) return;
int x = surface_rect.right() - e2e_alert_size - (devUiInfo > 0 ? 180 : 100) - (UI_BORDER_SIZE * 3);
int y = surface_rect.center().y() + 20;
QRect alertRect(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2);
// Alert Circle
QPoint center = alertRect.center();
QColor frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75);
QColor frameColor;
if (!alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75);
else frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75);
p.setPen(QPen(frameColor, 15));
p.setBrush(QColor(0, 0, 0, 190));
p.drawEllipse(center, size, size);
p.drawEllipse(center, e2e_alert_size, e2e_alert_size);
// Alert Text
QColor txtColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 255) : QColor(0, 255, 0, 255);
p.setFont(InterFont(48, QFont::Bold));
QColor txtColor;
QFont font;
int alert_bottom_adjustment;
if (!alert_alt_text.isEmpty()) {
font = InterFont(100, QFont::Bold);
alert_bottom_adjustment = 5;
txtColor = QColor(255, 255, 255, 255);
} else {
font = InterFont(48, QFont::Bold);
alert_bottom_adjustment = 7;
txtColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 255) : QColor(0, 255, 0, 190);
}
p.setPen(txtColor);
p.setFont(font);
QFontMetrics fm(p.font());
QRect textRect = fm.boundingRect(alertRect, Qt::TextWordWrap, alert_text);
textRect.moveCenter({alertRect.center().x(), alertRect.center().y()});
textRect.moveBottom(alertRect.bottom() - alertRect.height() / 7);
textRect.moveBottom(alertRect.bottom() - alertRect.height() / alert_bottom_adjustment);
p.drawText(textRect, Qt::AlignCenter, alert_text);
// Alert Image
QPointF pixmapCenterOffset = QPointF(alert_img.width() / 2.0, alert_img.height() / 2.0);
QPointF drawPoint = center - pixmapCenterOffset;
p.drawPixmap(drawPoint, alert_img);
if (!alert_alt_text.isEmpty()) {
// Alert Alternate Text
p.setFont(InterFont(80, QFont::Bold));
p.setPen(QColor(255, 175, 3, 240));
QFontMetrics fmt(p.font());
QRect topTextRect = fmt.boundingRect(alertRect, Qt::TextWordWrap, alert_alt_text);
topTextRect.moveCenter({alertRect.center().x(), alertRect.center().y()});
topTextRect.moveTop(alertRect.top() + alertRect.height() / 3.5);
p.drawText(topTextRect, Qt::AlignCenter, alert_alt_text);
} else {
// Alert Image instead of Top Text
QPointF pixmapCenterOffset = QPointF(alert_img.width() / 2.0, alert_img.height() / 2.0);
QPointF drawPoint = center - pixmapCenterOffset;
p.drawPixmap(drawPoint, alert_img);
}
}
void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) {
QString speedStr = QString::number(std::nearbyint(speed));
QString unit = is_metric ? tr("km/h") : tr("mph");
int speed_x = surface_rect.center().x();
int speed_y = 210;
int unit_y = 290;
QFont speed_font = InterFont(176, QFont::Bold);
QFont unit_font = InterFont(66);
if (navigationValid) {
speed_y = 75;
unit_y = 175;
speed_font = InterFont(100, QFont::Bold);
unit_font = InterFont(35, QFont::Normal);
}
// Draw speed
p.setFont(speed_font);
if (!navigationValid) {
HudRenderer::drawText(p, speed_x, speed_y, speedStr);
} else {
QRect current_speed_rect(40, speed_y, 200, 100);
p.setPen(Qt::white);
p.drawText(current_speed_rect, Qt::AlignCenter, speedStr);
}
// Draw unit
p.setFont(unit_font);
if (!navigationValid) {
HudRenderer::drawText(p, speed_x, unit_y, unit, 200);
} else {
QRect unit_rect(40, unit_y, 200, 40);
p.setPen(QColor(180, 180, 180, 255));
p.drawText(unit_rect, Qt::AlignCenter, unit);
}
}
void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) {
const bool hazard = leftBlinkerOn && rightBlinkerOn;
int blinkerStatus = hazard ? 2 : (leftBlinkerOn || rightBlinkerOn) ? 1 : 0;
if (!leftBlinkerOn && !rightBlinkerOn) {
blinkerFrameCounter = 0;
lastBlinkerStatus = 0;
return;
}
if (blinkerStatus != lastBlinkerStatus) {
blinkerFrameCounter = 0;
lastBlinkerStatus = blinkerStatus;
}
++blinkerFrameCounter;
const int BLINKER_COOLDOWN_FRAMES = UI_FREQ / 10;
if (blinkerFrameCounter < BLINKER_COOLDOWN_FRAMES) {
return;
}
const int circleRadius = 60;
const int arrowLength = 60;
const int x_gap = 160;
const int y_offset = navigationValid ? 352 : 300;
const int centerX = surface_rect.center().x();
const QPen bgBorder(Qt::white, 5);
const QPen arrowPen(Qt::NoPen);
p.save();
auto drawArrow = [&](int cx, int cy, int dir, const QBrush &arrowBrush) {
const int bodyLength = arrowLength / 2;
const int bodyWidth = arrowLength / 2;
const int headLength = arrowLength / 2;
const int headWidth = arrowLength;
QPolygon arrow;
arrow.reserve(7);
arrow << QPoint(cx - dir * bodyLength, cy - bodyWidth / 2)
<< QPoint(cx, cy - bodyWidth / 2)
<< QPoint(cx, cy - headWidth / 2)
<< QPoint(cx + dir * headLength, cy)
<< QPoint(cx, cy + headWidth / 2)
<< QPoint(cx, cy + bodyWidth / 2)
<< QPoint(cx - dir * bodyLength, cy + bodyWidth / 2);
p.setPen(arrowPen);
p.setBrush(arrowBrush);
p.drawPolygon(arrow);
};
auto drawCircle = [&](int cx, int cy, const QBrush &bgBrush) {
p.setPen(bgBorder);
p.setBrush(bgBrush);
p.drawEllipse(QPoint(cx, cy), circleRadius, circleRadius);
};
struct BlinkerSide { bool on; int dir; bool blocked; int cx; };
const std::array<BlinkerSide, 2> sides = {{
{leftBlinkerOn, -1, hazard ? true : (leftBlinkerOn && leftBlindspot), centerX - x_gap},
{rightBlinkerOn, 1, hazard ? true : (rightBlinkerOn && rightBlindspot), centerX + x_gap},
}};
for (const auto &s: sides) {
if (!s.on) continue;
QColor bgColor = s.blocked ? QColor(135, 23, 23) : QColor(23, 134, 68);
QColor arrowColor = s.blocked ? QColor(66, 12, 12) : QColor(12, 67, 34);
if (pulseElement(blinkerFrameCounter)) arrowColor = Qt::white;
const QBrush bgBrush(bgColor);
const QBrush arrowBrush(arrowColor);
drawCircle(s.cx, y_offset, bgBrush);
drawArrow(s.cx, y_offset, s.dir, arrowBrush);
}
p.restore();
}
QString HudRendererSP::getNavigationIconName(const QString &type, const QString &mod) {
static QMap<QString, QString> icon_map;
if (icon_map.isEmpty()) {
icon_map["turn|uturn"] = "direction_uturn.png";
icon_map["turn|sharp right"] = "direction_turn_sharp_right.png";
icon_map["turn|right"] = "direction_turn_right.png";
icon_map["turn|slight right"] = "direction_turn_slight_right.png";
icon_map["turn|straight"] = "direction_turn_straight.png";
icon_map["turn|slight left"] = "direction_turn_slight_left.png";
icon_map["turn|left"] = "direction_turn_left.png";
icon_map["turn|sharp left"] = "direction_turn_sharp_left.png";
icon_map["arrive|right"] = "direction_arrive_right.png";
icon_map["arrive|straight"] = "direction_arrive_straight.png";
icon_map["arrive|left"] = "direction_arrive_left.png";
icon_map["arrive|"] = "direction_arrive.png";
icon_map["merge|slight right"] = "direction_merge_slight_right.png";
icon_map["merge|right"] = "direction_merge_right.png";
icon_map["merge|straight"] = "direction_merge_straight.png";
icon_map["merge|slight left"] = "direction_merge_slight_left.png";
icon_map["merge|left"] = "direction_merge_left.png";
icon_map["on ramp|sharp right"] = "direction_on_ramp_sharp_right.png";
icon_map["on ramp|right"] = "direction_on_ramp_right.png";
icon_map["on ramp|slight right"] = "direction_on_ramp_slight_right.png";
icon_map["on ramp|straight"] = "direction_on_ramp_straight.png";
icon_map["on ramp|slight left"] = "direction_on_ramp_slight_left.png";
icon_map["on ramp|left"] = "direction_on_ramp_left.png";
icon_map["on ramp|sharp left"] = "direction_on_ramp_sharp_left.png";
icon_map["off ramp|slight right"] = "direction_off_ramp_slight_right.png";
icon_map["off ramp|right"] = "direction_off_ramp_right.png";
icon_map["off ramp|slight left"] = "direction_off_ramp_slight_left.png";
icon_map["off ramp|left"] = "direction_off_ramp_left.png";
icon_map["roundabout|sharp right"] = "direction_roundabout_sharp_right.png";
icon_map["roundabout|right"] = "direction_roundabout_right.png";
icon_map["roundabout|slight right"] = "direction_roundabout_slight_right.png";
icon_map["roundabout|straight"] = "direction_roundabout_straight.png";
icon_map["roundabout|slight left"] = "direction_roundabout_slight_left.png";
icon_map["roundabout|left"] = "direction_roundabout_left.png";
icon_map["roundabout|sharp left"] = "direction_roundabout_sharp_left.png";
icon_map["roundabout|"] = "direction_roundabout.png";
}
QString normalized_type = type;
if (normalized_type == "rotary") {
normalized_type = "roundabout";
} else if (normalized_type == "new name") {
normalized_type = "turn";
} else if (normalized_type == "continue") {
normalized_type = "turn";
}
QString icon_name;
QStringList keys = {normalized_type + "|" + mod, normalized_type + "|", "turn|" + mod};
for (const QString &key : keys) {
icon_name = icon_map.value(key);
if (!icon_name.isEmpty()) break;
}
if (icon_name.isEmpty()) {
icon_name = "direction_turn_straight.png";
}
return icon_name;
}
void HudRendererSP::drawNavigationHUD(QPainter &p, const QRect &surface_rect) {
if (!navigationValid) return;
p.save();
const int container_width = 1080;
const int container_height = 225;
const int container_x = (surface_rect.width() - container_width) / 2;
const int container_y = 62;
const int border_radius = 42;
QRect container_rect(container_x, container_y, container_width, container_height);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 180));
p.drawRoundedRect(container_rect, border_radius, border_radius);
// Navigation icon
const int icon_size = 150;
const int icon_padding = 30;
const int icon_x = container_x + icon_padding;
const int icon_y = container_y;
QString icon_name = getNavigationIconName(navigationManeuverType, navigationModifier);
QPixmap nav_icon = loadPixmap("../../sunnypilot/selfdrive/assets/navigation/" + icon_name, {icon_size, icon_size});
if (!nav_icon.isNull()) {
p.drawPixmap(icon_x, icon_y, nav_icon);
}
// Distance
p.setFont(InterFont(48, QFont::Bold));
p.setPen(Qt::white);
QRect distance_rect(icon_x, icon_y + icon_size, icon_size, 38);
p.drawText(distance_rect, Qt::AlignCenter, navigationDistance);
const int then_section_width = 180;
const int text_x = icon_x + icon_size + 53;
const int text_area_width = container_width - (text_x - container_x) - icon_padding - then_section_width;
// Street name
p.setFont(InterFont(75, QFont::Bold));
p.setPen(Qt::white);
QFontMetrics fm(p.font());
QString street_line1, street_line2;
QStringList words = navigationStreet.split(' ');
QString currentLine;
for (int i = 0; i < words.size(); ++i) {
QString testLine = currentLine.isEmpty() ? words[i] : currentLine + " " + words[i];
if (fm.horizontalAdvance(testLine) <= text_area_width) {
currentLine = testLine;
} else {
if (street_line1.isEmpty()) {
street_line1 = currentLine;
currentLine = words[i];
} else {
break;
}
}
}
if (street_line1.isEmpty()) {
street_line1 = currentLine;
} else if (!currentLine.isEmpty()) {
street_line2 = currentLine;
if (words.size() > words.indexOf(currentLine.split(' ').last()) + 1) {
street_line2 = fm.elidedText(street_line2, Qt::ElideRight, text_area_width);
}
}
if (street_line2.isEmpty()) {
QRect street_rect(text_x, container_y + (container_height - fm.height()) / 2, text_area_width, fm.height());
p.drawText(street_rect, Qt::AlignLeft | Qt::AlignVCenter, street_line1);
} else {
QRect street_rect1(text_x, container_y + 23, text_area_width, fm.height());
p.drawText(street_rect1, Qt::AlignLeft | Qt::AlignVCenter, street_line1);
QRect street_rect2(text_x, container_y + 23 + fm.height(), text_area_width, fm.height());
p.drawText(street_rect2, Qt::AlignLeft | Qt::AlignVCenter, street_line2);
}
// Next Maneuver
if (navigationHasNext) {
const int divider_x = container_x + container_width - then_section_width - 8;
p.setPen(QPen(QColor(255, 255, 255, 50), 2));
p.drawLine(divider_x, container_y + 23, divider_x, container_y + container_height - 23);
const int then_x = divider_x + 15;
const int then_icon_size = 105;
QRect then_label_rect(then_x, container_y + 30, then_section_width - 23, 38);
p.setFont(InterFont(53, QFont::Medium));
p.setPen(Qt::white);
p.drawText(then_label_rect, Qt::AlignCenter, tr("Then"));
// Next maneuver icon
const int then_icon_x = then_x + (then_section_width - 23 - then_icon_size) / 2;
const int then_icon_y = container_y + 75;
QString next_icon_name = getNavigationIconName(navigationNextManeuverType, navigationNextModifier);
QPixmap next_nav_icon = loadPixmap("../../sunnypilot/selfdrive/assets/navigation/" + next_icon_name, {then_icon_size, then_icon_size});
if (!next_nav_icon.isNull()) {
p.drawPixmap(then_icon_x, then_icon_y, next_nav_icon);
}
}
p.restore();
}

View File

@@ -36,7 +36,11 @@ private:
void drawRoadName(QPainter &p, const QRect &surface_rect);
void drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect);
void drawSetSpeedSP(QPainter &p, const QRect &surface_rect);
void drawE2eAlert(QPainter &p, const QRect &surface_rect);
void drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text = "");
void drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect);
void drawBlinker(QPainter &p, const QRect &surface_rect);
void drawNavigationHUD(QPainter &p, const QRect &surface_rect);
QString getNavigationIconName(const QString &type, const QString &mod);
bool lead_status;
float lead_d_rel;
@@ -81,6 +85,7 @@ private:
bool speedLimitValid;
bool speedLimitLastValid;
float speedLimitFinalLast;
int speedLimitSource; // 0=NONE, 1=MAP
bool speedLimitAheadValid;
float speedLimitAhead;
float speedLimitAheadDistance;
@@ -89,21 +94,52 @@ private:
SpeedLimitMode speedLimitMode = SpeedLimitMode::OFF;
bool roadName;
QString roadNameStr;
cereal::LongitudinalPlanSP::SpeedLimit::AssistState speedLimitAssistState;
int speedLimitAssistState; // 0=NONE, 1=PRE_ACTIVE, etc.
bool speedLimitAssistActive;
int speedLimitAssistFrame;
QPixmap plus_arrow_up_img;
QPixmap minus_arrow_down_img;
int e2e_alert_small = 250;
int e2e_alert_large = 300;
QPixmap green_light_alert_small_img;
QPixmap green_light_alert_large_img;
int e2e_alert_size = 250;
QPixmap green_light_alert_img;
bool greenLightAlert;
int e2eAlertFrame;
int e2eAlertDisplayTimer = 0;
bool allow_e2e_alerts;
bool leadDepartAlert;
QPixmap lead_depart_alert_small_img;
QPixmap lead_depart_alert_large_img;
QPixmap lead_depart_alert_img;
QString alert_text;
QPixmap alert_img;
bool hideVEgoUI;
bool leftBlinkerOn;
bool rightBlinkerOn;
bool leftBlindspot;
bool rightBlindspot;
int blinkerFrameCounter;
int lastBlinkerStatus;
bool showTurnSignals;
bool carControlEnabled;
float speedCluster = 0;
int icbm_active_counter = 0;
bool pcmCruiseSpeed = true;
bool navigationValid;
QString navigationStreet;
QString navigationDistance;
QString navigationModifier;
QString navigationManeuverType;
QString navigationNextModifier;
QString navigationNextManeuverType;
bool navigationHasNext;
QString wayName;
QString wayRef;
float mapdSpeedLimit;
float advisorySpeed;
float nextAdvisorySpeed;
float nextAdvisorySpeedDistance;
float suggestedSpeed;
float visionCurveSpeed;
float curveSpeed;
bool tileLoaded;
};

View File

@@ -21,70 +21,234 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con
mapLineToPolygon(model.getLaneLines()[2], 0.2, -0.05, &right_blindspot_vertices, max_idx_barrier);
}
void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {
void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) {
auto *s = uiState();
auto &sm = *(s->sm);
bool blindspot = Params().getBool("BlindSpot");
if (blindspot) {
bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot();
bool right_blindspot = sm["carState"].getCarState().getRightBlindspot();
if (sm.rcv_frame("liveCalibration") < s->scene.started_frame ||
sm.rcv_frame("modelV2") < s->scene.started_frame) {
return;
}
//painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.4)); // Red with alpha for blind spot
clip_region = surface_rect.adjusted(-CLIP_MARGIN, -CLIP_MARGIN, CLIP_MARGIN, CLIP_MARGIN);
experimental_mode = sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
path_offset_z = sm["liveCalibration"].getLiveCalibration().getHeight()[0];
if (left_blindspot && !left_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(left_blindspot_vertices);
painter.save();
const auto &model = sm["modelV2"].getModelV2();
const auto &radar_state = sm["radarState"].getRadarState();
const auto &lead_one = radar_state.getLeadOne();
const auto &car_state = sm["carState"].getCarState();
update_model(model, lead_one);
drawLaneLines(painter);
if (s->scene.rainbow_mode) {
drawRainbowPath(painter, surface_rect);
} else {
ModelRenderer::drawPath(painter, model, surface_rect.height(), surface_rect.width());
}
if (longitudinal_control && sm.alive("radarState")) {
update_leads(radar_state, model.getPosition());
const auto &lead_two = radar_state.getLeadTwo();
if (lead_one.getStatus()) {
drawLead(painter, lead_one, lead_vertices[0], surface_rect);
}
if (right_blindspot && !right_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(right_blindspot_vertices);
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
}
}
bool rainbow = Params().getBool("RainbowMode");
//float v_ego = sm["carState"].getCarState().getVEgo();
if (s->scene.blindspot_ui) {
const bool left_blindspot = car_state.getLeftBlindspot();
const bool right_blindspot = car_state.getRightBlindspot();
drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot);
}
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
if (rainbow) {
// Simple time-based animation
float time_offset = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f;
painter.restore();
}
// simple linear gradient from bottom to top
QLinearGradient bg(0, surface_rect.height(), 0, 0);
void ModelRendererSP::drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot) {
if (left_blindspot && !left_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(left_blindspot_vertices);
}
// evenly spaced colors across the spectrum
// The animation shifts the entire spectrum smoothly
float animation_speed = 40.0f; // speed vroom vroom
float hue_offset = fmod(time_offset * animation_speed, 360.0f);
// 6-8 color stops for smooth transitions more color makes it laggy
const int num_stops = 7;
for (int i = 0; i < num_stops; i++) {
float position = static_cast<float>(i) / (num_stops - 1);
float hue = fmod(hue_offset + position * 360.0f, 360.0f);
float saturation = 0.9f;
float lightness = 0.6f;
// Alpha fades out towards the far end of the path
float alpha = 0.8f * (1.0f - position * 0.3f);
QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha);
bg.setColorAt(position, color);
}
painter.setBrush(bg);
painter.drawPolygon(track_vertices);
} else {
// Normal path rendering
ModelRenderer::drawPath(painter, model, surface_rect.height());
if (right_blindspot && !right_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(right_blindspot_vertices);
}
}
void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) {
auto *s = uiState();
auto &sm = *(s->sm);
bool longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
if (!longitudinal_control) {
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
return;
}
if (!sm.alive("radarState")) {
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
return;
}
const auto &radar_state = sm["radarState"].getRadarState();
const auto &lead_one = radar_state.getLeadOne();
const auto &lead_two = radar_state.getLeadTwo();
bool has_lead_one = lead_one.getStatus();
bool has_lead_two = lead_two.getStatus();
if (!has_lead_one && !has_lead_two) {
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
if (lead_status_alpha <= 0.0f) return;
} else {
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
}
if (has_lead_one) {
drawLeadStatusPosition(painter, lead_one, lead_vertices[0], height, width);
}
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
drawLeadStatusPosition(painter, lead_two, lead_vertices[1], height, width);
}
}
void ModelRendererSP::drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos, int height, int width) {
float d_rel = lead_data.getDRel();
float v_rel = lead_data.getVRel();
auto *s = uiState();
auto &sm = *(s->sm);
float v_ego = sm["carState"].getCarState().getVEgo();
int chevron_data = s->scene.chevron_info;
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
QFont content_font = painter.font();
content_font.setPixelSize(50);
content_font.setBold(true);
painter.setFont(content_font);
bool is_metric = s->scene.is_metric;
QStringList text_lines;
const int chevron_all = 4;
QStringList chevron_text[3];
// Distance display
if (chevron_data == 1 || chevron_data == chevron_all) {
int pos = 0;
float val = std::max(0.0f, d_rel);
QString unit = is_metric ? "m" : "ft";
if (!is_metric) val *= 3.28084f;
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
}
// Speed display
if (chevron_data == 2 || chevron_data == chevron_all) {
int pos = (chevron_data == 2) ? 0 : 1;
float multiplier = is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH);
float val = std::max(0.0f, (v_rel + v_ego) * multiplier);
QString unit = is_metric ? "km/h" : "mph";
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
}
// Time to contact
if (chevron_data == 3 || chevron_data == chevron_all) {
int pos = (chevron_data == 3) ? 0 : 2;
float val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
QString ttc = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
chevron_text[pos].append(ttc);
}
for (int i = 0; i < 3; ++i) {
if (!chevron_text[i].isEmpty()) text_lines.append(chevron_text[i]);
}
if (text_lines.isEmpty()) return;
QFontMetrics fm(content_font);
float text_width = 120.0f;
for (const QString &line : text_lines) {
text_width = std::max(text_width, fm.horizontalAdvance(line) + 20.0f);
}
text_width = std::min(text_width, 250.0f);
float line_height = 50.0f;
float total_height = text_lines.size() * line_height;
float margin = 20.0f;
float text_y = chevron_pos.y() + sz + 15;
if (text_y + total_height > height - margin) {
float y_max = chevron_pos.y() > (height - margin) ? (height - margin) : chevron_pos.y();
text_y = y_max - 15 - total_height;
text_y = std::max(margin, text_y);
}
float text_x = chevron_pos.x() - text_width / 2;
text_x = std::clamp(text_x, margin, (float)width - text_width - margin);
QPoint shadow_offset(2, 2);
QColor text_color = QColor(255, 255, 255, (int)(255 * lead_status_alpha));
for (int i = 0; i < text_lines.size(); ++i) {
float y = text_y + (i * line_height);
if (y + line_height > height - margin) break;
QRect rect(text_x, y, text_width, line_height);
// Draw shadow
painter.setPen(QColor(0, 0, 0, (int)(200 * lead_status_alpha)));
painter.drawText(rect.translated(shadow_offset), Qt::AlignCenter, text_lines[i]);
painter.setPen(text_color);
painter.drawText(rect, Qt::AlignCenter, text_lines[i]);
}
painter.setPen(Qt::NoPen);
}
void ModelRendererSP::drawRainbowPath(QPainter &painter, const QRect &surface_rect) {
// Simple time-based animation
float time_offset = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f;
// simple linear gradient from bottom to top
QLinearGradient bg(0, surface_rect.height(), 0, 0);
// evenly spaced colors across the spectrum
// The animation shifts the entire spectrum smoothly
float animation_speed = 40.0f; // speed vroom vroom
float hue_offset = fmod(time_offset * animation_speed, 360.0f);
// 6-8 color stops for smooth transitions more color makes it laggy
const int num_stops = 7;
for (int i = 0; i < num_stops; i++) {
float position = static_cast<float>(i) / (num_stops - 1);
float hue = fmod(hue_offset + position * 360.0f, 360.0f);
float saturation = 0.9f;
float lightness = 0.6f;
// Alpha fades out towards the far end of the path
float alpha = 0.8f * (1.0f - position * 0.3f);
QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha);
bg.setColorAt(position, color);
}
painter.setBrush(bg);
painter.drawPolygon(track_vertices);
}

View File

@@ -13,10 +13,19 @@ class ModelRendererSP : public ModelRenderer {
public:
ModelRendererSP() = default;
void draw(QPainter &painter, const QRect &surface_rect);
private:
void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override;
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override;
void drawLeadStatus(QPainter &painter, int height, int width);
void drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos, int height, int width);
void drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot);
void drawRainbowPath(QPainter &painter, const QRect &surface_rect);
QPolygonF left_blindspot_vertices;
QPolygonF right_blindspot_vertices;
// Lead status animation
float lead_status_alpha = 0.0f;
};

View File

@@ -25,10 +25,8 @@ void OnroadWindowSP::updateState(const UIStateSP &s) {
void OnroadWindowSP::mousePressEvent(QMouseEvent *e) {
OnroadWindow::mousePressEvent(e);
uiStateSP()->reset_onroad_sleep_timer();
}
void OnroadWindowSP::offroadTransition(bool offroad) {
OnroadWindow::offroadTransition(offroad);
uiStateSP()->reset_onroad_sleep_timer();
}

View File

@@ -95,13 +95,12 @@ QStringList searchFromList(const QString &query, const QStringList &list) {
return list;
}
QStringList search_terms = query.simplified().toLower().split(" ", QString::SkipEmptyParts);
QStringList search_terms = query.simplified().toLower().replace(QRegularExpression("[^a-zA-Z0-9\\s]"), " ").split(" ", QString::SkipEmptyParts);
QStringList search_results;
for (const QString &element : list) {
if (std::all_of(search_terms.begin(), search_terms.end(), [&](const QString &term) {
QString normalized_term = term.normalized(QString::NormalizationForm_KD).toLower();
normalized_term.remove(QRegularExpression("[^a-zA-Z0-9\\s]"));
QString normalized_element = element.normalized(QString::NormalizationForm_KD).toLower();
return normalized_element.contains(normalized_term, Qt::CaseInsensitive);
})) {
@@ -123,3 +122,7 @@ std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::
return std::nullopt;
}
}
bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp) {
return car_params_sp.getIntelligentCruiseButtonManagementAvailable() && Params().getBool("IntelligentCruiseButtonManagement");
}

View File

@@ -23,3 +23,4 @@ std::optional<QString> getParamIgnoringDefault(const std::string &param_name, co
QMap<QString, QVariantMap> loadPlatformList();
QStringList searchFromList(const QString &query, const QStringList &list);
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param);
bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp);

View File

@@ -390,7 +390,7 @@ class ButtonParamControlSP : public MultiButtonControlSP {
Q_OBJECT
public:
ButtonParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon,
const std::vector<QString> &button_texts, const int minimum_button_width = 380, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon,
button_texts, minimum_button_width, inline_layout, advancedControl) {
key = param.toStdString();
int value = atoi(params.get(key).c_str());
@@ -545,10 +545,8 @@ public:
main_layout->removeWidget(title_label);
hlayout->addWidget(title_label, 1);
}
if (spacingItem != nullptr && main_layout->indexOf(spacingItem) != -1) {
main_layout->removeItem(spacingItem);
spacingItem = nullptr;
}
main_layout->removeItem(spacingItem);
spacingItem = nullptr;
}
label.setStyleSheet(label_enabled_style);

View File

@@ -14,10 +14,10 @@ ExpandableToggleRow::ExpandableToggleRow(const QString &param, const QString &ti
QObject::connect(this, &ExpandableToggleRow::toggleFlipped, this, &ExpandableToggleRow::toggleClicked);
collapsibleWidget = new QFrame(this);
collapsibleWidget->setContentsMargins(0, 0, 0, 0);
collapsibleWidget->setVisible(false);
QVBoxLayout *collapsible_layout = new QVBoxLayout();
collapsibleWidget->setLayout(collapsible_layout);
collapsible_layout->setContentsMargins(0, 0, 0, 0);
list = new ListWidgetSP(this, false);

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