Compare commits

..

325 Commits

Author SHA1 Message Date
DevTekVE 2ce3a8e4d4 test with regular lkas behavior 2026-08-03 21:17:57 +02:00
Jason Wen 82bcaa01a2 just for you devtekve 2026-07-27 18:41:44 -04:00
Jason Wen 54b380e7f7 bruh 2026-07-27 14:50:10 -04:00
Jason Wen 8ba3beda5f pls allow 2026-07-27 14:14:17 -04:00
Jason Wen b347de77d0 hard code it 2026-07-27 13:56:49 -04:00
Jason Wen b16524b5aa this is why 2026-07-27 13:15:55 -04:00
Jason Wen 8df99a700c no radar interface for now 2026-07-27 12:37:49 -04:00
Jason Wen ee464cc797 fix 2026-07-27 12:20:52 -04:00
Jason Wen ecec79f7be Merge branch 'hkg-angle-steering-2025' into hkg-adas-ecu-drv-interceptor
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
#	openpilot/system/manager/manager.py
2026-07-27 11:44:10 -04:00
Jason Wen e0cca951f3 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-07-25 08:52:04 -04:00
Jason Wen 2439c048ed Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-07-20 15:12:17 -04:00
Jason Wen 7b54d7d7a0 bump 2026-07-20 09:51:59 -04:00
Jason Wen b2bdb5787f Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	openpilot/tools/plotjuggler/layouts/analyzing-panda-block-angle-hkg.xml
#	openpilot/tools/plotjuggler/layouts/analyzing-torque-angle-hkg.xml
#	openpilot/tools/plotjuggler/layouts/hkg_angle_control.xml
#	openpilot/tools/plotjuggler/layouts/safety-limits-angle-kkg.xml
#	pyproject.toml
#	scripts/lint/lint.sh
2026-07-20 09:50:52 -04:00
Jason Wen 282f517b78 upstream changes 2026-06-09 00:18:40 -04:00
Jason Wen 1779144ff9 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	scripts/lint/lint.sh
2026-06-08 23:53:43 -04:00
Jason Wen 54fb2750ba Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-05-09 11:47:25 -04:00
DevTekVE 14431ab77f Merge branch 'master' into hkg-angle-steering-2025 2026-05-05 10:37:34 +02:00
Jason Wen 9d45db41b3 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	system/version.py
2026-05-03 16:08:40 -04:00
Jason Wen b57c593d92 bump 2026-04-20 23:31:05 -04:00
nayan 0674c42866 good bot
fix state
2026-04-19 08:52:19 +02:00
nayan 1f7bcf246a Bringing sl change to validate 2026-04-18 09:25:12 +02:00
DevTekVE b0512dc523 Revert "safety: dynamically relax lateral jerk limits during accel conflicts"
This reverts commit b0644a37e3b5a7b941bd4347f4c7ef0f25f44fbc.
2026-04-12 11:25:31 +02:00
DevTekVE cd88fd3850 Bringing shane's improvements on the angle steering branch 2026-04-10 12:09:12 +02:00
DevTekVE ff78eaeba1 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-04-10 11:38:19 +02:00
DevTekVE 92c8aeeb92 lint 2026-04-10 11:34:29 +02:00
DevTekVE ecaa20e7be Add source map configuration to VSCode launch settings
- Enables better debugging by mapping sources to `${workspaceFolder}/opendbc/safety`.
2026-04-05 15:10:21 +02:00
DevTekVE 859c98c9d8 Refactor PlotJuggler layouts and optimize custom math equations
- Introduced new tabs for `Smoothing and Torque ceilings`.
- Updated custom math equations for cleaner logic and added new snippets for angle smoothing, ceiling brackets, and roll compensation.
2026-04-05 12:14:13 +02:00
DevTekVE 69cbe8c6ed Enhance torque reduction logic with speed and steering error adjustments
- Introduced speed-dependent and error-sensitive dynamic torque ceilings.
- Improved interpolation for smoother torque application.
2026-04-04 11:55:14 +02:00
DevTekVE 1aafe92fc0 Remove hyundai_canfd_ccnc.dbc and update dependencies
- Deleted `_hyundai_canfd_ccnc.dbc` and its import references across related files.
- Merged relevant signals and comments into `hyundai_canfd_og.dbc` for consolidated usage.
- Cleaned up obsolete imports in `hyundai_canfd.dbc`.
2026-04-04 08:46:09 +02:00
DevTekVE adb6b9fb12 Test changing priority for reading dbc files to in memory first 2026-04-04 08:29:57 +02:00
DevTekVE f67a9f4624 Revised steering angle smoothing matrix and logic cleanup
- Adjusted `SMOOTHING_ANGLE_VEGO_MATRIX` to refine torque smoothing at mid-range speeds.
- Removed unused deadzone logic for cleaner and more consistent angle smoothing.
2026-04-03 18:10:05 +02:00
DevTekVE 094e834ac4 cleanup: remove unused datafile references and expand ignored patterns
- Deleted `<previouslyLoaded_Datafiles>` sections from PlotJuggler layouts to streamline configuration files.
- Added `.ipynb` files to the `pyproject.toml` ignore list for cleaner tooling.
2026-04-03 17:19:18 +02:00
DevTekVE e4067060b9 Adding ioniq 9 and updating ioniq 5 pe n-line fingerprint 2026-04-03 17:06:15 +02:00
DevTekVE 86a14640b5 Tune speed-dependent steering smoothing to eliminate EPS whine
Reimplemented an Exponential Moving Average (EMA) filter on the requested
steering angle (`apply_angle`). The model's raw high-frequency micro-corrections
at low speeds cause acoustic resonance (whine) in the EPS motor. This filter
dynamically adjusts the smoothing factor (alpha) based on vehicle speed to
silence the EPS at a crawl while maintaining zero-latency precision on the highway.

Key Behaviors & Speed Matrix:
* Deadzone: Ignores angle changes ≤ 0.1° to preserve straight-line tracking.
* 0 km/h (0 mph) -> Alpha: 0.05 (Max smoothing to eliminate stationary vibration)
* 30.6 km/h (19 mph) -> Alpha: 0.10 (Heavy smoothing for stable residential turning)
* 39.6 km/h (25 mph) -> Alpha: 0.30 (Moderate smoothing)
* 49.7 km/h (31 mph) -> Alpha: 0.60 (Light smoothing for responsive city driving)
* 80.0 km/h (50 mph) -> Alpha: 1.00 (Zero smoothing / raw signal for high-speed precision)
2026-04-03 16:13:05 +02:00
DevTekVE 59fb84f0d5 Remove HKG angle control tuning components and dependencies
- Deleted HKG-specific angle tuning settings and related UI elements.
    - Removed tuning parameter handling and smoothing logic from carcontroller.
    - Simplifies codebase and eliminates unused parameters.
2026-04-03 15:33:57 +02:00
DevTekVE 78de201ce7 Bring it back to 360 for hda1s 2026-04-03 15:14:37 +02:00
DevTekVE 17e49f69cc refactor: update parameterized tests and extend Hyundai Ioniq 5 PE model years
- Replaced `parameterized.expand` with new `parameterized` syntax for cleaner test definitions.
- Added 2026 model year to Hyundai Ioniq 5 PE in `car_list.json`.
2026-04-03 14:39:45 +02:00
DevTekVE bec75ebdd8 remove: TorqueReductionGainController and its associated tests
- Fully deprecates the no longer used torque reduction logic.
- Cleans up outdated functionality for clarity and maintainability.
2026-04-03 14:14:07 +02:00
DevTekVE 473832efa7 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-04-03 14:00:54 +02:00
DevTekVE 2cceeb3552 fix: clip steering angle to respect angle limits when latActive is off
- Prevents potential out-of-bound steering angle values.
- Ensures compliance with defined steering angle constraints.
2026-04-03 01:06:37 +02:00
DevTekVE 923a47f713 Reverting all the new control changes until we properly test them on a few variants to ensure it is safe.
Revert "upstream pending tune"

This reverts commit b51d9af9a0.

Revert "show torque reduction gain"

This reverts commit ded0b506d6.

Revert "must gate"

This reverts commit 8b60649eed.

Revert "bump"

This reverts commit 221c219fca.

Revert "temp: comment out blind-spot monitoring signals due to DBC changes"

This reverts commit 790a762a05.
2026-04-01 09:29:53 +02:00
DevTekVE 790a762a05 temp: comment out blind-spot monitoring signals due to DBC changes
- Avoided crash caused by missing signals in updated DBC definitions.
- Added a TODO to revisit and validate blind-spot logic.
2026-04-01 08:55:26 +02:00
Jason Wen 221c219fca bump 2026-03-31 21:57:58 -04:00
Jason Wen 8b60649eed must gate 2026-03-31 21:42:06 -04:00
Jason Wen ded0b506d6 show torque reduction gain 2026-03-31 07:46:13 -04:00
Jason Wen b51d9af9a0 upstream pending tune 2026-03-31 06:56:52 -04:00
Jason Wen 4460ce8166 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-03-18 03:43:10 -04:00
Jason Wen f1aa0c7f78 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-03-07 01:49:09 -05:00
Jason Wen e7c8126fd9 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-03-05 02:01:04 -05:00
Jason Wen 125999c364 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-03-01 16:34:41 -05:00
Jason Wen 5b25ea7f99 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-02-28 15:47:57 -05:00
Jason Wen b7e2631286 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-02-27 21:56:34 -05:00
Jason Wen a838871189 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-02-19 01:51:17 -05:00
Jason Wen 1827331599 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-02-17 20:12:13 -05:00
Jason Wen 5c777bbe01 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-02-13 23:41:06 -05:00
Jason Wen fb97f993d1 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-02-13 17:32:10 -05:00
Jason Wen 94b67077e3 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-02-12 23:35:46 -05:00
Jason Wen 14f17699b9 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-02-11 00:35:20 -05:00
Jason Wen 80e27d5cbb Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-02-10 23:41:27 -05:00
Jason Wen 039dbcd877 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-02-09 01:46:00 -05:00
Jason Wen 8c134ae555 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-02-08 20:04:01 -05:00
Jason Wen 276c7a2b34 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-02-02 22:40:14 -05:00
Jason Wen 0e2dbcebfa Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-01-26 11:45:29 -05:00
Jason Wen bbb7760a95 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2026-01-19 01:43:21 -05:00
Jason Wen 23a27b2fbf Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2026-01-09 18:48:43 -05:00
Jason Wen 8b78107a40 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-12-31 00:49:56 -05:00
Jason Wen c27b6007de wrong bump? 2025-12-26 10:08:31 -05:00
Jason Wen 36c2dce247 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-12-26 10:08:23 -05:00
Jason Wen 8035039731 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-12-23 00:52:24 -05:00
Jason Wen 8aa6c9440f Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-12-20 17:01:25 -05:00
Jason Wen 34ef40fd81 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-12-18 00:19:20 -05:00
Jason Wen 4d044d7618 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-12-15 02:25:04 -05:00
Jason Wen 6247e3dc84 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-12-13 01:59:06 -05:00
Jason Wen b1131289b7 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-12-07 01:37:16 -05:00
Jason Wen 35dc7d661e Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-12-07 00:11:54 -05:00
DevTekVE c39b2dad94 Merge branch 'master' into hkg-angle-steering-2025 2025-11-29 10:12:31 +01:00
Jason Wen e6b769245c Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	selfdrive/ui/sunnypilot/SConscript
#	selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc
#	selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h
#	selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc
2025-11-25 18:54:02 -05:00
DevTekVE 8b94f8b2f8 Merge branch 'master' into hkg-angle-steering-2025 2025-11-06 18:31:16 +01:00
Jason Wen dec014cd17 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	sunnypilot/selfdrive/car/interfaces.py
2025-11-04 17:45:05 -05:00
DevTekVE 7b40272866 Add sunnypilot-specific stats logging and handling
- Introduced `StatLogSP` for sunnypilot-specific metrics.
- Integrated stats collection and submission pathways for sunnylink.
- Extended parameters and handlers to support additional metrics.
- Added gzip compression and base64 encoding for oversized payload handling.
2025-11-04 21:20:16 +01:00
DevTekVE 506456e7f0 Merge branch 'master' into hkg-angle-steering-2025 2025-11-01 13:31:23 +01:00
DevTekVE 4ea4b9d177 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-10-31 06:59:39 +01:00
DevTekVE 0e2313dc31 Merge branch 'master' into hkg-angle-steering-2025 2025-10-18 11:14:45 +02:00
Jason Wen 79ea7db103 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-17 23:42:01 -04:00
Jason Wen f9ae9192fa Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 21:55:00 -04:00
Jason Wen 7ec23006c6 check this 2025-10-17 21:54:44 -04:00
Jason Wen 2be9447a6c Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 21:19:09 -04:00
Jason Wen cfd926778e always init true 2025-10-17 21:19:03 -04:00
Jason Wen a97a67e3d0 need 2025-10-17 21:17:31 -04:00
Jason Wen 518b6de08d Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 21:15:59 -04:00
Jason Wen 6933e3bcdb fix cruise toggles 2025-10-17 21:15:43 -04:00
Jason Wen 05e0ca8bee some more 2025-10-17 20:46:36 -04:00
Jason Wen 410614fcf3 single location 2025-10-17 20:26:18 -04:00
Jason Wen e2bc0996ef Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 12:21:47 -04:00
Jason Wen 1be0c20cf5 oops 2025-10-17 12:21:37 -04:00
Jason Wen 839143b9ed oops 2025-10-17 12:20:23 -04:00
Jason Wen bce86637ae Merge branch 'ui-icbm-universal' into hkg-angle-steering-2025 2025-10-17 12:15:23 -04:00
Jason Wen b833d3ee89 ui: update ICBM-related settings handling 2025-10-17 12:14:44 -04:00
Jason Wen e0441dfb4b Merge branch 'sla-event' into hkg-angle-steering-2025 2025-10-17 11:58:42 -04:00
Jason Wen 62ec40bba6 Speed Limit Assist: update active event handling 2025-10-17 11:58:19 -04:00
Jason Wen 15c6d38028 bump 2025-10-16 17:05:58 -04:00
Jason Wen 56eb9f555c Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py
2025-10-16 01:12:10 -04:00
Jason Wen 2f9951df02 Merge branch 'e2e-alert-state-machine' into hkg-angle-steering-2025 2025-10-15 23:55:40 -04:00
Jason Wen 6030bf4da3 less 2025-10-15 23:52:03 -04:00
Jason Wen 074694d660 lead depart: only arm if we have a confirmed close lead for over a second after allowing alert 2025-10-15 23:48:13 -04:00
Jason Wen df35f48f3b magic 2025-10-15 22:56:08 -04:00
Jason Wen 4fb9704540 time based 2025-10-15 22:51:40 -04:00
Jason Wen 48cbe266fc 10 frames for both 2025-10-15 22:50:42 -04:00
Jason Wen 21aa7ff367 rename 2025-10-15 22:47:08 -04:00
Jason Wen 7caf05dd51 not used 2025-10-15 22:41:00 -04:00
Jason Wen 2d779f5db9 E2E Helper: universal state machine 2025-10-15 22:38:57 -04:00
Jason Wen 18208f1da0 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-15 17:41:56 -04:00
Jason Wen 5a3c6ddf57 gate lka angle steering out of alpha long 2025-10-14 23:25:00 -04:00
Jason Wen eaa8732ab0 Merge branch 'e2e-alerts-cooldown' into hkg-angle-steering-2025 2025-10-14 14:44:00 -04:00
Jason Wen 6f0284c84f try preventing startup false trigger 2025-10-14 14:43:47 -04:00
Jason Wen 999ea03f23 try preventing startup false trigger 2025-10-14 14:42:42 -04:00
Jason Wen 0975db3ff1 Merge branch 'e2e-alerts-cooldown' into hkg-angle-steering-2025 2025-10-14 14:31:59 -04:00
Jason Wen 8d70a8b80a only when long not engaged 2025-10-14 14:31:48 -04:00
Jason Wen da93f92887 rename 2025-10-14 14:26:57 -04:00
Jason Wen 9117f6c071 Merge branch 'e2e-alerts-cooldown' into hkg-angle-steering-2025 2025-10-14 14:23:46 -04:00
Jason Wen 3567ff9691 introduce recent moving check 2025-10-14 14:22:41 -04:00
Jason Wen f44ae2ced9 too complicated 2025-10-14 14:08:40 -04:00
Jason Wen 376e0ca615 only allow one trigger per standstill session 2025-10-14 14:07:16 -04:00
Jason Wen 32b7686468 Merge branch 'master' into e2e-alerts-cooldown 2025-10-14 11:40:58 -04:00
nayan b9e0f52ea9 E2E Alert Cooldown 2025-10-14 07:43:15 -04:00
Jason Wen a3163b680f Merge branch 'sla-chimes' into hkg-angle-steering-2025 2025-10-14 02:01:05 -04:00
Jason Wen 8a927d808f Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-10-14 02:00:36 -04:00
Jason Wen bc7d5e474d Speed Limit Assist: audible alerts for certain states 2025-10-14 01:45:13 -04:00
Jason Wen 36f192b5fe Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-13 03:10:47 -04:00
Jason Wen ffd5cd4ac2 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-11 02:29:47 -04:00
Jason Wen e4a00fcd6c Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025 2025-10-10 17:28:16 -04:00
Jason Wen 0bdcb41103 Merge remote-tracking branch 'sunnypilot/sunnypilot/master' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
#	selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.cc
2025-10-10 17:02:14 -04:00
DevTekVE 78051085ca Merge branch 'master' into hkg-angle-steering-2025 2025-09-23 07:54:32 +02:00
DevTekVE 4910d5809a bump opendbc 2025-09-23 07:45:39 +02:00
DevTekVE 8dd862ff28 yikes, becoming picky huh? 2025-09-14 22:50:21 +02:00
DevTekVE 1b57497da9 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-09-14 22:48:00 +02:00
DevTekVE 8d8d1ffc7a bump opendbc again 2025-09-14 22:46:20 +02:00
DevTekVE c5919d5495 wrong dbc lol 2025-09-14 22:42:19 +02:00
DevTekVE da71951c95 This is no longer in use nor needed. Bai! 2025-09-14 12:56:28 +02:00
DevTekVE 25a152cd8b Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-09-14 12:44:23 +02:00
DevTekVE 9077a1082a Sorry for C3 :( but moving you out to a last working branch before I sync it up 2025-09-14 06:52:24 +02:00
DevTekVE 7ae7000254 Rework override behavior and feeling 2025-09-14 06:38:00 +02:00
DevTekVE 7029455706 better juggle 2025-09-13 08:32:27 +02:00
DevTekVE 3a71a62215 Merge branch 'master' into hkg-angle-steering-2025 2025-09-12 10:14:52 +02:00
DevTekVE 00622e8c33 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-09-05 09:46:41 +02:00
DevTekVE 549da3ee92 Merge branch 'master' into hkg-angle-steering-2025 2025-09-04 20:20:37 +02:00
DevTekVE e038a65ef8 Merge branch 'master' into hkg-angle-steering-2025 2025-08-31 13:14:33 +02:00
DevTekVE 8d0513c657 dbc: update CHECKSUM format for multiple messages to improve data integrity 2025-08-30 19:56:21 +02:00
DevTekVE 2cea48f4cd Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-08-30 15:27:45 +02:00
DevTekVE 8855a9ab65 Improve surprise jerk by safety blocks 2025-08-26 14:47:26 +02:00
DevTekVE 32321c01cc A bit more helpful safety block investigation help 2025-08-26 10:09:11 +02:00
DevTekVE 52cd65fefb lint dont bother me 2025-08-25 08:24:24 +02:00
DevTekVE 1fcdeccd40 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
2025-08-25 08:01:51 +02:00
DevTekVE 3517c36978 Revert "Add HkgAngleDebug structure and enhance angle debugging in car controller" 2025-08-24 19:02:48 +02:00
DevTekVE fb30c3c1e8 cleanup and honour params 2025-08-23 15:42:47 +02:00
DevTekVE 1c25e568d5 Update steering pressed logic to include hands-on-wheel detection for improved safety 2025-08-23 15:11:07 +02:00
DevTekVE f172122b7c Update steering pressed logic to include hands-on-wheel detection for improved safety 2025-08-22 19:55:16 +02:00
Jason Wen 67d6cdc7cd Merge remote-tracking branch 'sunnypilot/sunnypilot/hkg-angle-steering-2025' into hkg-angle-steering-2025 2025-08-21 16:39:36 -04:00
DevTekVE 6724085cfd Refactor angle limit calculations and adjust average road roll for improved steering dynamics 2025-08-21 20:09:57 +02:00
DevTekVE 6774f34eee Refine non-linear mapping in torque reduction gain calculation for improved steering response 2025-08-21 00:21:46 +02:00
DevTekVE 6d0402896d Adjust STEER_THRESHOLD and refine non-linear mapping in torque reduction gain calculation for improved steering response 2025-08-20 23:16:04 +02:00
DevTekVE 344021a3d9 Adjust non-linear mapping in torque reduction gain calculation for improved response 2025-08-20 19:54:31 +02:00
DevTekVE a9b85ab27d Refactor HkgAngleDebug structure to include current and baseline limits for angle parameters 2025-08-20 19:54:10 +02:00
DevTekVE 17204a46e4 Add HkgAngleDebug structure and enhance angle debugging in car controller 2025-08-20 18:39:53 +02:00
DevTekVE 7c4d415462 Enhance torque reduction gain calculation with non-linear mapping and smoothing 2025-08-20 00:12:43 +02:00
DevTekVE b8985b6d72 Enhance torque reduction gain calculation with non-linear mapping and smoothing 2025-08-19 19:50:13 +02:00
DevTekVE c669473f88 Refine torque reduction parameters and update UI for angle error analysis 2025-08-19 19:35:34 +02:00
DevTekVE 8751435bf5 Improving tq redc gain and override behavior 2025-08-19 10:08:51 +02:00
DevTekVE 30ae210761 Merge branch 'master' into hkg-angle-steering-2025 2025-08-19 10:07:42 +02:00
Jason Wen 3eb693f58b Merge remote-tracking branch 'sunnypilot/sunnypilot/hkg-angle-steering-2025' into hkg-angle-steering-2025 2025-08-18 12:30:29 -04:00
DevTekVE 7b4a31c5ac bugfix 2025-08-17 16:07:45 +02:00
DevTekVE 1527c8cf88 bump opendbc 2025-08-17 15:31:31 +02:00
DevTekVE ab0a7ae666 no joystick on this branch, causing issues 2025-08-17 15:25:40 +02:00
DevTekVE 11ec2f1f21 Apply suggestions from code review 2025-08-17 15:10:17 +02:00
DevTekVE 2c6808d37e Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-08-17 15:02:28 +02:00
Jason Wen 2e96382c49 notebook init 2025-08-17 00:34:01 -04:00
DevTekVE 0239e440ca Adjust replay 2025-08-15 10:32:07 +02:00
Jason Wen 76b972daff Hyundai angle steering: STEERING_ANGLE_2 available on all cars 2025-08-14 01:56:46 -04:00
Jason Wen bc3ef3e7dd Hyundai angle steering: hugging no more - use the true steering angle signal from MDPS 2025-08-13 23:08:32 -04:00
DevTekVE 9839291dd0 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025 2025-08-13 20:13:42 +02:00
DevTekVE 17cba328d6 refactor: update torque tuning configuration for angle steering
- Adjusted torque tuning configuration to avoid reliance on torque controller for Hyundai angle steering.
- Simplified control logic by removing unnecessary checks for torque control type.
refactor: clean up code formatting and improve test structure for torque reduction gain
2025-08-13 20:03:51 +02:00
DevTekVE 86093765d8 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
2025-08-13 07:55:46 +02:00
DevTekVE 05fa1c8ae8 Adjust default params and cleanup 2025-08-12 21:36:22 +02:00
DevTekVE e8a40d6b85 Merge branch 'master' into hkg-angle-steering-2025
# Conflicts:
#	opendbc_repo
#	system/manager/process_config.py
2025-08-10 14:24:10 +02:00
DevTekVE c265e0bb85 Merge remote-tracking branch 'origin/master' into hkg-angle-steering-2025
# Conflicts:
#	common/params_keys.h
#	opendbc_repo
#	system/manager/manager.py
2025-08-02 09:56:58 +02:00
DevTekVE c6b118788b Merge branch 'master-new' into hkg-angle-steering-2025 2025-07-31 18:25:08 +02:00
DevTekVE b004f6dbdc Merge branch 'master-new' into hkg-angle-steering-2025 2025-07-31 18:17:34 +02:00
DevTekVE cd4930b680 Bump opendbc 2025-07-26 10:56:49 +02:00
DevTekVE f861aca628 Refactor vehicle model initialization and adjust angle limits for baseline model 2025-07-26 08:12:06 +02:00
DevTekVE 1ff0d8e2ee please don't bother me anymore! 2025-07-26 08:09:51 +02:00
DevTekVE d76d70764e Update slip factor precision for Hyundai steering parameters
- Adjusted `slip_factor` in Hyundai CANFD safety modes for improved consistency and accuracy.
- Ensured proper representation of `slip_factor` output in test logs.
2025-07-25 20:17:19 +02:00
DevTekVE dc73ce0b71 save tools replay 2025-07-25 19:47:31 +02:00
DevTekVE 3f666748af bump opendbc 2025-07-25 14:16:02 +02:00
DevTekVE 8de8a8838c bumo opendbc 2025-07-25 14:08:53 +02:00
DevTekVE f563b7eb71 Refactor steering angle limit application for improved safety and model compliance 2025-07-25 12:12:42 +02:00
DevTekVE 3c18b83708 save temp 2025-07-25 09:56:30 +02:00
DevTekVE bd35f5904b Enhance steering angle rate limiting and safety enforcement logic
- Introduced explicit post-rate limiting using model-specific dynamics.
- Improved low-speed smoothing and precision of applied angles.
2025-07-25 08:12:41 +02:00
DevTekVE 474f2737f6 Refactor steering angle limit logic for conservative seleion
- Removed unused lateral accel/jerk logic for simplicity.
- Updated angle limit calculations to choose the smallest delta for safer control.
2025-07-24 21:48:24 +02:00
DevTekVE ea1ac4a212 Revert "Add configurable max lateral accel and jerk parameters for Hyundai vehicles"
This reverts commit b95f8c5929.

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

This reverts commit b53cbb2e18.

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

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

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

Refactor lateral control to combine torque and angle logic

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

Add angle-torque hybrid lateral control for Hyundai CAN FD

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

Add overriding cycles parameter for torque adjustment

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

Remove "inputs" package and update "pygame" dependency

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

Update dependencies: replace 'inputs' with 'pygame'

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

Ugly, I know, but soundd is unhappy with joystick

Allowing lat with mads

Invert steering input for joystick control

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

Refactor joystick control to use pygame for broader support

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

Revert "More improvement but still not quite"

This reverts commit ad95493c5c61b2ace7c459d2ebc151ddaa80040f.

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

This reverts commit 6f789ac1ebb66b0239b4028303573c2d7d386b39.

Revert "Refactor speed-based steering scaling logic."

This reverts commit 1d40735ab8db8d470ff3b287a6b42847beffff7d.
2025-05-25 17:31:18 +02:00
DevTekVE 10f345f956 Refactor speed-based steering scaling logic.
Updated the steering angle computation to use a clearer and more descriptive speed-scaling configuration. Replaced low-speed-specific logic with a generalized approach based on speed breakpoints and corresponding influence factors. This improves maintainability and ensures smoother steering adjustments at varying speeds.
2025-05-25 17:31:18 +02:00
DevTekVE 956d2c36d0 Adjust low-speed scaling for lateral control angle
Refined the low-speed scaling parameters by modifying speed breakpoints and factors. This improves handling at lower speeds for smoother and more predictable behavior.
2025-05-25 17:31:18 +02:00
DevTekVE 55e688b6f2 More improvement but still not quite 2025-05-25 17:31:17 +02:00
DevTekVE f017954027 Significant improvement on the jerkiness 2025-05-25 17:31:17 +02:00
DevTekVE 7e992d11b1 bump panda and opendbc 2025-05-25 17:31:15 +02:00
520 changed files with 10582 additions and 10514 deletions
+1
View File
@@ -2,5 +2,6 @@ Wen
REGIST
PullRequest
cancelled
indeces
FOF
NoO
+1 -1
View File
@@ -11,4 +11,4 @@
*.wav filter=lfs diff=lfs merge=lfs -text
openpilot/selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
openpilot/common/hardware/comma/updater filter=lfs diff=lfs merge=lfs -text
openpilot/common/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
+68
View File
@@ -0,0 +1,68 @@
<!-- Please copy and paste the relevant template -->
<!--- ***** Template: Fingerprint *****
**Car**
Which car (make, model, year) this fingerprint is for
**Route**
A route with the fingerprint
-->
<!--- ***** Template: Car Bugfix *****
**Description**
A description of the bug and the fix. Also link the issue if it exists.
**Verification**
Explain how you tested this bug fix.
**Route**
Route: [a route with the bug fix]
-->
<!--- ***** Template: Bugfix *****
**Description**
A description of the bug and the fix. Also link the issue if it exists.
**Verification**
Explain how you tested this bug fix.
-->
<!--- ***** Template: Car Port *****
**Checklist**
- [ ] added entry to CAR in openpilot/selfdrive/car/*/values.py and ran `openpilot/selfdrive/car/docs.py` to generate new docs
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/openpilot/selfdrive/car/tests/routes.py)
- [ ] route with openpilot:
- [ ] route with stock system:
- [ ] car harness used (if comma doesn't sell it, put N/A):
-->
<!--- ***** Template: Refactor *****
**Description**
A description of the refactor, including the goals it accomplishes.
**Verification**
Explain how you tested the refactor for regressions.
-->
@@ -12,11 +12,11 @@ on:
required: false
type: string
recompiled_dir:
description: 'Existing recompiled directory number (e.g. 1 for recompiled1)'
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
required: true
type: string
json_version:
description: 'driving_models version number to update (e.g. 18 for driving_models_v18.json)'
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
required: true
type: string
artifact_suffix:
@@ -63,11 +63,12 @@ on:
default: 'None'
options:
- None
- Master Models
- Release Models
- 2025 World Models
- 2026 World Models
- Simple Plan Models
- Space Lab Models
- TR Models
- DTR Models
- Custom Merge Models
- FOF series models
- Other
custom_model_folder:
description: 'Custom model folder name (if "Other" selected)'
+1 -2
View File
@@ -40,7 +40,6 @@ jobs:
echo "run-id=$run_id" >> "$GITHUB_OUTPUT"
- name: Download diff
if: steps.wait.outcome == 'success'
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -49,7 +48,7 @@ jobs:
name: diff_report_${{ github.event.number }}
path: .
- name: Comment on PR
if: steps.wait.outcome == 'success' && hashFiles('diff_report.txt') != ''
if: steps.wait.outcome == 'success'
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
with:
file-path: diff_report.txt
+3 -7
View File
@@ -15,11 +15,6 @@ concurrency:
group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
env:
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
jobs:
docs:
name: build docs
@@ -35,7 +30,8 @@ jobs:
- name: Build docs
run: |
git lfs pull
python docs/serve.py --build
pip install zensical
python scripts/docs.py build
# Push to docs.comma.ai
- uses: actions/checkout@v7
@@ -56,7 +52,7 @@ jobs:
git rm -rf .
# copy over docs
cp -r ../docs/_site/ docs/
cp -r ../docs_site/ docs/
# GitHub pages config
touch docs/.nojekyll
+42
View File
@@ -0,0 +1,42 @@
name: "model review"
on:
pull_request:
types: [opened, reopened, synchronize]
paths:
- 'openpilot/selfdrive/modeld/models/*.onnx'
workflow_dispatch:
jobs:
comment:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
if: github.repository == 'commaai/openpilot'
steps:
- name: Checkout
uses: actions/checkout@v7
with:
submodules: true
- name: Checkout master
uses: actions/checkout@v7
with:
ref: master
path: base
- run: git lfs pull
- run: cd base && git lfs pull
- name: scripts/reporter.py
id: report
run: |
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "## Model Review" >> $GITHUB_OUTPUT
PYTHONPATH=${{ github.workspace }} MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Post model report comment
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0
with:
header: model-review
message: ${{ steps.report.outputs.content }}
+39
View File
@@ -0,0 +1,39 @@
name: prebuilt
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: release/ci/docker_build_sp.sh
jobs:
build_prebuilt:
name: build prebuilt
runs-on: ubuntu-latest
if: github.repository == 'sunnypilot/sunnypilot'
env:
PUSH_IMAGE: true
permissions:
checks: read
contents: read
packages: write
steps:
- name: Wait for green check mark
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
with:
ref: master
wait-interval: 30
running-workflow-name: 'build prebuilt'
repo-token: ${{ secrets.GITHUB_TOKEN }}
check-regexp: ^((?!.*(build master-ci|create badges).*).)*$
- uses: actions/checkout@v6
with:
submodules: true
- run: git lfs pull
- name: Build and Push docker image
run: |
$DOCKER_LOGIN
eval "$BUILD"
-5
View File
@@ -4,11 +4,6 @@ on:
- cron: '0 9 * * *'
workflow_dispatch:
env:
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
jobs:
build___nightly:
name: build __nightly
+1 -4
View File
@@ -9,9 +9,6 @@ on:
env:
PYTHONPATH: ${{ github.workspace }}
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
jobs:
package_updates:
@@ -84,7 +81,7 @@ jobs:
labels: bot
cleanup_closed_branches:
if: github.repository == 'commaai/openpilot' && github.event_name == 'pull_request'
if: github.repository == 'commaai/openpilot'
runs-on: ubuntu-latest
permissions:
contents: write
+14 -35
View File
@@ -30,11 +30,6 @@ on:
required: false
type: string
default: ''
target_hardware:
description: 'Hardware target to compile for (qcom or usbgpu)'
required: false
type: string
default: 'qcom'
workflow_dispatch:
inputs:
upstream_branch:
@@ -51,14 +46,6 @@ on:
required: false
type: boolean
default: true
target_hardware:
description: 'Hardware target to compile for'
required: true
type: choice
options:
- qcom
- usbgpu
default: 'qcom'
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
@@ -174,30 +161,19 @@ jobs:
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
path: ${{ env.MODELS_DIR }}
- run: |
rm -f ${{ env.MODELS_DIR }}/{dmonitoring_model,big_driving_policy,big_driving_vision,big_driving_supercombo}.onnx
rm -f ${{ env.MODELS_DIR }}/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx
- name: Build Model
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
COMPILE_MODELD="${{ github.workspace }}/openpilot/sunnypilot/modeld_v2/compile_modeld.py"
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
echo "USBGPU build"
export USBGPU=1
TG_FLAGS="DEV=AMD USBGPU=1 IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
else
echo "QCOM build"
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
fi
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
# Generate metadata for all ONNX files
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
@@ -210,13 +186,7 @@ jobs:
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
SUPERCOMBO_ONNX=""
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx"; do
if [ -f "$f" ]; then
SUPERCOMBO_ONNX="$f"
break
fi
done
SUPERCOMBO_ONNX="${{ env.MODELS_DIR }}/supercombo.onnx"
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
if [ -f "$VISION_ONNX" ]; then
@@ -237,15 +207,24 @@ jobs:
fi
if [ -n "$MODEL_TYPE" ]; then
echo "Detected: $MODEL_TYPE -> $OUTPUT_PKL"
echo "Detected: $MODEL_TYPE -> driving_tinygrad.pkl"
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
--model-type $MODEL_TYPE \
--model-size $MODEL_SIZE \
--camera-resolutions $CAMERA_RES \
$ONNX_ARGS \
--output "$OUTPUT_PKL"
--output "${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
fi
- name: Validate Model Outputs
run: |
source /etc/profile
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
--validate-only \
--model-dir "${{ env.MODELS_DIR }}"
- name: Prepare Output
run: |
sudo rm -rf ${{ env.OUTPUT_DIR }}
+17 -9
View File
@@ -20,9 +20,7 @@ concurrency:
env:
CI: 1
PYTHONPATH: ${{ github.workspace }}
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: lfs.fetchexclude
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
jobs:
build_release:
@@ -127,10 +125,14 @@ jobs:
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
env:
RAYLIB_BACKEND: headless
run: tools/op.sh test
run: |
# Pre-compile Python bytecode so each pytest worker doesn't need to
$PYTEST --collect-only -m 'not slow' -qq
MAX_EXAMPLES=1 $PYTEST -m 'not slow'
process_replay:
name: process replay
if: false # disable process_replay for forks
runs-on: ${{
(github.repository == 'commaai/openpilot') &&
((github.event_name != 'pull_request') ||
@@ -167,14 +169,14 @@ jobs:
name: diff_report_${{ github.event.number }}
path: openpilot/selfdrive/test/process_replay/diff_report.txt
- name: Checkout ci-artifacts
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
uses: actions/checkout@v7
with:
repository: sunnypilot/ci-artifacts
repository: commaai/ci-artifacts
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
path: ${{ github.workspace }}/ci-artifacts
- name: Prepare refs
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
working-directory: ${{ github.workspace }}/ci-artifacts
run: |
git config user.name "GitHub Actions Bot"
@@ -186,12 +188,18 @@ jobs:
git add .
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
- name: Push refs
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
with:
timeout_minutes: 2
max_attempts: 3
command: cd ${{ github.workspace }}/ci-artifacts && git push origin process-replay --force
- name: Run regen
if: false
timeout-minutes: 4
env:
ONNXCPU: 1
run: $PYTEST openpilot/selfdrive/test/process_replay/test_regen.py
simulator_driving:
name: simulator driving
@@ -212,7 +220,7 @@ jobs:
env:
# MetaDrive renders offscreen through panda3d's EGL pipe on llvmpipe
EGL_PLATFORM: surfaceless
run: python openpilot/tools/sim/tests/test_metadrive_bridge.py
run: pytest -s openpilot/tools/sim/tests/test_metadrive_bridge.py
create_ui_report:
name: Create UI Report
+2 -4
View File
@@ -15,7 +15,7 @@ a.out
.cache/
bin/
# created at launch for comma hardware PYTHONPATH (PC uses editable installs via pyproject.toml)
# created at launch for TICI PYTHONPATH (PC uses editable installs via pyproject.toml)
/msgq
/opendbc
/rednose
@@ -39,8 +39,6 @@ bin/
*.os-*
*.so
*.a
st[0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]
*.unchunked
*.clb
*.class
*.pyxbldc
@@ -56,7 +54,7 @@ compare_runtime*.html
openpilot/selfdrive/modeld/models/tg_input_devices.json
# build artifacts
docs/_site/
docs_site/
openpilot/selfdrive/pandad/pandad
openpilot/cereal/services.h
openpilot/cereal/gen
+7
View File
@@ -21,5 +21,12 @@
</clean>
</configuration>
</target>
<target id="f2590b2b-9b93-49f9-8510-da3f3724a2ae" name="replay" defaultType="TOOL">
<configuration id="d475264f-6f4c-4092-9b4e-6773309f38b7" name="replay" toolchainName="Default">
<build type="TOOL">
<tool actionId="Tool_External Tools_uv build tools replay" />
</build>
</configuration>
</target>
</component>
</project>
+7
View File
@@ -20,4 +20,11 @@
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
<tool name="uv build tools replay" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec>
<option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) tools/replay/&quot;" />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
</toolSet>
+1 -1
View File
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="sunnypilot" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<envs>
<env name="QT_DBL_CLICK_DIST" value="150" />
</envs>
+27
View File
@@ -0,0 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Debug Route Controls" type="PythonConfigurationType" factoryName="Python">
<module name="openpilot-special" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="FINGERPRINT" value="KIA_EV9" />
<env name="SKIP_FW_QUERY" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/selfdrive/car" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/selfdrive/car/card.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Replay for controls + ui" type="Multirun" separateTabs="false" reuseTabsWithFailures="false" startOneByOne="true" markFailedProcess="true" hideSuccessProcess="false" delayTime="0.0">
<runConfiguration name="replay for controls" type="Native Application" />
<runConfiguration name="Build Debug" type="Custom Build Application" />
<method v="2" />
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="replay for controls" type="CLionNativeAppRunConfigurationType" focusToolWindowBeforeRun="true" PROGRAM_PARAMS="&quot;$Prompt$&quot; --block &quot;sendcan,carState,carParams,carOutput,liveTracks,carParamsSP,carStateSP,bookmarkButton&quot;" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="true" WORKING_DIR="file://$ProjectFileDir$/tools/replay" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="replay" CONFIG_NAME="replay" version="1" RUN_PATH="replay">
<method v="2">
<option name="CLION.COMPOUND.BUILD" enabled="true" />
</method>
</configuration>
</component>
Vendored
+14 -14
View File
@@ -30,14 +30,14 @@ export GIT_COMMIT=${env.GIT_COMMIT}
export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
export AZURE_TOKEN='${env.AZURE_TOKEN}'
# only use 1 thread since most require real hardware that can't be shared
# only use 1 thread for tici tests since most require HIL
export PYTEST_ADDOPTS="-n0 -s"
export GIT_SSH_COMMAND="ssh -i /data/gitkey"
source ~/.bash_profile
if [ -f /AGNOS ]; then
if [ -f /TICI ]; then
source /etc/profile
rm -rf /tmp/tmp*
@@ -206,35 +206,35 @@ node {
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
step("check dirty", "tools/release/check-dirty.sh"),
step("onroad tests", "./openpilot/selfdrive/test/test_onroad.py", [timeout: 60]),
step("onroad tests", "pytest openpilot/selfdrive/test/test_onroad.py -s", [timeout: 60]),
])
},
'HW + Unit Tests': {
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
step("build", "cd openpilot/system/manager && ./build.py"),
step("test power draw", "./openpilot/selfdrive/test/test_power_draw.py"),
step("test encoder", "./openpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["openpilot/system/loggerd/"]]),
step("test manager", "./openpilot/system/manager/test/test_manager.py"),
step("test power draw", "pytest -s openpilot/selfdrive/test//test_power_draw.py"),
step("test encoder", "pytest openpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["openpilot/system/loggerd/"]]),
step("test manager", "pytest openpilot/system/manager/test/test_manager.py"),
])
},
'camerad OX03C10': {
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
step("build", "cd openpilot/system/manager && ./build.py"),
step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
step("test pandad", "pytest openpilot/selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "pytest openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
])
},
'camerad OS04C10': {
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
step("build", "cd openpilot/system/manager && ./build.py"),
step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
step("test pandad", "pytest openpilot/selfdrive/pandad/tests/test_pandad.py"),
step("test camerad", "pytest openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
])
},
'sensord': {
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
step("build", "cd openpilot/system/manager && ./build.py"),
step("test sensord", "./openpilot/system/sensord/tests/test_sensord.py"),
step("test sensord", "pytest openpilot/system/sensord/tests/test_sensord.py"),
])
},
'replay': {
@@ -246,9 +246,9 @@ node {
'tizi': {
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
step("test pandad loopback", "./openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"),
step("test pandad spi", "./openpilot/selfdrive/pandad/tests/test_pandad_spi.py"),
step("test amp", "./openpilot/common/hardware/comma/tests/test_amplifier.py"),
step("test pandad loopback", "pytest openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"),
step("test pandad spi", "pytest openpilot/selfdrive/pandad/tests/test_pandad_spi.py"),
step("test amp", "pytest openpilot/common/hardware/tici/tests/test_amplifier.py"),
])
},
+3 -11
View File
@@ -1,14 +1,6 @@
Version 0.11.2 (2026-08-12)
=======================
* New driving model
* Big model with 880M parameters
* Support for big models running on an external GPU
* Live stream cameras from comma connect
* Generate dashcam clips from comma connect
* Remote comma body control from comma connect
* New alert sounds
* CUPRA Born 2021-2023 support thanks to DaHansi!
* Volkswagen ID.4 2021-2025 support thanks to DaHansi!
Version 0.11.2 (2026-06-15)
========================
Version 0.11.1 (2026-05-18)
========================
+18 -19
View File
@@ -10,7 +10,7 @@ import numpy as np
import SCons.Errors
from SCons.Defaults import _stripixes
COMMA_HARDWARE = os.path.isfile('/AGNOS')
TICI = os.path.isfile('/TICI')
SCons.Warnings.warningAsException(True)
@@ -24,7 +24,7 @@ release = not os.path.exists(File('#.gitattributes').abspath) # file absent on r
AddOption('--minimal',
action='store_false',
dest='extras',
default=(not COMMA_HARDWARE and not release),
default=(not TICI and not release),
help='the minimum build to run openpilot. no tests, tools, etc.')
submodule_python_paths = [
@@ -46,22 +46,22 @@ if external_pythonpath := os.environ.get("PYTHONPATH"):
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
if platform.system() == "Darwin":
arch = "Darwin"
elif arch == "aarch64" and COMMA_HARDWARE:
arch = "comma_arm64"
elif arch == "aarch64" and TICI:
arch = "larch64"
assert arch in [
"comma_arm64", # linux comma hardware (AGNOS) arm64
"aarch64", # linux pc arm64
"x86_64", # linux pc x64
"Darwin", # macOS arm64 (x86 not supported)
"larch64", # linux tici arm64
"aarch64", # linux pc arm64
"x86_64", # linux pc x64
"Darwin", # macOS arm64 (x86 not supported)
]
pkg_names = ['acados', 'capnproto', 'eigen', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd']
pkg_names = ['acados', 'bzip2', 'capnproto', 'catch2', 'eigen', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd']
pkgs = [importlib.import_module(name) for name in pkg_names]
acados = pkgs[pkg_names.index('acados')]
ffmpeg = pkgs[pkg_names.index('ffmpeg')]
# Shared package ships .so/.dylib; older device venvs still have static .a only.
# Keep static link deps (x264/z/va/drm) when the installed package is static so
# COMMA_HARDWARE CI works without upgrading the device venv yet.
# TICI CI works without upgrading the device venv yet.
# TODO: drop the static fallback once device venvs have comma-deps-ffmpeg>=7.1.0.post94
_ffmpeg_lib_names = os.listdir(ffmpeg.LIB_DIR) if os.path.isdir(ffmpeg.LIB_DIR) else []
ffmpeg_shared = any(
@@ -129,11 +129,10 @@ env = Environment(
CCFLAGS=[
"-g",
"-fPIC",
"-pipe",
"-O2",
"-Wunused",
"-Werror",
"-Wshadow" if arch in ("Darwin", "comma_arm64") else "-Wshadow=local",
"-Wshadow" if arch in ("Darwin", "larch64") else "-Wshadow=local",
"-Wno-unknown-warning-option",
"-Wno-inconsistent-missing-override",
"-Wno-c99-designator",
@@ -165,24 +164,24 @@ env = Environment(
COMPILATIONDB_USE_ABSPATH=True,
REDNOSE_ROOT="#rednose_repo",
tools=["default", "cython", "compilation_db", "rednose_filter"],
toolpath=["#msgq_repo/site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
)
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
if arch == "Darwin":
env["RPATHPREFIX"] = "-Wl,-rpath,"
env["RPATHSUFFIX"] = ""
env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}"
if arch != "comma_arm64":
if arch != "larch64":
env['_LIBFLAGS'] = _libflags
# Arch-specific flags and paths
if arch == "comma_arm64":
if arch == "larch64":
env["CC"] = "clang"
env["CXX"] = "clang++"
env.Append(LIBPATH=[
"/usr/lib/aarch64-linux-gnu",
])
arch_flags = ["-D__COMMA_HARDWARE__", "-mcpu=cortex-a57"]
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"]
env.Append(CCFLAGS=arch_flags)
env.Append(CXXFLAGS=arch_flags)
elif arch == "Darwin":
@@ -234,7 +233,7 @@ Export('envCython', 'np_version')
Export('env', 'arch', 'acados', 'release', 'ffmpeg_libs')
# Setup cache dir
default_cache_dir = '/data/scons_cache' if arch == "comma_arm64" else '/tmp/scons_cache'
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
cache_size_limit = 4e9 if "CI" in os.environ else 2e9
CacheDir(cache_dir)
@@ -281,7 +280,7 @@ SConscript([
'openpilot/system/loggerd/SConscript',
])
if arch == "comma_arm64":
if arch == "larch64":
SConscript(['openpilot/system/camerad/SConscript'])
# Build selfdrive
@@ -296,7 +295,7 @@ SConscript([
SConscript(['openpilot/sunnypilot/SConscript'])
# Build desktop-only tools
if GetOption('extras') and arch != "comma_arm64":
if GetOption('extras') and arch != "larch64":
SConscript([
'openpilot/tools/replay/SConscript',
'openpilot/tools/cabana/SConscript',
+99
View File
@@ -0,0 +1,99 @@
import contextlib
import gc
import os
import pytest
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.system.manager import manager
from openpilot.common.hardware import TICI, HARDWARE
# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml
collect_ignore = [
"openpilot/selfdrive/test/process_replay/test_processes.py",
"openpilot/selfdrive/test/process_replay/test_regen.py",
"openpilot/tools/sim/",
# tinygrad JIT has process-global state. Other test files import modeld → tinygrad,
# which corrupts JIT captures for test_warp.py in the same process. Run separately in CI.
"openpilot/sunnypilot/modeld_v2/tests/test_warp.py",
]
def pytest_sessionstart(session):
# TODO: fix tests and enable test order randomization
if session.config.pluginmanager.hasplugin('randomly'):
session.config.option.randomly_reorganize = False
@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_runtest_call(item):
# ensure we run as a hook after capturemanager's
if item.get_closest_marker("nocapture") is not None:
capmanager = item.config.pluginmanager.getplugin("capturemanager")
with capmanager.global_and_fixture_disabled():
yield
else:
yield
@contextlib.contextmanager
def clean_env():
starting_env = dict(os.environ)
yield
os.environ.clear()
os.environ.update(starting_env)
@pytest.fixture(scope="function", autouse=True)
def openpilot_function_fixture(request):
with clean_env():
# setup a clean environment for each test
with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix:
prefix = os.environ["OPENPILOT_PREFIX"]
yield
# ensure the test doesn't change the prefix
assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"]
# cleanup any started processes
manager.manager_cleanup()
# some processes disable gc for performance, re-enable here
if not gc.isenabled():
gc.enable()
gc.collect()
# If you use setUpClass, the environment variables won't be cleared properly,
# so we need to hook both the function and class pytest fixtures
@pytest.fixture(scope="class", autouse=True)
def openpilot_class_fixture():
with clean_env():
yield
@pytest.fixture(scope="function")
def tici_setup_fixture(request, openpilot_function_fixture):
"""Ensure a consistent state for tests on-device. Needs the openpilot function fixture to run first."""
if 'skip_tici_setup' in request.keywords:
return
HARDWARE.initialize_hardware()
HARDWARE.set_power_save(False)
os.system("pkill -9 -f athena")
@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(config, items):
skipper = pytest.mark.skip(reason="Skipping tici test on PC")
for item in items:
if "tici" in item.keywords:
if not TICI:
item.add_marker(skipper)
else:
item.fixturenames.append('tici_setup_fixture')
if "xdist_group_class_property" in item.keywords:
class_property_name = item.get_closest_marker('xdist_group_class_property').args[0]
class_property_value = getattr(item.cls, class_property_name)
item.add_marker(pytest.mark.xdist_group(class_property_value))
-44
View File
@@ -1,44 +0,0 @@
# AI policy
## Why this exists
We use AI tools ourselves, so this isn't an anti-AI stance. The problem is people submitting code, issues, or comments they don't actually understand. AI makes that very easy to do, and it creates real work for reviewers who have to figure out what you meant when you can't explain it yourself.
If you're not going to put effort into understanding and verifying your submission, we're not going to put effort into reviewing it.
## The rule
You are responsible for everything you submit: code, PR descriptions, issues, bug reports, comments.
1. Understand what you submit. If a reviewer asks why you did something, you answer from your own understanding, not by re-prompting. If you can't do that, don't submit it.
2. Test your change. AI gets things wrong all the time. Run it, break it, confirm it actually works.
3. Driving fixes need real evidence. Attach a dongle ID, upload logs, and include segments that show the fix working. A route hash by itself proves nothing.
4. No AI-generated media (images, diagrams, videos) in issues or PRs.
## Disclosure
If AI tools helped you write something, say so. Add an `Assisted-by:` line in your commit message:
```
Assisted-by: GitHub Copilot
Assisted-by: Claude
```
Disclosing won't count against your PR. It helps reviewers know where to look. Hiding it and getting caught will.
## How we review
Reviewers are looking at whether you understand your own change. Can you explain it? Can you respond to feedback without re-prompting? Does your PR description say why you made the change, not just list what changed?
Good code from someone who used AI and understands what they wrote is fine. How you got there doesn't matter as long as you can stand behind it.
## What happens
Submissions that don't meet this bar get closed. If it keeps happening, you get blocked.
## Maintainers
Maintainers use AI at their discretion. They've earned that through sustained contribution and they know the codebase.
+7 -8
View File
@@ -1,10 +1,10 @@
<!--- AUTOGENERATED FROM openpilot/selfdrive/car/CARS_template.md, DO NOT EDIT. --->
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->
# Supported Cars
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 342 Supported Cars
# 341 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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -78,8 +78,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Accord 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-26">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Civic 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
@@ -187,7 +187,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2022">Buy Here</a></sub></details>|||
|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima 2017">Buy Here</a></sub></details>|||
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima 2019-20">Buy Here</a></sub></details>|||
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Seltos 2021">Buy Here</a></sub></details>|||
|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
@@ -230,15 +230,14 @@ A supported vehicle is one that just works when you install a comma device. All
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Nissan[<sup>6</sup>](#footnotes)|Altima 2019-24|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Altima 2019-24">Buy Here</a></sub></details>|||
|Nissan[<sup>6</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Nissan[<sup>6</sup>](#footnotes)|Leaf IC 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf IC 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|Nissan[<sup>6</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|Nissan[<sup>6</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 2500 2020-24">Buy Here</a></sub></details>|||
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 3500 2019-22">Buy Here</a></sub></details>|||
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/dflSSGQwYNc" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2025">Buy Here</a></sub></details>|||
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/dflSSGQwYNc" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2025">Buy Here</a></sub></details>|||
|SEAT[<sup>12</sup>](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|SEAT[<sup>12</sup>](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
+1 -4
View File
@@ -1,5 +1,3 @@
> sunnypilot follows [commaai/openpilot](https://github.com/commaai/openpilot)'s contributing guidelines. The following applies to all contributions here.
# How to contribute
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
@@ -37,7 +35,6 @@ All of these are examples of good PRs:
* **UI design**: we do not have a good review process for this yet
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
* **AI-generated contributions**: see our [AI policy](AI_POLICY.md)
### First contribution
@@ -61,7 +58,7 @@ A good pull request has all of the following:
* Report bugs in GitHub issues.
* Report driving issues in the `#driving-feedback` Discord channel.
* Consider opting into cabin camera uploads to improve the driver monitoring model.
* Consider opting into driver camera uploads to improve the driver monitoring model.
* Connect your device to Wi-Fi regularly, so that we can pull data for training better driving models.
* Run the `nightly` branch and report issues. This branch is like `master` but it's built just like a release.
* Annotate images in the [comma10k dataset](https://github.com/commaai/comma10k).
+24
View File
@@ -0,0 +1,24 @@
# Docs development
The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai).
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
Those commands must be run in the root directory of openpilot, **not /docs**
**1. Install the docs dependencies**
``` bash
uv pip install .[docs]
```
**2. Build the new site**
``` bash
docs build
```
**3. Run the new site locally**
``` bash
docs serve
```
References:
* https://zensical.org/docs/
+2 -2
View File
@@ -52,7 +52,7 @@ Many factors can impact the performance of openpilot DM, causing it to be unable
* Low light conditions, such as driving at night or in dark tunnels.
* Bright light (due to oncoming headlights, direct sunlight, etc.).
* The driver's face is partially or completely outside field of view of the cabin camera.
* The cabin camera is obstructed, covered, or damaged.
* The driver's face is partially or completely outside field of view of the driver facing camera.
* The driver facing camera is obstructed, covered, or damaged.
The list above does not represent an exhaustive list of situations that may interfere with proper operation of openpilot components. A driver should not rely on openpilot DM to assess their level of attention.
-14
View File
@@ -1,14 +0,0 @@
# Docs development
The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai).
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
**1. Build the site**
``` bash
python docs/serve.py --build
```
**2. Run the site locally** (rebuilds on change)
``` bash
python docs/serve.py
```
+1 -1
View File
@@ -1 +1 @@
../../openpilot/selfdrive/assets/icons_mici/settings/comma_icon.png
../../selfdrive/assets/icons_mici/settings/comma_icon.png
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e019ed5af500e8820d05934d47e0380728e1b30e01a179f786dc4edb1eccbd7
size 349
+3
View File
@@ -0,0 +1,3 @@
# openpilot glossary
{{GLOSSARY_DEFINITIONS}}
+3 -3
View File
@@ -10,13 +10,13 @@ For each segment, openpilot records the following log types:
rlogs contain all the messages passed amongst openpilot's processes. See [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Capn Proto](https://capnproto.org/) messages.
## camera video files
## {f,e,d}camera.hevc
Each camera stream is H.265 encoded and written to its respective file.
* `fcamera.hevc` is the narrow road camera (the main forward camera)
* `fcamera.hevc` is the road camera
* `ecamera.hevc` is the wide road camera
* `dcamera.hevc` is the cabin camera
* `dcamera.hevc` is the driver camera
## qlog.zst & qcamera.ts
+3 -3
View File
@@ -21,14 +21,14 @@ In general, driver monitoring feedback is very actionable, and we can fix your c
To post your feedback:
1. Join the [community Discord](https://discord.comma.ai).
2. If cabin camera recording is toggled off, temporarily enable cabin camera recording in the settings until you reproduce the issue.
3. Using comma connect, identify the relevant segment and upload the segment's logs and cabin camera.
2. If driver camera recording is toggled off, temporarily enable driver camera recording in the settings until you reproduce the issue.
3. Using comma connect, identify the relevant segment and upload the segment's logs and driver camera.
4. Post the segment in the `#openpilot-experience` channel on Discord with a good description.
Before posting feedback, please ensure:
- **openpilot is up to date** you should be on the latest openpilot release or nightly
- **the cabin camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc.
- **the driver camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc.
- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield
## Other bugs
+216
View File
@@ -0,0 +1,216 @@
import posixpath
import re
import tomllib
import xml.etree.ElementTree as ET
from pathlib import Path
from markdown.extensions import Extension
from markdown.preprocessors import Preprocessor
from markdown.treeprocessors import Treeprocessor
from zensical.extensions.links import LinksTreeprocessor
GlossaryTerm = tuple[str, re.Pattern[str], str]
GLOSSARY_FILE = Path(__file__).with_name("glossary.toml")
GLOSSARY_PAGE = "concepts/glossary.md"
GLOSSARY_PLACEHOLDER = "{{GLOSSARY_DEFINITIONS}}"
SKIP_TAGS = {
"a",
"code",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"kbd",
"pre",
"script",
"style",
}
def clean_tooltip(description: str) -> str:
text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
text = re.sub(r"`([^`]+)`", r"\1", text)
text = re.sub(r"[*_~]", "", text)
return re.sub(r"\s+", " ", text).strip()
def load_glossary() -> tuple[list[GlossaryTerm], str]:
with GLOSSARY_FILE.open("rb") as f:
glossary_data = tomllib.load(f).get("glossary", {})
glossary: list[GlossaryTerm] = []
rendered = []
for key, value in glossary_data.items():
label = str(key).strip().replace("_", " ")
description = str(value).strip()
if not description:
continue
slug = label.replace(" ", "-").replace("_", "-").lower()
glossary.append((slug, re.compile(rf"(?<!\w){re.escape(label)}(?!\w)", re.IGNORECASE), clean_tooltip(description)))
rendered.append(f'* <span id="{slug}"></span>**{label}**: {description}')
return glossary, "\n".join(rendered)
class GlossaryPreprocessor(Preprocessor):
def __init__(self, md, glossary: str):
super().__init__(md)
self.glossary = glossary
def run(self, lines: list[str]) -> list[str]:
markdown = "\n".join(lines)
if GLOSSARY_PLACEHOLDER not in markdown:
return lines
return markdown.replace(GLOSSARY_PLACEHOLDER, self.glossary).splitlines()
class GlossaryTreeprocessor(Treeprocessor):
def __init__(self, md, glossary: list[GlossaryTerm]):
super().__init__(md)
self.glossary = glossary
self.seen: set[str] = set()
def run(self, root: ET.Element) -> None:
at = self.md.treeprocessors.get_index_for_name("zrelpath")
processor = self.md.treeprocessors[at]
if not isinstance(processor, LinksTreeprocessor):
raise TypeError("Links processor not registered")
if processor.path == GLOSSARY_PAGE:
return
self.seen.clear()
glossary_href = f"{posixpath.relpath(GLOSSARY_PAGE, posixpath.dirname(processor.path) or '.')}#"
self._walk(root, glossary_href)
def _walk(self, element: ET.Element, glossary_href: str) -> None:
if element.tag in SKIP_TAGS or element.attrib.get("data-glossary-skip") is not None:
return
self._replace(element, glossary_href)
idx = 0
while idx < len(element):
child = element[idx]
self._walk(child, glossary_href)
idx = self._replace(element, glossary_href, idx) + 1
def _replace(self, parent: ET.Element, glossary_href: str, index: int | None = None) -> int:
child = None if index is None else parent[index]
text = parent.text if child is None else child.tail
pieces = self._pieces(text or "", glossary_href)
if not pieces:
return -1 if index is None else index
if child is None:
parent.text = pieces[0] if isinstance(pieces[0], str) else ""
# Insert replacements for parent.text before the first existing child.
insert_at = -1
else:
assert index is not None
child.tail = pieces[0] if isinstance(pieces[0], str) else ""
insert_at = index
start = 1 if isinstance(pieces[0], str) else 0
previous = child
for piece in pieces[start:]:
if isinstance(piece, str):
previous.tail = (previous.tail or "") + piece
continue
insert_at += 1
parent.insert(insert_at, piece)
previous = piece
return insert_at
def _pieces(self, text: str, glossary_href: str) -> list[str | ET.Element]:
if not text.strip():
return []
pieces: list[str | ET.Element] = []
cursor = 0
while True:
best = None
for slug, pattern, tooltip in self.glossary:
if slug in self.seen:
continue
found = pattern.search(text, cursor)
if found is None:
continue
candidate = (slug, tooltip, found.start(), found.end())
if best is None:
best = candidate
continue
_, _, best_start, best_end = best
_, _, current_start, current_end = candidate
if current_start < best_start:
best = candidate
continue
if current_start == best_start and current_end - current_start > best_end - best_start:
best = candidate
if best is None:
break
slug, tooltip, start, end = best
if start > cursor:
pieces.append(text[cursor:start])
link = ET.Element(
"a",
{
"class": "glossary-term",
"data-glossary-term": "",
"href": f"{glossary_href}{slug}",
},
)
ET.SubElement(link, "span", {"class": "glossary-term__label"}).text = text[start:end]
ET.SubElement(
link,
"span",
{
"class": "glossary-term__tooltip",
"data-search-exclude": "",
},
).text = tooltip
pieces.append(link)
self.seen.add(slug)
cursor = end
if not pieces:
return []
if cursor < len(text):
pieces.append(text[cursor:])
return pieces
class GlossaryExtension(Extension):
def extendMarkdown(self, md) -> None:
md.registerExtension(self)
glossary, rendered = load_glossary()
md.preprocessors.register(
GlossaryPreprocessor(md, rendered),
"docs-ext-glossary-preprocessor",
27,
)
md.treeprocessors.register(
GlossaryTreeprocessor(md, glossary),
"docs-ext-glossary-treeprocessor",
0,
)
def makeExtension(**kwargs) -> GlossaryExtension:
return GlossaryExtension(**kwargs)
+8
View File
@@ -0,0 +1,8 @@
[glossary]
onroad = "openpilot's system state while ignition is on."
offroad = "openpilot's system state while ignition is off."
route = "A route is a recording of an onroad session."
segment = "Routes are split into one minute chunks called segments."
"comma connect" = "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai)."
panda = "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda)."
"comma four" = "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four)."
-555
View File
@@ -1,555 +0,0 @@
import argparse
import functools
import html
import http.server
import json
import posixpath
import re
import shutil
import threading
import time
import urllib.parse
from pathlib import Path
DOCS_DIR = Path(__file__).resolve().parent
SITE_DIR = DOCS_DIR / "_site"
TEMPLATE_FILE = DOCS_DIR / "template.html"
EXCLUDE_DIRS = {"_site", "__pycache__"}
REPO_URL = "https://github.com/commaai/openpilot/"
# (title, target) pairs. target is a page path or an absolute URL.
# A None target marks a section header.
NAV: list[tuple[str, str | None]] = [
("What is openpilot?", "index.md"),
("How-to", None),
("Turn the speed blue", "how-to/turn-the-speed-blue.md"),
("Connect to a comma 3X or four", "how-to/connect-to-comma.md"),
("Add support for a car", "how-to/car-port.md"),
("Concepts", None),
("Logs", "concepts/logs.md"),
("Safety", "concepts/safety.md"),
("Glossary", "concepts/glossary.md"),
("Contributing", None),
("Feedback", "contributing/feedback.md"),
("Roadmap", "contributing/roadmap.md"),
("Contributing Guide →", "https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md"),
("Links", None),
("Blog →", "https://blog.comma.ai"),
("Bounties →", "https://comma.ai/bounties"),
("GitHub →", "https://github.com/commaai"),
("Discord →", "https://discord.comma.ai"),
("X →", "https://x.com/comma_ai"),
]
GLOSSARY_DESCRIPTIONS = {
"onroad": "openpilot's system state while ignition is on.",
"offroad": "openpilot's system state while ignition is off.",
"route": "A route is a recording of an onroad session.",
"segment": "Routes are split into one minute chunks called segments.",
"comma connect": "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai).",
"panda": "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda).",
"comma four": "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four).",
}
GLOSSARY_PAGE = "concepts/glossary.md"
GLOSSARY_ROUTE = GLOSSARY_PAGE.removesuffix(".md")
GLOSSARY_SKIP = frozenset("a code h1 h2 h3 h4 h5 h6 kbd pre script style".split())
_ENTITY = re.compile(r"&(?:#x?[0-9a-fA-F]+|[a-zA-Z]+);")
_LIST = re.compile(r"^(\s*)([*+-]|\d+\.)\s+(.*)$")
_HEADING = re.compile(r"^(#{1,6})\s+(.*)$")
_HR = re.compile(r"^(-{3,}|\*{3,}|_{3,})$")
_ATTR_URL = re.compile(r"""(?P<pre>\b(?:href|src)=(?P<q>["']))(?P<url>.*?)(?P=q)""")
_VOID = frozenset("br img hr meta link input".split())
_URL = re.compile(r"https?://[^\s<>\[\]\"']+")
_ADMONITION = re.compile(r"^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]$", re.I)
def page_route(path: str) -> str:
path = path.removesuffix(".md")
return posixpath.dirname(path) or "." if posixpath.basename(path) == "index" else path
def page_href(current: str, target: str) -> str:
route = posixpath.relpath(page_route(target), page_route(current))
return ("." if route == "." else route) + "/"
def rewrite_relative_url(value: str, page: str) -> str | None:
url = urllib.parse.urlparse(value)
if value.startswith(("#", "/")) or url.scheme or url.netloc or not url.path:
return None
target = posixpath.normpath(posixpath.join(posixpath.dirname(page), url.path))
if target == ".." or target.startswith("../"):
return None
path = page_href(page, target) if target.endswith(".md") else posixpath.relpath(target, page_route(page))
return url._replace(path=path).geturl()
def rewrite_html_urls(fragment: str, page: str) -> str:
def repl(m: re.Match[str]) -> str:
r = rewrite_relative_url(m.group("url"), page)
return m.group(0) if r is None else f'{m.group("pre")}{r}{m.group("q")}'
return _ATTR_URL.sub(repl, fragment)
def esc(text: str, attr: bool = False) -> str:
held: list[str] = []
def hold(m: re.Match[str]) -> str:
held.append(m.group(0))
return f"\0{len(held) - 1}\0"
return re.sub(r"\0(\d+)\0", lambda m: held[int(m.group(1))], html.escape(_ENTITY.sub(hold, text), quote=attr))
def clean_tooltip(description: str) -> str:
text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
return re.sub(r"\s+", " ", re.sub(r"[*_~]", "", re.sub(r"`([^`]+)`", r"\1", text))).strip()
def glossary_slug(label: str) -> str:
return label.replace(" ", "-").replace("_", "-").lower()
GLOSSARY_TERMS = [(glossary_slug(l), re.compile(rf"(?<!\w){re.escape(l)}(?!\w)", re.I), clean_tooltip(d)) for l, d in GLOSSARY_DESCRIPTIONS.items()]
GLOSSARY_DEFINITIONS = "\n".join(f'* <span id="{glossary_slug(l)}"></span>**{l}**: {d}' for l, d in GLOSSARY_DESCRIPTIONS.items())
def inject_glossary(body: str, page: str) -> str:
if page == GLOSSARY_PAGE:
return body
route = "." if page == "index.md" else page.removesuffix(".md")
base, seen, out, skip, depth = f"{posixpath.relpath(GLOSSARY_ROUTE, route)}/#", set(), [], None, 0
for part in re.split(r"(<[^>]+>)", body):
if not part:
continue
if part.startswith("<"):
out.append(part)
if part.startswith("<!") or not (m := re.match(r"</?\s*([a-zA-Z0-9]+)", part)):
continue
tag = m.group(1).lower()
if tag not in GLOSSARY_SKIP:
continue
closing, void = part.startswith("</"), part.endswith("/>") or tag in _VOID
if closing and skip == tag and depth:
depth -= 1
skip = None if not depth else skip
elif not closing and not void:
skip, depth = (tag, 1) if skip is None else (skip, depth + (skip == tag))
continue
if depth:
out.append(part)
continue
cur, text = 0, part
while True:
best = None
for order, (slug, pat, tip) in enumerate(GLOSSARY_TERMS):
if slug in seen or (found := pat.search(text, cur)) is None:
continue
cand = (found.start(), found.start() - found.end(), order, slug, tip, found.end(), found.group(0))
if best is None or cand[:3] < best[:3]:
best = cand
if best is None:
out.append(text[cur:])
break
start, _, _, slug, tip, end, matched = best
out.append(text[cur:start])
out.append(
f'<a class="glossary-term" data-glossary-term="" href="{base}{slug}">'
+ f'<span class="glossary-term__label">{matched}</span>'
+ f'<span class="glossary-term__tooltip" data-search-exclude="">{esc(tip)}</span></a>'
)
seen.add(slug)
cur = end
return "".join(out)
def slugify(text: str) -> str:
text = html.unescape(re.sub(r"<[^>]+>", "", text)).lower()
return re.sub(r"[-\s]+", "-", re.sub(r"[^\w\s-]", "", text, flags=re.UNICODE)).strip("-")
def _parse_link(text: str, start: int) -> tuple[str, str, int] | None:
if start >= len(text) or text[start] != "[":
return None
depth, i = 0, start
while i < len(text):
depth += (text[i] == "[") - (text[i] == "]")
if text[i] == "]" and depth == 0:
label = text[start + 1 : i]
if i + 1 >= len(text) or text[i + 1] != "(":
return None
j, dp = i + 2, 1
while j < len(text) and dp:
dp += (text[j] == "(") - (text[j] == ")")
j += 1
return None if dp else (label, text[i + 2 : j - 1], j)
i += 1
return None
def autolink_plain(text: str) -> str:
parts: list[str] = []
last = 0
for m in _URL.finditer(text):
start = m.start()
if start > 0 and text[start - 1].isalnum():
continue
parts.append(esc(text[last:start]))
url = m.group(0).rstrip(".,;:!?)]")
parts.append(f'<a href="{esc(url, True)}">{esc(url)}</a>')
last = start + len(url)
parts.append(esc(text[last:]))
return "".join(parts)
def render_inline(text: str, page: str) -> str:
out, i, n = [], 0, len(text)
while i < n:
if text[i] == "\n" and i >= 2 and text[i - 2 : i] == " " and out and out[-1].endswith(" "):
out[-1] = out[-1][:-2]
out.append("<br>\n")
i += 1
continue
if text[i] == "`" and (end := text.find("`", i + 1)) != -1:
out.append(f"<code>{esc(text[i + 1 : end])}</code>")
i = end + 1
continue
if text[i] == "!" and i + 1 < n and text[i + 1] == "[" and (p := _parse_link(text, i + 1)):
label, url, end = p
src = rewrite_relative_url(url, page) or url
out.append(f'<img alt="{esc(label, True)}" src="{esc(src, True)}">')
i = end
continue
if text[i] == "[" and (p := _parse_link(text, i)):
label, url, end = p
href = rewrite_relative_url(url, page) or url
out.append(f'<a href="{esc(href, True)}">{render_inline(label, page)}</a>')
i = end
continue
if text[i] == "<":
if text.startswith("<!--", i):
end = text.find("-->", i + 4)
end = n if end < 0 else end + 3
out.append(rewrite_html_urls(text[i:end], page))
i = end
continue
if m := re.match(r"<[^>]+>", text[i:]):
out.append(rewrite_html_urls(m.group(0), page))
i += len(m.group(0))
continue
if (text.startswith("**", i) or text.startswith("__", i)) and (end := text.find(text[i : i + 2], i + 2)) != -1:
out.append(f"<strong>{render_inline(text[i + 2 : end], page)}</strong>")
i = end + 2
continue
if text[i] in "*_" and i + 1 < n and text[i + 1] not in " \t\n" and (end := text.find(text[i], i + 1)) > i + 1:
out.append(f"<em>{render_inline(text[i + 1 : end], page)}</em>")
i = end + 1
continue
j = i + 1
while j < n and text[j] not in "`[<!*_":
j += 1
out.append(autolink_plain(text[i:j]))
i = j
return "".join(out)
def _trow(line: str) -> list[str]:
return [c.strip() for c in line.strip().removeprefix("|").removesuffix("|").split("|")]
def _is_sep(line: str) -> bool:
return "|" in line and all(re.fullmatch(r":?-{3,}:?", c.strip()) for c in _trow(line))
def _align(sep: str) -> str:
s = sep.strip()
if s.startswith(":") and s.endswith(":"):
return ' style="text-align: center;"'
if s.endswith(":"):
return ' style="text-align: right;"'
if s.startswith(":"):
return ' style="text-align: left;"'
return ""
def _list_info(line: str) -> tuple[int, str, str] | None:
m = _LIST.match(line)
return None if not m else (len(m.group(1)) // 4, "ol" if m.group(2)[-1] == "." else "ul", m.group(3))
def _render_blocks(text: str, page: str) -> str:
lines, out, i, n = text.splitlines(), [], 0, 0
n = len(lines)
while i < n:
line, s = lines[i], lines[i].strip()
if not s:
i += 1
continue
if s.startswith("```"):
lang, body = s[3:].strip(), []
i += 1
while i < n and not lines[i].strip().startswith("```"):
body.append(lines[i])
i += 1
if i < n:
i += 1
code = html.escape("\n".join(body) + ("\n" if body else ""))
cls = f' class="language-{html.escape(lang)}"' if lang else ""
out.append(f"<pre><code{cls}>{code}</code></pre>")
continue
if m := _HEADING.match(s):
content, level = m.group(2).rstrip("#").strip(), len(m.group(1))
sid = slugify(content)
out.append(f'<h{level} id="{sid}">{render_inline(content, page)}<a class="headerlink" href="#{sid}" title="Permanent link">#</a></h{level}>')
i += 1
continue
if _HR.fullmatch(s):
out.append("<hr>")
i += 1
continue
if "|" in line and i + 1 < n and _is_sep(lines[i + 1]):
headers, aligns = _trow(line), [_align(c) for c in _trow(lines[i + 1])]
i += 2
rows = []
while i < n and "|" in lines[i] and lines[i].strip():
rows.append(_trow(lines[i]))
i += 1
parts = (
["<table>", "<thead>", "<tr>"]
+ [f"<th{aligns[j] if j < len(aligns) else ''}>{render_inline(h, page)}</th>" for j, h in enumerate(headers)]
+ ["</tr>", "</thead>", "<tbody>"]
)
for row in rows:
parts.append("<tr>")
for j in range(len(headers)):
parts.append(f"<td{aligns[j] if j < len(aligns) else ''}>{render_inline(row[j] if j < len(row) else '', page)}</td>")
parts.append("</tr>")
out.append("\n".join(parts + ["</tbody>", "</table>"]))
continue
if _list_info(line):
items: list[tuple[int, str, list[str]]] = []
while i < n:
if not lines[i].strip():
if i + 1 < n and _list_info(lines[i + 1]):
i += 1
continue
break
info = _list_info(lines[i])
if not info:
break
level, kind, body = info
chunk = [body]
i += 1
while i < n and lines[i].strip() and _list_info(lines[i]) is None:
t = lines[i].strip()
if t.startswith(("```", ">")) or _HEADING.match(t) or _HR.fullmatch(t):
break
chunk.append(lines[i])
i += 1
items.append((level, kind, chunk))
def render_list(items: list[tuple[int, str, list[str]]], start: int, min_level: int) -> tuple[str, int]:
if start >= len(items) or items[start][0] < min_level:
return "", start
kind, chunks, idx = items[start][1], [f"<{items[start][1]}>"], start
while idx < len(items) and items[idx][0] >= min_level:
level, ikind, body_lines = items[idx]
if level > min_level:
nested, idx = render_list(items, idx, level)
chunks[-1] = (chunks[-1][:-5] + nested + "</li>") if chunks[-1].endswith("</li>") else chunks[-1] + nested
continue
if ikind != kind:
chunks += [f"</{kind}>", f"<{ikind}>"]
kind = ikind
idx += 1
body = render_inline("\n".join(body_lines), page)
nested = ""
if idx < len(items) and items[idx][0] > min_level:
nested, idx = render_list(items, idx, min_level + 1)
chunks.append(f"<li>{body}{nested}\n</li>" if nested else f"<li>{body}</li>")
chunks.append(f"</{kind}>")
return "\n".join(chunks), idx
out.append(render_list(items, 0, items[0][0])[0])
continue
if s.startswith(">"):
q = []
while i < n and lines[i].strip().startswith(">"):
q.append(re.sub(r"^>\s?", "", lines[i].strip()))
i += 1
m = _ADMONITION.match(q[0].strip()) if q else None
if m:
kind = m.group(1).lower()
title = m.group(1).capitalize()
body = _render_blocks("\n".join(q[1:]), page)
out.append(f'<div class="admonition {kind}">\n<p class="admonition-title">{title}</p>\n{body}\n</div>')
else:
out.append(f"<blockquote>\n<p>{render_inline(chr(10).join(q), page)}</p>\n</blockquote>")
continue
if s.startswith("<!--"):
out.append(rewrite_html_urls(line, page))
i += 1
# Preserve a blank line after HTML comments (python-markdown does).
if i < n and not lines[i].strip():
out.append("")
while i < n and not lines[i].strip():
i += 1
continue
buf = [line]
i += 1
while i < n and lines[i].strip():
t = lines[i].strip()
if t.startswith(("```", ">")) or _HEADING.match(t) or _HR.fullmatch(t):
break
if "|" in lines[i] and i + 1 < n and _is_sep(lines[i + 1]):
break
buf.append(lines[i])
i += 1
out.append(f"<p>{render_inline(chr(10).join(buf), page)}</p>")
return "\n".join(out)
def render_markdown(text: str, page: str) -> str:
return inject_glossary(_render_blocks(text, page), page)
def page_title(source: str) -> str:
for line in source.splitlines():
if line.startswith("# "):
return line[2:].strip()
return "openpilot docs"
def write_html_redirect(rel: Path) -> None:
if rel.name == "index.md":
return
target = f"{rel.stem}/"
out = SITE_DIR / rel.with_suffix(".html")
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(
"\n".join(
[
"<!doctype html>",
f'<meta http-equiv="refresh" content="0; url={html.escape(target)}">',
f'<link rel="canonical" href="{html.escape(target)}">',
f"<script>location.replace({json.dumps(target)} + location.search + location.hash)</script>",
]
)
)
def copy_assets() -> None:
for src in DOCS_DIR.rglob("*"):
if not src.is_file():
continue
rel = src.relative_to(DOCS_DIR)
if any(part in EXCLUDE_DIRS for part in rel.parts):
continue
if src.suffix == ".md" or src in (Path(__file__).resolve(), TEMPLATE_FILE):
continue
dest = SITE_DIR / rel
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dest)
def render_nav_html(current_page: str) -> str:
parts: list[str] = []
for title, target in NAV:
if target is None:
parts.append(f'<div class="nav-section">{html.escape(title)}</div>')
elif target.startswith(("http://", "https://")):
parts.append(f'<a href="{html.escape(target)}">{html.escape(title)}</a>')
else:
active = ' class="active"' if target == current_page else ""
parts.append(f'<a href="{html.escape(page_href(current_page, target))}"{active}>{html.escape(title)}</a>')
return "\n".join(parts)
def build() -> None:
template = TEMPLATE_FILE.read_text()
pages = [
(path.relative_to(DOCS_DIR), path.read_text())
for path in sorted(DOCS_DIR.rglob("*.md"))
if path != DOCS_DIR / "README.md" and not any(part in EXCLUDE_DIRS for part in path.relative_to(DOCS_DIR).parts)
]
pages.append((Path(GLOSSARY_PAGE), f"# openpilot glossary\n\n{GLOSSARY_DEFINITIONS}"))
pages.sort()
if SITE_DIR.exists():
shutil.rmtree(SITE_DIR)
SITE_DIR.mkdir(parents=True)
copy_assets()
for rel_path, source in pages:
rel = rel_path.as_posix()
body = render_markdown(source, rel)
title = page_title(source)
route = page_route(rel)
root = "../" * (0 if route == "." else len(route.split("/")))
edit_path = "serve.py" if rel == GLOSSARY_PAGE else rel
page_html = template
for name, value in {
"TITLE": html.escape(title),
"ROOT": root,
"HOME_HREF": page_href(rel, "index.md"),
"NAV": render_nav_html(rel),
"BODY": body,
"EDIT_URL": html.escape(f"{REPO_URL}blob/master/docs/{edit_path}"),
}.items():
page_html = page_html.replace(f"{{{{{name}}}}}", value)
out = SITE_DIR / ("" if route == "." else route) / "index.html"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(page_html)
write_html_redirect(rel_path)
print(f"docs: built {len(pages)} pages into {SITE_DIR}")
def _watched_files() -> list[Path]:
return [p for p in DOCS_DIR.rglob("*") if p.is_file() and not any(part in EXCLUDE_DIRS for part in p.relative_to(DOCS_DIR).parts)]
def serve() -> None:
build()
mtimes = {p: p.stat().st_mtime for p in _watched_files()}
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(SITE_DIR))
httpd = http.server.ThreadingHTTPServer(("", 0), handler)
print(f"docs: serving on http://localhost:{httpd.server_port}/ (watching for changes)")
try:
threading.Thread(target=httpd.serve_forever, daemon=True).start()
while True:
time.sleep(0.5)
new_mtimes = {p: p.stat().st_mtime for p in _watched_files()}
if new_mtimes != mtimes:
mtimes = new_mtimes
print("docs: change detected, rebuilding...")
try:
build()
except Exception as e:
print(f"docs: build failed: {e}")
except KeyboardInterrupt:
pass
finally:
httpd.shutdown()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Build or serve the openpilot documentation site.")
parser.add_argument("--build", action="store_true", help="Build the site and exit.")
args = parser.parse_args()
if args.build:
build()
else:
serve()
+42
View File
@@ -0,0 +1,42 @@
.md-logo img {
filter: invert(1);
}
.glossary-term {
position: relative;
color: inherit;
text-decoration: none;
}
.glossary-term__label {
border-bottom: 1px dotted currentColor;
}
.glossary-term__tooltip {
position: absolute;
top: calc(100% + 0.4rem);
left: 50%;
width: max-content;
max-width: min(30rem, 80vw);
padding: 0.65rem 0.8rem;
border-radius: 0.6rem;
background: rgb(26 26 26 / 96%);
color: white;
box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%);
font-size: 0.85rem;
line-height: 1.45;
opacity: 0;
pointer-events: none;
transform: translateX(-50%) translateY(-0.15rem);
transition: opacity 120ms ease, transform 120ms ease;
visibility: hidden;
z-index: 20;
}
.glossary-term:hover .glossary-term__tooltip,
.glossary-term:focus-visible .glossary-term__tooltip,
.glossary-term:focus-within .glossary-term__tooltip {
opacity: 1;
transform: translateX(-50%) translateY(0);
visibility: visible;
}
-224
View File
@@ -1,224 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{TITLE}} · openpilot docs</title>
<link rel="icon" href="{{ROOT}}assets/favicon.svg">
<style>
:root {
--bg: #fff;
--bg-elev: #f5f5f5;
--bg-hover: #eef0ff;
--fg: #262626;
--fg-dim: #666;
--accent: #4051b5;
--border: #e5e5e5;
--max-width: 76rem;
}
* { box-sizing: border-box; }
html { scrollbar-gutter: stable; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--fg);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
font-size: 15px;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
header.site {
display: flex; align-items: center; gap: 0.75rem;
min-height: 3rem;
padding: 0.5rem max(1.25rem, calc((100% - var(--max-width)) / 2));
background: var(--bg);
border-bottom: 1px solid var(--border);
position: sticky; top: 0; z-index: 10;
}
header.site img { display: block; height: 24px; filter: invert(1); }
header.site .brand { display: flex; align-items: center; gap: 0.75rem; color: var(--fg); }
header.site .brand:hover { text-decoration: none; }
header.site .site-name { font-weight: 600; font-size: 1.05rem; }
header.site .spacer { flex: 1; }
header.site .social { display: flex; gap: 1rem; }
header.site .social a { display: flex; color: var(--fg); }
header.site .social a:hover { color: var(--accent); }
header.site .social svg { width: 1.75rem; height: 1.75rem; fill: currentColor; }
header.site .social a[aria-label="Discord"] svg { transform: scale(1.15); }
.layout { display: flex; width: 100%; max-width: var(--max-width); margin: 0 auto; }
nav.sidebar {
width: 14rem; flex-shrink: 0;
padding: 3.3rem 1.25rem 2rem 0;
position: sticky; top: 3rem; align-self: start;
max-height: calc(100vh - 3rem); overflow-y: auto;
}
nav.sidebar .nav-section { font-weight: 600; margin: 1.15rem 0.8rem 0.45rem; }
nav.sidebar a {
display: block; padding: 0.28rem 0.8rem; border-radius: 0.45rem;
color: var(--fg); line-height: 1.35;
}
nav.sidebar a:hover { background: var(--bg-hover); text-decoration: none; }
nav.sidebar a.active { background: var(--bg-hover); color: var(--accent); font-weight: 600; }
main.content { width: min(100%, 46.5rem); min-width: 0; padding: 2.7rem 2rem 5rem; }
main.content h1, main.content h2, main.content h3 { line-height: 1.25; margin-top: 1.8rem; }
main.content h1 { margin-top: 0; }
main.content img { max-width: 100%; }
main.content table { border-collapse: collapse; display: block; overflow-x: auto; }
main.content th, main.content td { border: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; }
main.content th { background: var(--bg-elev); }
main.content pre {
position: relative;
background: var(--bg-elev);
padding: 0.9rem 1rem;
border-radius: 0.25rem;
overflow-x: auto;
}
main.content code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
main.content :not(pre) > code { background: var(--bg-elev); padding: 0.1rem 0.35rem; border-radius: 0.2rem; font-size: 0.88em; }
main.content blockquote { border-left: 3px solid var(--border); margin: 1rem 0; padding: 0.2rem 1rem; color: var(--fg-dim); }
main.content .admonition {
margin: 1rem 0; padding: 0.5rem 1rem; border-radius: 0.3rem; border-left: 3px solid var(--border);
background: var(--bg-elev);
}
main.content .admonition .admonition-title { margin: 0.2rem 0 0.4rem; font-weight: 600; }
main.content .admonition.note { border-left-color: #0969da; }
main.content .admonition.note .admonition-title { color: #0969da; }
main.content .admonition.tip { border-left-color: #1a7f37; }
main.content .admonition.tip .admonition-title { color: #1a7f37; }
main.content .admonition.important { border-left-color: #8250df; }
main.content .admonition.important .admonition-title { color: #8250df; }
main.content .admonition.warning { border-left-color: #9a6700; }
main.content .admonition.warning .admonition-title { color: #9a6700; }
main.content .admonition.caution { border-left-color: #cf222e; }
main.content .admonition.caution .admonition-title { color: #cf222e; }
main.content details { margin: 0.5rem 0; }
main.content hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
.headerlink { margin-left: 0.25rem; opacity: 0; font-size: 0.7em; }
h1:hover .headerlink, h2:hover .headerlink, h3:hover .headerlink { opacity: 1; }
.edit-link { margin-top: 3rem; font-size: 0.85rem; }
.copy-btn {
position: absolute; top: 0.4rem; right: 0.4rem;
background: var(--bg); color: var(--fg-dim);
border: 1px solid var(--border); border-radius: 0.35rem;
padding: 0.2rem 0.5rem; font-size: 0.78rem; cursor: pointer;
opacity: 0; transition: opacity 120ms;
}
main.content pre:hover .copy-btn { opacity: 1; }
@media (max-width: 48rem) {
nav.sidebar { display: none; }
main.content { padding: 2rem 1.25rem 4rem; }
}
.glossary-term {
position: relative;
color: inherit;
}
.glossary-term__label {
border-bottom: 1px dotted currentColor;
}
.glossary-term__tooltip {
position: absolute;
top: calc(100% + 0.4rem);
left: 50%;
width: max-content;
max-width: min(30rem, 80vw);
padding: 0.65rem 0.8rem;
border-radius: 0.6rem;
background: rgb(26 26 26 / 96%);
color: white;
box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%);
font-size: 0.85rem;
line-height: 1.45;
opacity: 0;
pointer-events: none;
transform: translateX(-50%) translateY(-0.15rem);
transition: opacity 120ms ease, transform 120ms ease;
visibility: hidden;
z-index: 20;
}
.glossary-term:hover .glossary-term__tooltip,
.glossary-term:focus-visible .glossary-term__tooltip,
.glossary-term:focus-within .glossary-term__tooltip {
opacity: 1;
transform: translateX(-50%) translateY(0);
visibility: visible;
}
</style>
</head>
<body>
<header class="site">
<a class="brand" href="{{HOME_HREF}}">
<img src="{{ROOT}}assets/comma-logo.png" alt="">
<span class="site-name">openpilot docs</span>
</a>
<span class="spacer"></span>
<div class="social">
<a href="https://github.com/commaai" aria-label="GitHub">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
M12 .7A11.5 11.5 0 0 0 8.4 23c.6.1.8-.2.8-.5v-2c-3.3.7-4-1.4-4-1.4-.5-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7
1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-5.7 0-1.3.5-2.3 1.2-3.1
-.1-.3-.5-1.6.1-3.1 0 0 1-.3 3.2 1.2a11 11 0 0 1 5.8 0C15.8 6 16.8 6.3 16.8 6.3c.6 1.5.2 2.8.1 3.1
.8.8 1.2 1.8 1.2 3.1 0 4.4-2.8 5.4-5.5 5.7.4.4.8 1.1.8 2.2v2.1c0 .3.2.6.8.5A11.5 11.5 0 0 0 12 .7Z"/></svg>
</a>
<a href="https://discord.comma.ai" aria-label="Discord">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
M20.3 4.4A16 16 0 0 0 16.3 3l-.5 1.1a15 15 0 0 0-7.6 0L7.7 3a16 16 0 0 0-4 1.4C1.1 8.2.4 11.9.8 15.5
a16 16 0 0 0 4.9 2.6l1.2-1.7-1.8-.9.4-.3c3.5 1.6 9.4 1.6 13 0l.4.3-1.8.9 1.2 1.7a16 16 0 0 0 4.9-2.6
c.5-4.2-.8-7.8-2.9-11.1ZM8.3 13.3c-1.1 0-1.9-1-1.9-2.2 0-1.3.8-2.3 1.9-2.3s2 1 1.9 2.3c0 1.2-.8 2.2-1.9 2.2Z
m7.4 0c-1.1 0-1.9-1-1.9-2.2 0-1.3.8-2.3 1.9-2.3s2 1 1.9 2.3c0 1.2-.8 2.2-1.9 2.2Z"/></svg>
</a>
<a href="https://x.com/comma_ai" aria-label="X">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
M18.2 2.3h3.3l-7.2 8.3 8.5 11.2h-6.7l-5.2-6.8-6 6.8H1.6l7.8-8.9L1.2 2.3h6.8l4.7 6.2 5.5-6.2Z
m-1.2 17.5h1.8L7 4.2H5Z"/></svg>
</a>
</div>
</header>
<div class="layout">
<nav class="sidebar">
{{NAV}}
</nav>
<main class="content">
{{BODY}}
<div class="edit-link">
<a href="{{EDIT_URL}}">Edit this page on GitHub</a>
</div>
</main>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('pre').forEach(function (el) {
var btn = document.createElement('button');
btn.className = 'copy-btn';
btn.textContent = 'copy';
btn.addEventListener('click', function () {
var code = el.querySelector('code');
if (!code) return;
navigator.clipboard.writeText(code.innerText).then(function () {
btn.textContent = 'copied';
setTimeout(function () { btn.textContent = 'copy'; }, 1200);
});
});
el.appendChild(btn);
});
});
</script>
</body>
</html>
+3 -3
View File
@@ -19,12 +19,12 @@ function agnos_init {
# Check if AGNOS update is required
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
AGNOS_PY="$DIR/openpilot/common/hardware/comma/agnos.py"
MANIFEST="$DIR/openpilot/system/hardware/comma/agnos.json"
AGNOS_PY="$DIR/openpilot/common/hardware/tici/agnos.py"
MANIFEST="$DIR/openpilot/system/hardware/tici/agnos.json"
if $AGNOS_PY --verify $MANIFEST; then
sudo reboot
fi
$DIR/openpilot/common/hardware/comma/updater $AGNOS_PY $MANIFEST
$DIR/openpilot/common/hardware/tici/updater $AGNOS_PY $MANIFEST
fi
}
+1 -1
View File
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="19.6"
export AGNOS_VERSION="18.5"
fi
export STAGING_ROOT="/data/safe_staging"
-12
View File
@@ -69,8 +69,6 @@ struct LeadData {
struct SelfdriveStateSP @0x81c2f05a394cf4af {
mads @0 :ModularAssistiveDrivingSystem;
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
buttonsPressed @2 :UInt16;
buttonsReleaseToggle @3 :UInt16;
enum AudibleAlert {
none @0;
@@ -139,16 +137,10 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
eta @2 :UInt32;
}
struct Chunk {
fileName @0 :Text;
sha256 @1 :Text;
}
struct Artifact {
fileName @0 :Text;
downloadUri @1 :DownloadUri;
downloadProgress @2 :DownloadProgress;
chunks @3 :List(Chunk);
}
struct Model {
@@ -163,7 +155,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
policy @3;
offPolicy @4;
onPolicy @5;
chunked @6;
}
}
@@ -351,7 +342,6 @@ struct OnroadEventSP @0xda96579883444c35 {
speedLimitChanged @21;
speedLimitPending @22;
e2eChime @23;
laneChangeRoadEdge @24;
}
}
@@ -458,8 +448,6 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
struct ModelDataV2SP @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
leftLaneChangeEdgeBlock @1 :Bool;
rightLaneChangeEdgeBlock @2 :Bool;
enum TurnDirection {
none @0;
-10
View File
@@ -775,13 +775,3 @@ struct GpsTrajectory @0x8cfeb072f5301000 {
x @0 :List(Float32);
y @1 :List(Float32);
}
struct AudioFeedbackDEPRECATED @0xed47e3c075be372a {
audio @0 :AudioData;
blockNum @1 :UInt16;
struct AudioData {
data @0 :Data;
sampleRate @1 :UInt32;
}
}
+38 -59
View File
@@ -75,6 +75,7 @@ struct OnroadEvent @0xc4fa6047f024e718 {
driverUnresponsive2 @37;
driverUnresponsive3 @38;
belowSteerSpeed @39;
lowBattery @40;
accFaulted @41;
sensorDataInvalid @42;
commIssue @43;
@@ -106,17 +107,18 @@ struct OnroadEvent @0xc4fa6047f024e718 {
noGps @68;
wrongCruiseMode @69;
modeldLagging @70;
deviceFalling @71;
fanMalfunction @72;
cameraMalfunction @73;
cameraFrameRate @74;
processNotRunning @75;
dashcamMode @76;
selfdriveInitializing @77;
usbError @78;
cruiseMismatch @79;
canBusMissing @80;
selfdrivedLagging @81;
resumeBlocked @82;
carNotReady @103;
steerTimeLimit @83;
vehicleSensorsInvalid @84;
locationdTemporaryError @85;
@@ -129,15 +131,9 @@ struct OnroadEvent @0xc4fa6047f024e718 {
aeb @92;
userBookmark @95;
excessiveActuation @96;
bigModelLoading @100;
bigModelFailed @102;
audioFeedback @97;
lowBatteryDEPRECATED @40;
soundsUnavailableDEPRECATED @47;
deviceFallingDEPRECATED @71;
usbErrorDEPRECATED @78;
audioFeedbackDEPRECATED @97;
bigModelReadyDEPRECATED @101;
}
}
@@ -180,13 +176,13 @@ struct InitData {
enum DeviceType {
unknown @0;
neo @1; # NEO, EON, & comma two
neo @1;
chffrAndroid @2;
chffrIos @3;
tici @4; # comma three
tici @4;
pc @5;
tizi @6; # comma 3X
mici @7; # comma four
tizi @6;
mici @7;
}
struct PandaInfo {
@@ -704,29 +700,9 @@ struct UsbState {
manufacturer @6 :Text;
product @5 :Text;
linkErrorCount @7 :UInt16;
usb3Lane @8 :Usb3Lane;
enum Usb3Lane {
unknown @0;
a @1;
b @2;
}
}
}
struct ChestnutState {
tempC @0 :Float32;
memoryTempC @1 :Float32;
powerDrawW @2 :Float32;
powerLimitW @3 :Float32;
gpuUsagePercent @4 :UInt8;
gpuClockMhz @5 :UInt16;
fanSpeedRpm @6 :UInt16;
pcieLtssm @7 :UInt8;
supplyVoltage @8 :UInt16; # mV
supplyCurrent @9 :Int16; # mA
}
struct RadarState @0x9a185389d6fdd05f {
mdMonoTime @6 :UInt64; # for debugging
radarErrors @13 :Car.RadarData.Error;
@@ -770,7 +746,7 @@ struct RadarState @0x9a185389d6fdd05f {
}
}
struct ExtrinsicsCalibration @0x96df70754d8390bc {
struct LiveCalibrationData {
calStatus @11 :Status;
calCycle @2 :Int32;
calPerc @3 :Int8;
@@ -1050,7 +1026,6 @@ struct ModelDataV2 {
timestampEof @3 :UInt64;
modelExecutionTime @15 :Float32;
rawPredictions @16 :Data;
big @27 :Bool;
# predicted future position, orientation, etc..
position @4 :XYZTData;
@@ -1391,7 +1366,7 @@ struct LiveLocationKalman {
}
struct DeviceMotion @0xc24ca2b57206b44d {
struct LivePose {
# More info on reference frames:
# https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations
orientationNED @0 :XYZMeasurement;
@@ -2273,7 +2248,7 @@ struct Boot {
}
}
struct VehicleParameters @0xd9058dcb967c2753 {
struct LiveParametersData {
valid @0 :Bool;
gyroBias @1 :Float32;
angleOffsetDeg @2 :Float32;
@@ -2307,8 +2282,8 @@ struct VehicleParameters @0xd9058dcb967c2753 {
}
}
struct LateralTorqueParameters @0xe61690eb0b091692 {
valid @0 :Bool;
struct LiveTorqueParametersData {
liveValid @0 :Bool;
latAccelFactorRaw @1 :Float32;
latAccelOffsetRaw @2 :Float32;
frictionCoefficientRaw @3 :Float32;
@@ -2324,7 +2299,7 @@ struct LateralTorqueParameters @0xe61690eb0b091692 {
calPerc @13 :Int8;
}
struct LateralDelay @0x98dfdb22c44df8d4 {
struct LiveDelayData {
lateralDelay @0 :Float32;
validBlocks @1 :Int32;
status @2 :Status;
@@ -2510,6 +2485,11 @@ struct AudioData {
sampleRate @1 :UInt32;
}
struct AudioFeedback {
audio @0 :AudioData;
blockNum @1 :UInt16;
}
struct Touch {
sec @0 :Int64;
usec @1 :Int64;
@@ -2542,9 +2522,9 @@ struct Event {
pandaStates @81 :List(PandaState);
peripheralState @80 :PeripheralState;
radarState @13 :RadarState;
radarTracks @131 :Car.RadarData;
liveTracks @131 :Car.RadarData;
sendcan @17 :List(CanData);
extrinsicsCalibration @19 :ExtrinsicsCalibration;
liveCalibration @19 :LiveCalibrationData;
carState @22 :Car.CarState;
carControl @23 :Car.CarControl;
carOutput @127 :Car.CarOutput;
@@ -2555,31 +2535,31 @@ struct Event {
qcomGnss @31 :QcomGnss;
gpsLocationExternal @48 :GpsLocationData;
gpsLocation @21 :GpsLocationData;
vehicleParameters @61 :VehicleParameters;
lateralTorqueParameters @94 :LateralTorqueParameters;
lateralDelay @146 : LateralDelay;
liveParameters @61 :LiveParametersData;
liveTorqueParameters @94 :LiveTorqueParametersData;
liveDelay @146 : LiveDelayData;
cameraOdometry @63 :CameraOdometry;
thumbnail @66: Thumbnail;
onroadEvents @134: List(OnroadEvent);
carParams @69: Car.CarParams;
driverMonitoringState @151 :DriverMonitoringState;
deviceMotion @129 :DeviceMotion;
livePose @129 :LivePose;
modelV2 @75 :ModelDataV2;
drivingModelData @128 :DrivingModelData;
driverStateV2 @92 :DriverStateV2;
# camera stuff, each camera state has a matching encode idx
narrowRoadCameraState @2 :FrameData;
cabinCameraState @70: FrameData;
roadCameraState @2 :FrameData;
driverCameraState @70: FrameData;
wideRoadCameraState @74: FrameData;
narrowRoadEncodeIdx @15 :EncodeIndex;
cabinEncodeIdx @76 :EncodeIndex;
roadEncodeIdx @15 :EncodeIndex;
driverEncodeIdx @76 :EncodeIndex;
wideRoadEncodeIdx @77 :EncodeIndex;
qNarrowRoadEncodeIdx @90 :EncodeIndex;
qRoadEncodeIdx @90 :EncodeIndex;
livestreamNarrowRoadEncodeIdx @117 :EncodeIndex;
livestreamRoadEncodeIdx @117 :EncodeIndex;
livestreamWideRoadEncodeIdx @118 :EncodeIndex;
livestreamCabinEncodeIdx @119 :EncodeIndex;
livestreamDriverEncodeIdx @119 :EncodeIndex;
# microphone data
soundPressure @103 :SoundPressure;
@@ -2591,7 +2571,6 @@ struct Event {
procLog @33 :ProcLog;
clocks @35 :Clocks;
deviceState @6 :DeviceState;
chestnutState @152 :ChestnutState;
logMessage @18 :Text;
errorLogMessage @85 :Text;
@@ -2604,20 +2583,21 @@ struct Event {
# driving feedback
userBookmark @93 :UserBookmark;
bookmarkButton @148 :UserBookmark;
audioFeedback @149 :AudioFeedback;
lateralManeuverPlan @150 :LateralManeuverPlan;
# *********** debug ***********
testJoystick @52 :Joystick;
narrowRoadEncodeData @86 :EncodeData;
cabinEncodeData @87 :EncodeData;
roadEncodeData @86 :EncodeData;
driverEncodeData @87 :EncodeData;
wideRoadEncodeData @88 :EncodeData;
qNarrowRoadEncodeData @89 :EncodeData;
qRoadEncodeData @89 :EncodeData;
alertDebug @133 :DebugAlert;
livestreamNarrowRoadEncodeData @120 :EncodeData;
livestreamRoadEncodeData @120 :EncodeData;
livestreamWideRoadEncodeData @121 :EncodeData;
livestreamCabinEncodeData @122 :EncodeData;
livestreamDriverEncodeData @122 :EncodeData;
# *********** Custom: reserved for forks ***********
@@ -2653,7 +2633,6 @@ struct Event {
# *********** legacy + deprecated ***********
model @9 :Deprecated.ModelData; # TODO: rename modelV2 and mark this as deprecated
audioFeedbackDEPRECATED @149 :Deprecated.AudioFeedbackDEPRECATED;
liveMpcDEPRECATED @36 :Deprecated.LiveMpcData;
liveLongitudinalMpcDEPRECATED @37 :Deprecated.LiveLongitudinalMpcData;
liveLocationKalmanDeprecatedDEPRECATED @51 :Deprecated.LiveLocationData;
@@ -4,7 +4,6 @@ import numbers
import random
import threading
import time
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.parameterized import parameterized
from openpilot.cereal import log
@@ -47,7 +46,7 @@ def delayed_send(delay, sock, dat):
threading.Timer(delay, send_func).start()
class TestMessaging(OpenpilotTestCase):
class TestMessaging:
@parameterized.expand(events)
def test_new_message(self, evt):
try:
@@ -81,11 +80,10 @@ class TestMessaging(OpenpilotTestCase):
assert len(msgs) == 0
# no wait but msgs are queued up
pub_sock.send(messaging.new_message(sock).to_bytes())
assert sub_sock.receive() is not None # synchronize the PUB/SUB connection
num_msgs = random.randrange(3, 10)
for _ in range(num_msgs):
pub_sock.send(messaging.new_message(sock).to_bytes())
time.sleep(0.1)
msgs = func(sub_sock)
assert isinstance(msgs, list)
assert all(isinstance(msg, expected_type) for msg in msgs)
@@ -112,7 +110,7 @@ class TestMessaging(OpenpilotTestCase):
def test_recv_one(self):
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=10)
sub_sock = messaging.sub_sock(sock, timeout=1000)
# no msg in queue, socket should timeout
recvd = messaging.recv_one(sub_sock)
@@ -143,7 +141,7 @@ class TestMessaging(OpenpilotTestCase):
def test_recv_one_retry(self):
sock = "carState"
sock_timeout = 0.005
sock_timeout = 0.1
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
@@ -3,14 +3,13 @@ import time
from typing import cast
from collections.abc import Sized
from openpilot.common.test import OpenpilotTestCase
import openpilot.cereal.messaging as messaging
from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
random_bytes, random_carstate, assert_carstate
from openpilot.cereal.services import SERVICE_LIST
class TestSubMaster(OpenpilotTestCase):
class TestSubMaster:
def test_init(self):
sm = messaging.SubMaster(events)
@@ -60,17 +59,17 @@ class TestSubMaster(OpenpilotTestCase):
def test_update_timeout(self):
sock = random_sock()
sm = messaging.SubMaster([sock,])
timeout = random.randrange(10, 30)
timeout = random.randrange(1000, 3000)
start_time = time.monotonic()
sm.update(timeout)
t = time.monotonic() - start_time
assert t >= timeout/1000.
assert t < 0.1
assert t < 3
assert not any(sm.updated.values())
def test_avg_frequency_checks(self):
for poll in (True, False):
sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "extrinsicsCalibration"],
sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "liveCalibration"],
poll=("modelV2" if poll else None),
frequency=(20. if not poll else None))
@@ -78,7 +77,7 @@ class TestSubMaster(OpenpilotTestCase):
"carState": (20, 20),
"modelV2": (20, 20 if poll else 10),
"cameraOdometry": (20, 10),
"extrinsicsCalibration": (4, 4),
"liveCalibration": (4, 4),
"carParams": (None, None),
"userBookmark": (None, None),
}
@@ -98,19 +97,17 @@ class TestSubMaster(OpenpilotTestCase):
pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,])
pub_sock.send(messaging.new_message(sock).to_bytes())
sm.update(1000) # synchronize the PUB/SUB connection
n = 10
for i in range(n+1):
msg = messaging.new_message(sock)
msg.carState.vEgo = i
pub_sock.send(msg.to_bytes())
time.sleep(0.01)
sm.update(1000)
assert sm[sock].vEgo == n
class TestPubMaster(OpenpilotTestCase):
class TestPubMaster:
def test_init(self):
messaging.PubMaster(events)
@@ -1,14 +1,13 @@
import subprocess
import tempfile
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.parameterized import parameterized
import openpilot.cereal.services as services
from openpilot.cereal.services import SERVICE_LIST
class TestServices(OpenpilotTestCase):
class TestServices:
@parameterized.expand(SERVICE_LIST.keys())
def test_services(self, s):
+19 -19
View File
@@ -25,7 +25,6 @@ _services: dict[str, tuple] = {
"accelerometer": (True, 104., 104),
"temperatureSensor": (True, 2., 200),
"deviceState": (True, 2., 1),
"chestnutState": (True, 10., 10),
"touch": (True, 20., 1),
"can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment
"controlsState": (True, 100., 10, QueueSize.MEDIUM),
@@ -33,14 +32,14 @@ _services: dict[str, tuple] = {
"pandaStates": (True, 10., 1),
"peripheralState": (True, 2., 1),
"radarState": (True, 20., 5),
"narrowRoadEncodeIdx": (False, 20., 1),
"radarTracks": (True, 20.),
"roadEncodeIdx": (False, 20., 1),
"liveTracks": (True, 20.),
"sendcan": (True, 100., 139, QueueSize.MEDIUM),
"logMessage": (True, 0., None, QueueSize.BIG),
"errorLogMessage": (True, 0., 1, QueueSize.BIG),
"extrinsicsCalibration": (True, 4., 4),
"lateralTorqueParameters": (True, 4., 1),
"lateralDelay": (True, 4., 1),
"liveCalibration": (True, 4., 4),
"liveTorqueParameters": (True, 4., 1),
"liveDelay": (True, 4., 1),
"operatingSystemLog": (True, 0.),
"carState": (True, 100., 10),
"carControl": (True, 100., 10),
@@ -55,15 +54,15 @@ _services: dict[str, tuple] = {
"qcomGnss": (True, 2.),
"clocks": (True, 0.1, 1),
"ubloxRaw": (True, 20.),
"deviceMotion": (True, 20., 4),
"vehicleParameters": (True, 20., 5),
"livePose": (True, 20., 4),
"liveParameters": (True, 20., 5),
"cameraOdometry": (True, 20., 10),
"thumbnail": (True, 1 / 60., 1),
"onroadEvents": (True, 1., 1),
"carParams": (True, 0.02, 1),
"narrowRoadCameraState": (True, 20., 20),
"cabinCameraState": (True, 20., 20),
"cabinEncodeIdx": (False, 20., 1),
"roadCameraState": (True, 20., 20),
"driverCameraState": (True, 20., 20),
"driverEncodeIdx": (False, 20., 1),
"driverStateV2": (True, 20., 10),
"driverMonitoringState": (True, 20., 10),
"wideRoadEncodeIdx": (False, 20., 1),
@@ -71,15 +70,16 @@ _services: dict[str, tuple] = {
"drivingModelData": (True, 20., 10),
"modelV2": (True, 20., None, QueueSize.BIG),
"managerState": (True, 2., 1),
"qNarrowRoadEncodeIdx": (False, 20.),
"qRoadEncodeIdx": (False, 20.),
"userBookmark": (True, 0., 1),
"soundPressure": (True, 10., 10),
"rawAudioData": (False, 20.),
"bookmarkButton": (True, 0., 1),
"narrowRoadEncodeData": (False, 20., None, QueueSize.BIG),
"cabinEncodeData": (False, 20., None, QueueSize.BIG),
"audioFeedback": (True, 0., 1),
"roadEncodeData": (False, 20., None, QueueSize.BIG),
"driverEncodeData": (False, 20., None, QueueSize.BIG),
"wideRoadEncodeData": (False, 20., None, QueueSize.BIG),
"qNarrowRoadEncodeData": (False, 20., None, QueueSize.BIG),
"qRoadEncodeData": (False, 20., None, QueueSize.BIG),
# sunnypilot
"modelManagerSP": (False, 1., 1, QueueSize.BIG),
@@ -99,11 +99,11 @@ _services: dict[str, tuple] = {
"testJoystick": (True, 0.),
"alertDebug": (True, 20., 5),
"livestreamWideRoadEncodeIdx": (False, 20.),
"livestreamNarrowRoadEncodeIdx": (False, 20.),
"livestreamCabinEncodeIdx": (False, 20.),
"livestreamRoadEncodeIdx": (False, 20.),
"livestreamDriverEncodeIdx": (False, 20.),
"livestreamWideRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
"livestreamNarrowRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
"livestreamCabinEncodeData": (False, 20., None, QueueSize.MEDIUM),
"livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
"livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM),
"customReservedRawData0": (True, 0.),
}
SERVICE_LIST = {name: Service(*vals) for
-8
View File
@@ -1,8 +0,0 @@
from enum import IntEnum
class VisionStreamType(IntEnum):
VISION_STREAM_NARROW_ROAD = 0
VISION_STREAM_CABIN = 1
VISION_STREAM_WIDE_ROAD = 2
VISION_STREAM_MAP = 3
-10
View File
@@ -1,10 +0,0 @@
#pragma once
#include "msgq/visionipc/visionbuf.h"
enum VisionStreamValues : VisionStreamType {
VISION_STREAM_NARROW_ROAD = 0,
VISION_STREAM_CABIN = 1,
VISION_STREAM_WIDE_ROAD = 2,
VISION_STREAM_MAP = 3,
};
+11 -6
View File
@@ -1,4 +1,4 @@
Import('env')
Import('env', 'envCython')
common_libs = [
'params.cc',
@@ -11,9 +11,14 @@ common_libs = [
_common = env.Library('common', common_libs, LIBS="json11")
Export('_common')
params_python = env.SharedLibrary('params_c', 'params_c.cc', LIBS=[_common, 'zmq', 'json11'])
common_python = [params_python]
Export('common_python')
if GetOption('extras'):
env.Program('tests/test_swaglog', 'tests/test_swaglog.cc', LIBS=[_common, 'json11', 'zmq', 'pthread'])
env.Program('tests/test_common',
['tests/test_runner.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'],
LIBS=[_common, 'json11', 'zmq', 'pthread'])
# Cython bindings
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
common_python = [params_python]
Export('common_python')
+3 -19
View File
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
import argparse
import sys
import time
from openpilot.common.hardware import HARDWARE
from openpilot.common.esim.base import LPABase, Profile
@@ -29,17 +27,6 @@ def print_profiles(lpa: LPABase) -> None:
print(f'{i}. {p.iccid} (nickname: {p.nickname or "<none provided>"}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}')
def execute_and_process_notifications(lpa: LPABase, operation) -> None:
try:
operation()
finally:
time.sleep(1) # Need to wait for 1s after the operation is finished so the eUICC/modem can settle down.
try:
lpa.process_notifications()
except Exception as e:
print(f'failed to process eSIM notifications: {e}', file=sys.stderr)
if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai')
sub = parser.add_subparsers(dest='cmd')
@@ -63,21 +50,18 @@ if __name__ == '__main__':
args = parser.parse_args()
lpa = HARDWARE.get_sim_lpa()
if not lpa.is_euicc():
raise SystemExit("no eUICC detected")
if args.cmd == 'switch':
iccid = resolve_iccid(lpa, args.profile)
execute_and_process_notifications(lpa, lambda: lpa.switch_profile(iccid))
lpa.switch_profile(resolve_iccid(lpa, args.profile))
elif args.cmd == 'delete':
iccid = resolve_iccid(lpa, args.profile)
confirm = input(f'are you sure you want to delete profile {iccid}? (y/N) ')
if confirm == 'y':
execute_and_process_notifications(lpa, lambda: lpa.delete_profile(iccid))
lpa.delete_profile(iccid)
else:
print('cancelled')
exit(0)
elif args.cmd == 'download':
execute_and_process_notifications(lpa, lambda: lpa.download_profile(args.qr, args.name))
lpa.download_profile(args.qr, args.name)
elif args.cmd == 'nickname':
lpa.nickname_profile(resolve_iccid(lpa, args.profile), args.name)
else:
+2 -2
View File
@@ -451,7 +451,7 @@ def process_notifications(client: AtClient) -> None:
response = es10x_command(client, request)
content = require_tag(require_tag(response, TAG_RETRIEVE_NOTIFICATION, "RetrieveNotificationsListResponse"),
TAG_OK, "RetrieveNotificationsListResponse")
pending_notif = next((content[start:end] for t, _, start, end in iter_tlv(content, with_positions=True) if t in (TAG_PROFILE_INSTALL_RESULT, 0x30)), None)
pending_notif = next((v for t, v in iter_tlv(content) if t in (TAG_PROFILE_INSTALL_RESULT, 0x30)), None)
if pending_notif is None:
raise RuntimeError("Missing PendingNotification")
@@ -682,7 +682,7 @@ def download_profile(client: AtClient, activation_code: str) -> str:
session.close()
class LPA(LPABase):
class TiciLPA(LPABase):
def __init__(self):
if hasattr(self, '_client'):
return
-240
View File
@@ -1,240 +0,0 @@
import math
import os
import random
import secrets
import struct
from collections.abc import Callable, Sequence
from functools import wraps
from typing import Any, TypeVar
import capnp
T = TypeVar("T")
_EDGE_SLOTS = 16
_MINIMAL_EXAMPLES = 10
_INTEGER_RANGES = {
"int8": (-2**7, 2**7 - 1),
"int16": (-2**15, 2**15 - 1),
"int32": (-2**31, 2**31 - 1),
"int64": (-2**63, 2**63 - 1),
"uint8": (0, 2**8 - 1),
"uint16": (0, 2**16 - 1),
"uint32": (0, 2**32 - 1),
"uint64": (0, 2**64 - 1),
}
# One seed is shared by the whole test process. Individual tests derive their seed
# from their unittest ID, so FUZZ_SEED is reproducible under the parallel runner too.
FUZZ_SEED = int(os.environ.get("FUZZ_SEED", secrets.randbits(64)))
class Fuzzy:
"""Fast, deterministic data generator with systematic boundary coverage."""
def __init__(self, seed: int | str, example_index: int):
self.example_index = example_index
self._random = random.Random(seed)
self._draw_index = 0
def _draw(self, edges: Sequence[T], random_value: Callable[[], T]) -> T:
draw_index = self._draw_index
self._draw_index += 1
# Preserve the cheap minimal prefix Hypothesis produced, then interleave
# systematic boundaries and random values at every draw site.
if self.example_index < _MINIMAL_EXAMPLES:
return edges[0]
search_example = self.example_index - _MINIMAL_EXAMPLES
if search_example < _EDGE_SLOTS * 2 and search_example % 2 == 0:
return edges[(search_example // 2 + draw_index) % len(edges)]
if self._random.randrange(4) == 0:
return self._random.choice(edges)
return random_value()
def boolean(self) -> bool:
return self._draw((False, True), lambda: bool(self._random.getrandbits(1)))
def choice(self, values: Sequence[T]) -> T:
if not values:
raise ValueError("cannot choose from an empty sequence")
return self._draw(values, lambda: self._random.choice(values))
def integer(self, min_value: int, max_value: int) -> int:
if min_value > max_value:
raise ValueError(f"{min_value=} must not exceed {max_value=}")
edges = [
0, 1, -1, min_value, max_value,
min_value + 1, max_value - 1,
]
edges.extend(1 << bit for bit in range(max_value.bit_length()))
edges.extend(-(1 << bit) for bit in range((-min_value).bit_length()))
valid_edges = tuple(dict.fromkeys(v for v in edges if min_value <= v <= max_value))
return self._draw(valid_edges, lambda: self._random.randint(min_value, max_value))
def floating(self, width: int = 64, *, allow_nan: bool = True, allow_infinity: bool = True) -> float:
if width not in (32, 64):
raise ValueError("float width must be 32 or 64")
if width == 32:
unpack_format = "!f"
finite_edges = (
0.0, -0.0, 1.0, -1.0,
struct.unpack(unpack_format, b"\x00\x00\x00\x01")[0],
struct.unpack(unpack_format, b"\x80\x00\x00\x01")[0],
struct.unpack(unpack_format, b"\x7f\x7f\xff\xff")[0],
struct.unpack(unpack_format, b"\xff\x7f\xff\xff")[0],
struct.unpack(unpack_format, b"\x00\x80\x00\x00")[0],
struct.unpack(unpack_format, b"\x80\x80\x00\x00")[0],
)
else:
unpack_format = "!d"
finite_edges = (
0.0, -0.0, 1.0, -1.0,
math.ulp(0.0), -math.ulp(0.0),
float.fromhex("0x1.fffffffffffffp+1023"), -float.fromhex("0x1.fffffffffffffp+1023"),
float.fromhex("0x1p-1022"), -float.fromhex("0x1p-1022"),
)
edges = list(finite_edges)
if allow_infinity:
edges.extend((math.inf, -math.inf))
if allow_nan:
edges.append(math.nan)
def random_float() -> float:
while True:
value = struct.unpack(unpack_format, self._random.randbytes(width // 8))[0]
if (allow_nan or not math.isnan(value)) and (allow_infinity or not math.isinf(value)):
return value
return self._draw(tuple(edges), random_float)
def _length(self, min_length: int, max_length: int | None) -> int:
if min_length < 0:
raise ValueError("minimum length must be non-negative")
if max_length is not None and min_length > max_length:
raise ValueError(f"{min_length=} must not exceed {max_length=}")
if max_length == min_length:
return min_length
offsets = (0, 1, 2, 4, 8, 16, 32)
edges = tuple(min_length + offset for offset in offsets if max_length is None or min_length + offset <= max_length)
def random_length() -> int:
# A geometric tail keeps ordinary examples small without placing an
# artificial ceiling on an unbounded list.
length = min_length
while max_length is None or length < max_length:
if self._random.randrange(8) == 0:
break
length += 1
return length
return self._draw(edges, random_length)
def binary(self, min_size: int = 0, max_size: int | None = None) -> bytes:
size = self._length(min_size, max_size)
patterns = (
bytes(size),
b"\xff" * size,
(b"\xaa\x55" * ((size + 1) // 2))[:size],
bytes(i & 0xff for i in range(size)),
)
return self._draw(patterns, lambda: self._random.randbytes(size))
def text(self, min_size: int = 0, max_size: int | None = None) -> str:
size = self._length(min_size, max_size)
def scalar() -> str:
value = self._random.randrange(0x110000 - 0x800)
if value >= 0xd800:
value += 0x800
return chr(value)
patterns = (
"",
"a" * size,
"\0" * size,
"\U0010ffff" * size,
)
valid_patterns = tuple(value for value in patterns if len(value) == size)
return self._draw(valid_patterns, lambda: "".join(scalar() for _ in range(size)))
def list(self, generate: Callable[[], T], min_size: int = 0, max_size: int | None = None) -> list[T]:
return [generate() for _ in range(self._length(min_size, max_size))]
def fuzzy_test(max_examples: int) -> Callable[[Callable[..., None]], Callable[..., None]]:
"""Run a unittest method repeatedly with independent, reproducible fuzzy data."""
max_examples = int(os.environ.get("MAX_EXAMPLES", max_examples))
assert max_examples >= 1
def decorator(fn: Callable[..., None]) -> Callable[..., None]:
@wraps(fn)
def wrapper(*args: Any, **kwargs: Any) -> None:
test_seed = f"{FUZZ_SEED}:{args[0].id()}"
selected_example = os.environ.get("FUZZ_EXAMPLE")
examples = [int(selected_example, 0)] if selected_example is not None else range(max_examples)
for example_index in examples:
if not 0 <= example_index < max_examples:
raise ValueError(f"FUZZ_EXAMPLE={example_index} is outside [0, {max_examples})")
try:
fn(*args, **kwargs, fuzzy=Fuzzy(f"{test_seed}:{example_index}", example_index))
except Exception as exc:
exc.add_note(f"reproduce with FUZZ_SEED={FUZZ_SEED} FUZZ_EXAMPLE={example_index}")
raise
return wrapper
return decorator
def capnp_random_dict(fuzzy: Fuzzy, schema: Any, event: str | None = None, *, real_floats: bool = False) -> dict[str, Any]:
"""Generate a dictionary accepted by a pycapnp struct constructor."""
def native(type_name: str) -> bool | int | float | str | bytes:
if type_name == "bool":
return fuzzy.boolean()
if type_name in _INTEGER_RANGES:
return fuzzy.integer(*_INTEGER_RANGES[type_name])
if type_name in ("float32", "float64"):
return fuzzy.floating(width=int(type_name[-2:]), allow_nan=not real_floats, allow_infinity=not real_floats)
if type_name == "text":
return fuzzy.text(max_size=1000)
if type_name == "anyPointer":
return fuzzy.text()
if type_name == "data":
return fuzzy.binary(max_size=1000)
raise NotImplementedError(f"invalid Cap'n Proto type: {type_name}")
def generate_field(field: Any) -> Any:
def rec(field_type: Any, base_type: str) -> Any:
type_name = field_type.which()
if type_name == "struct":
struct_schema = field.schema.elementType if base_type == "list" else field.schema
return capnp_random_dict(fuzzy, struct_schema, real_floats=real_floats)
if type_name == "list":
return fuzzy.list(lambda: rec(field_type.list.elementType, "list"))
if type_name == "enum":
enum_schema = field.schema.elementType if base_type == "list" else field.schema
return fuzzy.choice(tuple(enum_schema.enumerants))
return native(type_name)
try:
if hasattr(field.proto, "slot"):
slot_type = field.proto.slot.type
return rec(slot_type, slot_type.which())
return capnp_random_dict(fuzzy, field.schema, real_floats=real_floats)
except capnp.lib.capnp.KjException:
return capnp_random_dict(fuzzy, field.schema, real_floats=real_floats)
union_field = event or (fuzzy.choice(tuple(schema.union_fields)) if schema.union_fields else None)
fields = schema.non_union_fields + ((union_field,) if union_field else ())
return {
field_name: generate_field(schema.fields[field_name])
for field_name in fields
if not field_name.endswith("DEPRECATED") and field_name != "deprecated"
}
+9
View File
@@ -31,3 +31,12 @@ def get_origin(cwd: str | None = None) -> str:
return run_cmd(["git", "config", "remote." + tracking_remote + ".url"], cwd=cwd)
except subprocess.CalledProcessError: # Not on a branch, fallback
return run_cmd_default(["git", "config", "--get", "remote.origin.url"], cwd=cwd)
@cache
def get_normalized_origin(cwd: str | None = None) -> str:
return get_origin(cwd) \
.replace("git@", "", 1) \
.replace(".git", "", 1) \
.replace("https://", "", 1) \
.replace(":", "/", 1)
+7 -7
View File
@@ -2,15 +2,15 @@ import os
from typing import cast
from openpilot.common.hardware.base import HardwareBase
from openpilot.common.hardware.comma.hardware import HardwareComma
from openpilot.common.hardware.pc.hardware import HardwarePc
from openpilot.common.hardware.tici.hardware import Tici
from openpilot.common.hardware.pc.hardware import Pc
TICI = os.path.isfile('/TICI')
AGNOS = os.path.isfile('/AGNOS')
COMMA_HARDWARE = AGNOS
PC = not COMMA_HARDWARE
PC = not TICI
if COMMA_HARDWARE:
HARDWARE = cast(HardwareBase, HardwareComma())
if TICI:
HARDWARE = cast(HardwareBase, Tici())
else:
HARDWARE = cast(HardwareBase, HardwarePc())
HARDWARE = cast(HardwareBase, Pc())
+3 -3
View File
@@ -5,9 +5,9 @@
#include "common/hardware/base.h"
#include "common/util.h"
#if __COMMA_HARDWARE__
#include "common/hardware/comma/hardware.h"
#define Hardware HardwareComma
#if __TICI__
#include "common/hardware/tici/hardware.h"
#define Hardware HardwareTici
#else
#include "common/hardware/pc/hardware.h"
#define Hardware HardwarePC
+1 -1
View File
@@ -1,7 +1,7 @@
from openpilot.cereal import log
from openpilot.common.hardware.base import HardwareBase
class HardwarePc(HardwareBase):
class Pc(HardwareBase):
def get_device_type(self):
return "pc"
@@ -23,14 +23,14 @@
},
{
"name": "abl",
"url": "https://commadist.azureedge.net/agnosupdate/abl-29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35.img.xz",
"hash": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35",
"hash_raw": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35",
"url": "https://commadist.azureedge.net/agnosupdate/abl-b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c.img.xz",
"hash": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c",
"hash_raw": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c",
"size": 274432,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35"
"ondevice_hash": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c"
},
{
"name": "aop",
@@ -56,29 +56,29 @@
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd.img.xz",
"hash": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd",
"hash_raw": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd",
"size": 46897152,
"url": "https://commadist.azureedge.net/agnosupdate/boot-19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39.img.xz",
"hash": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
"hash_raw": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
"size": 17487872,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "6650e4c46df99ae6dfd6ee895a34b8a2a3cc490a8ce18e16cc3c451c3f822b6e"
"ondevice_hash": "ddfe93cc6a8531af92ee331d9bbaeae2f1d933bdb38e579769dc9fe7998eb626"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img.xz",
"hash": "b134fd04e9da27fa1d359ea0f2742c216fa21a08b5c47e9be22ab3b0563d9b9b",
"hash_raw": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3",
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img.xz",
"hash": "4dc41c2c072f5f5d5cd484cd6173049cd96acfb9a67bc20049775585fe881539",
"hash_raw": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
"size": 4718592000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "91242772af771ae96fe2eebc105f2b80a7e1dbaaf6003c2574b62d51b806f468",
"ondevice_hash": "cf1229630b7a2b8497705bca4ba947dbf0c217418ff4febff571aa4f4a878134",
"alt": {
"hash": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3",
"url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img",
"hash": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img",
"size": 4718592000
}
}
]
]
@@ -12,7 +12,7 @@ import requests
SPARSE_CHUNK_FMT = struct.Struct('H2xI4x')
AGNOS_MANIFEST_FILE = "openpilot/system/hardware/comma/agnos.json"
AGNOS_MANIFEST_FILE = "openpilot/system/hardware/tici/agnos.json"
class StreamingDecompressor:
@@ -339,51 +339,51 @@
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-5463317ca7a231f2ddd55a667a8c2b0d6692982359773ca875a99a0e6fd67fb4.img.xz",
"hash": "5463317ca7a231f2ddd55a667a8c2b0d6692982359773ca875a99a0e6fd67fb4",
"hash_raw": "5463317ca7a231f2ddd55a667a8c2b0d6692982359773ca875a99a0e6fd67fb4",
"size": 46897152,
"url": "https://commadist.azureedge.net/agnosupdate/boot-8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8.img.xz",
"hash": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
"hash_raw": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
"size": 17487872,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "daefa6f897bdcc277e50dbf562fd63e57d50d9fc7a599f2d171208a490e725d9"
"ondevice_hash": "edca8bee1531e66953d107eeceeed2dc7b3ca46417e49d55508f94e58bf95db8"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d.img.xz",
"hash": "14ab660bb955604ab6c482b3f59e5239e2c40c54e798201afcaed43d5cb0dcfd",
"hash_raw": "2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d",
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img.xz",
"hash": "78acfe16a7b62a3a91fc7a81f40a693e4468cec1c69df7d0b1e550aacc646113",
"hash_raw": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
"size": 4718592000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "18666ca3b529304f5e7a8fc2e3c9dce715b55941e3965a7af43c613f8e18cfce",
"ondevice_hash": "743142c5a898f27b2a1029cca42c8a5d5d1fc0096414422b850fe84c8d0b8342",
"alt": {
"hash": "2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d",
"url": "https://commadist.azureedge.net/agnosupdate/system-2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d.img",
"hash": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img",
"size": 4718592000
}
},
{
"name": "userdata_90",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-871adca25cb254ad1704ce427f3faaae1027f74aff43a3e150188c8a1c3d3cb8.img.xz",
"hash": "e4f59e5dcf4758759da9284479163e420febf1c118bf33c83b402e80e241608d",
"hash_raw": "871adca25cb254ad1704ce427f3faaae1027f74aff43a3e150188c8a1c3d3cb8",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-14a3fc6e9bd148b9deebf6ae9df2f1b3b759e629b337e41b6895864cdd51f630.img.xz",
"hash": "52160dd01b30b3dc572226e8d549a034b03bc328e80f1f4cd6a857b6dd447687",
"hash_raw": "14a3fc6e9bd148b9deebf6ae9df2f1b3b759e629b337e41b6895864cdd51f630",
"size": 96636764160,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "17b4ec7449e62c52be5df551542684b953e5c7ffc95103008043602f79ae7112"
"ondevice_hash": "3bbc052c7793087946b0cd668c1778f930084f6b00896aeebd193dfce53fa518"
},
{
"name": "userdata_89",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-9d84bd64ef237a32675f936594c16932f8acbb52cf2dd82373a0e718cd32dfcf.img.xz",
"hash": "652e61ecb5b12250dbfeb9f12fd74af16b867b5ea9bf8a08b5731ffd63a76317",
"hash_raw": "9d84bd64ef237a32675f936594c16932f8acbb52cf2dd82373a0e718cd32dfcf",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-425c69d021f4ee2f767963bf7b991d1a492485fa465c5f5d001e1cf7de3d62a0.img.xz",
"hash": "02a8c5512754d7781d930d242be3fad01fbce652297c69dfe8722dc92b18dc09",
"hash_raw": "425c69d021f4ee2f767963bf7b991d1a492485fa465c5f5d001e1cf7de3d62a0",
"size": 95563022336,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "acad63821bb48823c871910b1a363ab924d2635a7574cc5045e877c438d3012f"
"ondevice_hash": "3667d500f91b08a4671d7c09623eb6ae8fc905d9273893b50689e214005c7fa6"
}
]
@@ -9,7 +9,7 @@
#include "common/util.h"
#include "common/hardware/base.h"
class HardwareComma : public HardwareNone {
class HardwareTici : public HardwareNone {
public:
static std::string get_name() {
static const std::string name = []() {
@@ -12,8 +12,9 @@ from openpilot.common.utils import sudo_read, sudo_write
from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action
from openpilot.common.esim.base import LPABase
from openpilot.common.hardware.base import HardwareBase, ThermalConfig, ThermalZone
from openpilot.common.hardware.comma.pins import GPIO
from openpilot.common.hardware.comma.amplifier import Amplifier
from openpilot.common.esim.lpa import TiciLPA
from openpilot.common.hardware.tici.pins import GPIO
from openpilot.common.hardware.tici.amplifier import Amplifier
MODEM_STATE_PATH = "/dev/shm/modem"
@@ -57,17 +58,7 @@ def get_default_route_iface():
routes = [(int(route[6]), route[0]) for line in f.readlines()[1:] if (route := line.split())[1] == "00000000" and int(route[3], 16) & 0x1]
return min(routes)[1] if routes else None
class HardwareComma(HardwareBase):
"""
This platform covers the Snapdragon 845-based comma devices:
- tici = comma three
- tizi = comma 3X
- mici = comma four
We strictly use only the device codenames in this codebase for
consistency, though all user-facing UI should use the product names.
"""
class Tici(HardwareBase):
@cached_property
def amplifier(self):
if self.get_device_type() == "mici":
@@ -154,8 +145,7 @@ class HardwareComma(HardwareBase):
}
def get_sim_lpa(self) -> LPABase:
from openpilot.common.esim.lpa import LPA
return LPA()
return TiciLPA()
def get_imei(self):
return self.get_modem_state().get('imei', '')
@@ -423,7 +413,7 @@ class HardwareComma(HardwareBase):
return True
if __name__ == "__main__":
t = HardwareComma()
t = Tici()
t.initialize_hardware()
t.set_power_save(False)
print(t.get_sim_info())
@@ -3,19 +3,17 @@ import fcntl
import json
import logging
import os
import select
import signal
import struct
import subprocess
import tempfile
import termios
import time
from contextlib import contextmanager
from ipaddress import IPv4Address, AddressValueError
from enum import Enum
from openpilot.common.serial import Serial
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s.%(msecs)03d %(levelname)-7s modem: %(message)s",
@@ -66,39 +64,6 @@ INITIAL_STATE: dict[str, object] = {
}
@contextmanager
def _serial_port(port: str, baudrate: int):
fd = os.open(port, os.O_RDWR | os.O_NOCTTY)
try:
attrs = termios.tcgetattr(fd)
attrs[0] = 0
attrs[1] = 0
attrs[2] = termios.CLOCAL | termios.CREAD | termios.CS8
attrs[3] = 0
attrs[4] = attrs[5] = getattr(termios, f"B{baudrate}")
attrs[6][termios.VMIN] = 0
attrs[6][termios.VTIME] = 0
termios.tcsetattr(fd, termios.TCSANOW, attrs)
yield fd
finally:
os.close(fd)
def _read_line(fd: int, timeout: float) -> bytes:
data = bytearray()
deadline = time.monotonic() + timeout
while True:
readable, _, _ = select.select([fd], [], [], max(0.0, deadline - time.monotonic()))
if not readable:
return bytes(data)
byte = os.read(fd, 1)
if not byte:
return bytes(data)
data.extend(byte)
if byte == b"\n":
return bytes(data)
class State(Enum):
INITIALIZING = "INITIALIZING"
SEARCHING = "SEARCHING"
@@ -132,11 +97,10 @@ class PPPSession:
def reset_data_port():
"""Drop DTR on PPP_PORT so the modem terminates any stuck PPP session."""
try:
with _serial_port(PPP_PORT, 460800) as fd:
dtr = struct.pack("I", termios.TIOCM_DTR)
fcntl.ioctl(fd, termios.TIOCMBIC, dtr)
with Serial(PPP_PORT, baudrate=460800, timeout=1) as s:
s.dtr = False
time.sleep(0.2)
fcntl.ioctl(fd, termios.TIOCMBIS, dtr)
s.dtr = True
except Exception as e:
logging.warning(f"data port reset failed: {e}")
@@ -257,14 +221,12 @@ class Modem:
os.close(fd)
return []
try:
with _serial_port(AT_PORT, 9600) as serial_fd:
termios.tcflush(serial_fd, termios.TCIFLUSH)
command = (cmd + "\r").encode()
while command:
command = command[os.write(serial_fd, command):]
with Serial(AT_PORT, baudrate=9600, timeout=5) as ser:
ser.reset_input_buffer()
ser.write((cmd + "\r").encode())
lines = []
while True:
raw = _read_line(serial_fd, 5)
raw = ser.readline()
if not raw:
raise TimeoutError("AT timeout")
line = raw.decode(errors="ignore").strip()
@@ -6,8 +6,7 @@ TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)))
MANIFEST = os.path.join(TEST_DIR, "../agnos.json")
from openpilot.common.test import OpenpilotTestCase
class TestAgnosUpdater(OpenpilotTestCase):
class TestAgnosUpdater:
def test_manifest(self):
with open(MANIFEST) as f:
@@ -1,17 +1,18 @@
#!/usr/bin/env python3
import pytest
import time
import subprocess
import unittest
from panda import Panda
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.hardware import HARDWARE
from openpilot.common.hardware.comma.amplifier import Amplifier
from openpilot.common.hardware import TICI, HARDWARE
from openpilot.common.hardware.tici.amplifier import Amplifier
class TestAmplifier(OpenpilotTestCase):
COMMA_HARDWARE_TEST = True
class TestAmplifier:
@classmethod
def setup_class(cls):
if not TICI:
pytest.skip()
def setup_method(self):
# clear dmesg
@@ -64,8 +65,4 @@ class TestAmplifier(OpenpilotTestCase):
if self._check_for_i2c_errors(True):
break
else:
self.fail("didn't hit any i2c errors")
if __name__ == "__main__":
unittest.main()
pytest.fail("didn't hit any i2c errors")
+4 -18
View File
@@ -1,19 +1,8 @@
import os
from pathlib import Path
CHESTNUT_FW_VERSION = "ed4e39b7"
CHESTNUT_USB_IDS = ((0xADD1, 0x0001), (0x3801, 0x0001))
CHESTNUT_ROM_USB_IDS = ((0x174C, 0x2464), (0x174C, 0x2463))
CHESTNUT_VENDOR_ID = 0xADD1
CHESTNUT_PRODUCT_ID = 0x0001
USB_DEVICES_PATH = Path("/sys/bus/usb/devices")
TYPEC_CC_ORIENTATION_PATH = Path("/sys/class/power_supply/usb/typec_cc_orientation")
PRIMARY_USB_CONTROLLER = "a600000.ssusb"
def get_usb_topology() -> set[str]:
try:
return set(os.listdir(USB_DEVICES_PATH))
except OSError:
return set()
def read(path: Path) -> str | None:
@@ -26,7 +15,7 @@ def read(path: Path) -> str | None:
def read_int(path: Path, base: int = 10) -> int:
try:
return int(path.read_text(), base)
except (OSError, ValueError, TypeError):
except (OSError, ValueError):
return 0
@@ -47,7 +36,6 @@ def controller(device: Path) -> Path | None:
def get_usb_state() -> list[dict]:
devices = []
typec_orientation = read_int(TYPEC_CC_ORIENTATION_PATH)
for device in usb_devices():
vendor_id = read_int(device / "idVendor", 16)
product_id = read_int(device / "idProduct", 16)
@@ -61,7 +49,6 @@ def get_usb_state() -> list[dict]:
"manufacturer": read(device / "manufacturer") or "",
"product": read(device / "product") or "",
"linkErrorCount": read_int(ctrl / "portli", 0) & 0xFFFF if ctrl is not None else 0,
"usb3Lane": {1: "a", 2: "b"}.get(typec_orientation, "unknown") if ctrl is not None and ctrl.name == PRIMARY_USB_CONTROLLER else "unknown",
})
return devices
@@ -79,9 +66,8 @@ def set_usb_state(device_state, devices: list[dict]) -> None:
entry.manufacturer = device["manufacturer"]
entry.product = device["product"]
entry.linkErrorCount = device["linkErrorCount"]
entry.usb3Lane = device.get("usb3Lane", "unknown")
if (entry.vendorId, entry.productId) in CHESTNUT_USB_IDS:
if (entry.vendorId, entry.productId) == (CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID):
chestnut_present = True
device_state.chestnutPresent = chestnut_present
+2 -2
View File
@@ -8,12 +8,12 @@ import functools
import threading
from openpilot.cereal.messaging import PubMaster
from openpilot.cereal.services import SERVICE_LIST
from openpilot.common.mock.generators import generate_deviceMotion
from openpilot.common.mock.generators import generate_livePose
from openpilot.common.realtime import Ratekeeper
MOCK_GENERATOR = {
"deviceMotion": generate_deviceMotion
"livePose": generate_livePose
}
+9 -9
View File
@@ -1,14 +1,14 @@
from openpilot.cereal import messaging
def generate_deviceMotion():
msg = messaging.new_message('deviceMotion')
def generate_livePose():
msg = messaging.new_message('livePose')
meas = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'xStd': 0.0, 'yStd': 0.0, 'zStd': 0.0, 'valid': True}
msg.deviceMotion.orientationNED = meas
msg.deviceMotion.velocityDevice = meas
msg.deviceMotion.angularVelocityDevice = meas
msg.deviceMotion.accelerationDevice = meas
msg.deviceMotion.inputsOK = True
msg.deviceMotion.posenetOK = True
msg.deviceMotion.sensorsOK = True
msg.livePose.orientationNED = meas
msg.livePose.velocityDevice = meas
msg.livePose.angularVelocityDevice = meas
msg.livePose.accelerationDevice = meas
msg.livePose.inputsOK = True
msg.livePose.posenetOK = True
msg.livePose.sensorsOK = True
return msg
+17 -56
View File
@@ -1,7 +1,7 @@
import re
import sys
import pytest
import inspect
import unittest
def _to_safe_name(s):
@@ -10,60 +10,22 @@ def _to_safe_name(s):
class parameterized:
@staticmethod
def expand(cases, names=None, ids=None, serial=False):
def expand(cases):
cases = list(cases)
if not cases:
return lambda func: unittest.skip("no parameterized cases")(func)
return lambda func: pytest.mark.skip("no parameterized cases")(func)
if serial:
def decorator(func):
normalized = [case if isinstance(case, tuple) else (case,) for case in cases]
def decorator(func):
params = [p for p in inspect.signature(func).parameters if p != 'self']
normalized = [c if isinstance(c, tuple) else (c,) for c in cases]
# Infer arg count from first case so extra params (e.g. from @given) are left untouched
expand_params = params[: len(normalized[0])]
if len(expand_params) == 1:
return pytest.mark.parametrize(expand_params[0], [c[0] for c in normalized])(func)
return pytest.mark.parametrize(', '.join(expand_params), normalized)(func)
def wrapper(self):
for case in normalized:
with self.subTest():
func(self, *case)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
return decorator
return lambda func: _Expanded(func, cases, names, ids)
class _Expanded:
"""Descriptor that turns every parameter case into a real unittest method."""
def __init__(self, func, cases, names, ids):
self.func = func
self.cases = [c if isinstance(c, tuple) else (c,) for c in cases]
self.names = names
self.ids = ids
def __set_name__(self, owner, name):
params = [p for p in inspect.signature(self.func).parameters if p != "self"]
for index, case in enumerate(self.cases):
label = self.ids(*case) if self.ids is not None else None
method_name = f"{name}_{index}" + (f"_{_to_safe_name(label)}" if label is not None else "")
def test_method(test_case, current_case=case):
if self.names is None:
self.func(test_case, *current_case)
else:
values = dict(zip(self.names, current_case, strict=True))
values.update({param: test_case._fixture(param) for param in params if param not in values})
self.func(test_case, **values)
test_method.__name__ = method_name
test_method.__doc__ = self.func.__doc__
setattr(owner, method_name, test_method)
# The descriptor itself is only a method factory, not a test.
setattr(owner, name, None)
return decorator
def parameterized_class(attrs, input_list=None):
@@ -77,17 +39,16 @@ def parameterized_class(attrs, input_list=None):
def decorator(cls):
globs = sys._getframe(1).f_globals
for i, params in enumerate(params_list):
# Append sanitized values so unittest's -k can filter by them.
# append sanitized string param values so pytest -k can filter by them
suffix = "_".join(filter(None, (_to_safe_name(v) for v in params.values() if isinstance(v, str))))
name = f"{cls.__name__}_{i}" + (f"_{suffix}" if suffix else "")
new_cls = type(name, (cls,), dict(params))
new_cls.__module__ = cls.__module__
new_cls.__unittest_skip__ = False
new_cls.__unittest_skip_why__ = "" # else inherited from the base and the collector drops it
new_cls.__test__ = True # override inherited False so pytest collects this subclass
globs[name] = new_cls
# Don't collect the un-parametrised base.
cls.__unittest_skip__ = True
cls.__unittest_skip_why__ = "parameterized base class"
# Don't collect the un-parametrised base, but return it so outer decorators
# (e.g. @pytest.mark.skip) land on it and propagate to subclasses via MRO.
cls.__test__ = False
return cls
return decorator
+7 -209
View File
@@ -1,218 +1,16 @@
import sys
import json
import ctypes
import weakref
import builtins
import datetime
from pathlib import Path
from enum import IntEnum, IntFlag
from openpilot.common.swaglog import cloudlog
class ParamKeyFlag(IntFlag):
PERSISTENT = 0x02
CLEAR_ON_MANAGER_START = 0x04
CLEAR_ON_ONROAD_TRANSITION = 0x08
CLEAR_ON_OFFROAD_TRANSITION = 0x10
DEVELOPMENT_ONLY = 0x40
CLEAR_ON_IGNITION_ON = 0x80
BACKUP = 0x100
ALL = 0xFFFFFFFF
class ParamKeyType(IntEnum):
STRING = 0
BOOL = 1
INT = 2
FLOAT = 3
TIME = 4
JSON = 5
BYTES = 6
_suffix = ".dylib" if sys.platform == "darwin" else ".so"
lib = ctypes.CDLL(Path(__file__).with_name(f"libparams_c{_suffix}"))
ParamsHandle = ctypes.c_void_p
class ParamsBuffer(ctypes.Structure):
_fields_ = [("data", ctypes.c_void_p), ("size", ctypes.c_size_t)]
def _bind_raw(name, args, result=None):
function = getattr(lib, name)
function.argtypes = args
function.restype = result
return function
params_last_error = _bind_raw("params_last_error", [], ctypes.c_char_p)
def _bind(name, args, result=None):
function = _bind_raw(name, args, result)
def checked(*call_args):
value = function(*call_args)
if error := params_last_error():
raise RuntimeError(error.decode())
return value
return checked
params_create = _bind("params_create", [ctypes.c_char_p, ctypes.c_size_t], ParamsHandle)
params_destroy = _bind("params_destroy", [ParamsHandle])
params_clear_all = _bind("params_clear_all", [ParamsHandle, ctypes.c_uint])
params_check_key = _bind("params_check_key", [ParamsHandle, ctypes.c_char_p], ctypes.c_bool)
params_get_key_type = _bind("params_get_key_type", [ParamsHandle, ctypes.c_char_p], ctypes.c_int)
params_get_default = _bind("params_get_default", [ParamsHandle, ctypes.c_char_p], ParamsBuffer)
params_get = _bind("params_get", [ParamsHandle, ctypes.c_char_p, ctypes.c_bool], ParamsBuffer)
params_get_bool = _bind("params_get_bool", [ParamsHandle, ctypes.c_char_p, ctypes.c_bool], ctypes.c_bool)
params_put = _bind("params_put", [ParamsHandle, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_bool], ctypes.c_int)
params_put_bool = _bind("params_put_bool", [ParamsHandle, ctypes.c_char_p, ctypes.c_bool, ctypes.c_bool], ctypes.c_int)
params_remove = _bind("params_remove", [ParamsHandle, ctypes.c_char_p], ctypes.c_int)
params_get_path = _bind("params_get_path", [ParamsHandle, ctypes.c_char_p, ctypes.c_size_t], ParamsBuffer)
params_keys_size = _bind("params_keys_size", [ParamsHandle], ctypes.c_size_t)
params_key_at = _bind("params_key_at", [ParamsHandle, ctypes.c_size_t], ParamsBuffer)
params_keys_by_flag = _bind("params_keys_by_flag", [ParamsHandle, ctypes.c_uint, ctypes.POINTER(ParamsBuffer), ctypes.c_size_t], ctypes.c_size_t)
PYTHON_2_CPP = {
(str, ParamKeyType.STRING): lambda v: v,
(builtins.bool, ParamKeyType.BOOL): lambda v: "1" if v else "0",
(int, ParamKeyType.INT): str,
(float, ParamKeyType.FLOAT): str,
(datetime.datetime, ParamKeyType.TIME): lambda v: v.isoformat(),
(dict, ParamKeyType.JSON): json.dumps,
(list, ParamKeyType.JSON): json.dumps,
(bytes, ParamKeyType.BYTES): lambda v: v,
}
CPP_2_PYTHON = {
ParamKeyType.STRING: lambda v: v.decode("utf-8"),
ParamKeyType.BOOL: lambda v: v == b"1",
ParamKeyType.INT: int,
ParamKeyType.FLOAT: float,
ParamKeyType.TIME: lambda v: datetime.datetime.fromisoformat(v.decode("utf-8")),
ParamKeyType.JSON: json.loads,
ParamKeyType.BYTES: lambda v: v,
}
def ensure_bytes(v):
return v.encode() if isinstance(v, str) else v
def _copy_string(value):
if value.data is None:
return None
return ctypes.string_at(value.data, value.size)
class UnknownKeyName(Exception):
pass
class Params:
def __init__(self, d=""):
path = ensure_bytes(d)
self.p = params_create(path, len(path))
self._finalizer = weakref.finalize(self, params_destroy, self.p)
self._finalizer.atexit = False # daemon threads can still be using live Params handles during interpreter shutdown
self.d = d
def __reduce__(self):
return (type(self), (self.d,))
def clear_all(self, tx_flag=ParamKeyFlag.ALL):
params_clear_all(self.p, int(tx_flag))
def check_key(self, key):
key = ensure_bytes(key)
if b"\0" in key or not params_check_key(self.p, key):
raise UnknownKeyName(key)
return key
def python2cpp(self, proposed_type, expected_type, value, key):
cast = PYTHON_2_CPP.get((proposed_type, expected_type))
if cast:
return cast(value)
raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}")
def _cpp2python(self, t, value, default, key):
if value is None:
return None
try:
return CPP_2_PYTHON[t](value)
except (KeyError, TypeError, ValueError):
cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}")
return self._cpp2python(t, default, None, key)
def _default(self, key):
return _copy_string(params_get_default(self.p, key))
def get(self, key, block=False, return_default=False):
k = self.check_key(key)
t = self.get_type(k)
default = self._default(k) if return_default else None
value = _copy_string(params_get(self.p, k, block))
if value == b"":
if block:
raise KeyboardInterrupt
return self._cpp2python(t, default, None, key)
return self._cpp2python(t, value, default, key)
def get_bool(self, key, block=False):
return bool(params_get_bool(self.p, self.check_key(key), block))
def _put_cast(self, key, dat):
return ensure_bytes(self.python2cpp(type(dat), self.get_type(key), dat, key))
def put(self, key, dat, block=False):
"""Write a parameter. block=True waits until it is persisted to disk."""
k = self.check_key(key)
value = self._put_cast(k, dat)
params_put(self.p, k, value, len(value), block)
def put_bool(self, key, val, block=False):
params_put_bool(self.p, self.check_key(key), val, block)
def remove(self, key):
params_remove(self.p, self.check_key(key))
def get_param_path(self, key=""):
key = ensure_bytes(key)
return _copy_string(params_get_path(self.p, key, len(key))).decode()
def get_type(self, key):
return ParamKeyType(params_get_key_type(self.p, self.check_key(key)))
def all_keys(self, flag=ParamKeyFlag.ALL):
if flag == ParamKeyFlag.ALL:
keys = []
for i in range(params_keys_size(self.p)):
keys.append(_copy_string(params_key_at(self.p, i)))
return keys
max_keys = 1024
buf = (ParamsBuffer * max_keys)()
count = params_keys_by_flag(self.p, int(flag), buf, max_keys)
return [_copy_string(buf[i]) for i in range(min(count, max_keys))]
def get_default_value(self, key):
k = self.check_key(key)
return self._cpp2python(self.get_type(k), self._default(k), None, key)
def cpp2python(self, key, value):
return self._cpp2python(self.get_type(key), value, None, key)
from openpilot.common.params_pyx import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
assert Params
assert ParamKeyFlag
assert ParamKeyType
assert UnknownKeyName
if __name__ == "__main__":
import sys
params = Params()
key = sys.argv[1]
params.check_key(key)
assert params.check_key(key), f"unknown param: {key}"
if len(sys.argv) == 3:
val = sys.argv[2]
print(f"SET: {key} = {val}")
-174
View File
@@ -1,174 +0,0 @@
#include <cstddef>
#include <cstdio>
#include <exception>
#include <string>
#include <utility>
#include <vector>
#include "common/params.h"
typedef struct {
const char *data;
size_t size;
} ParamsBuffer;
struct ParamsHandle {
ParamsHandle(const char *path, size_t path_size) : params(std::string(path, path_size)), keys(params.allKeys()) {
}
Params params;
const std::vector<std::string> keys;
};
namespace {
thread_local char last_error[512] = {};
thread_local std::string result;
void set_error(const char *error) {
snprintf(last_error, sizeof(last_error), "%s", error);
}
ParamsBuffer return_string(std::string value) {
result = std::move(value);
return {result.data(), result.size()};
}
template <typename Result, typename Callable>
Result translate_exceptions(Result failure, Callable &&callable) noexcept {
last_error[0] = '\0';
try {
return callable();
} catch (const std::exception &e) {
set_error(e.what());
} catch (...) {
set_error("unknown C++ exception");
}
return failure;
}
template <typename Callable>
void translate_exceptions(Callable &&callable) noexcept {
translate_exceptions(false, [&]() {
callable();
return true;
});
}
} // namespace
extern "C" {
ParamsHandle *params_create(const char *path, size_t path_size) noexcept {
return translate_exceptions(static_cast<ParamsHandle *>(nullptr), [&]() {
return new ParamsHandle(path, path_size);
});
}
void params_destroy(ParamsHandle *handle) noexcept {
translate_exceptions([&]() {
delete handle;
});
}
const char *params_last_error() noexcept {
return last_error;
}
void params_clear_all(ParamsHandle *handle, unsigned int flag) noexcept {
translate_exceptions([&]() {
handle->params.clearAll(static_cast<ParamKeyFlag>(flag));
});
}
bool params_check_key(ParamsHandle *handle, const char *key) noexcept {
return translate_exceptions(false, [&]() {
return handle->params.checkKey(key);
});
}
int params_get_key_type(ParamsHandle *handle, const char *key) noexcept {
return translate_exceptions(-1, [&]() {
return static_cast<int>(handle->params.getKeyType(key));
});
}
ParamsBuffer params_get_default(ParamsHandle *handle, const char *key) noexcept {
return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() {
auto value = handle->params.getKeyDefaultValue(key);
if (!value.has_value()) {
return ParamsBuffer{nullptr, 0};
}
return return_string(*value);
});
}
ParamsBuffer params_get(ParamsHandle *handle, const char *key, bool block) noexcept {
return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() {
return return_string(handle->params.get(key, block));
});
}
bool params_get_bool(ParamsHandle *handle, const char *key, bool block) noexcept {
return translate_exceptions(false, [&]() {
return handle->params.getBool(key, block);
});
}
int params_put(ParamsHandle *handle, const char *key, const char *value, size_t size, bool block) noexcept {
return translate_exceptions(-1, [&]() {
if (block) {
return handle->params.put(key, value, size);
}
handle->params.putNonBlocking(key, std::string(value, size));
return 0;
});
}
int params_put_bool(ParamsHandle *handle, const char *key, bool value, bool block) noexcept {
return translate_exceptions(-1, [&]() {
if (block) {
return handle->params.putBool(key, value);
}
handle->params.putBoolNonBlocking(key, value);
return 0;
});
}
int params_remove(ParamsHandle *handle, const char *key) noexcept {
return translate_exceptions(-1, [&]() {
return handle->params.remove(key);
});
}
ParamsBuffer params_get_path(ParamsHandle *handle, const char *key, size_t key_size) noexcept {
return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() {
return return_string(handle->params.getParamPath(std::string(key, key_size)));
});
}
size_t params_keys_size(ParamsHandle *handle) noexcept {
return translate_exceptions(size_t{0}, [&]() {
return handle->keys.size();
});
}
ParamsBuffer params_key_at(ParamsHandle *handle, size_t index) noexcept {
return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() {
if (index >= handle->keys.size()) {
return ParamsBuffer{nullptr, 0};
}
return return_string(handle->keys[index]);
});
}
size_t params_keys_by_flag(ParamsHandle *handle, uint32_t flag, ParamsBuffer *out, size_t out_size) noexcept {
return translate_exceptions(size_t{0}, [&]() {
auto filtered = handle->params.allKeys(static_cast<ParamKeyFlag>(flag));
size_t count = std::min(filtered.size(), out_size);
for (size_t i = 0; i < count; i++) {
out[i] = return_string(filtered[i]);
}
return filtered.size();
});
}
} // extern "C"
+3 -7
View File
@@ -106,6 +106,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"PrimeType", {PERSISTENT, INT}},
{"RecordAudio", {PERSISTENT | BACKUP, BOOL}},
{"RecordAudioFeedback", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RecordFront", {PERSISTENT | BACKUP, BOOL}},
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
{"SecOCKey", {PERSISTENT | DONT_LOG | BACKUP, STRING}},
@@ -130,8 +131,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
{"UsbGpuActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
{"UsbGpuLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
{"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"Version", {PERSISTENT, STRING}},
// --- sunnypilot params --- //
@@ -178,10 +179,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RoadEdgeLaneChangeEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RocketFuel", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ScreenSaverEnabled", {PERSISTENT | BACKUP, BOOL, "1"}},
{"ScreenSaverTimeout", {PERSISTENT | BACKUP, INT, "300"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -224,7 +222,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TeslaMadsScreenButton", {PERSISTENT | BACKUP, INT, "0"}},
{"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -275,7 +272,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// Torque lateral control custom params
{"CustomTorqueParams", {PERSISTENT | BACKUP , BOOL}},
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
{"LateralJerkTorqueController", {PERSISTENT | BACKUP, BOOL, "0"}},
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
{"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT, "0.0"}},
+192
View File
@@ -0,0 +1,192 @@
# distutils: language = c++
# cython: language_level = 3
import builtins
import datetime
import json
from libcpp cimport bool
from libcpp.string cimport string
from libcpp.vector cimport vector
from libcpp.optional cimport optional
from openpilot.common.swaglog import cloudlog
cdef extern from "common/params.h":
cpdef enum ParamKeyFlag:
PERSISTENT
CLEAR_ON_MANAGER_START
CLEAR_ON_ONROAD_TRANSITION
CLEAR_ON_OFFROAD_TRANSITION
DEVELOPMENT_ONLY
CLEAR_ON_IGNITION_ON
BACKUP
ALL
cpdef enum ParamKeyType:
STRING
BOOL
INT
FLOAT
TIME
JSON
BYTES
cdef cppclass c_Params "Params":
c_Params(string) except + nogil
string get(string, bool) nogil
bool getBool(string, bool) nogil
int remove(string) nogil
int put(string, string) nogil
void putNonBlocking(string, string) nogil
void putBoolNonBlocking(string, bool) nogil
int putBool(string, bool) nogil
bool checkKey(string) nogil
ParamKeyType getKeyType(string) nogil
optional[string] getKeyDefaultValue(string) nogil
string getParamPath(string) nogil
void clearAll(ParamKeyFlag)
vector[string] allKeys(ParamKeyFlag)
PYTHON_2_CPP = {
(str, STRING): lambda v: v,
(builtins.bool, BOOL): lambda v: "1" if v else "0",
(int, INT): str,
(float, FLOAT): str,
(datetime.datetime, TIME): lambda v: v.isoformat(),
(dict, JSON): json.dumps,
(list, JSON): json.dumps,
(bytes, BYTES): lambda v: v,
}
CPP_2_PYTHON = {
STRING: lambda v: v.decode("utf-8"),
BOOL: lambda v: v == b"1",
INT: int,
FLOAT: float,
TIME: lambda v: datetime.datetime.fromisoformat(v.decode("utf-8")),
JSON: json.loads,
BYTES: lambda v: v,
}
def ensure_bytes(v):
return v.encode() if isinstance(v, str) else v
class UnknownKeyName(Exception):
pass
cdef class Params:
cdef c_Params* p
cdef str d
def __cinit__(self, d=""):
cdef string path = <string>d.encode()
with nogil:
self.p = new c_Params(path)
self.d = d
def __reduce__(self):
return (type(self), (self.d,))
def __dealloc__(self):
del self.p
def clear_all(self, tx_flag=ParamKeyFlag.ALL):
self.p.clearAll(tx_flag)
def check_key(self, key):
key = ensure_bytes(key)
if not self.p.checkKey(key):
raise UnknownKeyName(key)
return key
def python2cpp(self, proposed_type, expected_type, value, key):
cast = PYTHON_2_CPP.get((proposed_type, expected_type))
if cast:
return cast(value)
raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}")
def _cpp2python(self, t, value, default, key):
if value is None:
return None
try:
return CPP_2_PYTHON[t](value)
except (KeyError, TypeError, ValueError):
cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}")
return self._cpp2python(t, default, None, key)
def get(self, key, bool block=False, bool return_default=False):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
cdef optional[string] default = self.p.getKeyDefaultValue(k)
cdef string val
with nogil:
val = self.p.get(k, block)
default_val = (default.value() if default.has_value() else None) if return_default else None
if val == b"":
if block:
# If we got no value while running in blocked mode
# it means we got an interrupt while waiting
raise KeyboardInterrupt
else:
return self._cpp2python(t, default_val, None, key)
return self._cpp2python(t, val, default_val, key)
def get_bool(self, key, bool block=False):
cdef string k = self.check_key(key)
cdef bool r
with nogil:
r = self.p.getBool(k, block)
return r
def _put_cast(self, key, dat):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
return ensure_bytes(self.python2cpp(type(dat), t, dat, key))
def put(self, key, dat, bool block = False):
"""
Warning: block=True blocks until the param is written to disk!
In very rare cases this can take over a second, and your code will hang.
Use block=False in time sensitive code, but in general try to avoid
writing params as much as possible.
"""
cdef string k = self.check_key(key)
cdef string dat_bytes = self._put_cast(key, dat)
with nogil:
if block:
self.p.put(k, dat_bytes)
else:
self.p.putNonBlocking(k, dat_bytes)
def put_bool(self, key, bool val, bool block = False):
cdef string k = self.check_key(key)
with nogil:
if block:
self.p.putBool(k, val)
else:
self.p.putBoolNonBlocking(k, val)
def remove(self, key):
cdef string k = self.check_key(key)
with nogil:
self.p.remove(k)
def get_param_path(self, key=""):
cdef string key_bytes = ensure_bytes(key)
return self.p.getParamPath(key_bytes).decode("utf-8")
def get_type(self, key):
return self.p.getKeyType(self.check_key(key))
def all_keys(self, flag=ParamKeyFlag.ALL):
return self.p.allKeys(flag)
def get_default_value(self, key):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
cdef optional[string] default = self.p.getKeyDefaultValue(k)
return self._cpp2python(t, default.value(), None, key) if default.has_value() else None
def cpp2python(self, key, value):
cdef string k = self.check_key(key)
cdef ParamKeyType t = self.p.getKeyType(k)
return self._cpp2python(t, value, None, key)
-218
View File
@@ -1,218 +0,0 @@
"""Small QR encoder for the UI's byte-mode, error-correction-level-L codes."""
import numpy as np
import pyray as rl
# Indexes are QR versions. These are the only two Reed-Solomon parameters needed
# for error-correction level L.
_ECC_LEN = (0, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28)
_NUM_BLOCKS = (0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8)
# 15 format-info bits for level L (01) with mask 0: ((0x08 << 10) | bch_remainder) ^ 0x5412
_FORMAT_BITS = 0b111011111000100
def _raw_modules(version: int) -> int:
result = (16 * version + 128) * version + 64
if version >= 2:
align = version // 7 + 2
result -= (25 * align - 10) * align - 55
return result - (36 if version >= 7 else 0)
def _capacity(version: int) -> int:
return _raw_modules(version) // 8 - _ECC_LEN[version] * _NUM_BLOCKS[version]
def _append_bits(bits: list[int], value: int, length: int) -> None:
bits.extend((value >> i) & 1 for i in range(length - 1, -1, -1))
def _data_codewords(data: bytes, version: int) -> bytes:
"""Byte-mode-encode the payload, terminated and padded to the version's capacity."""
capacity = _capacity(version)
bits: list[int] = []
_append_bits(bits, 4, 4) # byte mode
_append_bits(bits, len(data), 8 if version <= 9 else 16)
for value in data:
_append_bits(bits, value, 8)
bits.extend([0] * min(4, capacity * 8 - len(bits))) # terminator
bits.extend([0] * (-len(bits) % 8)) # byte alignment
result = bytearray(sum(bits[i + j] << (7 - j) for j in range(8)) for i in range(0, len(bits), 8))
pad = (0xEC, 0x11)
while len(result) < capacity:
result.append(pad[(len(result) - (len(bits) // 8)) & 1])
return bytes(result)
def _codewords(data: bytes, version: int) -> bytes:
"""Split data codewords into Reed-Solomon blocks and interleave data + ECC."""
data = _data_codewords(data, version)
num_blocks = _NUM_BLOCKS[version]
ecc_len = _ECC_LEN[version]
raw_codewords = _raw_modules(version) // 8
short_len = raw_codewords // num_blocks
num_short = num_blocks - raw_codewords % num_blocks
divisor = _divisor(ecc_len)
blocks: list[tuple[bytes, bytes]] = []
offset = 0
for i in range(num_blocks):
length = short_len - ecc_len + (0 if i < num_short else 1)
block = data[offset:offset + length]
blocks.append((block, _remainder(block, divisor)))
offset += length
result = bytearray()
for i in range(short_len - ecc_len + 1):
for block, _ in blocks:
result.extend(block[i:i + 1])
for i in range(ecc_len):
for _, ecc in blocks:
result.append(ecc[i])
return bytes(result)
def _multiply(x: int, y: int) -> int:
result = 0
for _ in range(8):
result = (result << 1) ^ (0x11D if result & 0x80 else 0)
if y & 0x80:
result ^= x
y <<= 1
return result
def _divisor(degree: int) -> bytes:
result = bytearray([0] * (degree - 1) + [1])
root = 1
for _ in range(degree):
for j in range(degree):
result[j] = _multiply(result[j], root)
if j + 1 < degree:
result[j] ^= result[j + 1]
root = _multiply(root, 2)
return bytes(result)
def _remainder(data: bytes, divisor: bytes) -> bytes:
result = bytearray(len(divisor))
for value in data:
factor = value ^ result.pop(0)
result.append(0)
for i, coefficient in enumerate(divisor):
result[i] ^= _multiply(coefficient, factor)
return bytes(result)
def _alignment_positions(version: int) -> list[int]:
if version == 1:
return []
count = version // 7 + 2
step = ((version * 4 + count * 2 + 1) // (count * 2 - 2)) * 2
return [6] + [version * 4 + 10 - step * i for i in range(count - 1)][::-1]
class _Qr:
def __init__(self, version: int, data: bytes):
self.version = version
self.size = version * 4 + 17
self.modules = [[False] * self.size for _ in range(self.size)]
self.function = [[False] * self.size for _ in range(self.size)]
self._draw_functions()
self._draw_data(_codewords(data, version))
for y in range(self.size):
for x in range(self.size):
if not self.function[y][x]:
self.modules[y][x] ^= (x + y) % 2 == 0
self._format()
def _set_function(self, x: int, y: int, dark: bool) -> None:
if 0 <= x < self.size and 0 <= y < self.size:
self.modules[y][x] = dark
self.function[y][x] = True
def _finder(self, x: int, y: int) -> None:
for dy in range(-4, 5):
for dx in range(-4, 5):
distance = max(abs(dx), abs(dy))
self._set_function(x + dx, y + dy, distance != 2 and distance != 4)
def _alignment(self, x: int, y: int) -> None:
for dy in range(-2, 3):
for dx in range(-2, 3):
self._set_function(x + dx, y + dy, max(abs(dx), abs(dy)) != 1)
def _draw_functions(self) -> None:
for i in range(self.size):
self._set_function(6, i, i % 2 == 0)
self._set_function(i, 6, i % 2 == 0)
self._finder(3, 3)
self._finder(self.size - 4, 3)
self._finder(3, self.size - 4)
positions = _alignment_positions(self.version)
for y in positions:
for x in positions:
if not ((x == 6 and y in (6, self.size - 7)) or (x == self.size - 7 and y == 6)):
self._alignment(x, y)
# reserve the format-info modules before the data is placed; the real
# values are written by the second _format call after masking
self._format()
if self.version >= 7:
value = self.version
for _ in range(12):
value = (value << 1) ^ ((value >> 11) * 0x1F25)
value = self.version << 12 | value
for i in range(18):
bit = ((value >> i) & 1) != 0
a = self.size - 11 + i % 3
b = i // 3
self._set_function(a, b, bit)
self._set_function(b, a, bit)
def _format(self) -> None:
for i in range(15):
bit = ((_FORMAT_BITS >> i) & 1) != 0
y_pos = i if i < 6 else i + 1 if i < 8 else self.size - 15 + i
self._set_function(8, y_pos, bit)
x_pos = self.size - 1 - i if i < 8 else 15 - i if i < 9 else 14 - i
self._set_function(x_pos, 8, bit)
self._set_function(8, self.size - 8, True)
def _draw_data(self, data: bytes) -> None:
bits = ((byte >> s) & 1 for byte in data for s in reversed(range(8)))
upward = True
right = self.size - 1
while right >= 1:
if right == 6: # skip the vertical timing column
right = 5
for vert in range(self.size):
y = self.size - 1 - vert if upward else vert
for x in (right, right - 1):
if not self.function[y][x]:
self.modules[y][x] = bool(next(bits, 0))
upward = not upward
right -= 2
def make_texture(data: str, inverted: bool = False) -> rl.Texture:
"""Render a URL as the RGBA QR texture used by the UI. The texture upload
copies the pixels, so the intermediate image/array don't need to outlive it."""
raw = data.encode()
for version in range(1, 21):
count_bits = 8 if version <= 9 else 16
if 4 + count_bits + len(raw) * 8 <= _capacity(version) * 8:
break
else:
raise ValueError("QR URL is too long")
modules = np.pad(_Qr(version, raw).modules, 0 if inverted else 4)
modules = np.repeat(np.repeat(modules, 10, axis=0), 10, axis=1)
gray = ((modules == inverted) * 255).astype(np.uint8)
img_array = np.dstack((gray, gray, gray, np.full_like(gray, 255)))
rl_image = rl.Image()
rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data)
rl_image.width = img_array.shape[1]
rl_image.height = img_array.shape[0]
rl_image.mipmaps = 1
rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
return rl.load_texture_from_image(rl_image)
+3
View File
@@ -48,6 +48,9 @@ class RunningStat:
def std(self):
return np.sqrt(self.variance())
def params_to_save(self):
return [self.M, self.S, self.n]
class RunningStatFilter:
def __init__(self, raw_priors=None, filtered_priors=None, max_trackable=-1):
self.raw_stat = RunningStat(raw_priors, -1)
+1 -2
View File
@@ -45,8 +45,7 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler):
fp = os.path.join(base_dir, fn)
if fp.startswith(self.base_filename) and os.path.isfile(fp):
log_files.append(fp)
# newest first, matching _open()'s insert(0, ...) so doRollover()'s pop() deletes the oldest
return sorted(log_files, reverse=True)
return sorted(log_files)
def shouldRollover(self, record):
size_exceeded = self.max_bytes > 0 and self.stream.tell() >= self.max_bytes
-166
View File
@@ -1,166 +0,0 @@
import contextlib
import gc
import inspect
import os
import subprocess
import unittest
from unittest import mock
from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.system.manager import manager
@contextlib.contextmanager
def clean_env():
starting_env = dict(os.environ)
try:
yield
finally:
os.environ.clear()
os.environ.update(starting_env)
class OpenpilotTestCase(unittest.TestCase):
"""TestCase with openpilot's per-test isolation."""
COMMA_HARDWARE_TEST = False
SHARED_DOWNLOAD_CACHE = False
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Preserve legacy xunit hooks and invoke them inside the prefix below.
for name in ("setup_method", "teardown_method"):
hook = cls.__dict__.get(name)
if hook is not None:
setattr(cls, f"openpilot_{name}", hook)
setattr(cls, name, None)
def _fixture(self, name):
if name == "mocker":
return Mocker(self.addCleanup)
if name == "monkeypatch":
return MonkeyPatch(self.addCleanup)
if name == "subtests":
return SubTests(self)
fixture = getattr(inspect.getmodule(type(self)), name)
kwargs = {p: self._fixture(p) for p in inspect.signature(fixture).parameters}
value = fixture(**kwargs)
if inspect.isgenerator(value):
generator = value
value = next(generator)
self.addCleanup(lambda: next(generator, None))
return value
def _callTestMethod(self, method):
params = [name for name, param in inspect.signature(method).parameters.items()
if param.default is inspect.Parameter.empty]
return method(**{name: self._fixture(name) for name in params})
def run(self, result=None):
# This boundary cannot live in setUp/tearDown: existing unittest classes
# are allowed to override those hooks without calling super().
if (self.COMMA_HARDWARE_TEST and not COMMA_HARDWARE) or getattr(type(self), "__unittest_skip__", False):
return super().run(result)
test_env = clean_env()
test_env.__enter__()
prefix = OpenpilotPrefix(shared_download_cache=self.SHARED_DOWNLOAD_CACHE)
prefix.__enter__()
try:
return super().run(result)
finally:
prefix.__exit__(None, None, None)
manager.manager_cleanup()
if not gc.isenabled():
gc.enable()
gc.collect()
test_env.__exit__(None, None, None)
@classmethod
def setUpClass(cls):
super().setUpClass()
if cls.COMMA_HARDWARE_TEST and not COMMA_HARDWARE:
raise unittest.SkipTest("Skipping comma hardware test on PC")
cls._class_env = clean_env()
cls._class_env.__enter__()
setup_class = getattr(cls, "setup_class", None)
if setup_class is not None:
setup_class()
@classmethod
def tearDownClass(cls):
try:
teardown_class = getattr(cls, "teardown_class", None)
if teardown_class is not None:
teardown_class()
finally:
cls._class_env.__exit__(None, None, None)
super().tearDownClass()
def setUp(self):
super().setUp()
if self.COMMA_HARDWARE_TEST and not COMMA_HARDWARE:
self.skipTest("Skipping comma hardware test on PC")
if self.COMMA_HARDWARE_TEST:
HARDWARE.initialize_hardware()
HARDWARE.set_power_save(False)
subprocess.run(["pkill", "-9", "-f", "athena"], check=False)
setup_method = getattr(self, "openpilot_setup_method", None)
if setup_method is not None:
setup_method()
def tearDown(self):
try:
teardown_method = getattr(self, "openpilot_teardown_method", None)
if teardown_method is not None:
teardown_method()
finally:
super().tearDown()
class Mocker:
Mock = mock.Mock
MagicMock = mock.MagicMock
call = mock.call
ANY = mock.ANY
def __init__(self, add_cleanup):
self._add_cleanup = add_cleanup
self.patch = Patch(self._start)
def _start(self, patcher):
value = patcher.start()
self._add_cleanup(patcher.stop)
return value
class Patch:
def __init__(self, start):
self._start = start
def __call__(self, *args, **kwargs):
return self._start(mock.patch(*args, **kwargs))
def object(self, *args, **kwargs):
return self._start(mock.patch.object(*args, **kwargs))
class MonkeyPatch:
def __init__(self, add_cleanup):
self._add_cleanup = add_cleanup
def setattr(self, target, name, value):
patcher = mock.patch.object(target, name, value)
patcher.start()
self._add_cleanup(patcher.stop)
class SubTests:
def __init__(self, test_case):
self._test_case = test_case
def test(self, label=None, **kwargs):
return self._test_case.subTest(**kwargs) if label is None else self._test_case.subTest(label, **kwargs)
-1
View File
@@ -1,2 +1 @@
test_common
test_swaglog
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include <iostream>
#include <stdexcept>
#include <string>
inline void native_test_check(bool condition, const char *expression, const char *file, int line) {
if (!condition) {
throw std::runtime_error(std::string(file) + ":" + std::to_string(line) + ": check failed: " + expression);
}
}
#define CHECK(condition) native_test_check(static_cast<bool>(condition), #condition, __FILE__, __LINE__)
#define REQUIRE(...) CHECK((__VA_ARGS__))
template <typename Function>
int run_native_test(Function &&function) {
try {
function();
return 0;
} catch (const std::exception &error) {
std::cerr << error.what() << '\n';
return 1;
}
}
+1 -2
View File
@@ -1,11 +1,10 @@
import os
from uuid import uuid4
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.utils import atomic_write
class TestFileHelpers(OpenpilotTestCase):
class TestFileHelpers:
def run_atomic_write_func(self, atomic_write_func):
path = f"/tmp/tmp{uuid4()}"
with atomic_write_func(path) as f:
+1 -2
View File
@@ -1,11 +1,10 @@
import os
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.basedir import BASEDIR
from openpilot.common.markdown import parse_markdown
class TestMarkdown(OpenpilotTestCase):
class TestMarkdown:
def test_all_release_notes(self):
with open(os.path.join(BASEDIR, "CHANGELOG.md")) as f:
release_notes = f.read().split("\n\n")
+6 -11
View File
@@ -1,13 +1,13 @@
import pytest
import datetime
import os
import threading
import time
import uuid
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName
class TestParams(OpenpilotTestCase):
class TestParams:
def setup_method(self):
self.params = Params()
@@ -50,23 +50,18 @@ class TestParams(OpenpilotTestCase):
assert self.params.get("CarParams", block=True) == b"test"
def test_params_unknown_key_fails(self):
with self.assertRaises(UnknownKeyName):
with pytest.raises(UnknownKeyName):
self.params.get("swag")
with self.assertRaises(UnknownKeyName):
with pytest.raises(UnknownKeyName):
self.params.get_bool("swag")
with self.assertRaises(UnknownKeyName):
with pytest.raises(UnknownKeyName):
self.params.put("swag", "abc", block=True)
with self.assertRaises(UnknownKeyName):
with pytest.raises(UnknownKeyName):
self.params.put_bool("swag", True, block=True)
with self.assertRaises(UnknownKeyName):
self.params.put(b"DongleId\0suffix", "abc", block=True)
assert self.params.get_param_path(b"key\0suffix").endswith("/key\0suffix")
def test_remove_not_there(self):
assert self.params.get("CarParams") is None
self.params.remove("CarParams")
+2
View File
@@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
+1 -2
View File
@@ -1,8 +1,7 @@
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.simple_kalman import KF1D
class TestSimpleKalman(OpenpilotTestCase):
class TestSimpleKalman:
def setup_method(self):
dt = 0.01
x0_0 = 0.0
+40 -52
View File
@@ -1,11 +1,12 @@
#include <cstdlib>
#include <string>
#include <zmq.h>
#include "common/hardware/hw.h"
#include <iostream>
#include "catch2/catch.hpp"
#include "common/swaglog.h"
#include "common/tests/native_test.h"
#include "common/util.h"
#include "common/version.h"
#include "common/hardware/hw.h"
#include "json11/json11.hpp"
#include "sunnypilot/common/version.h"
@@ -14,38 +15,12 @@ std::string daemon_name = "testy";
std::string dongle_id = "test_dongle_id";
int LINE_NO = 0;
void test_swaglog() {
setenv("MANAGER_DAEMON", "swaglog_test", 1);
setenv("DONGLE_ID", "test_dongle_id", 1);
setenv("CLEAN", "1", 1);
void *context = zmq_ctx_new();
CHECK(context != nullptr);
void *socket = zmq_socket(context, ZMQ_PULL);
CHECK(socket != nullptr);
int timeout = 5000;
CHECK(zmq_setsockopt(socket, ZMQ_RCVTIMEO, &timeout, sizeof(timeout)) == 0);
CHECK(zmq_bind(socket, Path::swaglog_ipc().c_str()) == 0);
LOGD("native-cpp-log");
char buffer[4096] = {};
const int size = zmq_recv(socket, buffer, sizeof(buffer), 0);
CHECK(size > 1);
CHECK(buffer[0] == CLOUDLOG_DEBUG);
std::string error;
const auto message = json11::Json::parse(std::string(buffer + 1, size - 1), error);
CHECK(error.empty());
CHECK(message["levelnum"].int_value() == CLOUDLOG_DEBUG);
CHECK(message["msg"].string_value() == "native-cpp-log");
CHECK(message["funcname"].string_value() == "test_swaglog");
CHECK(message["filename"].string_value().find("test_swaglog.cc") != std::string::npos);
CHECK(message["ctx"]["daemon"].string_value() == "swaglog_test");
CHECK(message["ctx"]["dongle_id"].string_value() == "test_dongle_id");
CHECK(message["ctx"]["dirty"].bool_value() == false);
CHECK(zmq_close(socket) == 0);
CHECK(zmq_ctx_destroy(context) == 0);
void log_thread(int thread_id, int msg_cnt) {
for (int i = 0; i < msg_cnt; ++i) {
LOGD("%d", thread_id);
LINE_NO = __LINE__ - 1;
usleep(1);
}
}
void recv_log(int thread_cnt, int thread_msg_cnt) {
@@ -64,39 +39,52 @@ void recv_log(int thread_cnt, int thread_msg_cnt) {
break;
}
CHECK(buf[0] == CLOUDLOG_DEBUG);
REQUIRE(buf[0] == CLOUDLOG_DEBUG);
std::string err;
auto msg = json11::Json::parse(buf + 1, err);
CHECK(!msg.is_null());
REQUIRE(!msg.is_null());
CHECK(msg["levelnum"].int_value() == CLOUDLOG_DEBUG);
CHECK(msg["filename"].string_value().find("test_swaglog.cc") != std::string::npos);
CHECK(msg["funcname"].string_value() == "log_thread");
CHECK(msg["lineno"].int_value() == LINE_NO);
REQUIRE(msg["levelnum"].int_value() == CLOUDLOG_DEBUG);
REQUIRE_THAT(msg["filename"].string_value(), Catch::Contains("test_swaglog.cc"));
REQUIRE(msg["funcname"].string_value() == "log_thread");
REQUIRE(msg["lineno"].int_value() == LINE_NO);
auto ctx = msg["ctx"];
CHECK(ctx["daemon"].string_value() == daemon_name);
CHECK(ctx["dongle_id"].string_value() == dongle_id);
CHECK(ctx["dirty"].bool_value() == true);
REQUIRE(ctx["daemon"].string_value() == daemon_name);
REQUIRE(ctx["dongle_id"].string_value() == dongle_id);
REQUIRE(ctx["dirty"].bool_value() == true);
CHECK(ctx["version"].string_value() == SUNNYPILOT_VERSION);
REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION);
std::string device = Hardware::get_name();
CHECK(ctx["device"].string_value() == device);
REQUIRE(ctx["device"].string_value() == device);
int thread_id = atoi(msg["msg"].string_value().c_str());
CHECK((thread_id >= 0 && thread_id < thread_cnt));
REQUIRE((thread_id >= 0 && thread_id < thread_cnt));
thread_msgs[thread_id]++;
total_count++;
}
for (int i = 0; i < thread_cnt; ++i) {
CHECK(thread_msgs[i] == thread_msg_cnt);
INFO("thread :" << i);
REQUIRE(thread_msgs[i] == thread_msg_cnt);
}
zmq_close(sock);
zmq_ctx_destroy(zctx);
}
int main() {
return run_native_test(test_swaglog);
TEST_CASE("swaglog") {
setenv("MANAGER_DAEMON", daemon_name.c_str(), 1);
setenv("DONGLE_ID", dongle_id.c_str(), 1);
setenv("dirty", "1", 1);
const int thread_cnt = 5;
const int thread_msg_cnt = 100;
std::vector<std::thread> log_threads;
for (int i = 0; i < thread_cnt; ++i) {
log_threads.push_back(std::thread(log_thread, i, thread_msg_cnt));
}
for (auto &t : log_threads) t.join();
recv_log(thread_cnt, thread_msg_cnt);
}
+147
View File
@@ -0,0 +1,147 @@
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <algorithm>
#include <climits>
#include <fstream>
#include <random>
#include <string>
#include "catch2/catch.hpp"
#include "common/util.h"
std::string random_bytes(int size) {
std::random_device rd;
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> rbe(rd());
std::string bytes(size + 1, '\0');
std::generate(bytes.begin(), bytes.end(), std::ref(rbe));
return bytes;
}
TEST_CASE("util::read_file") {
SECTION("read /proc/version") {
std::string ret = util::read_file("/proc/version");
REQUIRE(ret.find("Linux version") != std::string::npos);
}
SECTION("read from sysfs") {
std::string ret = util::read_file("/sys/power/wakeup_count");
REQUIRE(!ret.empty());
}
SECTION("read file") {
char filename[] = "/tmp/test_read_XXXXXX";
int fd = mkstemp(filename);
REQUIRE(util::read_file(filename).empty());
std::string content = random_bytes(64 * 1024);
REQUIRE(write(fd, content.c_str(), content.size()) == (ssize_t)content.size());
std::string ret = util::read_file(filename);
bool equal = (ret == content);
REQUIRE(equal);
close(fd);
}
SECTION("read directory") {
REQUIRE(util::read_file(".").empty());
}
SECTION("read non-existent file") {
std::string ret = util::read_file("does_not_exist");
REQUIRE(ret.empty());
}
SECTION("read non-permission") {
REQUIRE(util::read_file("/proc/kmsg").empty());
}
}
TEST_CASE("util::file_exists") {
char filename[] = "/tmp/test_file_exists_XXXXXX";
int fd = mkstemp(filename);
REQUIRE(fd != -1);
close(fd);
SECTION("existent file") {
REQUIRE(util::file_exists(filename));
REQUIRE(util::file_exists("/tmp"));
}
SECTION("nonexistent file") {
std::string fn = filename;
REQUIRE(!util::file_exists(fn + "/nonexistent"));
}
SECTION("file has no access permissions") {
std::string fn = "/proc/kmsg";
std::ifstream f(fn);
REQUIRE(f.good() == false);
REQUIRE(util::file_exists(fn));
}
::remove(filename);
}
TEST_CASE("util::read_files_in_dir") {
char tmp_path[] = "/tmp/test_XXXXXX";
const std::string test_path = mkdtemp(tmp_path);
const std::string files[] = {".test1", "'test2'", "test3"};
for (auto fn : files) {
std::ofstream{test_path + "/" + fn} << fn;
}
mkdir((test_path + "/dir").c_str(), 0777);
std::map<std::string, std::string> result = util::read_files_in_dir(test_path);
REQUIRE(result.find("dir") == result.end());
REQUIRE(result.size() == std::size(files));
for (auto& [k, v] : result) {
REQUIRE(k == v);
}
}
TEST_CASE("util::safe_fwrite") {
char filename[] = "/tmp/XXXXXX";
int fd = mkstemp(filename);
close(fd);
std::string dat = random_bytes(1024 * 1024);
FILE *f = util::safe_fopen(filename, "wb");
REQUIRE(f != nullptr);
size_t size = util::safe_fwrite(dat.data(), 1, dat.size(), f);
REQUIRE(size == dat.size());
int ret = util::safe_fflush(f);
REQUIRE(ret == 0);
ret = fclose(f);
REQUIRE(ret == 0);
bool equal = (dat == util::read_file(filename));
REQUIRE(equal);
}
TEST_CASE("util::create_directories") {
REQUIRE(system("rm /tmp/test_create_directories -rf") == 0);
std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f";
auto check_dir_permissions = [](const std::string &path, mode_t mode) -> bool {
struct stat st = {};
return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode;
};
SECTION("create_directories") {
REQUIRE(util::create_directories(dir, 0755));
REQUIRE(check_dir_permissions(dir, 0755));
}
SECTION("dir already exists") {
REQUIRE(util::create_directories(dir, 0755));
REQUIRE(util::create_directories(dir, 0755));
}
SECTION("a file exists with the same name") {
REQUIRE(util::create_directories(dir, 0755));
int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT, 0644);
REQUIRE(f != -1);
close(f);
REQUIRE(util::create_directories(dir + "/file", 0755) == false);
REQUIRE(util::create_directories(dir + "/file/1/2/3", 0755) == false);
}
SECTION("end with slashes") {
REQUIRE(util::create_directories(dir + "/", 0755));
}
SECTION("empty") {
REQUIRE(util::create_directories("", 0755) == false);
}
}
+4 -4
View File
@@ -37,12 +37,12 @@ class _NoneCameraConfig(CameraConfig):
@dataclass(frozen=True)
class DeviceCameraConfig:
narrow_road: CameraConfig
cabin: CameraConfig
wide_road: CameraConfig
fcam: CameraConfig
dcam: CameraConfig
ecam: CameraConfig
def all_cams(self):
for cam in ['narrow_road', 'cabin', 'wide_road']:
for cam in ['fcam', 'dcam', 'ecam']:
if not isinstance(getattr(self, cam), _NoneCameraConfig):
yield cam, getattr(self, cam)
@@ -1,6 +1,5 @@
import numpy as np
from openpilot.common.test import OpenpilotTestCase
import openpilot.common.transformations.coordinates as coord
geodetic_positions = np.array([[37.7610403, -122.4778699, 115],
@@ -42,7 +41,7 @@ ned_offsets_batch = np.array([[ 53.88103168, 43.83445935, -46.27488057],
[ 78.56272609, 18.53100158, -43.25290759]])
class TestNED(OpenpilotTestCase):
class TestNED:
def test_small_distances(self):
start_geodetic = np.array([33.8042184, -117.888593, 0.0])
local_coord = coord.LocalCoord.from_geodetic(start_geodetic)
@@ -1,6 +1,6 @@
import numpy as np
import pytest
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \
rot2quat, quat2rot, \
ned_euler_from_ecef
@@ -30,7 +30,7 @@ ned_eulers = np.array([[ 0.46806039, -0.4881889 , 1.65697808],
[ 2.50450101, 0.36304151, 0.33136365]])
class TestOrientation(OpenpilotTestCase):
class TestOrientation:
def test_quat_euler(self):
for i, eul in enumerate(eulers):
np.testing.assert_allclose(quats[i], euler2quat(eul), rtol=1e-7)
@@ -62,13 +62,13 @@ class TestOrientation(OpenpilotTestCase):
# np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7)
def test_inputs(self):
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
euler2quat([1, 2])
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
quat2rot([1, 2, 3])
with self.assertRaises(IndexError):
with pytest.raises(IndexError):
rot2quat(np.zeros((2, 2)))
def test_euler_rot_consistency(self):
+4 -8
View File
@@ -108,14 +108,10 @@ def atomic_write(path: str, mode: str = 'w', buffering: int = -1, encoding: str
if not overwrite and os.path.exists(path):
raise FileExistsError(f"File '{path}' already exists. To overwrite it, set 'overwrite' to True.")
tmp_file = tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=dir_name, delete=False)
try:
with tmp_file:
yield tmp_file
os.replace(tmp_file.name, path)
finally:
with contextlib.suppress(FileNotFoundError):
os.unlink(tmp_file.name)
with tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=dir_name, delete=False) as tmp_file:
yield tmp_file
tmp_file_name = tmp_file.name
os.replace(tmp_file_name, path)
def get_upload_stream(filepath: str, should_compress: bool) -> tuple[io.BufferedIOBase, int]:

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