Compare commits

..

55 Commits

Author SHA1 Message Date
firestar5683 22707891bd cilantro lime 2026-09-09 17:55:05 -05:00
AngusBell97 962d8b6719 Galaxy: show developer mode guidance
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:30:43 -05:00
AngusBell97 0f3bdb34b3 Galaxy: keep GM auto-hold visibility consistent
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:28:47 -05:00
AngusBell97 c2921c1a8f Galaxy: unify longitudinal control modes
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:26:07 -05:00
AngusBell97 a19beda327 Galaxy: add driving personality profiles
Add configurable acceleration, braking, following-distance, and advanced smoothness profiles to Galaxy with off-road writes and readback validation.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:07:41 -05:00
firestar5683 b7775991bf build 2026-09-09 15:33:21 -05:00
firestar5683 eedd73e522 iPhone FoldGate 2026-09-09 15:32:38 -05:00
Prabhaav Pillai 50e2c21dbd click for home! 2026-09-09 13:22:01 -04:00
Prabhaav Pillai 54c3fb13f3 galaxy banner clarity 2026-09-09 12:56:14 -04:00
Prabhaav Pillai 7f3bd61292 make the theme toggle consistient with back button 2026-09-09 02:44:16 -04:00
firestarsdog dec4a0884a Big Boom 2026-09-09 02:36:46 -04:00
firestarsdog 4edc8ab86a Revert "trying to make firefox less laggy"
This reverts commit b3a14cb48d.
2026-09-09 02:30:05 -04:00
Prabhaav Pillai b3a14cb48d trying to make firefox less laggy 2026-09-09 02:14:29 -04:00
firestarsdog f47322cbee are your fingies fixed 2026-09-09 02:09:10 -04:00
firestarsdog a08065f282 zik try dis 2026-09-09 01:45:06 -04:00
firestar5683 a33bec1ca4 uno mas lil dip 2026-09-08 21:15:21 -05:00
firestar5683 ca3d8a3816 Make external GPU CPU pinning conditional 2026-09-08 18:48:40 -05:00
firestar5683 2360ff9b0f build 2026-09-08 18:19:54 -05:00
firestar5683 0976fd804d The Final Countdown 2026-09-08 18:19:19 -05:00
firestar5683 2504441a4e build 2026-09-08 10:57:22 -05:00
firestar5683 0b5ccb31e1 The Rice Cake 2026-09-08 10:52:51 -05:00
firestar5683 b91ea3e1da Update manifest.json 2026-09-07 22:27:28 -05:00
firestar5683 1588f7041a App 2026-09-07 22:09:45 -05:00
firestar5683 bcf152e6f7 Sleppy time 2026-09-07 21:57:32 -05:00
firestar5683 249b03a3f5 ray 2026-09-07 11:47:50 -05:00
firestar5683 9ae473355a allow smol when beeg 2026-09-07 11:33:10 -05:00
whoisdomi 2ab6195d9b CSC rewrite
Credit: @whoisdomi
2026-09-07 11:32:03 -05:00
firestar5683 5df8b59964 Chestnut Diagnostics 2026-09-07 10:51:29 -05:00
firestar5683 c03d06b408 lfa 2026-09-07 09:39:54 -05:00
firestar5683 a695335f21 nos 2026-09-07 06:31:41 -05:00
firestar5683 9d1043ad01 build 2026-09-06 21:29:46 -05:00
firestar5683 a5d7db3b29 wowie zowie 2026-09-06 21:27:44 -05:00
firestar5683 bb284d0bc4 Fix offline GPU firmware and Connect streaming 2026-09-06 21:18:04 -05:00
firestarsdog 67bf3adfd4 LaCroixosse 2026-09-06 17:24:20 -04:00
firestar5683 7a83f8f430 fixes 2026-09-06 10:35:47 -05:00
firestar5683 4b507c960a Restore sunnypilot HKG attribution
Document reconstructed HKG lineage from sunnypilot's hkg-angle-steering-2025 branches and later opendbc history. Preserve applicable notices, credit upstream contributors, and mark the derived controller, CAN, platform, safety, and test boundaries.

Reference snapshots: sunnypilot/sunnypilot cfb38312db33779f4727c983d372474a56ccb5d8; sunnypilot/opendbc cc4b08625a98e94b318cab15e45e05dad58042bd; sunnypilot/opendbc f95f996f5917dcbbf2e32fe51b606a24cf836af6.
2026-09-06 09:58:12 -05:00
firestar5683 37908b698d BluePilot Rocks - restore Ford attribution
Document the substantial BluePilot bp-7.0 lineage behind StarPilot's
Ford support, preserve upstream license notices, credit the original
contributors, and add durable source references.
2026-09-06 08:16:52 -05:00
firestarsdog 759ff3107b GM/Bolt CAN GPS Accuracy 2026-09-06 04:19:00 -04:00
firestarsdog 422488562f SLC Alert QoL 2026-09-06 02:04:33 -04:00
Prabhaav Pillai 7f1f926c47 build - first from me 2026-09-06 01:58:30 -04:00
Prabhaav Pillai 9cb1b6b11d big dipper 2026-09-06 01:35:01 -04:00
firestarsdog 7d46313213 Sluglas, the Stripper Slug 2026-09-06 00:50:57 -04:00
firestarsdog edf76af796 Revert "Test fix - revert if nukes galaxy lol"
This reverts commit f51059956c.
2026-09-05 22:28:13 -04:00
firestarsdog f51059956c Test fix - revert if nukes galaxy lol 2026-09-05 19:18:02 -04:00
firestar5683 604a433ee4 optimize 2026-09-05 13:51:44 -05:00
firestar5683 87d007eb10 Patterson sand, llc 2026-09-05 13:13:53 -05:00
firestar5683 ab77a59497 In the naming is the catching 2026-09-05 11:50:30 -05:00
firestar5683 54a03a91b0 fix 2026-09-05 11:36:18 -05:00
firestar5683 cecc9bc8b6 build 2026-09-05 11:24:26 -05:00
firestar5683 cec1a0fb62 Guten Morgen 2026-09-05 11:21:21 -05:00
firestar5683 097d63caef and this is my lab 2026-09-04 23:01:30 -05:00
firestar5683 de9cb64165 Four Score & 7 2026-09-04 22:47:02 -05:00
firestar5683 53e5c5246d build 2026-09-04 21:58:10 -05:00
firestar5683 b5ab54ab6d this is my laboratory 2026-09-04 21:56:35 -05:00
Prabhaav Pillai f55ad9162d More Vue native windows. Reduce Duplicate code within API. 2026-09-04 15:52:40 -04:00
412 changed files with 34536 additions and 5713 deletions
+1
View File
@@ -81,6 +81,7 @@ selfdrive/modeld/models/*.pkl
# openpilot log files # openpilot log files
*.bz2 *.bz2
*.zst *.zst
!selfdrive/modeld/firmware/amdgpu/*.zst
build/ build/
+162
View File
@@ -0,0 +1,162 @@
# Credits and Code Provenance
StarPilot stands on work by comma.ai, FrogPilot, and many other open-source contributors. This
document records provenance that is not adequately represented by StarPilot's historical commit
authorship. It is not an assertion that an upstream contributor endorses, maintains, or is
responsible for StarPilot's adaptation.
## Ford support adapted from BluePilot
StarPilot commit [`3f6ccd104e826643887930b41ad3b4086b833d32`](https://github.com/firestar5683/StarPilot/commit/3f6ccd104e826643887930b41ad3b4086b833d32)
introduced a substantial adaptation of Ford work from BluePilot's `bp-7.0` branch. That commit's
message thanked the project, but it did not record a source revision or preserve the upstream
authors in the code. This file corrects that provenance gap without rewriting published history.
The exact checkout used for the original port was not recorded and therefore cannot now be proven.
At the time of the import,
[`3a838bba6d3d280592c2fd0496b3378977ae25f1`](https://github.com/BluePilotDev/bluepilot/commit/3a838bba6d3d280592c2fd0496b3378977ae25f1),
was the tip of the public `bp-7.0` line. Some imported platform lines instead trace to the
contemporaneous development line at
[`59e3f2f16a38ff0c16d173b0ccddded23aaa1cd8`](https://github.com/BluePilotDev/bluepilot/commit/59e3f2f16a38ff0c16d173b0ccddded23aaa1cd8).
Those histories were merged the next day as
[`e1d051d7ba270261b4455068bd68f1a58db15a4a`](https://github.com/BluePilotDev/bluepilot/commit/e1d051d7ba270261b4455068bd68f1a58db15a4a),
which is the complete `bp-7.0` snapshot used for this audit. This reconstruction is deliberately
recorded as a range rather than pretending that the missing original source SHA can be recovered.
### Upstream authors and work
- **Alan Polk (`alan-polk`)** is the principal upstream author of the Ford curvature controller,
angle-primary controller, manual-turn behavior, controller integration, and related panda safety
work. Important lineage commits include
[`db2bdff05`](https://github.com/BluePilotDev/bluepilot/commit/db2bdff05df103d71df62f45c2a3cb5211aba6e6),
[`d0aac605f`](https://github.com/BluePilotDev/bluepilot/commit/d0aac605f99d37e9da205e419f7989c1e9eaa386),
[`8f8d6d15f`](https://github.com/BluePilotDev/bluepilot/commit/8f8d6d15f0a590f42b78de964ffb0d0af7f5d63d), and
[`97867c1eb`](https://github.com/BluePilotDev/bluepilot/commit/97867c1eb57b7472f6fc3de62f0fef576e5a5497).
- **John Christman** contributed `bp-7.0` lateral integration, platform data, and the upstream
anti-stall work referenced by StarPilot's recovery logic, including
[`ec0ab181c`](https://github.com/BluePilotDev/bluepilot/commit/ec0ab181c344dccaae053e763bc6ce269551a2d8) and
[`9012f7666`](https://github.com/BluePilotDev/bluepilot/commit/9012f76666a5c90764fcaec40832b9a607488c27),
with additional vehicle data in
[`f0c6bf51f`](https://github.com/BluePilotDev/bluepilot/commit/f0c6bf51f19bb7b74226ead622cabef56e602125).
- **Jacob Neulight** contributed measured shadow-curvature and steering-pinion curvature/safety
work, including
[`699c17d9f`](https://github.com/BluePilotDev/bluepilot/commit/699c17d9fde62c690eed9d68eed7d731744d5137) and
[`26030f3cb`](https://github.com/BluePilotDev/bluepilot/commit/26030f3cb56a169ab0cf2b5a5ad15fa9af391b10).
- **Nathan Ingraham** contributed the separate high-speed damping adjustment in
[`1db52bc79`](https://github.com/BluePilotDev/bluepilot/commit/1db52bc79e607ddb00214254dd4d732615e27fe4) and
[`3610e3f18`](https://github.com/BluePilotDev/bluepilot/commit/3610e3f18a0ad37d793d7ada61dbc739ab1077a3).
- **Praeuner** contributed the damping-range update in
[`b600a8fdb`](https://github.com/BluePilotDev/bluepilot/commit/b600a8fdb985a2219fad4006ce9444b7071b52da).
- **tonesto7** contributed to the Ford integration and CAN-message history represented in the
imported branch, including
[`7f9212f7f`](https://github.com/BluePilotDev/bluepilot/commit/7f9212f7f8dc22c4a0a22443158554f4d3652a3c).
- **Haibin Wen and sunnypilot contributors** are named in file-level notices on upstream Ford
extension files that informed StarPilot's vehicle-state and lateral-limit integration. Their
published copyright and license notices are retained in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
- **Dirk Petersen, Alex Troxel, Kacer Aleks, and other BluePilot contributors** supplied Ford
fingerprints, VIN/platform data, tests, and integration changes that are represented in the
imported vehicle-support set. Examples include
[`3706b5645`](https://github.com/BluePilotDev/bluepilot/commit/3706b5645c270ad83cfecc3e92618924f0963166),
[`694bdbb7a`](https://github.com/BluePilotDev/bluepilot/commit/694bdbb7a9cc70af04d0118e891df96e47ded7c7), and
[`a02c06ef3`](https://github.com/BluePilotDev/bluepilot/commit/a02c06ef37acff518a511ba0a88a4dc65f506353).
This list identifies contributors whose work was found during the repository and blame audit. The
BluePilot repository and its Git history remain the authoritative record and may identify further
contributors.
### Local-to-upstream map
| StarPilot area | Upstream lineage | What StarPilot changed |
| --- | --- | --- |
| `starpilot/car/ford/lateral.py` | `human_turn.py`, `lateral_curv_ext.py`, and `values_ext.py` at the reference snapshot above; earlier StarPilot revisions also adapted `lateral_angle_ext.py` | Consolidated the runtime implementation on the extended-curvature strategy, integrated StarPilot Params, and added live-delay curvature lookahead and local tuning behavior. |
| `starpilot/car/ford/fordcan.py` | `fordcan_ext.py` and the extended-lateral protocol, especially `8f8d6d15f` | Reduced the extension to the curvature CAN constructors used by StarPilot and adapted it to the local controller interface. |
| `opendbc_repo/opendbc/car/ford/` | Ford controller/state/interface/radar/platform changes in the `bp-7.0` snapshot | Integrated the changes directly into StarPilot's opendbc layout instead of retaining sunnypilot mixins; subsequent fixes and behavior differ by file. |
| `opendbc_repo/opendbc/safety/modes/ford.h` and Ford safety tests | BluePilot panda enforcement for four-signal curvature and angle-primary control, especially `8f8d6d15f`, plus shadow-curvature work | Retained the extended-curvature checks, removed runtime path-angle selection, and continued adding local regression coverage. |
| Ford Params and settings surfaces | BluePilot's curvature tuning concepts | Renamed and implemented in StarPilot's native Params/Galaxy architecture; no BluePilot or sunnypilot UI classes were retained. |
The local code has materially diverged, but the first four rows remain derivative in design and in
parts of their implementation. Future ports should cite the exact upstream commit in the importing
commit and at the relevant source boundary; when history can be retained cleanly, use a merge,
subtree, or cherry-pick with origin metadata rather than a single squashed attribution.
## Hyundai, Kia, and Genesis support adapted from sunnypilot
Portions of StarPilot's HKG angle steering, CAN integration, panda safety enforcement, safety tests,
firmware fingerprints, and cruise-button management are adapted from sunnypilot. Some upstream HKG
authorship is retained in StarPilot's Git history, and StarPilot commit
[`e33305151`](https://github.com/firestar5683/StarPilot/commit/e33305151ba852ea3350aa9f7a12d1c8bd137c43)
identified its ICBM/CSLC work as a sunnypilot port. Other substantial imports were committed locally
without recording an exact source revision, so this section documents the reconstructed lineage.
The exact checkout used for each historical import cannot now be proven. The reference snapshots
used for this audit are:
- [`sunnypilot/sunnypilot` `hkg-angle-steering-2025`](https://github.com/sunnypilot/sunnypilot/tree/hkg-angle-steering-2025)
at [`cfb38312d`](https://github.com/sunnypilot/sunnypilot/commit/cfb38312db33779f4727c983d372474a56ccb5d8),
whose opendbc submodule points to the next snapshot.
- [`sunnypilot/opendbc` `hkg-angle-steering-2025`](https://github.com/sunnypilot/opendbc/tree/hkg-angle-steering-2025)
at [`cc4b08625`](https://github.com/sunnypilot/opendbc/commit/cc4b08625a98e94b318cab15e45e05dad58042bd).
- [`sunnypilot/opendbc` `master`](https://github.com/sunnypilot/opendbc)
at [`f95f996f5`](https://github.com/sunnypilot/opendbc/commit/f95f996f5917dcbbf2e32fe51b606a24cf836af6),
used to audit later HKG fingerprints and extension history.
### Upstream authors and work
- **Haibin (Jason) Wen** contributed the original Kia EV9 HDA2/LFA2 angle-steering port, Hyundai
angle integration and signals, non-SCC platform support, and Intelligent Cruise Button Management.
Important lineage commits include
[`abe78de2c`](https://github.com/sunnypilot/opendbc/commit/abe78de2c2f9d8774d343c35c181d27e9d944392),
[`333de6f1a`](https://github.com/sunnypilot/opendbc/commit/333de6f1a2097a27709a652d5862bad05d83ed1f),
[`559a37426`](https://github.com/sunnypilot/opendbc/commit/559a37426976171ceb56c541bec9362abd8b8bd2), and
[`862828ad6`](https://github.com/sunnypilot/opendbc/commit/862828ad6f870fb23dd8670fe2a18dc19313217b).
- **Shane Smiskol** contributed foundational angle-command limiting, driver-override behavior, and
EPS-fault avoidance, including
[`228a397a3`](https://github.com/sunnypilot/opendbc/commit/228a397a37618de1ecbaf06b70efe3e0e0a8eec6) and
[`42d84ff6c`](https://github.com/sunnypilot/opendbc/commit/42d84ff6ca3d48529b195395d652ad777ad397ca).
- **DevTekVE** contributed substantial angle-controller integration, tuning, platform support, panda
safety logic, and tests, including
[`c77c7ec2e`](https://github.com/sunnypilot/opendbc/commit/c77c7ec2e39959b65c42e2d70aa943fccd606361),
[`7ded99dba`](https://github.com/sunnypilot/opendbc/commit/7ded99dba145c56754e8bbe47217eb174ec03396),
[`3819ca7f0`](https://github.com/sunnypilot/opendbc/commit/3819ca7f0d2576771924bd60f47d3e8676b5d583), and
[`8d134e98f`](https://github.com/sunnypilot/opendbc/commit/8d134e98f3151a1aec354f93e81c3a4269788991).
- **Nicholas Evans, dany7915, janpoo6427, Tinkerpet, royjr, Taylor Hoshino, Intelli, Joshua Mack,
Mark McCallister, Discountchubbs, and other sunnypilot contributors** supplied vehicle ports,
fingerprints, firmware data, safety-limit updates, and related integration represented in the
adapted HKG support.
This list identifies contributors found during the repository, commit, and blame audit. The
sunnypilot repositories and their Git histories remain the authoritative record and may identify
additional contributors.
### Local-to-upstream map
| StarPilot area | Upstream lineage | What StarPilot changed |
| --- | --- | --- |
| `opendbc_repo/opendbc/car/hyundai/carcontroller.py` | Angle controller and driver-override work in `sunnypilot/opendbc` `hkg-angle-steering-2025` | Integrated the controller into StarPilot's opendbc layout and added substantial local longitudinal, smoothing, recovery, and platform behavior. |
| `opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py`, `interface.py`, and `values.py` | Angle commands, signal selection, safety flags, limits, and platform integration from the angle branch | Combined later upstream changes with StarPilot flags, Params, platform tuning, and local CAN/CAN-FD behavior. |
| `opendbc_repo/opendbc/safety/modes/hyundai_canfd.h` and its tests | Angle-command safety enforcement and regression tests from the angle branch | Extended and reorganized the safety mode and tests for StarPilot's current supported modes. |
| `opendbc_repo/opendbc/car/hyundai/fingerprints.py` and HKG platform data | sunnypilot HKG extension/fingerprint history on `master` plus angle-branch vehicle ports | Flattened extension data into the local opendbc tree and continued adding and updating platforms. |
| HKG cruise-button management and settings integration | sunnypilot ICBM/CSLC concepts and implementation history, including `862828ad6` and `2277e3d49` | Adapted the feature to StarPilot/FrogPilot controls and settings; later revisions changed or removed portions of the original integration. |
The current implementation is not a wholesale copy of either reference snapshot and has diverged
substantially. Its HKG angle-control and supporting safety architecture nevertheless remain
derivative in design and in identifiable portions of the implementation. The applicable upstream
notices are preserved in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
## Policy for future third-party ports
Before publishing a third-party port:
1. Read every repository-level and file-level license that may cover the source. Stop and ask the
rightsholder when the terms or their scope conflict.
2. Record the repository URL, exact commit SHA, source paths/symbols, authors found in the relevant
history, and the local destination in the importing commit and this file.
3. Preserve required copyright and license notices verbatim. Reorganization, translation, and
AI-assisted rewriting do not remove source provenance.
4. Retain authorship/history with a merge, subtree, or `cherry-pick -x` when practical. For a true
adaptation, keep the local adapter as commit author and use an `Adapted-from:` trailer and source
comments; do not add `Co-authored-by:` for a person without their agreement.
5. Describe material local deviations and make clear that upstream contributors do not support or
endorse the downstream version.
See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for licensing notices.
+1
View File
@@ -1,4 +1,5 @@
Copyright (c) 2018, Comma.ai, Inc. Copyright (c) 2018, Comma.ai, Inc.
Copyright (c) 2026, firestar5683 and StarPilot contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+19
View File
@@ -21,6 +21,20 @@ but [has expanded to offer Quality-Of-Life improvements for all](#features)!
StarPilot is built off of [FrogPilot](https://github.com/FrogAi/FrogPilot) StarPilot is built off of [FrogPilot](https://github.com/FrogAi/FrogPilot)
and supports the major features FrogPilot offers. and supports the major features FrogPilot offers.
Ford-specific lateral-control and vehicle-support work includes substantial adaptations from
[BluePilot](https://github.com/BluePilotDev/bluepilot/tree/bp-7.0), principally developed by
[Alan Polk](https://github.com/alan-polk) with additional BluePilot contributors. StarPilot's
implementation has since diverged, but that does not erase its lineage. See [CREDITS.md](CREDITS.md)
for the code-level provenance and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for applicable
upstream notices and terms. BluePilot and its contributors do not maintain or endorse StarPilot;
please direct support requests for this adaptation to the StarPilot project.
Hyundai, Kia, and Genesis angle steering and related vehicle support include substantial adaptations
from [sunnypilot](https://github.com/sunnypilot/sunnypilot/tree/hkg-angle-steering-2025) and its
[opendbc angle-steering branch](https://github.com/sunnypilot/opendbc/tree/hkg-angle-steering-2025).
StarPilot's implementation has diverged significantly; the upstream contributors do not maintain
this adaptation. Detailed code lineage is recorded in [CREDITS.md](CREDITS.md#hyundai-kia-and-genesis-support-adapted-from-sunnypilot).
StarPilot has a vibrant, welcoming community [discord](https://firestar.link/discord). StarPilot has a vibrant, welcoming community [discord](https://firestar.link/discord).
Stop by to chat or ask questions! Stop by to chat or ask questions!
@@ -77,4 +91,9 @@ Uses your comma's sysroot/toolchain
* Custom long maneuver tests, specifically designed for regen-only vehicles * Custom long maneuver tests, specifically designed for regen-only vehicles
## Third-Party Notices ## Third-Party Notices
* Portions of this software include modified versions of the Material Design Icons provided by Google under the Apache License 2.0. A copy of the license is included in the `LICENSE-MDI` file. * Portions of this software include modified versions of the Material Design Icons provided by Google under the Apache License 2.0. A copy of the license is included in the `LICENSE-MDI` file.
* Ford support includes software adapted from BluePilot's `bp-7.0` branch. The source repository contains both a standard MIT notice and a separate custom SUNNYPILOT LLC notice; StarPilot preserves both in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
* Hyundai, Kia, and Genesis support includes software adapted directly from sunnypilot's HKG angle-steering branch and sunnypilot/opendbc. StarPilot preserves the applicable notices in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
> This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use.
+125
View File
@@ -0,0 +1,125 @@
# Third-Party Notices
This file supplements StarPilot's root `LICENSE`. StarPilot-original contributions are offered
under that MIT license. Third-party material remains subject to its original terms; inclusion here
does not relicense it or imply endorsement by an upstream project or contributor.
## BluePilot `bp-7.0` Ford support
Portions of StarPilot's Ford lateral control, CAN integration, panda safety logic, vehicle data,
and related tests are adapted from the public BluePilot repository:
- Source: <https://github.com/BluePilotDev/bluepilot/tree/bp-7.0>
- Audited `bp-7.0` snapshot: [`e1d051d7ba270261b4455068bd68f1a58db15a4a`](https://github.com/BluePilotDev/bluepilot/commit/e1d051d7ba270261b4455068bd68f1a58db15a4a)
- Historical reconstruction: [CREDITS.md](CREDITS.md#ford-support-adapted-from-bluepilot)
- Provenance and contributors: [CREDITS.md](CREDITS.md)
## sunnypilot Hyundai, Kia, and Genesis support
Portions of StarPilot's HKG angle steering, CAN integration, panda safety enforcement and tests,
firmware fingerprints, platform data, and cruise-button management are adapted directly from the
public sunnypilot repositories:
- Source: <https://github.com/sunnypilot/sunnypilot/tree/hkg-angle-steering-2025>
- Audited superproject snapshot: [`cfb38312db33779f4727c983d372474a56ccb5d8`](https://github.com/sunnypilot/sunnypilot/commit/cfb38312db33779f4727c983d372474a56ccb5d8)
- Source: <https://github.com/sunnypilot/opendbc/tree/hkg-angle-steering-2025>
- Audited angle-branch snapshot: [`cc4b08625a98e94b318cab15e45e05dad58042bd`](https://github.com/sunnypilot/opendbc/commit/cc4b08625a98e94b318cab15e45e05dad58042bd)
- Later HKG history was audited against `sunnypilot/opendbc` `master` at
[`f95f996f5917dcbbf2e32fe51b606a24cf836af6`](https://github.com/sunnypilot/opendbc/commit/f95f996f5917dcbbf2e32fe51b606a24cf836af6).
- Historical reconstruction and contributors: [CREDITS.md](CREDITS.md#hyundai-kia-and-genesis-support-adapted-from-sunnypilot)
## Applicable upstream license notices
The upstream repositories and branches identified above publish both `LICENSE` and `LICENSE.md`.
Their READMEs point to `LICENSE` for openpilot licensing, while some extension files point to
`LICENSE.md`. Because the scope of those two notices is not unambiguous, StarPilot preserves both
and treats the more restrictive notice conservatively for upstream-derived material. This is a
record of the published notices, not a legal conclusion about their scope.
### Upstream `LICENSE` notice (MIT)
Copyright (c) 2018, Comma.ai, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
### Upstream `opendbc` `LICENSE` notice (MIT)
Copyright (c) 2020, Comma.ai, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
### Upstream extension file notice
Several upstream Ford and HKG extension files carry this file-level notice:
> Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
>
> This file is part of sunnypilot and is licensed under the MIT License.
> See the LICENSE.md file in the root directory for more details.
StarPilot's Ford and HKG integrations were informed by extension files carrying this notice. The
reference to “MIT License” conflicts with the nonstandard restrictions in the referenced upstream
`LICENSE.md`; both texts are retained here rather than silently choosing between them.
### Upstream `LICENSE.md` notice (published as “Custom MIT License”)
The following notice is reproduced verbatim from the reference branch:
> # Custom MIT License
>
> Copyright (c) 2024, Haibin Wen, SUNNYPILOT LLC
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to view and modify the Software, subject to the following conditions:
>
> 1. **Permission Required**: Permission Required for Commercial, For-Profit, or Closed Source Use: Use of the Software, in whole or in part, for any commercial purposes, for-profit projects, or in closed source projects requires explicit written permission from the original author(s).
>
> 2. **Redistribution**: Any redistribution of the Software, modified or unmodified, must retain this license notice and the following acknowledgment:
> "This software is licensed under a custom license requiring permission for use."
>
> 3. **Visibility**: Any project that uses the Software must visibly mention the following acknowledgment:
> "This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use."
>
> 4. **No Warranty**: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
>
> Contact sunnypilot Support <support@sunnypilot.ai> for permission requests.
>
> ---
>
> Haibin Wen, SUNNYPILOT LLC
Required upstream acknowledgments:
> This software is licensed under a custom license requiring permission for use.
>
> This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use.
For commercial, for-profit, or closed-source use, consult the upstream notice and obtain any
permission it requires. The upstream repository's simultaneous publication of two differently
scoped notices should be clarified with the relevant copyright holders before relying on one to
the exclusion of the other.
Binary file not shown.
+1
View File
@@ -774,6 +774,7 @@ struct ChestnutState {
pcieLtssm @7 :UInt8; pcieLtssm @7 :UInt8;
supplyVoltage @8 :UInt16; # mV supplyVoltage @8 :UInt16; # mV
supplyCurrent @9 :Int16; # mA supplyCurrent @9 :Int16; # mA
supplyFault @10 :Bool;
} }
struct RadarState @0x9a185389d6fdd05f { struct RadarState @0x9a185389d6fdd05f {
Binary file not shown.
+26 -8
View File
@@ -18,6 +18,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"BootCount", {PERSISTENT, INT}}, {"BootCount", {PERSISTENT, INT}},
{"BluetoothAudioAddress", {PERSISTENT, STRING}}, {"BluetoothAudioAddress", {PERSISTENT, STRING}},
{"BluetoothAudioTestActive", {CLEAR_ON_MANAGER_START | DONT_LOG, BOOL}}, {"BluetoothAudioTestActive", {CLEAR_ON_MANAGER_START | DONT_LOG, BOOL}},
{"BluetoothDisconnectControllersOffroad", {PERSISTENT, BOOL, "0"}},
{"BluetoothEnabled", {PERSISTENT, BOOL, "0"}}, {"BluetoothEnabled", {PERSISTENT, BOOL, "0"}},
{"CalibrationParams", {PERSISTENT, BYTES}}, {"CalibrationParams", {PERSISTENT, BYTES}},
{"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}}, {"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}},
@@ -109,10 +110,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LocationFilterInitialState", {PERSISTENT, BYTES}}, {"LocationFilterInitialState", {PERSISTENT, BYTES}},
{"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"LongitudinalPersonality", {PERSISTENT, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}}, {"LongitudinalPersonality", {PERSISTENT, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}},
{"LongitudinalPersonalityProfiles", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
{"NetworkMetered", {PERSISTENT, BOOL}}, {"NetworkMetered", {PERSISTENT, BOOL}},
{"ObdMultiplexingChanged", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"ObdMultiplexingChanged", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
{"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
{"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutNotDetected", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutOverheated", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ChestnutPcieUnavailable", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ChestnutUncompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutUpdateFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutUsbSlow", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}}, {"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
@@ -310,6 +318,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}}, {"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}}, {"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}}, {"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}}, {"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}}, {"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}}, {"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}},
@@ -330,6 +339,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DownloadAllModels", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"DownloadAllModels", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"DriverCamera", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}}, {"DriverCamera", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"ActiveBigModel", {PERSISTENT, STRING}},
{"ActiveBigModelName", {PERSISTENT, STRING}},
{"ActiveBigModelVersion", {PERSISTENT, STRING}},
{"ActiveSmallModel", {PERSISTENT, STRING}},
{"ActiveSmallModelName", {PERSISTENT, STRING}},
{"ActiveSmallModelVersion", {PERSISTENT, STRING}},
{"Model", {PERSISTENT, STRING, "rdf43", "rdf43", 1}}, {"Model", {PERSISTENT, STRING, "rdf43", "rdf43", 1}},
{"ModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}}, {"ModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}},
{"DrivingModel", {PERSISTENT, STRING, "rdf43", "rdf43", 1}}, {"DrivingModel", {PERSISTENT, STRING, "rdf43", "rdf43", 1}},
@@ -372,17 +387,13 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"FordLKASButtonControlMigrated", {PERSISTENT, BOOL, "0", "0"}}, {"FordLKASButtonControlMigrated", {PERSISTENT, BOOL, "0", "0"}},
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}}, {"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
{"FordAngleBlend", {PERSISTENT, FLOAT, "0.5", "0.5", 2}}, // These Ford curvature tuning concepts descend from BluePilot bp-7.0. StarPilot's key names and
{"FordAngleHighSpeedDamping", {PERSISTENT, FLOAT, "1.0", "1.0", 2}}, // settings integration are local; see /CREDITS.md and /THIRD_PARTY_NOTICES.md for provenance.
{"FordAngleHighSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordAngleLaneChangeFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordAngleLowSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordCurvatureBlendHigh", {PERSISTENT, FLOAT, "0.4", "0.4", 2}}, {"FordCurvatureBlendHigh", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
{"FordCurvatureBlendLow", {PERSISTENT, FLOAT, "0.4", "0.4", 2}}, {"FordCurvatureBlendLow", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
{"FordCurvatureLaneChangeFactor", {PERSISTENT, FLOAT, "0.85", "0.85", 2}}, {"FordCurvatureLaneChangeFactor", {PERSISTENT, FLOAT, "0.85", "0.85", 2}},
{"FordHandsFreeCluster", {PERSISTENT, BOOL, "0", "0", 2}}, {"FordHandsFreeCluster", {PERSISTENT, BOOL, "0", "0", 2}},
{"FordHumanTurnDetection", {PERSISTENT, BOOL, "1", "1", 2}}, {"FordHumanTurnDetection", {PERSISTENT, BOOL, "1", "1", 2}},
{"FordLateralMode", {PERSISTENT, INT, "1", "1", 2}},
{"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}}, {"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
{"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}}, {"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
{"FLMSubmittedTune", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}}, {"FLMSubmittedTune", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
@@ -454,7 +465,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}}, {"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}}, {"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}},
{"LeadIndicator", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"LeadIndicator", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"LeadInfo", {PERSISTENT, BOOL, "1", "0", 3}}, {"LeadInfo", {PERSISTENT, BOOL, "0", "0", 3}},
{"LeadInfoMode", {PERSISTENT, INT, "2", "2", 3}},
{"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}}, {"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}}, {"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}}, {"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
@@ -510,6 +522,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}}, {"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, {"ModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
{"ModelDrivesAndScores", {PERSISTENT, JSON, "{}", "{}"}}, {"ModelDrivesAndScores", {PERSISTENT, JSON, "{}", "{}"}},
{"ModelLabConfig", {PERSISTENT, JSON, "{}", "{}"}},
{"ModelLabModelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
{"ModelLabRuntime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}},
{"ModelReleasedDates", {PERSISTENT, STRING, "", "", 1}}, {"ModelReleasedDates", {PERSISTENT, STRING, "", "", 1}},
{"ModelRandomizer", {PERSISTENT, BOOL, "0", "0", 2}}, {"ModelRandomizer", {PERSISTENT, BOOL, "0", "0", 2}},
{"LatSmoothSeconds", {PERSISTENT, FLOAT, "0.1", "0.1", 3}}, {"LatSmoothSeconds", {PERSISTENT, FLOAT, "0.1", "0.1", 3}},
@@ -544,6 +559,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"FavoriteTrafficModeCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"FavoriteTrafficModeCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelButtonBookmarkCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"WheelButtonBookmarkCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlAOLCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"WheelControlAOLCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlDisengageCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlEngageCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlForceCoastCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"WheelControlForceCoastCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlPulseGlideCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"WheelControlPulseGlideCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}}, {"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}},
@@ -594,6 +611,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}}, {"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
{"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}}, {"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
@@ -705,7 +723,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}}, {"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruAvhOnAtStartup", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"SubaruRedneckCruise", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}}, {"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, {"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
Binary file not shown.
+26 -1
View File
@@ -5,7 +5,7 @@ import threading
import time import time
import uuid import uuid
from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
class TestParams: class TestParams:
def setup_method(self): def setup_method(self):
@@ -128,6 +128,31 @@ class TestParams:
assert self.params.get("LiveParameters") is None assert self.params.get("LiveParameters") is None
assert self.params.get("LiveParameters", return_default=True) is None assert self.params.get("LiveParameters", return_default=True) is None
def test_longitudinal_personality_profiles_json_round_trip(self):
key = "LongitudinalPersonalityProfiles"
value = {
"schemaVersion": 1,
"enabled": False,
"axes": {
"acceleration": {
"speed": {"unit": "mph", "values": [0.0, 11.184681, 22.369363, 33.554044, 44.738726, 55.923407, 89.477452]},
"value": {"unit": "m/s^2", "meaning": "maximum_requested_acceleration"},
},
"braking": {
"speed": {"unit": "mph", "values": [0.0, 11.184681, 22.369363, 33.554044, 44.738726, 55.923407, 89.477452]},
"value": {"unit": "m/s^2", "meaning": "cruise_slc_deceleration_magnitude"},
},
"following": {"speed": {"unit": "mph", "values": [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]}, "value": {"unit": "s", "meaning": "base_time_headway"}},
},
"profiles": {},
}
self.params.remove(key)
assert self.params.get_type(key) == ParamKeyType.JSON
assert self.params.get(key) is None
self.params.put(key, value)
assert self.params.get(key) == value
def test_params_get_type(self): def test_params_get_type(self):
# json # json
self.params.put("ApiCache_DriveStats", {"a": 0}) self.params.put("ApiCache_DriveStats", {"a": 0})
+1 -1
View File
@@ -21,7 +21,7 @@ fi
export QCOM_PRIORITY=12 export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="19.6.19" export AGNOS_VERSION="19.6.20"
fi fi
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
+1 -1
View File
@@ -194,7 +194,7 @@ def get_checksum_state(dbc_name: str) -> ChecksumState | None:
return ChecksumState(4, 2, 3, 5, False, SignalType.HONDA_CHECKSUM, honda_checksum) return ChecksumState(4, 2, 3, 5, False, SignalType.HONDA_CHECKSUM, honda_checksum)
elif dbc_name.startswith(("toyota_", "lexus_")): elif dbc_name.startswith(("toyota_", "lexus_")):
return ChecksumState(8, -1, 7, -1, False, SignalType.TOYOTA_CHECKSUM, toyota_checksum) return ChecksumState(8, -1, 7, -1, False, SignalType.TOYOTA_CHECKSUM, toyota_checksum)
elif dbc_name.startswith("hyundai_canfd_generated"): elif dbc_name.startswith(("hyundai_canfd_generated", "hyundai_radar_210_21f_generated")):
return ChecksumState(16, -1, 0, -1, True, SignalType.HKG_CAN_FD_CHECKSUM, hkg_can_fd_checksum) return ChecksumState(16, -1, 0, -1, True, SignalType.HKG_CAN_FD_CHECKSUM, hkg_can_fd_checksum)
elif dbc_name.startswith(("vw_mqb", "vw_mqbevo", "vw_meb")): elif dbc_name.startswith(("vw_mqb", "vw_mqbevo", "vw_meb")):
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_mqb_meb_checksum) return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_mqb_meb_checksum)
+8 -2
View File
@@ -10,6 +10,7 @@ from opendbc.car.carlog import carlog
from opendbc.car.structs import CarParams, CarParamsT from opendbc.car.structs import CarParams, CarParamsT
from opendbc.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from opendbc.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
from opendbc.car.fw_versions import ObdCallback, get_fw_versions_ordered, get_present_ecus, match_fw_to_car from opendbc.car.fw_versions import ObdCallback, get_fw_versions_ordered, get_present_ecus, match_fw_to_car
from opendbc.car.hyundai.values import kia_ray_ev_vin
from opendbc.car.mock.values import CAR as MOCK from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.toyota.values import ToyotaSafetyFlags from opendbc.car.toyota.values import ToyotaSafetyFlags
from opendbc.car.values import BRANDS from opendbc.car.values import BRANDS
@@ -246,8 +247,13 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
set_obd_multiplexing(True) set_obd_multiplexing(True)
# VIN query only reliably works through OBDII # VIN query only reliably works through OBDII
vin_rx_addr, vin_rx_bus, vin = get_vin(can_recv, can_send, (0, 1)) vin_rx_addr, vin_rx_bus, vin = get_vin(can_recv, can_send, (0, 1))
ecu_rx_addrs = get_present_ecus(can_recv, can_send, set_obd_multiplexing, num_pandas=num_pandas) skip_fw_buses = {1} if kia_ray_ev_vin(vin) else set()
car_fw = get_fw_versions_ordered(can_recv, can_send, set_obd_multiplexing, vin, ecu_rx_addrs, num_pandas=num_pandas) if skip_fw_buses:
carlog.warning("Kia Ray EV: skipping CAN1 firmware queries")
ecu_rx_addrs = get_present_ecus(can_recv, can_send, set_obd_multiplexing,
num_pandas=num_pandas, skip_buses=skip_fw_buses)
car_fw = get_fw_versions_ordered(can_recv, can_send, set_obd_multiplexing, vin, ecu_rx_addrs,
num_pandas=num_pandas, skip_buses=skip_fw_buses)
cached = False cached = False
exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin) exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin)
+11 -72
View File
@@ -1,13 +1,15 @@
import math import math
import numpy as np import numpy as np
from opendbc.can import CANPacker from opendbc.can import CANPacker
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, apply_hysteresis, structs from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, structs
from opendbc.car.lateral import ISO_LATERAL_ACCEL, apply_std_steer_angle_limits from opendbc.car.lateral import ISO_LATERAL_ACCEL, apply_std_steer_angle_limits
from opendbc.car.ford import fordcan from opendbc.car.ford import fordcan
from opendbc.car.ford.values import CarControllerParams, FordFlags, CAR from opendbc.car.ford.values import CarControllerParams, FordFlags
from opendbc.car.interfaces import CarControllerBase, V_CRUISE_MAX from opendbc.car.interfaces import CarControllerBase, V_CRUISE_MAX
# This Ford extension boundary substantially adapts BluePilot bp-7.0 work. See the root CREDITS.md
# (including Alan Polk's d0aac605f and db2bdff05) and THIRD_PARTY_NOTICES.md.
from openpilot.starpilot.car.ford import fordcan as starpilot_fordcan from openpilot.starpilot.car.ford import fordcan as starpilot_fordcan
from openpilot.starpilot.car.ford.lateral import FordLateralController, FordLateralMode, FordLateralResult from openpilot.starpilot.car.ford.lateral import FordLateralController, FordLateralResult
LongCtrlState = structs.CarControl.Actuators.LongControlState LongCtrlState = structs.CarControl.Actuators.LongControlState
VisualAlert = structs.CarControl.HUDControl.VisualAlert VisualAlert = structs.CarControl.HUDControl.VisualAlert
@@ -43,22 +45,6 @@ def apply_ford_angle(desired_angle_deg: float, current_angle_deg: float) -> floa
return float(np.clip(relative_angle, -5.8, 5.8)) return float(np.clip(relative_angle, -5.8, 5.8))
def anti_overshoot(apply_curvature, apply_curvature_last, v_ego):
diff = 0.1
tau = 5 # 5s smooths over the overshoot
dt = DT_CTRL * CarControllerParams.STEER_STEP
alpha = 1 - np.exp(-dt / tau)
lataccel = apply_curvature * (v_ego ** 2)
last_lataccel = apply_curvature_last * (v_ego ** 2)
last_lataccel = apply_hysteresis(lataccel, last_lataccel, diff)
last_lataccel = alpha * lataccel + (1 - alpha) * last_lataccel
output_curvature = last_lataccel / (max(v_ego, 1) ** 2)
return float(np.interp(v_ego, [5, 10], [apply_curvature, output_curvature]))
def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_curvature, v_ego_raw, steering_angle, lat_active, CP): def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_curvature, v_ego_raw, steering_angle, lat_active, CP):
# No blending at low speed due to lack of torque wind-up and inaccurate current curvature # No blending at low speed due to lack of torque wind-up and inaccurate current curvature
if v_ego_raw > 9: if v_ego_raw > 9:
@@ -93,7 +79,6 @@ class CarController(CarControllerBase):
self.apply_curvature_last = 0 self.apply_curvature_last = 0
self.apply_angle_last = 0 self.apply_angle_last = 0
self.anti_overshoot_curvature_last = 0
self.accel = 0.0 self.accel = 0.0
self.gas = 0.0 self.gas = 0.0
self.brake_request = False self.brake_request = False
@@ -103,8 +88,7 @@ class CarController(CarControllerBase):
self.lead_distance_bars_last = None self.lead_distance_bars_last = None
self.distance_bar_frame = 0 self.distance_bar_frame = 0
self.ford_lateral = None if CP.flags & FordFlags.LKA_STEERING else FordLateralController(CP) self.ford_lateral = None if CP.flags & FordFlags.LKA_STEERING else FordLateralController(CP)
self.ford_shadow_curvature = 0.0 self.ford_extended_lateral_announced = False
self.ford_lateral_announced_mode = FordLateralMode.native
self.stock_cruise_button = FordStockCruiseButton() self.stock_cruise_button = FordStockCruiseButton()
def update(self, CC, CS, now_nanos, starpilot_toggles): def update(self, CC, CS, now_nanos, starpilot_toggles):
@@ -171,70 +155,26 @@ class CarController(CarControllerBase):
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN, active=lka_active, apply_angle=self.apply_angle_last, can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN, active=lka_active, apply_angle=self.apply_angle_last,
direction=direction, ramp_type=ramp_type, curvature=-self.apply_curvature_last)) direction=direction, ramp_type=ramp_type, curvature=-self.apply_curvature_last))
else: else:
lateral_mode = self.ford_lateral.mode
lateral_mode_ready = lateral_mode == self.ford_lateral_announced_mode
# Keep the original Ford path available without changing its command behavior.
if lateral_mode == FordLateralMode.native:
if (self.frame % CarControllerParams.STEER_STEP) == 0: if (self.frame % CarControllerParams.STEER_STEP) == 0:
if not lateral_mode_ready: lateral = self.ford_lateral.update(CC, CS, actuators) \
self.apply_curvature_last = 0.0 if self.ford_extended_lateral_announced else FordLateralResult()
apply_curvature = 0.0
elif self.CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_F_150_MK14):
self.anti_overshoot_curvature_last = anti_overshoot(
actuators.curvature, self.anti_overshoot_curvature_last, CS.out.vEgoRaw)
apply_curvature = self.anti_overshoot_curvature_last
else:
apply_curvature = actuators.curvature
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
self.apply_curvature_last = apply_ford_curvature_limits(
apply_curvature, self.apply_curvature_last, current_curvature,
CS.out.vEgoRaw, 0., CC.latActive and lateral_mode_ready, self.CP)
if self.CP.flags & FordFlags.CANFD:
mode = 1 if CC.latActive and lateral_mode_ready else 0
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
can_sends.append(fordcan.create_lat_ctl2_msg(
self.packer, self.CAN, mode, 0., 0., -self.apply_curvature_last, 0., counter))
else:
can_sends.append(fordcan.create_lat_ctl_msg(
self.packer, self.CAN, CC.latActive and lateral_mode_ready, 0., 0., -self.apply_curvature_last, 0.))
elif (self.frame % CarControllerParams.STEER_STEP) == 0:
if not lateral_mode_ready:
lateral = FordLateralResult(shadow_curvature=self.ford_lateral._current_curvature(CS))
elif lateral_mode == FordLateralMode.angle:
lateral = self.ford_lateral.update_angle(CC, CS, actuators)
else:
lateral = self.ford_lateral.update_curvature(CC, CS, actuators)
self.apply_curvature_last = lateral.curvature self.apply_curvature_last = lateral.curvature
self.ford_shadow_curvature = lateral.shadow_curvature
if self.CP.flags & FordFlags.CANFD: if self.CP.flags & FordFlags.CANFD:
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10 counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
can_sends.append(starpilot_fordcan.create_lat_ctl2_msg( can_sends.append(starpilot_fordcan.create_lat_ctl2_msg(
self.packer, self.CAN, 1 if lateral.active else 0, self.packer, self.CAN, 1 if lateral.active else 0,
lateral.ramp_type, lateral.precision_type, lateral.ramp_type, lateral.precision_type,
-lateral.path_offset, -lateral.path_angle,
-lateral.curvature, -lateral.curvature_rate, counter)) -lateral.curvature, -lateral.curvature_rate, counter))
else: else:
can_sends.append(starpilot_fordcan.create_lat_ctl_msg( can_sends.append(starpilot_fordcan.create_lat_ctl_msg(
self.packer, self.CAN, lateral.active, self.packer, self.CAN, lateral.active,
lateral.ramp_type, lateral.precision_type, lateral.ramp_type, lateral.precision_type,
-lateral.path_offset, -lateral.path_angle,
-lateral.curvature, -lateral.curvature_rate)) -lateral.curvature, -lateral.curvature_rate))
if (self.frame % CarControllerParams.LKA_STEP) == 0: if (self.frame % CarControllerParams.LKA_STEP) == 0:
if lateral_mode == FordLateralMode.native: can_sends.append(starpilot_fordcan.create_lka_msg(self.packer, self.CAN))
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN)) self.ford_extended_lateral_announced = True
else:
angle_mode = lateral_mode == FordLateralMode.angle
shadow_curvature = -self.ford_lateral._current_curvature(CS)
if angle_mode:
shadow_curvature = -self.ford_shadow_curvature
can_sends.append(starpilot_fordcan.create_lka_msg(
self.packer, self.CAN, angle_mode=angle_mode, shadow_curvature=shadow_curvature))
self.ford_lateral_announced_mode = lateral_mode
### longitudinal control ### ### longitudinal control ###
# send acc msg at 50Hz # send acc msg at 50Hz
@@ -292,8 +232,7 @@ class CarController(CarControllerBase):
show_distance_bars = self.frame - self.distance_bar_frame < 400 show_distance_bars = self.frame - self.distance_bar_frame < 400
hands_free_cluster = bool( hands_free_cluster = bool(
self.ford_lateral is not None self.ford_lateral is not None
and self.ford_lateral.mode != FordLateralMode.native and self.ford_extended_lateral_announced
and self.ford_lateral.mode == self.ford_lateral_announced_mode
and self.ford_lateral.hands_free_cluster_enabled) and self.ford_lateral.hands_free_cluster_enabled)
can_sends.append(fordcan.create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive, can_sends.append(fordcan.create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive,
fcw_alert, CS.out.cruiseState.standstill, show_distance_bars, fcw_alert, CS.out.cruiseState.standstill, show_distance_bars,
@@ -1,3 +1,6 @@
# Ford-specific additions first imported in StarPilot 3f6ccd104e substantially adapt BluePilot
# bp-7.0 vehicle-state work, including a sunnypilot extension with the Haibin Wen/contributors
# notice retained in THIRD_PARTY_NOTICES.md. See the repository root CREDITS.md for provenance.
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs from opendbc.car import Bus, create_button_events, structs
@@ -1,4 +1,6 @@
""" AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE.""" """ AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
# Some Ford firmware entries first imported in StarPilot 3f6ccd104e came from BluePilot bp-7.0
# contributors. The exact authors and source revisions are recorded in the root CREDITS.md.
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car.ford.values import CAR from opendbc.car.ford.values import CAR
@@ -1,3 +1,5 @@
# Ford-specific additions first imported in StarPilot 3f6ccd104e substantially adapt BluePilot
# bp-7.0 interface work. See the repository root CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from opendbc.car import Bus, get_safety_config, structs from opendbc.car import Bus, get_safety_config, structs
from opendbc.car.carlog import carlog from opendbc.car.carlog import carlog
@@ -1,3 +1,5 @@
# Ford-specific additions first imported in StarPilot 3f6ccd104e substantially adapt BluePilot
# bp-7.0 radar work. See the repository root CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from collections import deque from collections import deque
from typing import cast from typing import cast
+2
View File
@@ -1,3 +1,5 @@
# Ford platform data and limits first imported in StarPilot 3f6ccd104e substantially adapt
# BluePilot bp-7.0 contributor work. See the repository root CREDITS.md and THIRD_PARTY_NOTICES.md.
import copy import copy
import re import re
from dataclasses import dataclass, field, replace from dataclasses import dataclass, field, replace
+12 -6
View File
@@ -170,7 +170,9 @@ def match_fw_to_car(fw_versions: list[CarParams.CarFw], vin: str, allow_exact: b
return True, set() return True, set()
def get_present_ecus(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, num_pandas: int = 1) -> set[EcuAddrBusType]: def get_present_ecus(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback,
num_pandas: int = 1, skip_buses: set[int] | None = None) -> set[EcuAddrBusType]:
skip_buses = skip_buses or set()
# queries are split by OBD multiplexing mode # queries are split by OBD multiplexing mode
queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []} queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []}
parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []} parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []}
@@ -178,7 +180,7 @@ def get_present_ecus(can_recv: CanRecvCallable, can_send: CanSendCallable, set_o
for brand, config, r in REQUESTS: for brand, config, r in REQUESTS:
# Skip query if no panda available # Skip query if no panda available
if r.bus > num_pandas * 4 - 1: if r.bus > num_pandas * 4 - 1 or r.bus in skip_buses:
continue continue
for ecu_type, addr, sub_addr in config.get_all_ecus(VERSIONS[brand]): for ecu_type, addr, sub_addr in config.get_all_ecus(VERSIONS[brand]):
@@ -235,7 +237,8 @@ def get_brand_ecu_matches(ecu_rx_addrs: set[EcuAddrBusType]) -> dict[str, list[b
def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, vin: str, def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, vin: str,
ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1, progress: bool = False) -> list[CarParams.CarFw]: ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1,
progress: bool = False, skip_buses: set[int] | None = None) -> list[CarParams.CarFw]:
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" """Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
all_car_fw = [] all_car_fw = []
@@ -248,7 +251,8 @@ def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable
if True not in brand_matches[brand]: if True not in brand_matches[brand]:
continue continue
car_fw = get_fw_versions(can_recv, can_send, set_obd_multiplexing, query_brand=brand, timeout=timeout, num_pandas=num_pandas, progress=progress) car_fw = get_fw_versions(can_recv, can_send, set_obd_multiplexing, query_brand=brand, timeout=timeout,
num_pandas=num_pandas, progress=progress, skip_buses=skip_buses)
all_car_fw.extend(car_fw) all_car_fw.extend(car_fw)
# If there is a match using this brand's FW alone, finish querying early # If there is a match using this brand's FW alone, finish querying early
@@ -260,7 +264,9 @@ def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable
def get_fw_versions(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, query_brand: str = None, def get_fw_versions(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, query_brand: str = None,
extra: OfflineFwVersions = None, timeout: float = 0.1, num_pandas: int = 1, progress: bool = False) -> list[CarParams.CarFw]: extra: OfflineFwVersions = None, timeout: float = 0.1, num_pandas: int = 1, progress: bool = False,
skip_buses: set[int] | None = None) -> list[CarParams.CarFw]:
skip_buses = skip_buses or set()
versions = VERSIONS.copy() versions = VERSIONS.copy()
if query_brand is not None: if query_brand is not None:
@@ -298,7 +304,7 @@ def get_fw_versions(can_recv: CanRecvCallable, can_send: CanSendCallable, set_ob
for addr_chunk in chunks(addr_group): for addr_chunk in chunks(addr_group):
for brand, config, r in requests: for brand, config, r in requests:
# Skip query if no panda available # Skip query if no panda available
if r.bus > num_pandas * 4 - 1: if r.bus > num_pandas * 4 - 1 or r.bus in skip_buses:
continue continue
# Toggle OBD multiplexing for each request # Toggle OBD multiplexing for each request
+2 -2
View File
@@ -6,7 +6,7 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits
from opendbc.car.gm import gmcan from opendbc.car.gm import gmcan
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.values import ( from opendbc.car.gm.values import (
ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, CC_REGEN_PADDLE_CAR, DBC, EV_CAR, SDGM_CAR, AccState, CanBus, CarControllerParams, ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, CC_REGEN_PADDLE_CAR, DBC, EV_CAR, GM_AUTO_HOLD_CARS, SDGM_CAR, AccState, CanBus, CarControllerParams,
CruiseButtons, GMFlags, GMSafetyFlags, CruiseButtons, GMFlags, GMSafetyFlags,
) )
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
@@ -309,7 +309,7 @@ def supports_volt_auto_hold(CP, auto_hold_enabled: bool):
auto_hold_enabled and auto_hold_enabled and
getattr(CP, "openpilotLongitudinalControl", False) and getattr(CP, "openpilotLongitudinalControl", False) and
stock_hold_safety_ready and stock_hold_safety_ready and
CP.carFingerprint in AUTO_HOLD_VOLT_CARS CP.carFingerprint in GM_AUTO_HOLD_CARS
) )
+59 -5
View File
@@ -1,4 +1,5 @@
import copy import copy
import math
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs from opendbc.car import Bus, create_button_events, structs
@@ -31,6 +32,7 @@ STANDSTILL_THRESHOLD = 10 * 0.0311
VOLT_EBCM_BRAKE_PRESSED_THRESHOLD = 6 / 0xd0 VOLT_EBCM_BRAKE_PRESSED_THRESHOLD = 6 / 0xd0
AUTO_HOLD_MIN_DRIVE_TIME_S = 3.0 AUTO_HOLD_MIN_DRIVE_TIME_S = 3.0
AUTO_HOLD_REGEN_RELEASE_COOLDOWN_S = 1.0 AUTO_HOLD_REGEN_RELEASE_COOLDOWN_S = 1.0
ACC_STARTUP_FAULT_GRACE_PERIOD_S = 5.0
BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise, BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel} CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel}
@@ -66,6 +68,24 @@ def update_auto_hold_drive_timers(in_drive_for_hold: bool, moving_for_hold: bool
return auto_hold_drive_time, one_pedal_drive_time return auto_hold_drive_time, one_pedal_drive_time
def update_startup_acc_fault_suppression(car_fingerprint: str, system_power_mode: int,
previous_system_power_mode: int, timer: float,
acc_state: int, friction_brake_unavailable: bool) -> tuple[float, bool]:
if car_fingerprint != CAR.BUICK_LACROSSE:
return 0.0, False
if system_power_mode == 2 and previous_system_power_mode != 2:
timer = ACC_STARTUP_FAULT_GRACE_PERIOD_S
elif system_power_mode != 2:
timer = 0.0
if timer <= 0.0 or acc_state != AccState.FAULTED:
return 0.0, False
timer = max(timer - DT_CTRL, 0.0)
return timer, timer > 0.0 and not friction_brake_unavailable
class CarState(CarStateBase): class CarState(CarStateBase):
def __init__(self, CP, FPCP): def __init__(self, CP, FPCP):
super().__init__(CP, FPCP) super().__init__(CP, FPCP)
@@ -104,13 +124,18 @@ class CarState(CarStateBase):
self.lkas_previously_enabled = 0 self.lkas_previously_enabled = 0
self.lkas_enabled = 0 self.lkas_enabled = 0
self.pcm_acc_status = AccState.OFF self.pcm_acc_status = AccState.OFF
self.system_power_mode = 0
self.startup_acc_fault_suppression_timer = 0.0
self.stock_fcw_alert = 0 self.stock_fcw_alert = 0
self.car_gps_config = get_car_gps_config(CP) self.car_gps_config = get_car_gps_config(CP)
self.car_gps_supported = self.car_gps_config is not None self.car_gps_supported = self.car_gps_config is not None
self.car_gps = None self.car_gps = None
self._car_gps_timestamp_nanos = 0 self._car_gps_timestamp_nanos = 0
self._prev_gps_lat = None
self._prev_gps_lon = None
self._last_gps_bearing = None
def _update_car_gps(self, cp) -> None: def _update_car_gps(self, cp, v_ego: float = 0.0) -> None:
if self.car_gps_config is None: if self.car_gps_config is None:
return return
@@ -125,6 +150,25 @@ class CarState(CarStateBase):
gps = self.car_gps_config.decoder(*(cp.vl[name] for name in self.car_gps_config.messages)) gps = self.car_gps_config.decoder(*(cp.vl[name] for name in self.car_gps_config.messages))
if gps is not None: if gps is not None:
gps["timestamp_nanos"] = timestamp_nanos gps["timestamp_nanos"] = timestamp_nanos
if gps["hasFix"]:
lat, lon = gps["latitude"], gps["longitude"]
if self._prev_gps_lat is not None and (lat, lon) != (self._prev_gps_lat, self._prev_gps_lon):
d_lat = (lat - self._prev_gps_lat) * 111139.0
d_lon = (lon - self._prev_gps_lon) * 111139.0 * math.cos(math.radians(lat))
if math.hypot(d_lat, d_lon) > 1.5 and v_ego > 1.0 and not self.moving_backward:
self._last_gps_bearing = math.degrees(math.atan2(d_lon, d_lat)) % 360.0
self._prev_gps_lat, self._prev_gps_lon = lat, lon
bearing = self._last_gps_bearing if self._last_gps_bearing is not None else 0.0
gps["speed"] = max(0.0, v_ego)
gps["bearingDeg"] = bearing
gps["bearingAccuracyDeg"] = 5.0 if (v_ego > 1.0 and self._last_gps_bearing is not None) else 180.0
heading_rad = math.radians(bearing)
gps["vNED"] = [v_ego * math.cos(heading_rad), v_ego * math.sin(heading_rad), 0.0]
else:
self._prev_gps_lat = self._prev_gps_lon = None
self.car_gps = gps self.car_gps = gps
self._car_gps_timestamp_nanos = timestamp_nanos self._car_gps_timestamp_nanos = timestamp_nanos
@@ -145,8 +189,6 @@ class CarState(CarStateBase):
cam_cp = can_parsers[Bus.cam] cam_cp = can_parsers[Bus.cam]
loopback_cp = can_parsers[Bus.loopback] loopback_cp = can_parsers[Bus.loopback]
self._update_car_gps(pt_cp)
ret = structs.CarState() ret = structs.CarState()
volt_like = { volt_like = {
@@ -217,6 +259,8 @@ class CarState(CarStateBase):
ret.standstill = abs(pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"]) <= STANDSTILL_THRESHOLD and \ ret.standstill = abs(pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"]) <= STANDSTILL_THRESHOLD and \
abs(pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"]) <= STANDSTILL_THRESHOLD abs(pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"]) <= STANDSTILL_THRESHOLD
self._update_car_gps(pt_cp, ret.vEgo)
if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1: if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1:
ret.gearShifter = self.parse_gear_shifter("T") ret.gearShifter = self.parse_gear_shifter("T")
else: else:
@@ -327,8 +371,18 @@ class CarState(CarStateBase):
ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0 ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1 ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or acc_state = pt_cp.vl["AcceleratorPedal2"]["CruiseState"]
pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1) friction_brake_unavailable = pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1
self.startup_acc_fault_suppression_timer, suppress_startup_acc_fault = update_startup_acc_fault_suppression(
self.CP.carFingerprint,
int(pt_cp.vl["BCMGeneralPlatformStatus"]["SystemPowerMode"]),
self.system_power_mode,
self.startup_acc_fault_suppression_timer,
acc_state,
friction_brake_unavailable,
)
self.system_power_mode = int(pt_cp.vl["BCMGeneralPlatformStatus"]["SystemPowerMode"])
ret.accFaulted = (acc_state == AccState.FAULTED and not suppress_startup_acc_fault) or friction_brake_unavailable
ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
+38
View File
@@ -28,6 +28,11 @@ BOLT_CC_BUTTON_CARS = {
BOLT_CC_TARGET_DEADBAND_MPH = 0.75 BOLT_CC_TARGET_DEADBAND_MPH = 0.75
BOLT_CC_REVERSE_CONFIRM_S = 0.6 BOLT_CC_REVERSE_CONFIRM_S = 0.6
BOLT_CC_DIRECTION_MEMORY_S = 1.5 BOLT_CC_DIRECTION_MEMORY_S = 1.5
VOLT_CC_CARS = {
CAR.CHEVROLET_VOLT_CC,
}
VOLT_CC_TARGET_DEADBAND_MPH = 5.0
VOLT_CC_ACCEL_DEADBAND_MS2 = 0.15
def malibu_phase_map_for_button(button): def malibu_phase_map_for_button(button):
@@ -336,6 +341,36 @@ def stabilize_bolt_cc_button(controller, CP, requested_button):
return requested_button return requested_button
def _create_volt_cc_spam_command(CS, actuators, ms_convert):
accel = float(actuators.accel)
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
ego_speed = CS.out.vEgo * ms_convert
v_cruise_kph = float(getattr(CS.out, "vCruise", 0.0))
if 0.0 < v_cruise_kph < 255.0:
is_metric = ms_convert == CV.MS_TO_KPH
target_setpoint = v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH
target_deadband = VOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0)
if abs(target_setpoint - speed_setpoint) <= target_deadband:
return CruiseButtons.INIT, float("inf")
if abs(accel) <= VOLT_CC_ACCEL_DEADBAND_MS2:
return CruiseButtons.INIT, float("inf")
if accel < 0.0:
if speed_setpoint > ego_speed + 3.0:
rate = 0.2
else:
rate = max(1.0 / (-accel * ms_convert), 0.2)
return CruiseButtons.DECEL_SET, rate
if speed_setpoint < ego_speed - 3.0:
rate = 0.2
else:
rate = max(1.0 / (accel * ms_convert), 0.2)
return CruiseButtons.RES_ACCEL, rate
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles): def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles):
accel = actuators.accel accel = actuators.accel
v_ego = CS.out.vEgo v_ego = CS.out.vEgo
@@ -350,6 +385,9 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
target_deadband = BOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0) if bolt_cc else 0.0 target_deadband = BOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0) if bolt_cc else 0.0
comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint
if CS.CP.carFingerprint in VOLT_CC_CARS:
cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert)
else:
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25: if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
cruise_btn = CruiseButtons.CANCEL cruise_btn = CruiseButtons.CANCEL
elif comparison_setpoint < speed_setpoint - target_deadband and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1: elif comparison_setpoint < speed_setpoint - target_deadband and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1:
+10 -8
View File
@@ -15,6 +15,7 @@ from opendbc.car.gm.values import (
CC_ONLY_CAR, CC_ONLY_CAR,
CC_REGEN_PADDLE_CAR, CC_REGEN_PADDLE_CAR,
EV_CAR, EV_CAR,
GM_AUTO_HOLD_CARS,
SDGM_CAR, SDGM_CAR,
CarControllerParams, CarControllerParams,
CanBus, CanBus,
@@ -408,7 +409,7 @@ class CarInterface(CarInterfaceBase):
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
ret.radarTimeStepDEPRECATED = 0.0667 # GM radar runs at 15Hz instead of the standard 20Hz ret.radarTimeStepDEPRECATED = 0.15 if candidate == CAR.BUICK_LACROSSE else 0.0667
ret.longitudinalActuatorDelay = 0.5 # large delay to initially start braking ret.longitudinalActuatorDelay = 0.5 # large delay to initially start braking
if candidate in ( if candidate in (
@@ -440,7 +441,7 @@ class CarInterface(CarInterfaceBase):
elif candidate in (CAR.BUICK_LACROSSE, CAR.BUICK_LACROSSE_ASCM, CAR.BUICK_LACROSSE_ASCM_19US): elif candidate in (CAR.BUICK_LACROSSE, CAR.BUICK_LACROSSE_ASCM, CAR.BUICK_LACROSSE_ASCM_19US):
CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning) CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning)
if candidate == CAR.BUICK_LACROSSE_ASCM_19US: if candidate == CAR.BUICK_LACROSSE_ASCM_19US:
ret.minSteerSpeed = 27 * CV.MPH_TO_MS ret.minSteerSpeed = 28 * CV.MPH_TO_MS
elif candidate == CAR.CADILLAC_ESCALADE: elif candidate == CAR.CADILLAC_ESCALADE:
ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.minEnableSpeed = -1. # engage speed is decided by pcm
@@ -710,18 +711,19 @@ class CarInterface(CarInterfaceBase):
if remote_start_boots_comma: if remote_start_boots_comma:
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value
volt_stock_friction_brake_safety = ( gm_stock_friction_brake_safety = (
ret.openpilotLongitudinalControl and ret.openpilotLongitudinalControl and
(gm_auto_hold or volt_one_pedal_mode) and (
candidate in { (gm_auto_hold and candidate in GM_AUTO_HOLD_CARS) or
(volt_one_pedal_mode and candidate in {
CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019, CAR.CHEVROLET_VOLT_2019,
CAR.CHEVROLET_VOLT_ASCM, CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_CAMERA,
} })
) )
if volt_stock_friction_brake_safety: )
# Reuse the paddle-scheduler safety bit as a Volt stock friction-brake if gm_stock_friction_brake_safety:
# marker on non-pedal paths. Auto hold and one-pedal can run while OP # marker on non-pedal paths. Auto hold and one-pedal can run while OP
# longitudinal is configured but not currently active, so the bit must # longitudinal is configured but not currently active, so the bit must
# be present regardless of the current long-control mode. Do not expose # be present regardless of the current long-control mode. Do not expose
@@ -431,6 +431,15 @@ def test_volt_auto_hold_requires_toggle_supported_non_cc_only_volt_and_stock_saf
), ),
True, True,
) )
assert supports_volt_auto_hold(
SimpleNamespace(
carFingerprint=CAR.BUICK_LACROSSE,
openpilotLongitudinalControl=True,
networkLocation=CarParams.NetworkLocation.gateway,
safetyConfigs=stock_safety,
),
True,
)
assert not supports_volt_auto_hold( assert not supports_volt_auto_hold(
SimpleNamespace( SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT, carFingerprint=CAR.CHEVROLET_VOLT,
+334 -1
View File
@@ -8,7 +8,12 @@ from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, DT_CTRL, structs from opendbc.car import Bus, DT_CTRL, structs
from opendbc.car.car_helpers import interfaces from opendbc.car.car_helpers import interfaces
from opendbc.car.gm import gmcan from opendbc.car.gm import gmcan
from opendbc.car.gm.carstate import CarState as GMCarState, get_hard_cruise_buttons, update_auto_hold_drive_timers from opendbc.car.gm.carstate import (
CarState as GMCarState,
get_hard_cruise_buttons,
update_auto_hold_drive_timers,
update_startup_acc_fault_suppression,
)
from opendbc.car.gm.carcontroller import ( from opendbc.car.gm.carcontroller import (
VisualAlert, VisualAlert,
get_acc_dashboard_always_one, get_acc_dashboard_always_one,
@@ -88,6 +93,107 @@ class TestBoltGps:
assert gps["latitude"] == 0.0 assert gps["latitude"] == 0.0
assert gps["longitude"] == 0.0 assert gps["longitude"] == 0.0
def test_bolt_gps_accuracy_metrics(self):
gps = parse_chevrolet_bolt_can_gps({"GPSLatitude": 145292743.0, "GPSLongitude": -267520892.0})
assert gps is not None
assert gps["horizontalAccuracy"] == 6.0
assert gps["verticalAccuracy"] == 10.0
assert gps["speedAccuracy"] == 0.5
def test_bolt_gps_heading_and_speed_derivation(self):
cp = SimpleNamespace(
brand="gm",
carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021,
flags=0,
networkLocation=structs.CarParams.NetworkLocation.gateway,
transmissionType=structs.CarParams.TransmissionType.direct,
enableBsm=False,
enableGasInterceptorDEPRECATED=False,
pcmCruise=False,
)
fpcp = custom.StarPilotCarParams.new_message()
cs = GMCarState(cp, fpcp)
# First position (stationary)
mock_cp = SimpleNamespace(
ts_nanos={"TCICOnStarGPSPosition": {"GPSLatitude": 1_000_000}},
vl={"TCICOnStarGPSPosition": {"GPSLatitude": 145292743.0, "GPSLongitude": -267520892.0}},
)
cs._update_car_gps(mock_cp, v_ego=0.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["speed"] == 0.0
assert gps["bearingDeg"] == 0.0
assert gps["bearingAccuracyDeg"] == 180.0
# Move East at 15 m/s
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 2_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = -267520892.0 + 1000.0 # Eastward shift
cs._update_car_gps(mock_cp, v_ego=15.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["speed"] == 15.0
assert gps["bearingDeg"] == pytest.approx(90.0, abs=1.0)
assert gps["bearingAccuracyDeg"] == 5.0
assert gps["vNED"][1] > 0.0 # East velocity positive
# Stop moving (v_ego=0.0): heading should be retained, not reset to 0
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 3_000_000_000
cs._update_car_gps(mock_cp, v_ego=0.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["speed"] == 0.0
assert gps["bearingDeg"] == pytest.approx(90.0, abs=1.0)
# Reversing: coordinate changes while moving backward should not flip heading
cs.moving_backward = True
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 4_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = -267520892.0 - 1000.0 # Westward shift
cs._update_car_gps(mock_cp, v_ego=3.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["bearingDeg"] == pytest.approx(90.0, abs=1.0)
# Drive True North: verify bearing is 0.0 deg and accuracy is 5.0 deg (not degraded to 180.0)
cs.moving_backward = False
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 5_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 145292743.0 + 1000.0 # Northward shift
cs._update_car_gps(mock_cp, v_ego=12.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["bearingDeg"] == pytest.approx(0.0, abs=1.0)
assert gps["bearingAccuracyDeg"] == 5.0
# Tunnel / fix loss: invalid coordinates cause hasFix=False and clear previous coordinates
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 6_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 0.0
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = 0.0
cs._update_car_gps(mock_cp, v_ego=20.0)
gps = cs.get_car_gps()
assert gps is not None
assert not gps["hasFix"]
assert cs._prev_gps_lat is None and cs._prev_gps_lon is None
# Tunnel exit: GPS fix re-acquired 5 km away heading South
# The first sample after fix loss sets initial coordinates without calculating a phantom jump vector
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 7_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 145292743.0 - 50000.0
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = -267520892.0
cs._update_car_gps(mock_cp, v_ego=20.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["hasFix"]
assert gps["bearingDeg"] == pytest.approx(0.0, abs=1.0)
assert cs._prev_gps_lat is not None
# Second sample: moving Southward -> bearing smoothly updates to 180 deg
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 8_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 145292743.0 - 51000.0
cs._update_car_gps(mock_cp, v_ego=20.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["bearingDeg"] == pytest.approx(180.0, abs=1.0)
@parameterized.expand(CHEVROLET_BOLT_GPS_CARS) @parameterized.expand(CHEVROLET_BOLT_GPS_CARS)
def test_gps_message_is_added_to_powertrain_parser(self, car_model): def test_gps_message_is_added_to_powertrain_parser(self, car_model):
cp = SimpleNamespace( cp = SimpleNamespace(
@@ -103,7 +209,78 @@ class TestBoltGps:
assert all(message in parsers[Bus.pt].vl for message in CHEVROLET_BOLT_GPS_MESSAGES) assert all(message in parsers[Bus.pt].vl for message in CHEVROLET_BOLT_GPS_MESSAGES)
class TestGMCarState:
def test_lacrosse_startup_acc_fault_is_suppressed(self):
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 0, 0.0, 3, False,
)
assert suppressed
assert timer == pytest.approx(5.0 - DT_CTRL)
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 2, timer, 0, False,
)
assert timer == 0.0
assert not suppressed
def test_lacrosse_persistent_acc_fault_is_reported_after_startup(self):
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 0, 0.0, 3, False,
)
for _ in range(int(5.0 / DT_CTRL)):
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 2, timer, 3, False,
)
assert timer == 0.0
assert not suppressed
def test_lacrosse_brake_unavailable_fault_is_never_suppressed(self):
_, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 0, 0.0, 3, True,
)
assert not suppressed
def test_startup_acc_fault_suppression_is_scoped_to_lacrosse(self):
_, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_REGAL, 2, 0, 0.0, 3, False,
)
assert not suppressed
class TestGMInterface: class TestGMInterface:
def test_lacrosse_obd_and_ascm_integrations_remain_separate(self):
obd_params = interfaces[CAR.BUICK_LACROSSE].get_params(
CAR.BUICK_LACROSSE,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
ascm_params = interfaces[CAR.BUICK_LACROSSE_ASCM].get_params(
CAR.BUICK_LACROSSE_ASCM,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert obd_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert obd_params.openpilotLongitudinalControl
assert obd_params.radarTimeStepDEPRECATED == pytest.approx(0.15)
assert ascm_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
assert not ascm_params.openpilotLongitudinalControl
assert ascm_params.radarTimeStepDEPRECATED == pytest.approx(0.0667)
@parameterized.expand([ @parameterized.expand([
CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2017,
CAR.CHEVROLET_BOLT_CC_2018_2021, CAR.CHEVROLET_BOLT_CC_2018_2021,
@@ -190,6 +367,14 @@ class TestGMInterface:
assert car_params.minSteerSpeed == pytest.approx(7 * CV.MPH_TO_MS) assert car_params.minSteerSpeed == pytest.approx(7 * CV.MPH_TO_MS)
def test_lacrosse_2019_ascm_min_steer_speed_is_28_mph(self):
car_model = CAR.BUICK_LACROSSE_ASCM_19US
CarInterface = interfaces[car_model]
car_params = CarInterface.get_params(car_model, _empty_fingerprint(), [], alpha_long=False, is_release=False, docs=False,
starpilot_toggles=_test_starpilot_toggles())
assert car_params.minSteerSpeed == pytest.approx(28 * CV.MPH_TO_MS)
@parameterized.expand([ @parameterized.expand([
("interceptor", True), ("interceptor", True),
("ascm_int", False), ("ascm_int", False),
@@ -385,6 +570,43 @@ class TestGMInterface:
assert car_params.openpilotLongitudinalControl assert car_params.openpilotLongitudinalControl
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
def test_buick_lacrosse_auto_hold_sets_stock_hold_safety_bit_with_op_long_enabled(self):
params = Params()
try:
params.put_bool("GMAutoHold", True)
car_params = interfaces[CAR.BUICK_LACROSSE].get_params(
CAR.BUICK_LACROSSE,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
finally:
params.remove("GMAutoHold")
assert car_params.openpilotLongitudinalControl
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
def test_buick_lacrosse_auto_hold_is_off_when_toggle_is_disabled(self):
params = Params()
try:
params.put_bool("GMAutoHold", False)
car_params = interfaces[CAR.BUICK_LACROSSE].get_params(
CAR.BUICK_LACROSSE,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
finally:
params.remove("GMAutoHold")
assert not car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
def test_volt_auto_hold_does_not_set_stock_hold_safety_bit_with_op_long_disabled(self): def test_volt_auto_hold_does_not_set_stock_hold_safety_bit_with_op_long_disabled(self):
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM] CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
fingerprint = _empty_fingerprint() fingerprint = _empty_fingerprint()
@@ -657,6 +879,117 @@ class TestGMCarController:
assert [msg[2] for msg in msgs] == [0, 2] assert [msg[2] for msg in msgs] == [0, 2]
def test_volt_cc_redneck_holds_setpoint_without_planner_acceleration(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=60.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=60.0 * CV.KPH_TO_MS),
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.0), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 60
def test_volt_cc_redneck_rate_limits_setpoint_changes_by_planner_acceleration(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.5 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=60.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=60.0 * CV.KPH_TO_MS),
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert msgs == []
controller.frame = int(0.7 / DT_CTRL)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
def test_volt_cc_redneck_holds_when_stock_setpoint_is_within_target_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 99
def test_volt_cc_redneck_catches_up_when_target_exceeds_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.5 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=90.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=90.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert msgs == []
controller.frame = int(0.7 / DT_CTRL)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 91
def test_volt_cc_no_camera_redneck_spam_stays_on_powertrain_bus(self): def test_volt_cc_no_camera_redneck_spam_stays_on_powertrain_bus(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt]) packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.3 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0) controller = SimpleNamespace(frame=int(0.3 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
+8
View File
@@ -533,6 +533,14 @@ EV_CAR = {
CAR.CHEVROLET_MALIBU_HYBRID_CC, CAR.CHEVROLET_MALIBU_HYBRID_CC,
} }
GM_AUTO_HOLD_CARS = {
CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019,
CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA,
CAR.BUICK_LACROSSE,
}
# We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) # We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness)
CAMERA_ACC_CAR = { CAMERA_ACC_CAR = {
CAR.CHEVROLET_BOLT_ACC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023,
+3 -3
View File
@@ -113,11 +113,11 @@ def parse_chevrolet_bolt_can_gps(position: Mapping[str, float]) -> CarGpsSample
"altitude": 0.0, "altitude": 0.0,
"speed": 0.0, "speed": 0.0,
"bearingDeg": 0.0, "bearingDeg": 0.0,
"horizontalAccuracy": 100.0, "horizontalAccuracy": 6.0,
"unixTimestampMillis": int(datetime.now(UTC).timestamp() * 1000), "unixTimestampMillis": int(datetime.now(UTC).timestamp() * 1000),
"verticalAccuracy": 100.0, "verticalAccuracy": 10.0,
"bearingAccuracyDeg": 180.0, "bearingAccuracyDeg": 180.0,
"speedAccuracy": 100.0, "speedAccuracy": 0.5,
"hasFix": coordinates_valid, "hasFix": coordinates_valid,
"satelliteCount": 0, "satelliteCount": 0,
"vNED": [0.0, 0.0, 0.0], "vNED": [0.0, 0.0, 0.0],
@@ -1,5 +1,7 @@
from dataclasses import dataclass from dataclasses import dataclass
# Provenance: portions of HKG angle control are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from opendbc.can import CANPacker from opendbc.can import CANPacker
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs
@@ -8,8 +10,8 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_an
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai import hyundaicanfd, hyundaican from opendbc.car.hyundai import hyundaicanfd, hyundaican
from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \ from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \ CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
from opendbc.car.vehicle_model import VehicleModel from opendbc.car.vehicle_model import VehicleModel
@@ -24,6 +26,9 @@ LongCtrlState = structs.CarControl.Actuators.LongControlState
MAX_ANGLE = 85 MAX_ANGLE = 85
MAX_ANGLE_FRAMES = 89 MAX_ANGLE_FRAMES = 89
MAX_ANGLE_CONSECUTIVE_FRAMES = 2 MAX_ANGLE_CONSECUTIVE_FRAMES = 2
CANCEL_BUTTON_DELAY_FRAMES = 10
CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000 CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000
CANFD_CAMERA_LEAD_STALE_NS = 300_000_000 CANFD_CAMERA_LEAD_STALE_NS = 300_000_000
CANFD_LEAD_MIN_DISTANCE = 0.1 CANFD_LEAD_MIN_DISTANCE = 0.1
@@ -454,6 +459,7 @@ class CarController(CarControllerBase):
self.apply_angle_last = 0.0 self.apply_angle_last = 0.0
self.car_fingerprint = CP.carFingerprint self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0 self.last_button_frame = 0
self.cancel_counter = 0
self.redneck_button_frame = 0 self.redneck_button_frame = 0
self.ecu_disable_failed = False self.ecu_disable_failed = False
self._ecu_disable_checked = False self._ecu_disable_checked = False
@@ -471,6 +477,11 @@ class CarController(CarControllerBase):
self._dash_lat_disengage_init = False self._dash_lat_disengage_init = False
self._dash_prev_lat_active = False self._dash_prev_lat_active = False
self._ray_lkas11_active = False self._ray_lkas11_active = False
self._ray_lfa_8byte = CP.carFingerprint == CAR.KIA_RAY_EV and bool(
getattr(CP, "safetyConfigs", None) and
CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS
)
self._ray_lfa_packer = CANPacker("hyundai_kia_ray_lfa") if self._ray_lfa_8byte else None
def _update_dash_icon_state(self, CC): def _update_dash_icon_state(self, CC):
if CC.latActive: if CC.latActive:
@@ -717,6 +728,8 @@ class CarController(CarControllerBase):
if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS:
can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True)) can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True))
self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0
# *** CAN/CAN FD specific *** # *** CAN/CAN FD specific ***
if self.CP.flags & HyundaiFlags.CANFD: if self.CP.flags & HyundaiFlags.CANFD:
can_sends.extend(self.create_canfd_msgs(now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel, can_sends.extend(self.create_canfd_msgs(now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel,
@@ -742,6 +755,7 @@ class CarController(CarControllerBase):
can_sends = [] can_sends = []
can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED) can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
blended_hda2 = can_canfd_blended and bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) blended_hda2 = can_canfd_blended and bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
# HUD messages # HUD messages
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint, sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
@@ -750,6 +764,7 @@ class CarController(CarControllerBase):
if blended_hda2: if blended_hda2:
can_sends.extend(hyundaicanfd.create_steering_messages( can_sends.extend(hyundaicanfd.create_steering_messages(
self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0, self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0,
longitudinal_active=longitudinal_active,
)) ))
if self.long_active_ecu: if self.long_active_ecu:
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended( can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(
@@ -782,7 +797,7 @@ class CarController(CarControllerBase):
# Button messages # Button messages
if not self.long_active_ecu: if not self.long_active_ecu:
if CC.cruiseControl.cancel: if self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP)) can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
elif CC.cruiseControl.resume: elif CC.cruiseControl.resume:
# send resume at a max freq of 10Hz # send resume at a max freq of 10Hz
@@ -824,6 +839,9 @@ class CarController(CarControllerBase):
# 20 Hz LFA MFA message # 20 Hz LFA MFA message
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)): if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)):
if self._ray_lfa_8byte:
can_sends.append(hyundaican.create_ray_lfahda_mfc(self._ray_lfa_packer, CC.latActive, lfa_icon))
else:
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon)) can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon))
# 5 Hz ACC options # 5 Hz ACC options
@@ -841,7 +859,15 @@ class CarController(CarControllerBase):
can_sends = [] can_sends = []
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
lka_steering_long = lka_steering and self.long_active_ecu longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
lfa_status_cars = (
CAR.HYUNDAI_IONIQ_6,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
CAR.KIA_EV6,
)
lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \
if self.CP.carFingerprint in lfa_status_cars else longitudinal_active
lka_steering_long = lka_steering and lfa_longitudinal_active
ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \ use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
CC.actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping) CC.actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
@@ -870,7 +896,7 @@ class CarController(CarControllerBase):
if angle_lkas_alt: if angle_lkas_alt:
steering_msg_active = bool(steering_msg_active and drive_gear) steering_msg_active = bool(steering_msg_active and drive_gear)
angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive) angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive)
forward_stock_lkas = angle_lkas_alt and ( forward_stock_lkas = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and angle_lkas_alt and (
angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled)) angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled))
) )
preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint) preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint)
@@ -879,7 +905,8 @@ class CarController(CarControllerBase):
steering_msg_active, apply_torque, apply_angle, steering_msg_active, apply_torque, apply_angle,
CS.stock_lfa_msg if preserve_stock_lfa_status else None, CS.stock_lfa_msg if preserve_stock_lfa_status else None,
CS.stock_lkas_msg if preserve_stock_lkas else None, CS.stock_lkas_msg if preserve_stock_lkas else None,
lka_icon=lka_icon)) lka_icon=lka_icon,
longitudinal_active=lfa_longitudinal_active))
direct_steering_active = ccnc_angle_long and drive_gear and CC.latActive and self.direct_angle_request_allowed and not CS.angle_steering_fault direct_steering_active = ccnc_angle_long and drive_gear and CC.latActive and self.direct_angle_request_allowed and not CS.angle_steering_fault
inactive_steering_angle = float(np.clip(CS.angle_steering_angle, inactive_steering_angle = float(np.clip(CS.angle_steering_angle,
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX, -self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
@@ -971,7 +998,7 @@ class CarController(CarControllerBase):
# The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat # The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
# and stops publishing object tracks when it disappears. # and stops publishing object tracks when it disappears.
radar_heartbeat_step = 1 if ccnc_angle_long else 4 radar_heartbeat_step = 1 if ccnc_angle_long else 4
if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and self.frame % radar_heartbeat_step == 0: if self.CP.carFingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR and self.frame % radar_heartbeat_step == 0:
can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step, can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step,
CS.out.brakePressed, CS.out.gasPressed, CS.out.brakePressed, CS.out.gasPressed,
self.CP.carFingerprint)) self.CP.carFingerprint))
@@ -1046,7 +1073,7 @@ class CarController(CarControllerBase):
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info))
self.last_button_frame = self.frame self.last_button_frame = self.frame
else: elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
for _ in range(20): for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL)) can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL))
self.last_button_frame = self.frame self.last_button_frame = self.frame
+6 -1
View File
@@ -2,6 +2,8 @@ from collections import deque
import copy import copy
import math import math
# Provenance: portions of HKG angle-state integration are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs from opendbc.car import Bus, create_button_events, structs
@@ -245,7 +247,10 @@ class CarState(CarStateBase):
return button_events return button_events
def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]: def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]:
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA: if self.CP.carFingerprint == CAR.KIA_RAY_EV:
self.lda_button = int(cp.vl["BCM_PO_11"]["RAY_LKAS_BTN"] != 0) \
if cp.ts_nanos["BCM_PO_11"]["RAY_LKAS_BTN"] > 0 else 0
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0 self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID: elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp) self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
@@ -1,4 +1,6 @@
""" AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE.""" """ AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
# Provenance: portions of HKG firmware data are adapted from sunnypilot/opendbc master at
# f95f996f5 and its hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md.
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car.hyundai.values import CAR from opendbc.car.hyundai.values import CAR
@@ -203,6 +203,17 @@ def create_lfahda_mfc(packer, enabled, frame=None, CP=None, lfa_icon=None):
return packer.make_can_msg("LFAHDA_MFC", bus, values) return packer.make_can_msg("LFAHDA_MFC", bus, values)
def create_ray_lfahda_mfc(packer, lat_active, lfa_icon):
values = {
"HDA_USM": 2,
"HDA_Icon_State": 2 if lfa_icon else 0,
"HDA_VSetReq": 0,
"HDA_Icon_Wheel": int(lat_active),
"LFA_Icon_State": lfa_icon,
}
return packer.make_can_msg("LFAHDA_MFC", 0, values)
def create_acc_commands_can_canfd_blended(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, def create_acc_commands_can_canfd_blended(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed,
stopping, long_override, use_fca, CP): stopping, long_override, use_fca, CP):
commands = [] commands = []
@@ -1,4 +1,6 @@
import copy import copy
# Provenance: portions of HKG angle-command construction are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from opendbc.car import CanBusBase, CanData from opendbc.car import CanBusBase, CanData
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
@@ -97,9 +99,12 @@ def create_angle_adas_cmd(packer, CAN, apply_angle: float, lat_active: bool, tor
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle, def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle,
lfa_base_values=None, lkas_base_values=None, lka_icon=None): lfa_base_values=None, lkas_base_values=None, lka_icon=None,
longitudinal_active=None):
if lka_icon is None: if lka_icon is None:
lka_icon = 2 if enabled else 1 lka_icon = 2 if enabled else 1
if longitudinal_active is None:
longitudinal_active = CP.openpilotLongitudinalControl
angle_lkas_alt = CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT angle_lkas_alt = CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
@@ -193,7 +198,7 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
ret = [] ret = []
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING: if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS" lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS"
if CP.openpilotLongitudinalControl and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED: if longitudinal_active and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values)) ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values))
ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values)) ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values))
else: else:
+18 -12
View File
@@ -1,4 +1,6 @@
import time import time
# Provenance: portions of HKG angle integration are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
from opendbc.car import get_safety_config, structs, uds from opendbc.car import get_safety_config, structs, uds
from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
@@ -6,6 +8,7 @@ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
CANFD_SECURITYACCESS_CAR, \ CANFD_SECURITYACCESS_CAR, \
CANFD_ANGLE_LONGITUDINAL_CAR, \ CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \ CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
CANFD_RADAR_ECU_KEEPALIVE_CAR, \
RADAR_LIVE_LONGITUDINAL_CAR, \ RADAR_LIVE_LONGITUDINAL_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \ UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
LEGACY_LONGITUDINAL_CAR, \ LEGACY_LONGITUDINAL_CAR, \
@@ -25,6 +28,15 @@ from openpilot.starpilot.common.testing_grounds import testing_ground
ButtonType = structs.CarState.ButtonEvent.Type ButtonType = structs.CarState.ButtonEvent.Type
Ecu = structs.CarParams.Ecu Ecu = structs.CarParams.Ecu
def get_communication_control_request(car_fingerprint):
if car_fingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR:
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
# Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars # Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars
ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise) ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise)
@@ -48,7 +60,7 @@ def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None: def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None:
ret.startAccel = 1.4 ret.startAccel = 1.4
ret.longitudinalActuatorDelay = 0.35 ret.longitudinalActuatorDelay = 0.5
ret.vEgoStarting = 0.5 ret.vEgoStarting = 0.5
@@ -209,9 +221,7 @@ class CarInterface(CarInterfaceBase):
ret.enableBsm = 0x58b in fingerprint[CAN.ECAN] ret.enableBsm = 0x58b in fingerprint[CAN.ECAN]
# Send LFA message on cars with HDA # Send LFA message on cars with HDA
if 0x485 in fingerprint[CAN.CAM] and ( if 0x485 in fingerprint[CAN.CAM]:
candidate != CAR.KIA_RAY_EV or fingerprint[CAN.CAM][0x485] == 4
):
ret.flags |= HyundaiFlags.SEND_LFA.value ret.flags |= HyundaiFlags.SEND_LFA.value
# These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 # These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
@@ -226,6 +236,9 @@ class CarInterface(CarInterfaceBase):
else: else:
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hyundai, 0)] ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hyundai, 0)]
if candidate == CAR.KIA_RAY_EV and fingerprint[CAN.CAM].get(0x485) == 8:
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CAN_REFRESH_MSGS.value
if ret.flags & HyundaiFlags.CAMERA_SCC: if ret.flags & HyundaiFlags.CAMERA_SCC:
ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value
if candidate in (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024): if candidate in (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024):
@@ -350,14 +363,7 @@ class CarInterface(CarInterfaceBase):
params = Params() params = Params()
if communication_control is None: if communication_control is None:
if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR: communication_control = get_communication_control_request(CP.carFingerprint)
# Don't use 0x80 suppress bit so we can read the ECU response.
# Use ENABLE_RX_DISABLE_TX (0x01) so the ECU can still receive from rear radars for BSM
# while blocking SCC TX.
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
else:
# 0x80 silences response for other cars (original behavior)
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===") ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===")
@@ -19,6 +19,9 @@ MRR30_RADAR_START_ADDR = 0x210
MRR30_RADAR_MSG_COUNT = 16 MRR30_RADAR_MSG_COUNT = 16
MRR35_RADAR_START_ADDR = 0x3A5 MRR35_RADAR_START_ADDR = 0x3A5
MRR35_RADAR_MSG_COUNT = 32 MRR35_RADAR_MSG_COUNT = 32
GV70_RADAR_START_ADDR = 0x210
GV70_RADAR_MSG_COUNT = 16
GV70_RADAR_DBC = "hyundai_radar_210_21f_generated"
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -30,6 +33,7 @@ class RadarTrackConfig:
frequency: int = 50 frequency: int = 50
parser_msg_count: int | None = None parser_msg_count: int | None = None
expected_length: int | None = None expected_length: int | None = None
dbc_name: str | None = None
@property @property
def can_parser_msg_count(self) -> int: def can_parser_msg_count(self) -> int:
@@ -47,6 +51,10 @@ RADAR_TRACK_CONFIGS = {
def get_radar_track_config(car_fingerprint, flags: int = 0) -> RadarTrackConfig | None: def get_radar_track_config(car_fingerprint, flags: int = 0) -> RadarTrackConfig | None:
if car_fingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN:
return RadarTrackConfig(GV70_RADAR_START_ADDR, GV70_RADAR_MSG_COUNT, "gv70_210", bus=0,
frequency=20, expected_length=32, dbc_name=GV70_RADAR_DBC)
radar_dbc = DBC[car_fingerprint].get(Bus.radar) radar_dbc = DBC[car_fingerprint].get(Bus.radar)
if car_fingerprint == CAR.GENESIS_G90 and radar_dbc == HYUNDAI_MANDO_FRONT_RADAR_DBC: if car_fingerprint == CAR.GENESIS_G90 and radar_dbc == HYUNDAI_MANDO_FRONT_RADAR_DBC:
return RadarTrackConfig(RADAR_START_ADDR, G90_RADAR_MSG_COUNT, "mando", parser_msg_count=RADAR_MSG_COUNT) return RadarTrackConfig(RADAR_START_ADDR, G90_RADAR_MSG_COUNT, "mando", parser_msg_count=RADAR_MSG_COUNT)
@@ -65,6 +73,10 @@ def radar_tracks_available(radar_config: RadarTrackConfig | None, fingerprint) -
if radar_config is None: if radar_config is None:
return False return False
if radar_config.radar_type == "gv70_210":
return all(fingerprint[radar_config.bus].get(addr) == radar_config.expected_length
for addr in range(radar_config.start_addr, radar_config.start_addr + radar_config.msg_count))
msg_len = fingerprint[radar_config.bus].get(radar_config.start_addr) msg_len = fingerprint[radar_config.bus].get(radar_config.start_addr)
if msg_len is None: if msg_len is None:
return False return False
@@ -78,7 +90,8 @@ def get_radar_can_parser(CP, radar_config):
messages = [(f"RADAR_TRACK_{addr:x}", radar_config.frequency) messages = [(f"RADAR_TRACK_{addr:x}", radar_config.frequency)
for addr in range(radar_config.start_addr, radar_config.start_addr + radar_config.can_parser_msg_count)] for addr in range(radar_config.start_addr, radar_config.start_addr + radar_config.can_parser_msg_count)]
return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, radar_config.bus) dbc_name = radar_config.dbc_name or DBC[CP.carFingerprint][Bus.radar]
return CANParser(dbc_name, messages, radar_config.bus)
class RadarInterface(RadarInterfaceBase): class RadarInterface(RadarInterfaceBase):
@@ -223,6 +236,27 @@ class RadarInterface(RadarInterfaceBase):
del self.pts[track_key] del self.pts[track_key]
continue continue
if radar_type == "gv70_210":
for i in ("1", "2"):
track_key = addr * 2 + int(i) - 1
valid = msg[f"{i}_STATE"] in (3, 4)
if valid:
pt = self.pts.get(track_key)
if pt is None:
pt = structs.RadarData.RadarPoint()
pt.trackId = self.track_id
self.track_id += 1
self.pts[track_key] = pt
pt.measured = True
pt.dRel = msg[f"{i}_LONG_DIST"]
pt.yRel = msg[f"{i}_LAT_DIST"]
pt.vRel = msg[f"{i}_REL_SPEED"]
pt.aRel = msg[f"{i}_REL_ACCEL"]
pt.yvRel = msg[f"{i}_REL_LAT_SPEED"]
elif track_key in self.pts:
del self.pts[track_key]
continue
if radar_type == "mrrevo14f": if radar_type == "mrrevo14f":
for i in ("1", "2"): for i in ("1", "2"):
track_key = addr * 2 + int(i) - 1 track_key = addr * 2 + int(i) - 1
@@ -7,7 +7,7 @@ from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
from opendbc.car.structs import CarControl, CarParams from opendbc.car.structs import CarControl, CarParams
from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
EV9LongitudinalTuningState, update_ev9_longitudinal_tuning, \ EV9LongitudinalTuningState, update_ev9_longitudinal_tuning, \
BlindspotWarningState, update_blindspot_warning, \ BlindspotWarningState, update_blindspot_warning, \
reset_egmp_longitudinal_tuning, \ reset_egmp_longitudinal_tuning, \
@@ -24,16 +24,17 @@ from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalT
clear_ioniq_6_torque_when_request_inactive clear_ioniq_6_torque_when_request_inactive
from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \
get_canfd_cruise_available get_canfd_cruise_available
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX, get_communication_control_request
from opendbc.car.hyundai import hyundaican, hyundaicanfd from opendbc.car.hyundai import hyundaican, hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \ from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
RADAR_START_ADDR, get_radar_track_config RADAR_START_ADDR, RadarInterface, get_radar_track_config, radar_tracks_available
from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \ from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \
HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, CANFD_FUZZY_WHITELIST, \ HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, CANFD_FUZZY_WHITELIST, \
UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \ UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \
LEGACY_LONGITUDINAL_CAR, DBC, HyundaiFlags, get_platform_codes, HyundaiSafetyFlags, \ LEGACY_LONGITUDINAL_CAR, DBC, HyundaiFlags, get_platform_codes, HyundaiSafetyFlags, \
HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, Buttons, CarControllerParams, kia_ev6_gt_line_longitudinal_tuning HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, Buttons, CarControllerParams, \
CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning
LongCtrlState = CarControl.Actuators.LongControlState LongCtrlState = CarControl.Actuators.LongControlState
from opendbc.car.hyundai.fingerprints import FW_VERSIONS from opendbc.car.hyundai.fingerprints import FW_VERSIONS
@@ -129,6 +130,15 @@ def get_test_toggles() -> SimpleNamespace:
class TestHyundaiFingerprint: class TestHyundaiFingerprint:
def test_ev6_uses_stock_hda2_communication_control_path(self):
stock_request = bytes([0x28, 0x83, 0x01])
radar_keepalive_request = bytes([0x28, 0x01, 0x01])
assert CAR.KIA_EV6 in CANFD_RADAR_LIVE_LONGITUDINAL_CAR
assert CAR.KIA_EV6 not in CANFD_RADAR_ECU_KEEPALIVE_CAR
assert get_communication_control_request(CAR.KIA_EV6) == stock_request
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request
def test_carnival_hev_low_speed_torque_rate_limits(self): def test_carnival_hev_low_speed_torque_rate_limits(self):
CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [], CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [],
False, False, False, None) False, False, False, None)
@@ -426,6 +436,42 @@ class TestHyundaiFingerprint:
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_LKA_STEERING assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_LKA_STEERING
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.EV_GAS assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.EV_GAS
gv70_radar_config = get_radar_track_config(CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN)
assert gv70_radar_config.radar_type == "gv70_210"
for addr in range(gv70_radar_config.start_addr, gv70_radar_config.start_addr + gv70_radar_config.msg_count):
gv70_fingerprint[gv70_radar_config.bus][addr] = gv70_radar_config.expected_length
assert radar_tracks_available(gv70_radar_config, gv70_fingerprint)
CP = CarInterface.get_params(CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, gv70_fingerprint, gv70_car_fw,
True, False, False, None)
assert not CP.radarUnavailable
radar = RadarInterface(CP)
packer = CANPacker(gv70_radar_config.dbc_name)
messages = []
for addr in range(gv70_radar_config.start_addr, gv70_radar_config.start_addr + gv70_radar_config.msg_count):
message = packer.make_can_msg(f"RADAR_TRACK_{addr:x}", 0, {
"1_STATE": 3,
"1_LONG_DIST": 25.0,
"1_LAT_DIST": 0.5,
"1_REL_SPEED": -2.0,
"1_REL_LAT_SPEED": 0.1,
"1_REL_ACCEL": -0.2,
})
data = bytearray(message[1])
checksum = hkg_can_fd_checksum(addr, None, data)
data[0] = checksum & 0xff
data[1] = (checksum >> 8) & 0xff
messages.append((message[0], bytes(data), message[2]))
radar_data = radar.update([(1, messages)])
assert radar_data is not None
assert len(radar_data.points) == 16
assert radar_data.points[0].dRel == pytest.approx(25.0)
other_config = get_radar_track_config(CAR.HYUNDAI_IONIQ_5)
assert other_config.radar_type == "mrr30"
assert other_config.dbc_name is None
for candidate in HYUNDAI_NON_SCC_CARS: for candidate in HYUNDAI_NON_SCC_CARS:
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, None) CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, None)
assert bool(CP.flags & HyundaiFlags.NON_SCC) assert bool(CP.flags & HyundaiFlags.NON_SCC)
@@ -783,6 +829,36 @@ class TestHyundaiFingerprint:
assert not any(addr == 0x340 for addr, _, _ in first) assert not any(addr == 0x340 for addr, _, _ in first)
assert any(addr == 0x340 for addr, _, _ in second) assert any(addr == 0x340 for addr, _, _ in second)
def test_stock_scc_cancel_waits_for_factory_disengagement(self):
CP = CarInterface.get_params(CAR.HYUNDAI_SANTA_FE_2022, gen_empty_fingerprint(), [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
hud_control = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
leftLaneVisible=True,
rightLaneVisible=True,
leftLaneDepart=False,
rightLaneDepart=False,
)
CS = SimpleNamespace(
lkas11=parser.vl["LKAS11"],
clu11=parser.vl["CLU11"],
redneck_send_button=Buttons.NONE,
is_metric=False,
)
CC = SimpleNamespace(enabled=False, cruiseControl=SimpleNamespace(cancel=True, resume=False))
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
for counter in range(1, CANCEL_BUTTON_DELAY_FRAMES + 1):
controller.cancel_counter = counter
msgs = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2)
assert not any(addr == 0x4F1 for addr, _, _ in msgs)
controller.cancel_counter = CANCEL_BUTTON_DELAY_FRAMES + 1
msgs = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2)
assert any(addr == 0x4F1 for addr, _, _ in msgs)
@pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024)) @pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024))
def test_hyundai_can_refresh_platforms_use_refresh_dbc_and_safety_param(self, candidate): def test_hyundai_can_refresh_platforms_use_refresh_dbc_and_safety_param(self, candidate):
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None) CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None)
@@ -871,7 +947,21 @@ class TestHyundaiFingerprint:
fingerprint[2][0x485] = 8 fingerprint[2][0x485] = 8
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None) CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
assert not (CP.flags & HyundaiFlags.SEND_LFA) assert CP.flags & HyundaiFlags.SEND_LFA
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS
def test_ray_ev_uses_carrot_eight_byte_lfa_frame(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x485] = 8
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
msg = hyundaican.create_ray_lfahda_mfc(controller._ray_lfa_packer, True, 2)
assert msg[0] == 0x485
assert len(msg[1]) == 8
assert msg[1][0] & 0x03 == 2
assert msg[1][2] & 0x10 == 0x10
assert msg[1][3] & 0x03 == 2
def test_non_ray_legacy_platform_keeps_53e_lkas12_detection(self): def test_non_ray_legacy_platform_keeps_53e_lkas12_detection(self):
fingerprint = gen_empty_fingerprint() fingerprint = gen_empty_fingerprint()
@@ -988,6 +1078,49 @@ class TestHyundaiFingerprint:
assert ret.cruiseState.enabled assert ret.cruiseState.enabled
assert ret.cruiseState.speed == pytest.approx(10 * 0.2777778) assert ret.cruiseState.speed == pytest.approx(10 * 0.2777778)
def test_kia_ray_ev_decodes_bcm_lkas_button_pulse(self):
toggles = get_test_toggles()
CP = CarInterface.get_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], True, False, False, toggles)
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], CP, toggles)
car_state = CarState(CP, FPCP)
can_parsers = car_state.get_can_parsers(CP)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
def update(ray_lkas_button: int, frame: int):
msg = packer.make_can_msg("BCM_PO_11", 0, {"RAY_LKAS_BTN": ray_lkas_button})
can_parsers[Bus.pt].update([(frame, [msg])])
return car_state.update(can_parsers, toggles)[0]
update(0, 1)
ret = update(1, 2)
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
ret = update(0, 3)
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
ret = update(1, 4)
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
raw_button_msg = packer.make_can_msg("BCM_PO_11", 0, {"RAY_LKAS_BTN": 1})
assert raw_button_msg[1][0] == 0x10
def test_non_ray_does_not_use_ray_lkas_signal(self):
CP = CarInterface.get_params(CAR.KIA_FORTE_2021_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
car_state = CarState(CP, CarInterface.get_starpilot_params(CAR.KIA_FORTE_2021_NON_SCC,
gen_empty_fingerprint(), [], CP, get_test_toggles()))
parser_cycle = SimpleNamespace(
vl={
"CLU13": {"CF_Clu_LdwsLkasSW": 0},
"BCM_PO_11": {"LDA_BTN": 0, "RAY_LKAS_BTN": 1},
},
ts_nanos={
"CLU13": {"CF_Clu_LdwsLkasSW": 1},
"BCM_PO_11": {"LDA_BTN": 1, "RAY_LKAS_BTN": 1},
},
)
assert not car_state.create_lkas_button_events(parser_cycle, 0)
def test_hyundai_redneck_cruise_availability(self, monkeypatch): def test_hyundai_redneck_cruise_availability(self, monkeypatch):
class FakeParams: class FakeParams:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -1281,7 +1414,7 @@ class TestHyundaiFingerprint:
assert CP.startAccel == pytest.approx(1.4) assert CP.startAccel == pytest.approx(1.4)
assert CP.vEgoStarting == pytest.approx(0.5) assert CP.vEgoStarting == pytest.approx(0.5)
assert CP.longitudinalActuatorDelay == pytest.approx(0.35) assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
assert CP.vEgoStopping == pytest.approx(0.3) assert CP.vEgoStopping == pytest.approx(0.3)
assert CP.stoppingDecelRate == pytest.approx(0.4) assert CP.stoppingDecelRate == pytest.approx(0.4)
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin) assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin)
@@ -1310,7 +1443,7 @@ class TestHyundaiFingerprint:
assert CP.startAccel == pytest.approx(1.4) assert CP.startAccel == pytest.approx(1.4)
assert CP.vEgoStarting == pytest.approx(0.5) assert CP.vEgoStarting == pytest.approx(0.5)
assert CP.longitudinalActuatorDelay == pytest.approx(0.35) assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, testing_ground_active=True) assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, testing_ground_active=True)
assert not kia_ev6_gt_line_longitudinal_tuning(CAR.KIA_EV6_2025, CP.carVin, testing_ground_active=True) assert not kia_ev6_gt_line_longitudinal_tuning(CAR.KIA_EV6_2025, CP.carVin, testing_ground_active=True)
@@ -2397,10 +2530,11 @@ class TestHyundaiFingerprint:
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING) CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
CP.openpilotLongitudinalControl = False CP.openpilotLongitudinalControl = True
controller = CarController(DBC[CP.carFingerprint], CP) controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 1 controller.frame = 1
controller.long_active_ecu = True
can_bus = CanBus(CP) can_bus = CanBus(CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN) parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN)
stock_lkas = { stock_lkas = {
@@ -2442,7 +2576,6 @@ class TestHyundaiFingerprint:
assert parser.vl["LKAS"]["STEER_MODE"] == 0 assert parser.vl["LKAS"]["STEER_MODE"] == 0
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 0 assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 0
CP.openpilotLongitudinalControl = True
lfa_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LFA", 0)], can_bus.ECAN) lfa_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LFA", 0)], can_bus.ECAN)
lfa_msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 0, 0.0) lfa_msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 0, 0.0)
assert [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in lfa_msgs] == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)] assert [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in lfa_msgs] == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
@@ -2450,6 +2583,46 @@ class TestHyundaiFingerprint:
assert lfa_parser.can_valid assert lfa_parser.can_valid
assert lfa_parser.vl["LFA"]["DAMP_FACTOR"] == 100 assert lfa_parser.vl["LFA"]["DAMP_FACTOR"] == 100
cc.longActive = False
inactive_msgs = controller.create_canfd_msgs(0, True, 0.44, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2)
steering_names = [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in inactive_msgs
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
controller.frame = 1
cc.longActive = True
active_msgs = controller.create_canfd_msgs(0, True, 0.44, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2)
steering_names = [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in active_msgs
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
@pytest.mark.parametrize("car", [CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6])
def test_egmp_keeps_lfa_status_when_longitudinal_is_inactive(self, car):
CP = CarParams.new_message()
CP.carFingerprint = car
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
CP.openpilotLongitudinalControl = True
controller = CarController(DBC[CP.carFingerprint], CP)
cc = SimpleNamespace(
enabled=False, latActive=False, longActive=False,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace(),
)
cs = SimpleNamespace(
stock_lfa_msg=None, stock_lkas_msg=None,
left_blindspot_from_radar=False, right_blindspot_from_radar=False,
out=SimpleNamespace(gearShifter=structs.CarState.GearShifter.park),
)
controller.frame = 1
for controller.long_active_ecu in (False, True):
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1)
assert any(addr == 0x12A for addr, _, _ in msgs)
def test_gv70_electrified_longitudinal_uses_hda2_scc_contract(self): def test_gv70_electrified_longitudinal_uses_hda2_scc_contract(self):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
@@ -2581,6 +2754,36 @@ class TestHyundaiFingerprint:
get_test_toggles(), lka_icon=1, lfa_icon=1) get_test_toggles(), lka_icon=1, lfa_icon=1)
assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs
@pytest.mark.parametrize("standstill", [False, True])
def test_sportage_angle_lkas_alt_publishes_inactive_status(self, standstill):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING |
HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
CP.openpilotLongitudinalControl = False
controller = CarController(DBC[CP.carFingerprint], CP)
can_bus = CanBus(CP)
cc = SimpleNamespace(enabled=False, latActive=False,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={},
out=SimpleNamespace(standstill=standstill, steeringAngleDeg=0.0,
gearShifter=structs.CarState.GearShifter.drive))
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
get_test_toggles(), lka_icon=1, lfa_icon=1)
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
assert len(lkas_msgs) == 1
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN)
parser.update([(1, lkas_msgs)])
assert parser.can_valid
assert parser.vl["LKAS_ALT"]["LKA_ICON"] == 1
assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 1
assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 0
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1
def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self): def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9 CP.carFingerprint = CAR.KIA_EV9
@@ -2,6 +2,8 @@ import re
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import IntFlag from enum import IntFlag
# Provenance: portions of HKG angle limits, flags, and platform data are adapted from
# sunnypilot/opendbc's hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md.
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
from opendbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL from opendbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
@@ -998,6 +1000,10 @@ KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
}) })
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID = "5" KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID = "5"
KIA_RAY_EV_VIN_VDS_PREFIXES = frozenset({
"CG81A",
})
ALT_BUS_LDA_BUTTON_CARS = frozenset() ALT_BUS_LDA_BUTTON_CARS = frozenset()
ALT_BUS_LDA_BUTTON_SWL_STAT_CARS = frozenset() ALT_BUS_LDA_BUTTON_SWL_STAT_CARS = frozenset()
@@ -1012,6 +1018,10 @@ def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str, testing_groun
return car_fingerprint == CAR.KIA_EV6 and (vin_match or testing_ground_active) return car_fingerprint == CAR.KIA_EV6 and (vin_match or testing_ground_active)
def kia_ray_ev_vin(vin: str) -> bool:
return isinstance(vin, str) and len(vin) == 17 and vin[3:8] in KIA_RAY_EV_VIN_VDS_PREFIXES
def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]: def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]:
# Returns unique, platform-specific identification codes for a set of versions # Returns unique, platform-specific identification codes for a set of versions
codes = set() # (code-Optional[part], date) codes = set() # (code-Optional[part], date)
@@ -1208,7 +1218,9 @@ CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_
CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9} CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9}
CANFD_RADAR_LIVE_LONGITUDINAL_CAR = { CANFD_RADAR_LIVE_LONGITUDINAL_CAR = {
CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
} }
CANFD_RADAR_ECU_KEEPALIVE_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR - {CAR.KIA_EV6}
RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | { RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | {
CAR.HYUNDAI_IONIQ, CAR.HYUNDAI_IONIQ,
CAR.HYUNDAI_KONA_EV_2022, CAR.HYUNDAI_KONA_EV_2022,
+9 -1
View File
@@ -22,7 +22,7 @@ from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HondaFlags, Hond
from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, ALT_BUS_LDA_BUTTON_CARS from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, ALT_BUS_LDA_BUTTON_CARS
from opendbc.car.mock.values import CAR as MOCK from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.subaru.values import CAR as SUBARU, SubaruSafetyFlags from opendbc.car.subaru.values import CAR as SUBARU, SUBARU_REDNECK_CRUISE_CARS, SubaruSafetyFlags
from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags
from opendbc.car.values import PLATFORMS from opendbc.car.values import PLATFORMS
from opendbc.can import CANParser from opendbc.can import CANParser
@@ -300,6 +300,14 @@ class CarInterfaceBase(ABC):
if getattr(starpilot_toggles, "subaru_sng", False): if getattr(starpilot_toggles, "subaru_sng", False):
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value
fp_ret.redneckCruiseAvailable = candidate in SUBARU_REDNECK_CRUISE_CARS
if fp_ret.redneckCruiseAvailable and params.get_bool("SubaruRedneckCruise") and \
not CP.openpilotLongitudinalControl:
fp_ret.pcmCruiseSpeed = False
CP.openpilotLongitudinalControl = True
CP.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
return fp_ret return fp_ret
@staticmethod @staticmethod
@@ -4,7 +4,7 @@ from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, structs
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_std_steer_angle_limits, apply_steer_angle_limits_vm, common_fault_avoidance from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_std_steer_angle_limits, apply_steer_angle_limits_vm, common_fault_avoidance
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
from opendbc.car.subaru import subarucan from opendbc.car.subaru import subarucan
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, SUBARU_AVH_CARS, SUBARU_STOP_START_CARS, CanBus, CarControllerParams, SubaruFlags from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, CanBus, CarControllerParams, SubaruFlags
from opendbc.car.vehicle_model import VehicleModel from opendbc.car.vehicle_model import VehicleModel
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and # FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
@@ -37,9 +37,8 @@ _STOP_START_STARTUP_DELAY_FRAMES = 100
_STOP_START_STARTUP_DEADLINE_FRAMES = 1000 _STOP_START_STARTUP_DEADLINE_FRAMES = 1000
_STOP_START_PULSE_FRAMES = 30 _STOP_START_PULSE_FRAMES = 30
_STOP_START_PULSE_PERIOD_FRAMES = 5 _STOP_START_PULSE_PERIOD_FRAMES = 5
_AVH_STARTUP_DELAY_FRAMES = _STOP_START_STARTUP_DELAY_FRAMES _REDNECK_BUTTON_INTERVAL_FRAMES = 10
_AVH_STARTUP_DEADLINE_FRAMES = _STOP_START_STARTUP_DEADLINE_FRAMES _REDNECK_BUTTON_COPIES = 2
_AVH_PULSE_MESSAGES = 15 # Match the native 10 Hz AVH frame for roughly 1.5 seconds
def get_safety_CP(): def get_safety_CP():
@@ -78,7 +77,7 @@ class CarController(CarControllerBase):
self.angle_bus = CanBus.angle_for_cp(CP) self.angle_bus = CanBus.angle_for_cp(CP)
self.status_bus = CanBus.camera if CP.flags & SubaruFlags.D_PLATFORM_CAMERA else CanBus.main self.status_bus = CanBus.camera if CP.flags & SubaruFlags.D_PLATFORM_CAMERA else CanBus.main
if CP.flags & SubaruFlags.LKAS_ANGLE: if CP.flags & SubaruFlags.LKAS_ANGLE and CP.carFingerprint != CAR.SUBARU_OUTBACK_2023:
self.VM = VehicleModel(get_safety_CP()) self.VM = VehicleModel(get_safety_CP())
self.prev_close_distance = 0 self.prev_close_distance = 0
@@ -90,10 +89,7 @@ class CarController(CarControllerBase):
self.stop_start_initial_state = None self.stop_start_initial_state = None
self.stop_start_counter = 0 self.stop_start_counter = 0
self.stop_start_acknowledged = False self.stop_start_acknowledged = False
self.avh_attempted = False self.last_redneck_button_frame = 0
self.avh_request_started = False
self.avh_last_counter = None
self.avh_messages_sent = 0
def _stop_start_off_request(self, CC, CS, starpilot_toggles): def _stop_start_off_request(self, CC, CS, starpilot_toggles):
"""Send one bounded Subaru Stop/Start OFF request after ignition. """Send one bounded Subaru Stop/Start OFF request after ignition.
@@ -150,55 +146,6 @@ class CarController(CarControllerBase):
self.stop_start_counter = (self.stop_start_counter + 1) % 0x10 self.stop_start_counter = (self.stop_start_counter + 1) % 0x10
return msg return msg
def _avh_on_request(self, CC, CS, starpilot_toggles):
"""Send a bounded Subaru AVH ON pulse after ignition.
The AVH button frame was identified on the 2025 Legacy only. Keep this
independent from Stop/Start so the existing Outback request is unchanged.
"""
if self.CP.carFingerprint not in SUBARU_AVH_CARS or \
not getattr(starpilot_toggles, "subaru_avh_on", False) or self.avh_attempted:
return None
if self.frame > _AVH_STARTUP_DEADLINE_FRAMES or getattr(CC, "enabled", False):
self.avh_attempted = True
return None
if self.frame < _AVH_STARTUP_DELAY_FRAMES or not getattr(getattr(CS, "out", None), "canValid", True):
return None
out = CS.out
if not getattr(out, "standstill", False) or out.gearShifter not in (
structs.CarState.GearShifter.park,
structs.CarState.GearShifter.neutral,
):
return None
avh_msg = getattr(CS, "avh_msg", None)
avh_dat = getattr(CS, "avh_dat", None)
if not avh_msg or not avh_dat:
return None
if not self.avh_request_started:
self.avh_request_started = True
self.avh_last_counter = int(avh_msg.get("COUNTER", 0)) % 0x10
if self.avh_messages_sent >= _AVH_PULSE_MESSAGES:
self.avh_attempted = True
return None
counter = int(avh_msg.get("COUNTER", 0)) % 0x10
if counter == self.avh_last_counter:
return None
msg = subarucan.create_avh_control(
self.packer, avh_msg, raw_dat=avh_dat,
counter=counter, bus=CanBus.alt_for_cp(self.CP),
)
self.avh_last_counter = counter
self.avh_messages_sent += 1
return msg
def _reset_legacy_2025_handoff(self): def _reset_legacy_2025_handoff(self):
self.driver_override = False self.driver_override = False
self.angle_override_confirm_frames = 0 self.angle_override_confirm_frames = 0
@@ -355,7 +302,7 @@ class CarController(CarControllerBase):
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \ lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill
manual_handoff = self._legacy_2025_manual_handoff(CS, lkas_available) manual_handoff = self._legacy_2025_manual_handoff(CS, CC.latActive)
lkas_active = lkas_available and not manual_handoff lkas_active = lkas_available and not manual_handoff
steer_target = self._legacy_2025_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg steer_target = self._legacy_2025_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
@@ -378,14 +325,14 @@ class CarController(CarControllerBase):
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \ lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill
manual_handoff = self._angle_manual_handoff(CS, lkas_available) manual_handoff = self._angle_manual_handoff(CS, CC.latActive)
lkas_active = lkas_available and not manual_handoff lkas_active = lkas_available and not manual_handoff
if lkas_active and not self.angle_lkas_active: if lkas_active and not self.angle_lkas_active:
self.apply_steer_last = CS.out.steeringAngleDeg self.apply_steer_last = CS.out.steeringAngleDeg
steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023: if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
apply_steer = apply_std_steer_angle_limits( apply_steer = apply_std_steer_angle_limits(
steer_target, steer_target,
self.apply_steer_last, self.apply_steer_last,
@@ -414,7 +361,7 @@ class CarController(CarControllerBase):
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \ lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
getattr(CS.out, "gearShifter", structs.CarState.GearShifter.drive) == structs.CarState.GearShifter.drive and \ getattr(CS.out, "gearShifter", structs.CarState.GearShifter.drive) == structs.CarState.GearShifter.drive and \
not getattr(CS.out, "standstill", False) not getattr(CS.out, "standstill", False)
manual_handoff = self._angle_manual_handoff(CS, lkas_available) manual_handoff = self._angle_manual_handoff(CS, CC.latActive)
lat_active = lkas_available and not self.driver_override and not manual_handoff lat_active = lkas_available and not self.driver_override and not manual_handoff
if lat_active and not self.angle_lkas_active: if lat_active and not self.angle_lkas_active:
self.apply_steer_last = CS.out.steeringAngleDeg self.apply_steer_last = CS.out.steeringAngleDeg
@@ -465,6 +412,10 @@ class CarController(CarControllerBase):
actuators = CC.actuators actuators = CC.actuators
hud_control = CC.hudControl hud_control = CC.hudControl
pcm_cancel_cmd = CC.cruiseControl.cancel pcm_cancel_cmd = CC.cruiseControl.cancel
subaru_redneck_cruise = bool(
self.CP.carFingerprint == CAR.SUBARU_IMPREZA_2020 and
getattr(starpilot_toggles, "subaru_redneck_cruise", False)
)
can_sends = [] can_sends = []
@@ -472,10 +423,6 @@ class CarController(CarControllerBase):
if stop_start_msg is not None: if stop_start_msg is not None:
can_sends.append(stop_start_msg) can_sends.append(stop_start_msg)
avh_msg = self._avh_on_request(CC, CS, starpilot_toggles)
if avh_msg is not None:
can_sends.append(avh_msg)
# *** steering *** # *** steering ***
if (self.frame % self.p.STEER_STEP) == 0: if (self.frame % self.p.STEER_STEP) == 0:
if self.CP.flags & SubaruFlags.LKAS_ANGLE: if self.CP.flags & SubaruFlags.LKAS_ANGLE:
@@ -533,7 +480,8 @@ class CarController(CarControllerBase):
else: else:
if self.frame % 10 == 0: if self.frame % 10 == 0:
can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled, can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled,
self.CP.openpilotLongitudinalControl, CC.longActive, hud_control.leadVisible, self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise,
CC.longActive, hud_control.leadVisible,
self.status_bus)) self.status_bus))
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive, hud_control.visualAlert, can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive, hud_control.visualAlert,
@@ -551,7 +499,7 @@ class CarController(CarControllerBase):
can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg, can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg,
speed_cmd, pcm_cancel_cmd)) speed_cmd, pcm_cancel_cmd))
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise:
if self.frame % 5 == 0: if self.frame % 5 == 0:
can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg, can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg,
self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm)) self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm))
@@ -567,6 +515,20 @@ class CarController(CarControllerBase):
bus = CanBus.alt_for_cp(self.CP) if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else self.main_bus bus = CanBus.alt_for_cp(self.CP) if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else self.main_bus
can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd)) can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd))
if subaru_redneck_cruise:
redneck_button = {
1: subarucan.CRUISE_BUTTON_RESUME,
2: subarucan.CRUISE_BUTTON_SET,
}.get(getattr(CS, "redneck_send_button", 0))
cruise_buttons_msg = getattr(CS, "cruise_buttons_msg", None)
if redneck_button and cruise_buttons_msg and self.frame - self.last_redneck_button_frame >= _REDNECK_BUTTON_INTERVAL_FRAMES:
counter = (int(cruise_buttons_msg["COUNTER"]) + 1) % 0x10
for copy_idx in range(_REDNECK_BUTTON_COPIES):
can_sends.append(subarucan.create_cruise_buttons(
self.packer, counter + copy_idx, cruise_buttons_msg, redneck_button, self.main_bus,
))
self.last_redneck_button_frame = self.frame
if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT: if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT:
# Tester present (keeps eyesight disabled) # Tester present (keeps eyesight disabled)
if self.frame % 100 == 0: if self.frame % 100 == 0:
+27 -14
View File
@@ -1,12 +1,20 @@
import copy import copy
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, structs from opendbc.car import Bus, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase from opendbc.car.interfaces import CarStateBase
from opendbc.car.subaru.values import DBC, CanBus, SUBARU_AVH_CARS, SUBARU_STOP_START_CARS, SubaruFlags from opendbc.car.subaru.values import DBC, CanBus, SUBARU_REDNECK_CRUISE_CARS, SUBARU_STOP_START_CARS, SubaruFlags
from opendbc.car import CanSignalRateCalculator from opendbc.car import CanSignalRateCalculator
ButtonType = structs.CarState.ButtonEvent.Type
SUBARU_CRUISE_BUTTONS = {
"Main": ButtonType.mainCruise,
"Set": ButtonType.decelCruise,
"Resume": ButtonType.accelCruise,
}
class CarState(CarStateBase): class CarState(CarStateBase):
def __init__(self, CP, FPCP): def __init__(self, CP, FPCP):
@@ -18,8 +26,8 @@ class CarState(CarStateBase):
self.dashlights_msg = {} self.dashlights_msg = {}
self.dashlights_dat = b"" self.dashlights_dat = b""
self.stop_start_state = 0 self.stop_start_state = 0
self.avh_msg = {} self.cruise_buttons_msg = {}
self.avh_dat = b"" self.cruise_buttons = {button: 0 for button in SUBARU_CRUISE_BUTTONS}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState: def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt] cp = can_parsers[Bus.pt]
@@ -35,11 +43,6 @@ class CarState(CarStateBase):
self.dashlights_dat = stop_start_cp.vl_raw["Dashlights"] self.dashlights_dat = stop_start_cp.vl_raw["Dashlights"]
self.stop_start_state = stop_start_cp.vl["Engine_Stop_Start"]["STOP_START_STATE"] self.stop_start_state = stop_start_cp.vl["Engine_Stop_Start"]["STOP_START_STATE"]
if self.CP.carFingerprint in SUBARU_AVH_CARS:
avh_cp = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
self.avh_msg = copy.copy(avh_cp.vl["AVH"])
self.avh_dat = avh_cp.vl_raw["AVH"]
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"] throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"]
ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5 ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5
if self.CP.flags & SubaruFlags.PREGLOBAL: if self.CP.flags & SubaruFlags.PREGLOBAL:
@@ -82,14 +85,14 @@ class CarState(CarStateBase):
if self.CP.flags & SubaruFlags.LKAS_ANGLE: if self.CP.flags & SubaruFlags.LKAS_ANGLE:
ret.steeringAngleDeg = cp_angle.vl["Steering_2"]["Steering_Angle"] ret.steeringAngleDeg = cp_angle.vl["Steering_2"]["Steering_Angle"]
steering_updated = len(cp_angle.vl_all["Steering_2"]["Steering_Angle"]) > 0 steering_counter = cp_angle.vl["Steering_2"]["COUNTER"]
else: else:
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"] ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]
steering_updated = len(cp.vl_all["Steering_Torque"]["Steering_Angle"]) > 0 steering_counter = cp.vl["Steering_Torque"].get("COUNTER", 0)
if not (self.CP.flags & SubaruFlags.PREGLOBAL): if not (self.CP.flags & SubaruFlags.PREGLOBAL):
# ideally we get this from the car, but unclear if it exists. diagnostic software doesn't even have it # ideally we get this from the car, but unclear if it exists. diagnostic software doesn't even have it
ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, steering_updated) ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, steering_counter)
ret.steeringTorque = cp_angle.vl["Steering_Torque"]["Steer_Torque_Sensor"] ret.steeringTorque = cp_angle.vl["Steering_Torque"]["Steer_Torque_Sensor"]
ret.steeringTorqueEps = cp_angle.vl["Steering_Torque"]["Steer_Torque_Output"] ret.steeringTorqueEps = cp_angle.vl["Steering_Torque"]["Steer_Torque_Output"]
@@ -143,6 +146,17 @@ class CarState(CarStateBase):
self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"]) self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"])
self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"]) self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"])
if self.CP.carFingerprint in SUBARU_REDNECK_CRUISE_CARS:
cruise_buttons = cp.vl["Cruise_Buttons"]
if getattr(starpilot_toggles, "subaru_redneck_cruise", False):
ret.buttonEvents = []
for button, button_type in SUBARU_CRUISE_BUTTONS.items():
ret.buttonEvents.extend(create_button_events(
int(bool(cruise_buttons[button])), self.cruise_buttons[button], {1: button_type},
))
self.cruise_buttons = {button: int(bool(cruise_buttons[button])) for button in SUBARU_CRUISE_BUTTONS}
self.cruise_buttons_msg = copy.copy(cruise_buttons)
if not (self.CP.flags & SubaruFlags.HYBRID): if not (self.CP.flags & SubaruFlags.HYBRID):
self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
@@ -163,11 +177,10 @@ class CarState(CarStateBase):
@staticmethod @staticmethod
def get_can_parsers(CP): def get_can_parsers(CP):
avh_messages = [("AVH", 0)] if CP.carFingerprint in SUBARU_AVH_CARS else []
parsers = { parsers = {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main_for_cp(CP)), Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main_for_cp(CP)),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.camera), Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.camera),
Bus.alt: CANParser(DBC[CP.carFingerprint][Bus.pt], avh_messages, CanBus.alt_for_cp(CP)) Bus.alt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.alt_for_cp(CP))
} }
if CP.flags & SubaruFlags.D_PLATFORM: if CP.flags & SubaruFlags.D_PLATFORM:
parsers[Bus.main] = CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main) parsers[Bus.main] = CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main)
+2 -4
View File
@@ -3,7 +3,7 @@ from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.subaru.carcontroller import CarController from opendbc.car.subaru.carcontroller import CarController
from opendbc.car.subaru.carstate import CarState from opendbc.car.subaru.carstate import CarState
from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SUBARU_AVH_CARS, SUBARU_STOP_START_CARS, SubaruFlags, SubaruSafetyFlags from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, SubaruFlags, SubaruSafetyFlags
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@@ -42,9 +42,7 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value
if candidate in SUBARU_STOP_START_CARS: if candidate in SUBARU_STOP_START_CARS:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
if candidate in SUBARU_AVH_CARS: if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.AVH_BUTTON.value
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023):
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
+17 -25
View File
@@ -3,6 +3,10 @@ from opendbc.car.subaru.values import CanBus
VisualAlert = structs.CarControl.HUDControl.VisualAlert VisualAlert = structs.CarControl.HUDControl.VisualAlert
CRUISE_BUTTON_MAIN = 1
CRUISE_BUTTON_SET = 2
CRUISE_BUTTON_RESUME = 3
def create_steering_control(packer, apply_torque, steer_req): def create_steering_control(packer, apply_torque, steer_req):
values = { values = {
@@ -67,6 +71,19 @@ def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long
return packer.make_can_msg("ES_Distance", bus, values) return packer.make_can_msg("ES_Distance", bus, values)
def create_cruise_buttons(packer, frame, cruise_buttons_msg, button, bus=CanBus.main):
values = {s: cruise_buttons_msg[s] for s in [
"CHECKSUM",
"Signal1",
"Signal2",
]}
values["COUNTER"] = frame % 0x10
values["Main"] = button == CRUISE_BUTTON_MAIN
values["Set"] = button == CRUISE_BUTTON_SET
values["Resume"] = button == CRUISE_BUTTON_RESUME
return packer.make_can_msg("Cruise_Buttons", bus, values)
def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart, def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart,
bus=CanBus.main): bus=CanBus.main):
values = {s: es_lkas_state_msg[s] for s in [ values = {s: es_lkas_state_msg[s] for s in [
@@ -208,31 +225,6 @@ def create_stop_start_control(packer, dashlights_msg, raw_dat=None, counter=None
return packer.make_can_msg("Dashlights", bus, values) return packer.make_can_msg("Dashlights", bus, values)
def create_avh_control(packer, avh_msg, raw_dat=None, counter=None, bus=CanBus.alt):
"""Create the supported Subaru Legacy AVH ON request.
AVH is carried in the live 0x32b frame. Preserve the other bytes and update
only the rolling counter, AVH bit, and Subaru additive checksum.
"""
if raw_dat:
dat = bytearray(raw_dat)
if len(dat) != 8:
raise ValueError(f"AVH frame must be 8 bytes, got {len(dat)}")
if counter is None:
counter = (int(avh_msg.get("COUNTER", 0)) + 1) % 0x10
dat[1] = (dat[1] & 0xF0) | (counter % 0x10)
dat[5] |= 0x20 # AVH, big-endian bit 45
dat[0] = ((0x32B & 0xFF) + ((0x32B >> 8) & 0xFF) + sum(dat[1:])) & 0xFF
return 0x32B, bytes(dat), bus
values = dict(avh_msg)
if counter is None:
counter = (int(values.get("COUNTER", 0)) + 1) % 0x10
values["COUNTER"] = counter % 0x10
values["AVH"] = 1
return packer.make_can_msg("AVH", bus, values)
def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value, bus=CanBus.main): def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value, bus=CanBus.main):
values = {s: es_brake_msg[s] for s in [ values = {s: es_brake_msg[s] for s in [
"CHECKSUM", "CHECKSUM",
@@ -5,7 +5,7 @@ from types import SimpleNamespace
import pytest import pytest
from opendbc.can import CANPacker, CANParser from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, fw_versions, structs from opendbc.car import Bus, fw_versions, gen_empty_fingerprint, structs
from opendbc.car.fw_query_definitions import StdQueries from opendbc.car.fw_query_definitions import StdQueries
from opendbc.car.subaru import subarucan from opendbc.car.subaru import subarucan
from opendbc.car.subaru.carcontroller import CarController from opendbc.car.subaru.carcontroller import CarController
@@ -67,6 +67,56 @@ def test_preglobal_sng_does_not_send_standstill_keepalive_without_manual_toggle(
assert speed_cmd is False assert speed_cmd is False
def test_redneck_cruise_buttons_use_resume_for_increase_and_set_for_decrease():
dbc = DBC[CAR.SUBARU_IMPREZA_2020][Bus.pt]
packer = CANPacker(dbc)
parser = CANParser(dbc, [("Cruise_Buttons", 0)], CanBus.main)
stock_buttons = defaultdict(int)
resume_msg = subarucan.create_cruise_buttons(
packer, 1, stock_buttons, subarucan.CRUISE_BUTTON_RESUME, CanBus.main,
)
parser.update([(1, [resume_msg])])
assert parser.vl["Cruise_Buttons"]["Resume"] == 1
assert parser.vl["Cruise_Buttons"]["Set"] == 0
set_msg = subarucan.create_cruise_buttons(
packer, 2, stock_buttons, subarucan.CRUISE_BUTTON_SET, CanBus.main,
)
parser.update([(2, [set_msg])])
assert parser.vl["Cruise_Buttons"]["Resume"] == 0
assert parser.vl["Cruise_Buttons"]["Set"] == 1
def test_redneck_cruise_is_only_available_on_the_experimental_impreza(monkeypatch):
class FakeParams:
def __init__(self, **_kwargs):
pass
def get_bool(self, key):
return key == "SubaruRedneckCruise"
monkeypatch.setattr("opendbc.car.interfaces.Params", FakeParams)
toggles = SimpleNamespace(subaru_sng=False)
impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA_2020)
impreza_fpcp = CarInterface.get_starpilot_params(
CAR.SUBARU_IMPREZA_2020, gen_empty_fingerprint(), [], impreza_cp, toggles,
)
assert impreza_fpcp.redneckCruiseAvailable
assert not impreza_fpcp.pcmCruiseSpeed
assert impreza_cp.openpilotLongitudinalControl
assert impreza_cp.safetyConfigs[0].safetyParam & SubaruSafetyFlags.REDNECK_CRUISE
old_impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA)
old_impreza_fpcp = CarInterface.get_starpilot_params(
CAR.SUBARU_IMPREZA, gen_empty_fingerprint(), [], old_impreza_cp, toggles,
)
assert not old_impreza_fpcp.redneckCruiseAvailable
assert old_impreza_fpcp.pcmCruiseSpeed
assert not old_impreza_cp.openpilotLongitudinalControl
class TestSubaruFingerprint: class TestSubaruFingerprint:
def test_eyesight_queries_do_not_change_diagnostic_state(self, monkeypatch): def test_eyesight_queries_do_not_change_diagnostic_state(self, monkeypatch):
camera_requests = [request for request in FW_QUERY_CONFIG.requests if CarParams.Ecu.fwdCamera in request.whitelist_ecus] camera_requests = [request for request in FW_QUERY_CONFIG.requests if CarParams.Ecu.fwdCamera in request.whitelist_ecus]
@@ -194,8 +244,7 @@ def test_outback_2023_uses_d_platform_bus_layout():
assert CP.flags & SubaruFlags.D_PLATFORM assert CP.flags & SubaruFlags.D_PLATFORM
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.AVH_BUTTON) assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.LEGACY_2025_ANGLE_LIMITS)
assert CanBus.main_for_cp(CP) == CanBus.alt assert CanBus.main_for_cp(CP) == CanBus.alt
assert CanBus.angle_for_cp(CP) == CanBus.main assert CanBus.angle_for_cp(CP) == CanBus.main
assert parsers[Bus.pt].bus == CanBus.alt assert parsers[Bus.pt].bus == CanBus.alt
@@ -225,21 +274,6 @@ def test_stop_start_inputs_are_captured_for_supported_models(platform):
assert car_state.stop_start_state == 3 assert car_state.stop_start_state == 3
def test_avh_inputs_are_captured_for_legacy_2025():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
car_state = CarState(CP, None)
parsers = car_state.get_can_parsers(CP)
raw_avh = bytes.fromhex("230f1c4208800000")
parsers[Bus.alt].vl["AVH"]["COUNTER"] = 15
parsers[Bus.alt].vl["AVH"]["AVH"] = 0
parsers[Bus.alt].vl_raw["AVH"] = raw_avh
car_state.update(parsers, SimpleNamespace(subaru_sng=False))
assert car_state.avh_msg["COUNTER"] == 15
assert car_state.avh_dat == raw_avh
@pytest.mark.parametrize("platform, expected_bus, start_frame", [ @pytest.mark.parametrize("platform, expected_bus, start_frame", [
(CAR.SUBARU_OUTBACK_2023, CanBus.alt, 101), (CAR.SUBARU_OUTBACK_2023, CanBus.alt, 101),
(CAR.SUBARU_LEGACY_2025, CanBus.alt, 401), (CAR.SUBARU_LEGACY_2025, CanBus.alt, 401),
@@ -292,94 +326,6 @@ def test_stop_start_request_is_bounded_and_uses_live_dashlights(platform, expect
assert controller.stop_start_acknowledged assert controller.stop_start_acknowledged
def test_avh_request_sets_observed_bit_and_pulses_at_native_rate():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
controller = CarController({}, CP)
controller.frame = 101
class TestActuators:
steeringAngleDeg = 0.0
def as_builder(self):
return SimpleNamespace(steeringAngleDeg=self.steeringAngleDeg)
CC = SimpleNamespace(
enabled=False,
latActive=False,
longActive=False,
actuators=TestActuators(),
hudControl=SimpleNamespace(leadVisible=False),
cruiseControl=SimpleNamespace(cancel=False),
)
CS = SimpleNamespace(
canValid=True,
avh_msg={"COUNTER": 15, "AVH": 0},
avh_dat=bytes.fromhex("230f1c4208800000"),
out=SimpleNamespace(
standstill=True,
gearShifter=structs.CarState.GearShifter.park,
vEgoRaw=0.0,
steeringAngleDeg=0.0,
),
)
toggles = SimpleNamespace(subaru_stop_start_off=False, subaru_avh_on=True, subaru_sng=False)
# Start the request from the current live counter. AVH is a native 10 Hz
# frame, so the controller waits for each next live counter before sending
# its matching button frame.
_, can_sends = controller.update(CC, CS, 0, toggles)
avh_msgs = [msg for msg in can_sends if msg[0] == 0x32b]
assert not avh_msgs
CS.avh_msg["COUNTER"] = 0
CS.avh_dat = bytes.fromhex("14001c4208800000")
controller.frame = 103
_, can_sends = controller.update(CC, CS, 0, toggles)
avh_msgs = [msg for msg in can_sends if msg[0] == 0x32b]
assert avh_msgs == [(0x32b, bytes.fromhex("34001c4208a00000"), CanBus.alt)]
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("AVH", 0)], CanBus.alt)
parser.update([(CanBus.alt, avh_msgs)])
assert parser.vl["AVH"]["AVH"] == 1
assert parser.vl["AVH"]["COUNTER"] == 0
controller.frame = 104
_, can_sends = controller.update(CC, CS, 0, toggles)
assert not any(msg[0] == 0x32b for msg in can_sends)
avh_msgs = []
for counter in range(1, 15):
CS.avh_msg["COUNTER"] = counter
raw_dat = bytearray.fromhex("14001c4208800000")
raw_dat[1] = counter
raw_dat[0] = ((0x32B & 0xFF) + ((0x32B >> 8) & 0xFF) + sum(raw_dat[1:])) & 0xFF
CS.avh_dat = bytes(raw_dat)
controller.frame = 103 + (counter * 10)
_, can_sends = controller.update(CC, CS, 0, toggles)
sent = [msg for msg in can_sends if msg[0] == 0x32b]
assert len(sent) == 1
avh_msgs.extend(sent)
assert len(avh_msgs) == 14
assert [msg[1][1] & 0x0F for msg in avh_msgs] == list(range(1, 15))
assert all(msg[1][5] & 0x20 for msg in avh_msgs)
assert not controller.avh_attempted
CS.avh_msg["COUNTER"] = 15
CS.avh_dat = bytes.fromhex("230f1c4208800000")
controller.frame = 253
_, can_sends = controller.update(CC, CS, 0, toggles)
assert not any(msg[0] == 0x32b for msg in can_sends)
assert controller.avh_attempted
CS.avh_msg["COUNTER"] = 0
CS.avh_dat = bytes.fromhex("14001c4208800000")
controller.frame = 131
_, can_sends = controller.update(CC, CS, 0, toggles)
assert not any(msg[0] == 0x32b for msg in can_sends)
assert controller.avh_attempted
def test_legacy_2025_uses_gen2_angle_bus_layout(): def test_legacy_2025_uses_gen2_angle_bus_layout():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025) CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
parsers = CarState.get_can_parsers(CP) parsers = CarState.get_can_parsers(CP)
@@ -391,7 +337,6 @@ def test_legacy_2025_uses_gen2_angle_bus_layout():
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA) assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA)
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.AVH_BUTTON
assert CanBus.main_for_cp(CP) == CanBus.main assert CanBus.main_for_cp(CP) == CanBus.main
assert CanBus.angle_for_cp(CP) == CanBus.main assert CanBus.angle_for_cp(CP) == CanBus.main
assert parsers[Bus.pt].bus == CanBus.main assert parsers[Bus.pt].bus == CanBus.main
@@ -601,6 +546,25 @@ def test_ascent_2023_uses_gen2_angle_bus_layout():
assert controller.status_bus == CanBus.main assert controller.status_bus == CanBus.main
def test_ascent_steering_rate_retains_last_can_sample():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
car_state = CarState(CP, None)
parsers = car_state.get_can_parsers(CP)
toggles = SimpleNamespace(subaru_sng=False)
parsers[Bus.pt].vl["Steering_2"]["Steering_Angle"] = 1.0
parsers[Bus.pt].vl["Steering_2"]["COUNTER"] = 1
car_state.update(parsers, toggles)
parsers[Bus.pt].vl["Steering_2"]["Steering_Angle"] = 2.0
parsers[Bus.pt].vl["Steering_2"]["COUNTER"] = 2
state, _ = car_state.update(parsers, toggles)
assert state.steeringRateDeg == pytest.approx(50.0)
state, _ = car_state.update(parsers, toggles)
assert state.steeringRateDeg == pytest.approx(50.0)
def test_other_angle_platforms_keep_existing_bus_layout(): def test_other_angle_platforms_keep_existing_bus_layout():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025) CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
parsers = CarState.get_can_parsers(CP) parsers = CarState.get_can_parsers(CP)
@@ -677,8 +641,9 @@ def test_angle_controller_blocks_low_speed_mads_engagement():
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
def test_ascent_angle_controller_uses_fixed_angle_rate_limits(): @pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023) def test_angle_controller_uses_fixed_angle_rate_limits(platform):
CP = CarInterface.get_non_essential_params(platform)
controller = CarController({}, CP) controller = CarController({}, CP)
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-14.88)) CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-14.88))
CS = SimpleNamespace(out=SimpleNamespace( CS = SimpleNamespace(out=SimpleNamespace(
@@ -761,11 +726,20 @@ def test_ascent_angle_controller_blocks_parking_lot_aol_engagement():
CS.out.steeringRateDeg = 0.0 CS.out.steeringRateDeg = 0.0
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])]) parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
for i in range(8):
msg = controller.lateral_angle(CC, CS)
parser.update([(3 + i, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
msg = controller.lateral_angle(CC, CS)
parser.update([(11, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
CS.out.gearShifter = structs.CarState.GearShifter.reverse CS.out.gearShifter = structs.CarState.GearShifter.reverse
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(3, [msg])]) parser.update([(12, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg) assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
+3 -4
View File
@@ -89,7 +89,7 @@ class SubaruSafetyFlags(IntFlag):
D_PLATFORM_CAMERA = 64 D_PLATFORM_CAMERA = 64
FIXED_ANGLE_LIMITS = 128 FIXED_ANGLE_LIMITS = 128
STOP_START_BUTTON = 256 STOP_START_BUTTON = 256
AVH_BUTTON = 512 REDNECK_CRUISE = 512
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
@@ -276,11 +276,10 @@ SUBARU_STOP_START_CARS = (
CAR.SUBARU_LEGACY_2025, CAR.SUBARU_LEGACY_2025,
) )
SUBARU_AVH_CARS = ( SUBARU_REDNECK_CRUISE_CARS = (
CAR.SUBARU_LEGACY_2025, CAR.SUBARU_IMPREZA_2020,
) )
SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
+1
View File
@@ -108,6 +108,7 @@ non_tested_cars = [
TOYOTA.TOYOTA_RAV4H, TOYOTA.TOYOTA_RAV4H,
# No recorded routes yet # No recorded routes yet
VOLVO.VOLVO_V40,
VOLVO.VOLVO_XC40_RECHARGE, VOLVO.VOLVO_XC40_RECHARGE,
VOLVO.VOLVO_S60_RECHARGE, VOLVO.VOLVO_S60_RECHARGE,
VOLVO.POLESTAR_2, VOLVO.POLESTAR_2,
@@ -145,6 +145,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"PORSCHE_MACAN_MK1" = [2.0, 2.0, 0.2] "PORSCHE_MACAN_MK1" = [2.0, 2.0, 0.2]
"VOLVO_XC40_RECHARGE" = [1.5, 1.5, 0.1] "VOLVO_XC40_RECHARGE" = [1.5, 1.5, 0.1]
"VOLVO_S60_RECHARGE" = [1.5, 1.5, 0.1] "VOLVO_S60_RECHARGE" = [1.5, 1.5, 0.1]
"VOLVO_V40" = [1.5, 1.5, 0.1]
# Dashcam or fallback configured as ideal car # Dashcam or fallback configured as ideal car
"MOCK" = [10.0, 10, 0.0] "MOCK" = [10.0, 10, 0.0]
@@ -11,7 +11,7 @@ from opendbc.car.interfaces import CarControllerBase
from opendbc.car.toyota import toyotacan from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.values import CAR, MIN_ACC_SPEED, NO_STOP_TIMER_CAR, PEDAL_TRANSITION, TSS2_CAR, \ from opendbc.car.toyota.values import CAR, MIN_ACC_SPEED, NO_STOP_TIMER_CAR, PEDAL_TRANSITION, TSS2_CAR, \
CarControllerParams, ToyotaFlags, \ CarControllerParams, ToyotaFlags, \
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS
from opendbc.can import CANPacker from opendbc.can import CANPacker
Ecu = structs.CarParams.Ecu Ecu = structs.CarParams.Ecu
@@ -37,6 +37,11 @@ TOYOTA_COAST_BRAKE_DISABLE_ACCEL = -0.06 # m/s^2
TOYOTA_NO_LEAD_COAST_BRAKE_ACCEL = -0.30 # m/s^2 TOYOTA_NO_LEAD_COAST_BRAKE_ACCEL = -0.30 # m/s^2
TOYOTA_INTERCEPTOR_COMFORT_TARGET_ACCEL = 2.0 # m/s^2 TOYOTA_INTERCEPTOR_COMFORT_TARGET_ACCEL = 2.0 # m/s^2
TOYOTA_NO_LEAD_CRUISE_SIGN_FLIP_MIN_SET_SPEED_ERROR = 0.35 # m/s TOYOTA_NO_LEAD_CRUISE_SIGN_FLIP_MIN_SET_SPEED_ERROR = 0.35 # m/s
TOYOTA_RAV4_LAUNCH_PEDAL_BLEND_SPEED = 5.0 # m/s
TOYOTA_RAV4_LAUNCH_PEDAL_SCALE = 0.11
TOYOTA_RAV4_LOW_SPEED_PEDAL_SCALE = 0.23
TOYOTA_AUTO_HOLD_ACCEL = -1.0
TOYOTA_AUTO_HOLD_ACTIVATION_FRAMES = 100
# LKA limits # LKA limits
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long # EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
@@ -72,6 +77,22 @@ def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
) or highlander_sdsu) ) or highlander_sdsu)
def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
return (
auto_hold_enabled and
CP.carFingerprint in TOYOTA_AUTO_HOLD_CARS and
bool(CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
)
def get_rav4_interceptor_pedal_scale(v_ego: float) -> float:
return float(np.interp(
max(float(v_ego), 0.0),
[0.0, TOYOTA_RAV4_LAUNCH_PEDAL_BLEND_SPEED, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION],
[TOYOTA_RAV4_LAUNCH_PEDAL_SCALE, TOYOTA_RAV4_LOW_SPEED_PEDAL_SCALE, 0.3, 0.0],
))
def get_long_tune(CP, params): def get_long_tune(CP, params):
kiBP = [2., 5.] kiBP = [2., 5.]
kiV = [0.5, 0.25] kiV = [0.5, 0.25]
@@ -243,11 +264,8 @@ class CarController(CarControllerBase):
self.secoc_prev_reset_counter = 0 self.secoc_prev_reset_counter = 0
self.doors_locked = False self.doors_locked = False
self.auto_brake_hold = bool(self.CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
self.brake_hold_active = False self.brake_hold_active = False
self._brake_hold_counter = 0 self._brake_hold_counter = 0
self._brake_hold_reset = False
self._prev_brake_pressed = False
def _compute_interceptor_gas_cmd(self, CC, CS): def _compute_interceptor_gas_cmd(self, CC, CS):
if not (self.CP.enableGasInterceptorDEPRECATED and self.CP.openpilotLongitudinalControl and CC.longActive): if not (self.CP.enableGasInterceptorDEPRECATED and self.CP.openpilotLongitudinalControl and CC.longActive):
@@ -258,7 +276,7 @@ class CarController(CarControllerBase):
max_interceptor_gas = 0.5 max_interceptor_gas = 0.5
if self.CP.carFingerprint == CAR.TOYOTA_RAV4: if self.CP.carFingerprint == CAR.TOYOTA_RAV4:
pedal_scale = float(np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0])) pedal_scale = get_rav4_interceptor_pedal_scale(CS.out.vEgo)
elif self.CP.carFingerprint in (CAR.TOYOTA_COROLLA, CAR.TOYOTA_MATRIX_RETROFIT): elif self.CP.carFingerprint in (CAR.TOYOTA_COROLLA, CAR.TOYOTA_MATRIX_RETROFIT):
pedal_scale = float(np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0])) pedal_scale = float(np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0]))
else: else:
@@ -293,26 +311,24 @@ class CarController(CarControllerBase):
self.last_standstill = CS.out.standstill self.last_standstill = CS.out.standstill
def create_auto_brake_hold_messages(self, CS: structs.CarState, brake_hold_allowed_timer: int = 100): def update_auto_hold_state(self, CS: structs.CarState, cancel_requested: bool = False,
can_sends = [] activation_frames: int = TOYOTA_AUTO_HOLD_ACTIVATION_FRAMES):
brake_hold_allowed = (CS.out.standstill and CS.out.cruiseState.available and brake_hold_allowed = (not cancel_requested and CS.out.standstill and CS.out.cruiseState.available and
not CS.out.gasPressed and not CS.out.cruiseState.enabled and not CS.out.gasPressed and not CS.out.cruiseState.enabled and
CS.out.gearShifter not in (PARK, REVERSE)) CS.out.gearShifter not in (PARK, REVERSE))
if brake_hold_allowed: if brake_hold_allowed and not self.brake_hold_active and CS.out.brakePressed:
self._brake_hold_counter += 1 self._brake_hold_counter += 1
self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer and not self._brake_hold_reset self.brake_hold_active = self._brake_hold_counter > activation_frames
self._brake_hold_reset = not self._prev_brake_pressed and CS.out.brakePressed and not self._brake_hold_reset elif not brake_hold_allowed:
else:
self._brake_hold_counter = 0 self._brake_hold_counter = 0
self.brake_hold_active = False self.brake_hold_active = False
self._brake_hold_reset = False
self._prev_brake_pressed = CS.out.brakePressed
if self.frame % 2 == 0: return self.brake_hold_active
can_sends.append(toyotacan.create_brake_hold_command(self.packer, self.frame, CS.pre_collision_2, self.brake_hold_active))
return can_sends def reset_auto_hold_state(self):
self._brake_hold_counter = 0
self.brake_hold_active = False
def update(self, CC, CS, now_nanos, starpilot_toggles): def update(self, CC, CS, now_nanos, starpilot_toggles):
actuators = CC.actuators actuators = CC.actuators
@@ -409,8 +425,10 @@ class CarController(CarControllerBase):
# *** gas and brake *** # *** gas and brake ***
self._update_standstill_request(CC, CS, actuators, starpilot_toggles) self._update_standstill_request(CC, CS, actuators, starpilot_toggles)
if self.auto_brake_hold: if supports_toyota_auto_hold(self.CP, getattr(starpilot_toggles, "toyota_auto_hold", False)):
can_sends.extend(self.create_auto_brake_hold_messages(CS)) self.update_auto_hold_state(CS, pcm_cancel_cmd)
else:
self.reset_auto_hold_state()
interceptor_gas_cmd = self._compute_interceptor_gas_cmd(CC, CS) interceptor_gas_cmd = self._compute_interceptor_gas_cmd(CC, CS)
@@ -518,6 +536,11 @@ class CarController(CarControllerBase):
pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX)) pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
if self.brake_hold_active:
pcm_accel_cmd = TOYOTA_AUTO_HOLD_ACCEL
self.permit_braking = True
self.standstill_req = True
main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd
can_sends.append(toyotacan.create_accel_command(self.packer, main_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead, can_sends.append(toyotacan.create_accel_command(self.packer, main_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead,
CS.acc_type, fcw_alert, self.distance_button, CS.acc_type, fcw_alert, self.distance_button,
+4 -7
View File
@@ -75,6 +75,7 @@ class CarState(CarStateBase):
self.distance_button = 0 self.distance_button = 0
self.pcm_follow_distance = 0 self.pcm_follow_distance = 0
self.pcm_acc_status = 0
self.acc_type = 1 self.acc_type = 1
self.lkas_hud = {} self.lkas_hud = {}
@@ -89,8 +90,6 @@ class CarState(CarStateBase):
self.has_can_filter = self.FPCP.flags & ToyotaStarPilotFlags.RADAR_CAN_FILTER.value self.has_can_filter = self.FPCP.flags & ToyotaStarPilotFlags.RADAR_CAN_FILTER.value
self.has_SDSU = self.FPCP.flags & ToyotaStarPilotFlags.SMART_DSU.value self.has_SDSU = self.FPCP.flags & ToyotaStarPilotFlags.SMART_DSU.value
self.has_ZSS = self.FPCP.flags & ToyotaStarPilotFlags.ZSS.value self.has_ZSS = self.FPCP.flags & ToyotaStarPilotFlags.ZSS.value
self.auto_brake_hold = bool(self.CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
self.pre_collision_2 = {}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState: def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt] cp = can_parsers[Bus.pt]
@@ -208,6 +207,7 @@ class CarState(CarStateBase):
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
ret.accFaulted = ret.accFaulted or cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 ret.accFaulted = ret.accFaulted or cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2
prev_pcm_acc_status = self.pcm_acc_status
self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"]
if self.CP.carFingerprint not in (NO_STOP_TIMER_CAR - TSS2_CAR): if self.CP.carFingerprint not in (NO_STOP_TIMER_CAR - TSS2_CAR):
# ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request # ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request
@@ -225,9 +225,6 @@ class CarState(CarStateBase):
if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V: if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V:
self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"]) self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"])
if self.auto_brake_hold:
self.pre_collision_2 = copy.copy(cp_cam.vl["PRE_COLLISION_2"])
if self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR: if self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR:
self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"] self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"]
@@ -264,8 +261,8 @@ class CarState(CarStateBase):
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
buttonEvents += [ buttonEvents += [
*create_button_events(self.pcm_acc_status == 9, False, {1: ButtonType.accelCruise}), *create_button_events(self.pcm_acc_status == 9, prev_pcm_acc_status == 9, {1: ButtonType.accelCruise}),
*create_button_events(self.pcm_acc_status == 10, False, {1: ButtonType.decelCruise}), *create_button_events(self.pcm_acc_status == 10, prev_pcm_acc_status == 10, {1: ButtonType.decelCruise}),
] ]
fp_ret.dashboardSpeedLimit = calculate_speed_limit(cp_cam) fp_ret.dashboardSpeedLimit = calculate_speed_limit(cp_cam)
+4 -4
View File
@@ -2,9 +2,9 @@ from opendbc.car import Bus, structs, get_safety_config, uds
from opendbc.car.toyota.carstate import CarState from opendbc.car.toyota.carstate import CarState
from opendbc.car.toyota.carcontroller import CarController from opendbc.car.toyota.carcontroller import CarController
from opendbc.car.toyota.radar_interface import RadarInterface from opendbc.car.toyota.radar_interface import RadarInterface
from opendbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, SECOC_CAR, NO_DSU_CAR, \ from opendbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \
MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR, \ MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR, \
ToyotaSafetyFlags, LEGACY_PRIUS_CAR ToyotaSafetyFlags, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS
from opendbc.car.disable_ecu import disable_ecu from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.interfaces import CarInterfaceBase
from opendbc.safety import ALTERNATIVE_EXPERIENCE from opendbc.safety import ALTERNATIVE_EXPERIENCE
@@ -164,8 +164,8 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.GAS_INTERCEPTOR.value ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.GAS_INTERCEPTOR.value
toyota_auto_hold = Params(return_defaults=True).get_bool("ToyotaAutoHold") toyota_auto_hold = Params(return_defaults=True).get_bool("ToyotaAutoHold")
if toyota_auto_hold and candidate in (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR): if toyota_auto_hold and ret.openpilotLongitudinalControl and candidate in TOYOTA_AUTO_HOLD_CARS:
ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
ret.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value ret.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value
if not ret.openpilotLongitudinalControl: if not ret.openpilotLongitudinalControl:
@@ -10,16 +10,19 @@ from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
from opendbc.car.toyota import toyotacan from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \ from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \
get_prius_positive_feedforward_scale, \ get_prius_positive_feedforward_scale, \
get_rav4_interceptor_pedal_scale, \
limit_interceptor_pcm_accel, \ limit_interceptor_pcm_accel, \
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \ limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
limit_prius_stopping_accel, should_bypass_toyota_long_pid, update_permit_braking limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
update_permit_braking
from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_interceptor_gas_pressed, create_lkas_button_events from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_interceptor_gas_pressed, create_lkas_button_events
from opendbc.car.toyota.fingerprints import FW_VERSIONS from opendbc.car.toyota.fingerprints import FW_VERSIONS
from opendbc.car.toyota.interface import CarInterface from opendbc.car.toyota.interface import CarInterface
from opendbc.car.toyota.radar_interface import RadarInterface, TSSP_RADAR_EGO_SPEED_SCALE from opendbc.car.toyota.radar_interface import RadarInterface, TSSP_RADAR_EGO_SPEED_SCALE
from opendbc.car.toyota.values import CAR, DBC, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, SECOC_CAR, \ from opendbc.car.toyota.values import CAR, DBC, MIN_ACC_SPEED, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, SECOC_CAR, \
FW_QUERY_CONFIG, PLATFORM_CODE_ECUS, FUZZY_EXCLUDED_PLATFORMS, \ FW_QUERY_CONFIG, PLATFORM_CODE_ECUS, FUZZY_EXCLUDED_PLATFORMS, \
ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, get_platform_codes ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, TOYOTA_AUTO_HOLD_CARS, \
get_platform_codes
from opendbc.safety import ALTERNATIVE_EXPERIENCE from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params from openpilot.common.params import Params
@@ -186,13 +189,15 @@ class TestToyotaInterfaces:
if car_model in TSS2_CAR and car_model not in SECOC_CAR: if car_model in TSS2_CAR and car_model not in SECOC_CAR:
assert dbc[Bus.pt] == "toyota_nodsu_pt_generated" assert dbc[Bus.pt] == "toyota_nodsu_pt_generated"
def test_auto_hold_sets_flag_on_supported_tss2(self): @pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H])
def test_auto_hold_sets_flag_on_supported_toyota(self, candidate):
params = Params() params = Params()
try: try:
params.put_bool("ToyotaAutoHold", True) params.put_bool("ToyotaAutoHold", True)
car_params = CarInterface.get_params( car_params = CarInterface.get_params(
CAR.TOYOTA_CAMRY_TSS2, candidate,
{bus: {} for bus in range(8)}, {bus: ({0x2FF: 8} if candidate in (CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H) and bus == 0 else {})
for bus in range(8)},
[], [],
alpha_long=False, alpha_long=False,
is_release=False, is_release=False,
@@ -203,7 +208,30 @@ class TestToyotaInterfaces:
params.remove("ToyotaAutoHold") params.remove("ToyotaAutoHold")
assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
can_parsers = CarState.get_can_parsers(car_params)
car_state = CarState(car_params, SimpleNamespace(flags=0))
car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
assert "PRE_COLLISION_2" not in can_parsers[Bus.cam].vl
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H])
def test_auto_hold_is_disabled_by_default(self, candidate):
params = Params()
params.remove("ToyotaAutoHold")
car_params = CarInterface.get_params(
candidate,
{bus: {} for bus in range(8)},
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=SimpleNamespace(),
)
assert not car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
def test_prius_openpilot_long_uses_hybrid_long_defaults(self): def test_prius_openpilot_long_uses_hybrid_long_defaults(self):
car_params = CarInterface.get_params( car_params = CarInterface.get_params(
@@ -718,6 +746,8 @@ class TestToyotaCarController:
controller.standstill_req = standstill_req controller.standstill_req = standstill_req
controller.last_standstill = last_standstill controller.last_standstill = last_standstill
controller.accel = 0.0 controller.accel = 0.0
controller.brake_hold_active = False
controller._brake_hold_counter = 0
return controller return controller
@staticmethod @staticmethod
@@ -750,6 +780,74 @@ class TestToyotaCarController:
assert controller.standstill_req is True assert controller.standstill_req is True
def test_toyota_auto_hold_requires_toggle_supported_car_and_capability(self):
CP = SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
)
assert supports_toyota_auto_hold(CP, True)
assert supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_RAV4,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
), True)
assert supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_RAV4H,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
), True)
assert not supports_toyota_auto_hold(CP, False)
assert not supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
flags=0,
), True)
assert not supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
), True)
assert TOYOTA_AUTO_HOLD_CARS >= {CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H}
def test_toyota_auto_hold_latches_after_brake_press_until_gas(self):
controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
cs = SimpleNamespace(
out=SimpleNamespace(
standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False,
brakePressed=True,
gearShifter=structs.CarState.GearShifter.drive,
),
)
controller.update_auto_hold_state(cs, activation_frames=0)
assert controller.brake_hold_active
cs.out.brakePressed = False
controller.update_auto_hold_state(cs, activation_frames=0)
assert controller.brake_hold_active
cs.out.gasPressed = True
controller.update_auto_hold_state(cs, activation_frames=0)
assert not controller.brake_hold_active
def test_toyota_auto_hold_does_not_trigger_without_brake_press(self):
controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
cs = SimpleNamespace(
out=SimpleNamespace(
standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False,
brakePressed=False,
gearShifter=structs.CarState.GearShifter.drive,
),
)
controller.update_auto_hold_state(cs, activation_frames=0)
assert not controller.brake_hold_active
def test_prius_resume_request_releases_standstill_latch(self): def test_prius_resume_request_releases_standstill_latch(self):
controller = self._make_controller(standstill_req=True, last_standstill=True) controller = self._make_controller(standstill_req=True, last_standstill=True)
@@ -887,32 +985,30 @@ class TestToyotaCarController:
assert parser.can_valid assert parser.can_valid
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1 assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1
def test_auto_brake_hold_sends_modified_pre_collision_after_timer(self): def test_auto_hold_uses_acc_control_brake_path(self):
controller = self._make_controller() controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt]) controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
controller.frame = 0
controller.brake_hold_active = False
controller._brake_hold_counter = 0
controller._brake_hold_reset = False
controller._prev_brake_pressed = False
cs = SimpleNamespace( cs = SimpleNamespace(
out=SimpleNamespace( out=SimpleNamespace(
standstill=True, standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False), cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False, gasPressed=False,
brakePressed=False, brakePressed=True,
gearShifter=structs.CarState.GearShifter.drive, gearShifter=structs.CarState.GearShifter.drive,
), ),
pre_collision_2={},
) )
can_sends = controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0) controller.update_auto_hold_state(cs, activation_frames=0)
can_sends = [toyotacan.create_accel_command(
controller.packer, -1.0, False, True, True, False, 1, False, 0, False,
)]
parser = CANParser(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt], [("PRE_COLLISION_2", 0)], 0) parser = CANParser(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt], [("ACC_CONTROL", 0)], 0)
parser.update([(1, can_sends)]) parser.update([(1, can_sends)])
assert controller.brake_hold_active assert controller.brake_hold_active
assert parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -1.0 assert parser.vl["ACC_CONTROL"]["ACCEL_CMD"] == -1.0
assert parser.vl["PRE_COLLISION_2"]["PBRTRGR"] == 1 assert parser.vl["ACC_CONTROL"]["PERMIT_BRAKING"] == 1
assert parser.vl["ACC_CONTROL"]["RELEASE_STANDSTILL"] == 0
def test_interceptor_stop_and_go_holds_small_launch_at_standstill(self): def test_interceptor_stop_and_go_holds_small_launch_at_standstill(self):
controller = self._make_controller() controller = self._make_controller()
@@ -940,6 +1036,27 @@ class TestToyotaCarController:
assert 0.0 < gas_cmd <= 0.5 assert 0.0 < gas_cmd <= 0.5
def test_rav4_interceptor_launch_mapping_is_softer_than_generic_mapping(self):
controller = self._make_controller()
controller.CP.enableGasInterceptorDEPRECATED = True
controller.CP.carFingerprint = CAR.TOYOTA_RAV4
controller.accel = 1.5
rav4_gas = controller._compute_interceptor_gas_cmd(
SimpleNamespace(longActive=True),
SimpleNamespace(out=SimpleNamespace(standstill=False, vEgo=1.0)),
)
controller.CP.carFingerprint = CAR.TOYOTA_AVALON_2019
generic_gas = controller._compute_interceptor_gas_cmd(
SimpleNamespace(longActive=True),
SimpleNamespace(out=SimpleNamespace(standstill=False, vEgo=1.0)),
)
assert rav4_gas < generic_gas
assert get_rav4_interceptor_pedal_scale(5.0) == pytest.approx(0.23)
assert get_rav4_interceptor_pedal_scale(MIN_ACC_SPEED) == pytest.approx(0.3)
def test_interceptor_corolla_scales_with_accel_request_when_pedal_enables_sng(self): def test_interceptor_corolla_scales_with_accel_request_when_pedal_enables_sng(self):
controller = self._make_controller() controller = self._make_controller()
controller.CP.enableGasInterceptorDEPRECATED = True controller.CP.enableGasInterceptorDEPRECATED = True
@@ -89,38 +89,6 @@ def create_pcs_commands(packer, accel, active, mass):
return [msg1, msg2] return [msg1, msg2]
def create_brake_hold_command(packer, frame, pre_collision_2, brake_hold_active):
values = {s: pre_collision_2[s] for s in [
"DSS1GDRV",
"DS1STAT2",
"DS1STBK2",
"PCSWAR",
"PCSALM",
"PCSOPR",
"PCSABK",
"PBATRGR",
"PPTRGR",
"IBTRGR",
"CLEXTRGR",
"IRLT_REQ",
"BRKHLD",
"AVSTRGR",
"VGRSTRGR",
"PREFILL",
"PBRTRGR",
"PCSDIS",
"PBPREPMP",
] if s in pre_collision_2}
if brake_hold_active:
values = {
"DSS1GDRV": 0x3FF,
"PBRTRGR": frame % 730 < 727,
}
return packer.make_can_msg("PRE_COLLISION_2", 0, values)
def create_acc_cancel_command(packer): def create_acc_cancel_command(packer):
values = { values = {
"GAS_RELEASED": 0, "GAS_RELEASED": 0,
@@ -624,6 +624,11 @@ ANGLE_CONTROL_CAR = CAR.with_flags(ToyotaFlags.ANGLE_CONTROL)
SECOC_CAR = CAR.with_flags(ToyotaFlags.SECOC) SECOC_CAR = CAR.with_flags(ToyotaFlags.SECOC)
TOYOTA_AUTO_HOLD_CARS = (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR) | {
CAR.TOYOTA_RAV4,
CAR.TOYOTA_RAV4H,
}
# no resume button press required # no resume button press required
NO_STOP_TIMER_CAR = CAR.with_flags(ToyotaFlags.NO_STOP_TIMER) NO_STOP_TIMER_CAR = CAR.with_flags(ToyotaFlags.NO_STOP_TIMER)
@@ -1,3 +1,5 @@
from collections import deque
import numpy as np import numpy as np
from opendbc.can.packer import CANPacker from opendbc.can.packer import CANPacker
@@ -5,16 +7,20 @@ from opendbc.car import Bus
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
from opendbc.car.lateral import apply_std_steer_angle_limits from opendbc.car.lateral import apply_std_steer_angle_limits
from opendbc.car.volvo.helpers import LCA3CounterSync from opendbc.car.volvo.helpers import LCA3CounterSync
from opendbc.car.volvo.volvocan import (create_lca_message, create_pscm_message, create_lca_3_message, create_lca_2_message, create_lca_4_message, from opendbc.car.volvo.volvocan import (create_c1_cancel, create_c1_pscm_message, create_c1_steering_control, create_lca_message,
create_lca_5_message, create_lca_6_message, create_lca_7_message, create_pscm_related_message) create_pscm_message, create_lca_3_message, create_lca_2_message, create_lca_4_message, create_lca_5_message,
from opendbc.car.volvo.values import CarControllerParams create_lca_6_message, create_lca_7_message, create_pscm_related_message)
from opendbc.car.volvo.values import CAR, CarControllerParams, VolvoC1PlatformConfig
class CarController(CarControllerBase): class CarController(CarControllerBase):
def __init__(self, dbc_names, CP): def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP) super().__init__(dbc_names, CP)
self.packer = CANPacker(dbc_names[Bus.party]) self.is_c1 = isinstance(CAR(CP.carFingerprint).config, VolvoC1PlatformConfig)
self.packer = CANPacker(dbc_names[Bus.pt] if self.is_c1 else dbc_names[Bus.party])
self.apply_angle_last = 0.0 # Track last applied steering angle self.apply_angle_last = 0.0 # Track last applied steering angle
self.c1_torque_samples = deque(maxlen=CarControllerParams.C1_N_ZERO_TORQUE)
self.c1_recovery_until = -1
self.gear_acc = 60 self.gear_acc = 60
self.lca_4_acc = 0 # Bresenham accumulator for 29 Hz self.lca_4_acc = 0 # Bresenham accumulator for 29 Hz
@@ -62,6 +68,9 @@ class CarController(CarControllerBase):
self.lca_auth_drv_mag_filt = 0.0 self.lca_auth_drv_mag_filt = 0.0
def update(self, CC, CS, now_nanos, starpilot_toggles): def update(self, CC, CS, now_nanos, starpilot_toggles):
if self.is_c1:
return self._update_c1(CC, CS)
can_sends = [] can_sends = []
actuators = CC.actuators actuators = CC.actuators
@@ -285,3 +294,50 @@ class CarController(CarControllerBase):
self.frame += 1 self.frame += 1
self.last_lat_active = CC.latActive self.last_lat_active = CC.latActive
return new_actuators, can_sends return new_actuators, can_sends
def _update_c1(self, CC, CS):
can_sends = []
actuators = CC.actuators
if self.frame % 2 == 0: # stock FSM1 and PSCM1 messages are 50 Hz
requested_active = CC.latActive and CS.out.vEgo > self.CP.minSteerSpeed
recovering = requested_active and self.frame < self.c1_recovery_until
if not requested_active:
self.c1_torque_samples.clear()
self.c1_recovery_until = -1
elif recovering:
self.c1_torque_samples.clear()
else:
if self.c1_recovery_until >= 0:
self.c1_recovery_until = -1
self.c1_torque_samples.clear()
self.c1_torque_samples.append(CS.c1_lka_torque)
if (len(self.c1_torque_samples) == CarControllerParams.C1_N_ZERO_TORQUE and
all(torque == 0 for torque in self.c1_torque_samples)):
self.c1_recovery_until = self.frame + 100
self.c1_torque_samples.clear()
recovering = True
lat_active = requested_active and not recovering
desired_angle = float(np.clip(
actuators.steeringAngleDeg,
CS.out.steeringAngleDeg - CarControllerParams.C1_ANGLE_ERROR,
CS.out.steeringAngleDeg + CarControllerParams.C1_ANGLE_ERROR,
))
apply_angle = apply_std_steer_angle_limits(
desired_angle, self.apply_angle_last, CS.out.vEgoRaw,
CS.out.steeringAngleDeg, lat_active, CarControllerParams.C1_ANGLE_LIMITS,
)
can_sends.append(create_c1_pscm_message(self.packer, CS.c1_msg_pscm))
can_sends.append(create_c1_steering_control(self.packer, apply_angle, lat_active))
self.apply_angle_last = apply_angle
if CC.cruiseControl.cancel and self.frame % 10 == 0:
can_sends.append(create_c1_cancel(self.packer))
new_actuators = actuators.as_builder()
new_actuators.steeringAngleDeg = self.apply_angle_last
self.frame += 1
return new_actuators, can_sends
+93 -5
View File
@@ -1,8 +1,9 @@
from cereal import custom from cereal import custom
from opendbc.car import structs, Bus from opendbc.car import Bus, ButtonType, create_button_events, structs
from opendbc.can.parser import CANParser from opendbc.can.parser import CANParser
from opendbc.car.volvo.values import DBC, VolvoSPAPlatformConfig, CAR from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase from opendbc.car.interfaces import CarStateBase
from opendbc.car.volvo.values import CAR, DBC, VolvoC1PlatformConfig, VolvoSPAPlatformConfig
GearShifter = structs.CarState.GearShifter GearShifter = structs.CarState.GearShifter
TransmissionType = structs.CarParams.TransmissionType TransmissionType = structs.CarParams.TransmissionType
@@ -11,12 +12,12 @@ TransmissionType = structs.CarParams.TransmissionType
# Must match VOLVO_SPEED_TO_MS in opendbc/safety/modes/volvo.h. # Must match VOLVO_SPEED_TO_MS in opendbc/safety/modes/volvo.h.
SPEED_TO_MS = 0.003977 SPEED_TO_MS = 0.003977
STEERING_PRESSED_THRESHOLD = 2 STEERING_PRESSED_THRESHOLD = 2
STEERING_DISENGAGE_THRESHOLD = 5
class CarState(CarStateBase): class CarState(CarStateBase):
def __init__(self, CP, FPCP): def __init__(self, CP, FPCP):
super().__init__(CP, FPCP) super().__init__(CP, FPCP)
self.is_c1 = isinstance(CAR(CP.carFingerprint).config, VolvoC1PlatformConfig)
self.is_spa = isinstance(CAR(CP.carFingerprint).config, VolvoSPAPlatformConfig) self.is_spa = isinstance(CAR(CP.carFingerprint).config, VolvoSPAPlatformConfig)
self.gas_pressed_prev = False self.gas_pressed_prev = False
self.dispatch_lca_2_msg = False self.dispatch_lca_2_msg = False
@@ -35,8 +36,22 @@ class CarState(CarStateBase):
self.msg_lca_4 = {} self.msg_lca_4 = {}
self.msg_lca_6 = {} self.msg_lca_6 = {}
self.msg_lca_7 = {} self.msg_lca_7 = {}
self.c1_msg_pscm = {}
self.c1_lka_torque = 0
self.c1_button_states = {
"ACCOnOffBtn": False,
"ACCStopBtn": False,
"ACCSetBtn": False,
"ACCResumeBtn": False,
"ACCMinusBtn": False,
"TimeGapIncreaseBtn": False,
"TimeGapDecreaseBtn": False,
}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState: def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
if self.is_c1:
return self._update_c1(can_parsers)
cp_main = can_parsers[Bus.main] cp_main = can_parsers[Bus.main]
cp_pt = can_parsers[Bus.pt] cp_pt = can_parsers[Bus.pt]
cp_party = can_parsers[Bus.party] cp_party = can_parsers[Bus.party]
@@ -75,11 +90,9 @@ class CarState(CarStateBase):
ret.steeringAngleDeg = cp_party.vl['PSCM']['PSCM_ANGLE_SENSOR'] # openpilot expects a negative value for a right turn ret.steeringAngleDeg = cp_party.vl['PSCM']['PSCM_ANGLE_SENSOR'] # openpilot expects a negative value for a right turn
#ret.steeringAngleDeg = cp_party.vl['SAS']['SAS_ANGLE_SENSOR'] #ret.steeringAngleDeg = cp_party.vl['SAS']['SAS_ANGLE_SENSOR']
# Driver steering torque feedback (used for driver override detection)
ret.steeringTorque = -cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'] # Car right turn is negative, openpilot right turn is positive ret.steeringTorque = -cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'] # Car right turn is negative, openpilot right turn is positive
driver_input = abs(cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT']) driver_input = abs(cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'])
ret.steeringPressed = driver_input > STEERING_PRESSED_THRESHOLD ret.steeringPressed = driver_input > STEERING_PRESSED_THRESHOLD
ret.steeringDisengage = driver_input > STEERING_DISENGAGE_THRESHOLD
# EPS status - placeholder until actual signal is found # EPS status - placeholder until actual signal is found
self.eps_active = True # Assume EPS is active for now self.eps_active = True # Assume EPS is active for now
@@ -140,8 +153,83 @@ class CarState(CarStateBase):
fp_ret = custom.StarPilotCarState.new_message() fp_ret = custom.StarPilotCarState.new_message()
return ret, fp_ret return ret, fp_ret
def _update_c1(self, can_parsers):
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
ret = structs.CarState()
ret.vEgoRaw = cp.vl["VehicleSpeed1"]["VehicleSpeed"] * CV.KPH_TO_MS
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = ret.vEgoRaw < 0.1
ret.steeringAngleDeg = cp.vl["PSCM1"]["SteeringAngleServo"]
ret.steeringTorque = cp.vl["PSCM1"]["LKATorque"]
ret.steeringPressed = False
ret.gasPressed = cp.vl["PedalandBrake"]["AccPedal"] > 5.0
ret.brakePressed = bool(cp.vl["PedalandBrake"]["BrakePedalActive2"] or
cp.vl["PedalandBrake"]["BrakePedalActive"])
ret.gearShifter = {
0: GearShifter.park,
1: GearShifter.reverse,
2: GearShifter.neutral,
3: GearShifter.drive,
}.get(int(cp.vl["TCM0"]["GearShifter"]), GearShifter.unknown)
ret.cruiseState.available = bool(cp_cam.vl["FSM0"]["ACCStatusOnOff"])
ret.cruiseState.enabled = bool(cp_cam.vl["FSM0"]["ACCStatusActive"])
ret.cruiseState.speed = cp.vl["ACC"]["SpeedTargetACC"] * CV.KPH_TO_MS
ret.cruiseState.nonAdaptive = False
ret.cruiseState.standstill = ret.standstill
turn_signal = int(cp.vl["MiscCarInfo"]["TurnSignal"])
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_stalk(
50, turn_signal == 1, turn_signal == 3)
ret.doorOpen = False
ret.seatbeltUnlatched = False
button_types = {
"ACCOnOffBtn": ButtonType.mainCruise,
"ACCStopBtn": ButtonType.cancel,
"ACCSetBtn": ButtonType.setCruise,
"ACCResumeBtn": ButtonType.resumeCruise,
"ACCMinusBtn": ButtonType.decelCruise,
"TimeGapIncreaseBtn": ButtonType.gapAdjustCruise,
"TimeGapDecreaseBtn": ButtonType.gapAdjustCruise,
}
button_events = []
for signal, button_type in button_types.items():
pressed = bool(cp.vl["CCButtons"][signal])
button_events.extend(create_button_events(pressed, self.c1_button_states[signal], {True: button_type}))
self.c1_button_states[signal] = pressed
ret.buttonEvents = button_events
self.c1_msg_pscm = cp.vl["PSCM1"]
self.c1_lka_torque = int(cp.vl["PSCM1"]["LKATorque"])
fp_ret = custom.StarPilotCarState.new_message()
return ret, fp_ret
@staticmethod @staticmethod
def get_can_parsers(CP): def get_can_parsers(CP):
if isinstance(CAR(CP.carFingerprint).config, VolvoC1PlatformConfig):
return {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [
("VehicleSpeed1", 50),
("CCButtons", 100),
("PSCM1", 50),
("PedalandBrake", 100),
("TCM0", 10),
("ACC", 17),
("MiscCarInfo", 25),
], 0),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.cam], [
("FSM0", 100),
("FSM1", 50),
], 2),
}
return { return {
Bus.main: CANParser(DBC[CP.carFingerprint][Bus.main], [], 0), Bus.main: CANParser(DBC[CP.carFingerprint][Bus.main], [], 0),
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 1), Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 1),
@@ -3,6 +3,14 @@
from opendbc.car.volvo.values import CAR from opendbc.car.volvo.values import CAR
FINGERPRINTS = { FINGERPRINTS = {
CAR.VOLVO_V40: [
# V40 2017
{8: 8, 16: 8, 48: 8, 64: 8, 85: 8, 101: 8, 112: 8, 114: 8, 117: 8, 128: 8, 176: 8, 192: 8, 208: 8, 224: 8, 240: 8, 245: 8, 256: 8, 272: 8, 288: 8, 291: 8, 293: 8, 304: 8, 325: 8, 336: 8, 352: 8, 424: 8, 432: 8, 437: 8, 464: 8, 472: 8, 480: 8, 528: 8, 608: 8, 624: 8, 640: 8, 648: 8, 652: 8, 656: 8, 657: 8, 681: 8, 693: 8, 704: 8, 707: 8, 709: 8, 816: 8, 832: 8, 848: 8, 853: 8, 864: 8, 880: 8, 912: 8, 928: 8, 943: 8, 944: 8, 968: 8, 970: 8, 976: 8, 992: 8, 997: 8, 1024: 8, 1029: 8, 1061: 8, 1072: 8, 1409: 8},
# V40 2015
{8: 8, 16: 8, 64: 8, 85: 8, 101: 8, 112: 8, 114: 8, 117: 8, 128: 8, 176: 8, 192: 8, 224: 8, 240: 8, 245: 8, 256: 8, 272: 8, 288: 8, 291: 8, 293: 8, 304: 8, 325: 8, 336: 8, 424: 8, 432: 8, 437: 8, 464: 8, 472: 8, 480: 8, 528: 8, 608: 8, 648: 8, 652: 8, 656: 8, 657: 8, 681: 8, 693: 8, 704: 8, 707: 8, 709: 8, 816: 8, 832: 8, 864: 8, 880: 8, 912: 8, 928: 8, 943: 8, 944: 8, 968: 8, 970: 8, 976: 8, 992: 8, 997: 8, 1024: 8, 1029: 8, 1061: 8, 1072: 8, 1409: 8},
# V40 2014
{8: 8, 16: 8, 64: 8, 85: 8, 101: 8, 112: 8, 114: 8, 117: 8, 128: 8, 176: 8, 192: 8, 224: 8, 240: 8, 245: 8, 256: 8, 272: 8, 288: 8, 291: 8, 293: 8, 304: 8, 325: 8, 336: 8, 424: 8, 432: 8, 437: 8, 464: 8, 472: 8, 480: 8, 528: 8, 608: 8, 648: 8, 652: 8, 657: 8, 681: 8, 693: 8, 704: 8, 707: 8, 709: 8, 816: 8, 864: 8, 880: 8, 912: 8, 928: 8, 943: 8, 944: 8, 968: 8, 970: 8, 976: 8, 992: 8, 997: 8, 1024: 8, 1029: 8, 1072: 8, 1409: 8},
],
CAR.VOLVO_XC40_RECHARGE: [{ CAR.VOLVO_XC40_RECHARGE: [{
21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 304: 8, 336: 8, 339: 8, 341: 8, 394: 8, 395: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1302: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8 21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 304: 8, 336: 8, 339: 8, 341: 8, 394: 8, 395: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1302: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8
}], }],
+11 -6
View File
@@ -2,11 +2,10 @@ from opendbc.car import structs, get_safety_config
from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.volvo.carcontroller import CarController from opendbc.car.volvo.carcontroller import CarController
from opendbc.car.volvo.carstate import CarState from opendbc.car.volvo.carstate import CarState
from opendbc.car.volvo.values import VolvoSPAPlatformConfig, CAR from opendbc.car.volvo.values import CAR, VolvoC1PlatformConfig, VolvoSafetyFlags, VolvoSPAPlatformConfig
TransmissionType = structs.CarParams.TransmissionType TransmissionType = structs.CarParams.TransmissionType
VOLVO_FLAG_SPA = 1
SAFETY_VOLVO = structs.CarParams.SafetyModel.volvo SAFETY_VOLVO = structs.CarParams.SafetyModel.volvo
@@ -18,17 +17,20 @@ class CarInterface(CarInterfaceBase):
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
ret.brand = 'volvo' ret.brand = 'volvo'
platform = CAR(candidate).config
safety_param = 0 safety_param = 0
if isinstance(CAR(candidate).config, VolvoSPAPlatformConfig): if isinstance(platform, VolvoSPAPlatformConfig):
safety_param = VOLVO_FLAG_SPA safety_param = VolvoSafetyFlags.SPA.value
elif isinstance(platform, VolvoC1PlatformConfig):
safety_param = VolvoSafetyFlags.C1.value
ret.safetyConfigs = [get_safety_config(SAFETY_VOLVO, safety_param)] ret.safetyConfigs = [get_safety_config(SAFETY_VOLVO, safety_param)]
#ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.noOutput)] #ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.noOutput)]
ret.dashcamOnly = False ret.dashcamOnly = False
ret.steerActuatorDelay = 0.3 ret.steerActuatorDelay = 0.2 if isinstance(platform, VolvoC1PlatformConfig) else 0.3
ret.steerLimitTimer = 0.1 ret.steerLimitTimer = 0.1
ret.steerAtStandstill = True ret.steerAtStandstill = not isinstance(platform, VolvoC1PlatformConfig)
# Use angle-based steering control for Volvo CMA platform # Use angle-based steering control for Volvo CMA platform
ret.steerControlType = structs.CarParams.SteerControlType.angle ret.steerControlType = structs.CarParams.SteerControlType.angle
@@ -39,4 +41,7 @@ class CarInterface(CarInterfaceBase):
ret.pcmCruise = True ret.pcmCruise = True
if isinstance(platform, VolvoC1PlatformConfig):
ret.transmissionType = TransmissionType.automatic
return ret return ret
@@ -0,0 +1,57 @@
import pytest
from cereal import custom
from opendbc.can.packer import CANPacker
from opendbc.car import Bus, ButtonType, CanData, structs
from opendbc.car.volvo.carstate import CarState
from opendbc.car.volvo.interface import CarInterface
from opendbc.car.volvo.values import CAR, DBC
def _can_data(msg):
address, data, bus = msg
return CanData(address, data, bus)
def test_c1_carstate_decodes_vehicle_and_cruise_signals():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
cs = CarState(cp, custom.StarPilotCarParams.new_message())
parsers = CarState.get_can_parsers(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
messages = [
packer.make_can_msg("VehicleSpeed1", 0, {"VehicleSpeed": 72}),
packer.make_can_msg("CCButtons", 0, {"ACCStopBtn": 1, "ACCSetBtn": 1}),
packer.make_can_msg("PSCM1", 0, {"SteeringAngleServo": -12.5, "LKATorque": 7}),
packer.make_can_msg("PedalandBrake", 0, {"AccPedal": 6, "BrakePedalActive2": 1}),
packer.make_can_msg("TCM0", 0, {"GearShifter": 3}),
packer.make_can_msg("ACC", 0, {"SpeedTargetACC": 100}),
packer.make_can_msg("MiscCarInfo", 0, {"TurnSignal": 1}),
packer.make_can_msg("FSM0", 2, {"ACCStatusOnOff": 1, "ACCStatusActive": 1}),
packer.make_can_msg("FSM1", 2, {}),
]
packets = [(1_000_000, [_can_data(msg) for msg in messages])]
for parser in parsers.values():
parser.update(packets)
ret, _ = cs.update(parsers, None)
assert ret.vEgoRaw == pytest.approx(20.0)
assert ret.steeringAngleDeg == pytest.approx(-12.5, abs=0.05)
assert ret.steeringTorque == 7
assert ret.gasPressed and ret.brakePressed
assert ret.gearShifter == structs.CarState.GearShifter.drive
assert ret.cruiseState.available and ret.cruiseState.enabled
assert ret.cruiseState.speed == pytest.approx(100 / 3.6)
assert ret.leftBlinker and not ret.rightBlinker
assert len(ret.buttonEvents) == 2
assert any(event.type == ButtonType.cancel and event.pressed for event in ret.buttonEvents)
assert any(event.type == ButtonType.setCruise and event.pressed for event in ret.buttonEvents)
release = packer.make_can_msg("CCButtons", 0, {})
packets = [(2_000_000, [_can_data(release)])]
for parser in parsers.values():
parser.update(packets)
ret, _ = cs.update(parsers, None)
assert len(ret.buttonEvents) == 2
assert any(event.type == ButtonType.cancel and not event.pressed for event in ret.buttonEvents)
assert any(event.type == ButtonType.setCruise and not event.pressed for event in ret.buttonEvents)
@@ -4,7 +4,8 @@ from types import SimpleNamespace
from opendbc.car.volvo.carcontroller import CarController from opendbc.car.volvo.carcontroller import CarController
from opendbc.car.volvo.helpers import checksum_lca_5_message from opendbc.car.volvo.helpers import checksum_lca_5_message
from opendbc.car.volvo.interface import CarInterface from opendbc.car.volvo.interface import CarInterface
from opendbc.car.volvo.values import DBC from opendbc.car.volvo.values import CAR, DBC
from opendbc.car.volvo.volvocan import create_c1_checksum
def _zero_message(): def _zero_message():
@@ -67,3 +68,70 @@ def test_controller_relays_stock_lca5_angle_when_inactive():
if raw & (1 << 14): if raw & (1 << 14):
raw -= 1 << 15 raw -= 1 << 15
assert abs(raw * 0.05596 - 12.0) < 0.1 assert abs(raw * 0.05596 - 12.0) < 0.1
def _c1_state():
return SimpleNamespace(
out=SimpleNamespace(steeringAngleDeg=10.0, vEgo=12.0, vEgoRaw=12.0),
c1_lka_torque=5,
c1_msg_pscm=_zero_message(),
)
def test_c1_controller_emits_checked_steering_and_pscm_relay():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
controller = CarController(DBC[cp.carFingerprint], cp)
cc = SimpleNamespace(
latActive=True,
actuators=_Actuators(),
cruiseControl=SimpleNamespace(cancel=False),
)
actuators, can_sends = controller.update(cc, _c1_state(), 0, None)
assert [(msg[0], msg[2]) for msg in can_sends] == [(0x125, 2), (0xD0, 0)]
fsm = can_sends[1][1]
assert fsm[7] & 0x3 == 3
assert fsm[6] == create_c1_checksum(fsm)
assert 0.0 < actuators.steeringAngleDeg <= 2.0
def test_c1_controller_sends_only_cancel_button():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
controller = CarController(DBC[cp.carFingerprint], cp)
cc = SimpleNamespace(
latActive=False,
actuators=_Actuators(),
cruiseControl=SimpleNamespace(cancel=True),
)
_, can_sends = controller.update(cc, _c1_state(), 0, None)
buttons = next(msg for msg in can_sends if msg[0] == 0x10)
assert buttons[2] == 0
assert buttons[1][7] == 0x10
assert buttons[1][6] == 0
def test_c1_controller_temporarily_drops_steering_on_zero_torque_fault():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
controller = CarController(DBC[cp.carFingerprint], cp)
cs = _c1_state()
cs.c1_lka_torque = 0
cc = SimpleNamespace(
latActive=True,
actuators=_Actuators(),
cruiseControl=SimpleNamespace(cancel=False),
)
directions = []
for _ in range(23):
_, can_sends = controller.update(cc, cs, 0, None)
directions.extend(msg[1][7] & 0x3 for msg in can_sends if msg[0] == 0xD0)
assert directions[:-1] == [3] * 11
assert directions[-1] == 0
while controller.frame <= 122:
_, can_sends = controller.update(cc, cs, 0, None)
fsm = next(msg for msg in can_sends if msg[0] == 0xD0)
assert fsm[1][7] & 0x3 == 3
+42
View File
@@ -1,13 +1,23 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import IntFlag
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms
from opendbc.car.lateral import AngleSteeringLimits from opendbc.car.lateral import AngleSteeringLimits
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.docs_definitions import CarDocs, CarHarness, CarParts from opendbc.car.docs_definitions import CarDocs, CarHarness, CarParts
from opendbc.car.fw_query_definitions import FwQueryConfig from opendbc.car.fw_query_definitions import FwQueryConfig
Ecu = CarParams.Ecu Ecu = CarParams.Ecu
# C1 support is adapted from the original dragonpilot V40 port:
# https://github.com/dragonpilot/dragonpilot/commit/773dce507082d931236b64dca8024dce9625446f
class VolvoSafetyFlags(IntFlag):
SPA = 1
C1 = 2
class CarControllerParams: class CarControllerParams:
STEER_STEP = 1 # 100 Hz LCA command frequency (controlsd runs at 100 Hz) STEER_STEP = 1 # 100 Hz LCA command frequency (controlsd runs at 100 Hz)
@@ -81,6 +91,19 @@ class CarControllerParams:
([0., 5., 25.], [5., 2., .3]), # rate down limits at different speeds ([0., 5., 25.], [5., 2., .3]), # rate down limits at different speeds
) )
C1_STEER_NO = 0
C1_STEER = 3
C1_N_ZERO_TORQUE = 12
C1_ANGLE_ERROR = 20.0
C1_ANGLE_DELTA_BP = [0., 8.33, 13.89, 19.44, 25., 30.55, 36.1]
C1_ANGLE_DELTA_UP = [2., 1.2, .25, .20, .15, .10, .10]
C1_ANGLE_DELTA_DOWN = [2., 1.2, .25, .20, .15, .10, .10]
C1_ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
359.9,
(C1_ANGLE_DELTA_BP, C1_ANGLE_DELTA_UP),
(C1_ANGLE_DELTA_BP, C1_ANGLE_DELTA_DOWN),
)
@dataclass @dataclass
class VolvoCarDocs(CarDocs): class VolvoCarDocs(CarDocs):
@@ -105,7 +128,26 @@ class VolvoSPAPlatformConfig(PlatformConfig):
}) })
@dataclass
class VolvoC1PlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: {
Bus.pt: 'volvo_v40_2017_pt',
Bus.cam: 'volvo_v40_2017_pt',
})
class CAR(Platforms): class CAR(Platforms):
VOLVO_V40 = VolvoC1PlatformConfig(
[VolvoCarDocs("Volvo V40 2013-19")],
CarSpecs(
mass=1610,
wheelbase=2.647,
steerRatio=14.7,
centerToFrontRatio=0.44,
minSteerSpeed=1.0 * CV.KPH_TO_MS,
),
)
VOLVO_XC40_RECHARGE = VolvoCMAPlatformConfig( VOLVO_XC40_RECHARGE = VolvoCMAPlatformConfig(
[VolvoCarDocs("Volvo XC40 Recharge 2021-23")], [VolvoCarDocs("Volvo XC40 Recharge 2021-23")],
CarSpecs( CarSpecs(
@@ -2,6 +2,47 @@ from opendbc.car.volvo.helpers import (checksum_lca_2_message, checksum_2_0x69_m
checksum_2_pscm_related_message, checksum_lca_5_message) checksum_2_pscm_related_message, checksum_lca_5_message)
from opendbc.car.carlog import carlog from opendbc.car.carlog import carlog
def create_c1_pscm_message(packer, msg_pscm: dict):
values = {
"LKATorque": 0,
"SteeringAngleServo": msg_pscm["SteeringAngleServo"],
"byte0": msg_pscm["byte0"],
"byte3": msg_pscm["byte3"],
"byte4": msg_pscm["byte4"],
"byte7": msg_pscm["byte7"],
"LKAActive": int(msg_pscm["LKAActive"]) & 0xD,
}
return packer.make_can_msg("PSCM1", 2, values)
def create_c1_checksum(data: bytes) -> int:
angle_raw = ((data[4] & 0x3F) << 8) | data[5]
direction = data[7] & 0x3
checksum_sum = (data[3] + direction + angle_raw + (angle_raw >> 8)) & 0xFF
return checksum_sum ^ 0xFF
def create_c1_steering_control(packer, apply_angle: float, lat_active: bool):
values = {
"SET_X_E3": 0xE3,
"SET_X_B4": 0xB4,
"SET_X_08": 0x08,
"TrqLim": 0,
"LKAAngleReq": apply_angle,
"LKASteerDirection": 3 if lat_active else 0,
"SET_X_25": 0x25,
"SET_X_02": 0x02,
}
data = packer.make_can_msg("FSM1", 0, values)[1]
values["Checksum"] = create_c1_checksum(data)
return packer.make_can_msg("FSM1", 0, values)
def create_c1_cancel(packer):
return packer.make_can_msg("CCButtons", 0, {"ACCStopBtn": 1})
def create_lca_message(packer, lat_active: bool, apply_angle: float, msg_lca: dict, def create_lca_message(packer, lat_active: bool, apply_angle: float, msg_lca: dict,
authority_pos: int = 614, authority_neg: int = -614, authority_pos: int = 614, authority_neg: int = -614,
overrides: dict | None = None): overrides: dict | None = None):
File diff suppressed because it is too large Load Diff
@@ -1,10 +1,5 @@
CM_ "IMPORT _subaru_global.dbc"; CM_ "IMPORT _subaru_global.dbc";
BO_ 811 AVH: 8 XXX
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
SG_ AVH : 45|1@0+ (1,0) [0|1] "" XXX
BO_ 72 Transmission: 8 XXX BO_ 72 Transmission: 8 XXX
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
@@ -1497,6 +1497,7 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
SG_ RAY_LKAS_BTN : 4|1@0+ (1,0) [0|1] "" XXX
BO_ 1426 LABEL11: 8 XXX BO_ 1426 LABEL11: 8 XXX
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
@@ -1497,6 +1497,7 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
SG_ RAY_LKAS_BTN : 4|1@0+ (1,0) [0|1] "" XXX
BO_ 1426 LABEL11: 8 XXX BO_ 1426 LABEL11: 8 XXX
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
@@ -0,0 +1,25 @@
VERSION ""
NS_ :
NS_DESC_
CM_
BA_DEF_
BA_
VAL_
BS_:
BU_: XXX
BO_ 1157 LFAHDA_MFC: 8 XXX
SG_ HDA_USM : 0|2@1+ (1,0) [0|3] "" XXX
SG_ HDA_Active : 2|1@1+ (1,0) [0|1] "" XXX
SG_ HDA_Icon_State : 3|2@1+ (1,0) [0|3] "" XXX
SG_ HDA_Chime : 7|1@1+ (1,0) [0|1] "" XXX
SG_ HDA_VSetReq : 8|8@1+ (1,0) [0|255] "km/h" XXX
SG_ LFA_SysWarning : 16|3@1+ (1,0) [0|7] "" XXX
SG_ HDA_Icon_Wheel : 20|1@1+ (1,0) [0|1] "" XXX
SG_ HDA_LdwSysState : 21|2@1+ (1,0) [0|3] "" XXX
SG_ LFA_Icon_State : 24|2@1+ (1,0) [0|3] "" XXX
SG_ LFA_USM : 27|2@1+ (1,0) [0|3] "" XXX
SG_ HDA_SysWarning : 29|2@1+ (1,0) [0|3] "" XXX
File diff suppressed because it is too large Load Diff
@@ -307,11 +307,6 @@ VAL_ 544 AEB_Status 12 "AEB related" 8 "AEB actuation" 4 "AEB related" 0 "No AEB
CM_ "subaru_global_2017.dbc starts here"; CM_ "subaru_global_2017.dbc starts here";
BO_ 811 AVH: 8 XXX
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
SG_ AVH : 45|1@0+ (1,0) [0|1] "" XXX
BO_ 72 Transmission: 8 XXX BO_ 72 Transmission: 8 XXX
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
+1
View File
@@ -11,3 +11,4 @@ class ALTERNATIVE_EXPERIENCE:
ALWAYS_ON_LATERAL = 32 ALWAYS_ON_LATERAL = 32
GM_REMAP_CANCEL_TO_DISTANCE = 64 GM_REMAP_CANCEL_TO_DISTANCE = 64
TOYOTA_AUTO_HOLD = 128
@@ -339,6 +339,7 @@ extern bool gm_remote_start_boots_comma;
#define ALT_EXP_ALWAYS_ON_LATERAL 32 #define ALT_EXP_ALWAYS_ON_LATERAL 32
#define ALT_EXP_GM_REMAP_CANCEL_TO_DISTANCE 64 #define ALT_EXP_GM_REMAP_CANCEL_TO_DISTANCE 64
#define ALT_EXP_TOYOTA_AUTO_HOLD 128
extern int alternative_experience; extern int alternative_experience;
+7 -62
View File
@@ -2,6 +2,12 @@
#include "opendbc/safety/declarations.h" #include "opendbc/safety/declarations.h"
// StarPilot's extended Ford curvature enforcement below is substantially adapted from
// BluePilot bp-7.0 panda work, principally Alan Polk's 8f8d6d15f0a590f42b78de964ffb0d0af7f5d63d
// See /CREDITS.md and /THIRD_PARTY_NOTICES.md. This comment does not attribute the surrounding
// upstream openpilot code.
// Safety-relevant CAN messages for Ford vehicles. // Safety-relevant CAN messages for Ford vehicles.
#define FORD_EngBrakeData 0x165U // RX from PCM, for driver brake pedal and cruise state #define FORD_EngBrakeData 0x165U // RX from PCM, for driver brake pedal and cruise state
#define FORD_EngVehicleSpThrottle 0x204U // RX from PCM, for driver throttle input #define FORD_EngVehicleSpThrottle 0x204U // RX from PCM, for driver throttle input
@@ -88,10 +94,8 @@ static bool ford_get_quality_flag_valid(const CANPacket_t *msg) {
static bool ford_lka_steering = false; static bool ford_lka_steering = false;
static bool ford_extended_lateral = false; static bool ford_extended_lateral = false;
static bool ford_angle_mode = false;
static bool ford_longitudinal = false; static bool ford_longitudinal = false;
static bool ford_cancel_resume_button = false; static bool ford_cancel_resume_button = false;
static int16_t ford_shadow_curvature = 0;
// Curvature rate limits // Curvature rate limits
#define FORD_LIMITS(limit_lateral_acceleration) { \ #define FORD_LIMITS(limit_lateral_acceleration) { \
@@ -138,38 +142,6 @@ static const AngleSteeringLimits FORD_STEERING_LIMITS = FORD_LIMITS(false);
static const AngleSteeringLimits FORD_EXTENDED_STEERING_LIMITS = FORD_EXTENDED_LIMITS(false); static const AngleSteeringLimits FORD_EXTENDED_STEERING_LIMITS = FORD_EXTENDED_LIMITS(false);
static int ford_desired_path_angle_last = 0;
static bool ford_path_angle_checks(int desired_path_angle, bool steer_control_enabled) {
bool violation = false;
if (steer_control_enabled) {
float speed = ((float)vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.0;
const struct lookup_t path_angle_rate = {
.x = {10., 15., 25.},
.y = {0.0561, 0.04335, 0.00918},
};
int max_delta = (safety_interpolate(path_angle_rate, speed) * 2000.0) + 1.0;
violation |= safety_max_limit_check(desired_path_angle,
ford_desired_path_angle_last + max_delta,
ford_desired_path_angle_last - max_delta);
} else {
violation |= desired_path_angle != 0;
}
ford_desired_path_angle_last = violation ? 0 : desired_path_angle;
return violation;
}
static bool ford_shadow_curvature_check(int desired_curvature, bool steer_control_enabled,
const AngleSteeringLimits limits) {
if (steer_control_enabled && limits.enforce_angle_error &&
((vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR) > limits.angle_error_min_speed)) {
int lowest_allowed = angle_meas.min - limits.max_angle_error - 1;
int highest_allowed = angle_meas.max + limits.max_angle_error + 1;
return safety_max_limit_check(desired_curvature, highest_allowed, lowest_allowed);
}
return false;
}
static void ford_rx_hook(const CANPacket_t *msg) { static void ford_rx_hook(const CANPacket_t *msg) {
if (msg->bus == FORD_MAIN_BUS) { if (msg->bus == FORD_MAIN_BUS) {
// Update in motion state from standstill signal // Update in motion state from standstill signal
@@ -300,10 +272,8 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
} }
if (!ford_lka_steering) { if (!ford_lka_steering) {
ford_angle_mode = (msg->data[4] & 0x1U) != 0U;
ford_extended_lateral = (msg->data[4] & 0x2U) != 0U; ford_extended_lateral = (msg->data[4] & 0x2U) != 0U;
ford_shadow_curvature = (int16_t)((msg->data[5] << 8) | msg->data[6]); if ((msg->data[4] & 0x1U) != 0U) {
if (ford_angle_mode && !ford_extended_lateral) {
tx = false; tx = false;
} }
} }
@@ -327,20 +297,9 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
if (ford_extended_lateral) { if (ford_extended_lateral) {
violation |= desired_path_offset != 0; violation |= desired_path_offset != 0;
violation |= (desired_curvature_rate < -4096) || (desired_curvature_rate > 4095); violation |= (desired_curvature_rate < -4096) || (desired_curvature_rate > 4095);
violation |= ford_path_angle_checks(desired_path_angle, steer_control_enabled);
if (ford_angle_mode) {
violation |= (desired_path_angle < -1000) || (desired_path_angle > 1047);
violation |= desired_curvature != 0;
violation |= steer_control_enabled && !(aol_allowed || controls_allowed);
int shadow_curvature_can = ROUND((float)ford_shadow_curvature * 0.05);
violation |= ford_shadow_curvature_check(shadow_curvature_can, steer_control_enabled,
FORD_EXTENDED_STEERING_LIMITS);
desired_angle_last = 0;
} else {
violation |= desired_path_angle != 0; violation |= desired_path_angle != 0;
violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled, violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
FORD_EXTENDED_STEERING_LIMITS); FORD_EXTENDED_STEERING_LIMITS);
}
if (!steer_control_enabled) { if (!steer_control_enabled) {
violation |= (desired_curvature != 0) || (desired_curvature_rate != 0); violation |= (desired_curvature != 0) || (desired_curvature_rate != 0);
} }
@@ -377,20 +336,9 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
if (ford_extended_lateral) { if (ford_extended_lateral) {
violation |= desired_path_offset != 0; violation |= desired_path_offset != 0;
violation |= (desired_curvature_rate < -1024) || (desired_curvature_rate > 1023); violation |= (desired_curvature_rate < -1024) || (desired_curvature_rate > 1023);
violation |= ford_path_angle_checks(desired_path_angle, steer_control_enabled);
if (ford_angle_mode) {
violation |= (desired_path_angle < -1000) || (desired_path_angle > 1047);
violation |= desired_curvature != 0;
violation |= steer_control_enabled && !(aol_allowed || controls_allowed);
int shadow_curvature_can = ROUND((float)ford_shadow_curvature * 0.05);
violation |= ford_shadow_curvature_check(shadow_curvature_can, steer_control_enabled,
FORD_CANFD_EXTENDED_STEERING_LIMITS);
desired_angle_last = 0;
} else {
violation |= desired_path_angle != 0; violation |= desired_path_angle != 0;
violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled, violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
FORD_CANFD_EXTENDED_STEERING_LIMITS); FORD_CANFD_EXTENDED_STEERING_LIMITS);
}
if (!steer_control_enabled) { if (!steer_control_enabled) {
violation |= (desired_curvature != 0) || (desired_curvature_rate != 0); violation |= (desired_curvature != 0) || (desired_curvature_rate != 0);
} }
@@ -461,10 +409,7 @@ static safety_config ford_init(uint16_t param) {
const bool ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD); const bool ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD);
ford_lka_steering = GET_FLAG(param, FORD_PARAM_LKA_STEERING); ford_lka_steering = GET_FLAG(param, FORD_PARAM_LKA_STEERING);
ford_extended_lateral = false; ford_extended_lateral = false;
ford_angle_mode = false;
ford_cancel_resume_button = false; ford_cancel_resume_button = false;
ford_shadow_curvature = 0;
ford_desired_path_angle_last = 0;
ford_longitudinal = false; ford_longitudinal = false;
@@ -1,5 +1,7 @@
#pragma once #pragma once
// Provenance: portions of HKG angle-command safety are adapted from sunnypilot/opendbc's
// hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
#include "opendbc/safety/declarations.h" #include "opendbc/safety/declarations.h"
#include "opendbc/safety/modes/hyundai_common.h" #include "opendbc/safety/modes/hyundai_common.h"
+28 -32
View File
@@ -35,6 +35,7 @@
#define MSG_SUBARU_ES_DashStatus 0x321U #define MSG_SUBARU_ES_DashStatus 0x321U
#define MSG_SUBARU_ES_LKAS_State 0x322U #define MSG_SUBARU_ES_LKAS_State 0x322U
#define MSG_SUBARU_ES_Infotainment 0x323U #define MSG_SUBARU_ES_Infotainment 0x323U
#define MSG_SUBARU_Cruise_Buttons 0x146U
#define MSG_SUBARU_ES_UDS_Request 0x787U #define MSG_SUBARU_ES_UDS_Request 0x787U
@@ -42,7 +43,6 @@
#define MSG_SUBARU_ES_STATIC_1 0x22aU #define MSG_SUBARU_ES_STATIC_1 0x22aU
#define MSG_SUBARU_ES_STATIC_2 0x325U #define MSG_SUBARU_ES_STATIC_2 0x325U
#define MSG_SUBARU_Dashlights 0x390U #define MSG_SUBARU_Dashlights 0x390U
#define MSG_SUBARU_AVH 0x32bU
#define SUBARU_MAIN_BUS 0U #define SUBARU_MAIN_BUS 0U
#define SUBARU_ALT_BUS 1U #define SUBARU_ALT_BUS 1U
@@ -57,6 +57,9 @@
#define SUBARU_COMMON_TX_MSGS(alt_bus) \ #define SUBARU_COMMON_TX_MSGS(alt_bus) \
{MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = false}, \ {MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = false}, \
#define SUBARU_REDNECK_TX_MSGS() \
{MSG_SUBARU_Cruise_Buttons, SUBARU_MAIN_BUS, 8, .check_relay = false}, \
#define SUBARU_D_PLATFORM_ANGLE_TX_MSGS(bus) \ #define SUBARU_D_PLATFORM_ANGLE_TX_MSGS(bus) \
{MSG_SUBARU_ES_LKAS_ANGLE, bus, 8, .check_relay = true}, \ {MSG_SUBARU_ES_LKAS_ANGLE, bus, 8, .check_relay = true}, \
{MSG_SUBARU_ES_DashStatus, bus, 8, .check_relay = true}, \ {MSG_SUBARU_ES_DashStatus, bus, 8, .check_relay = true}, \
@@ -66,13 +69,6 @@
#define SUBARU_STOP_START_TX_MSGS(bus) \ #define SUBARU_STOP_START_TX_MSGS(bus) \
{MSG_SUBARU_Dashlights, bus, 8, .check_relay = false}, \ {MSG_SUBARU_Dashlights, bus, 8, .check_relay = false}, \
#define SUBARU_AVH_TX_MSGS(bus) \
{MSG_SUBARU_AVH, bus, 8, .check_relay = false}, \
#define SUBARU_STOP_START_AVH_TX_MSGS(bus) \
SUBARU_STOP_START_TX_MSGS(bus) \
SUBARU_AVH_TX_MSGS(bus)
#define SUBARU_COMMON_LONG_TX_MSGS(alt_bus) \ #define SUBARU_COMMON_LONG_TX_MSGS(alt_bus) \
{MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = true}, \ {MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = true}, \
{MSG_SUBARU_ES_Brake, alt_bus, 8, .check_relay = true}, \ {MSG_SUBARU_ES_Brake, alt_bus, 8, .check_relay = true}, \
@@ -121,7 +117,7 @@ static bool subaru_lkas_angle = false;
static bool subaru_d_platform = false; static bool subaru_d_platform = false;
static bool subaru_fixed_angle_limits = false; static bool subaru_fixed_angle_limits = false;
static bool subaru_stop_start_button = false; static bool subaru_stop_start_button = false;
static bool subaru_avh_button = false; static bool subaru_redneck_cruise = false;
static uint32_t subaru_get_checksum(const CANPacket_t *msg) { static uint32_t subaru_get_checksum(const CANPacket_t *msg) {
return (uint8_t)msg->data[0]; return (uint8_t)msg->data[0];
@@ -306,10 +302,9 @@ static bool subaru_tx_hook(const CANPacket_t *msg) {
violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg); violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg);
} }
if (msg->addr == MSG_SUBARU_AVH) { if (msg->addr == MSG_SUBARU_Cruise_Buttons) {
violation |= !subaru_avh_button; violation |= !subaru_redneck_cruise;
violation |= msg->bus != (subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS); violation |= msg->bus != SUBARU_MAIN_BUS;
violation |= !GET_BIT(msg, 45U);
violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg); violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg);
} }
@@ -325,6 +320,19 @@ static safety_config subaru_init(uint16_t param) {
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS) SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
}; };
static const CanMsg SUBARU_REDNECK_TX_MSGS_CONFIG[] = {
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
SUBARU_REDNECK_TX_MSGS()
};
static const CanMsg SUBARU_REDNECK_STOP_AND_GO_TX_MSGS_CONFIG[] = {
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
SUBARU_REDNECK_TX_MSGS()
SUBARU_STOP_AND_GO_ADDITIONAL_TX_MSGS()
};
static const CanMsg SUBARU_LONG_TX_MSGS[] = { static const CanMsg SUBARU_LONG_TX_MSGS[] = {
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS) SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_LONG_TX_MSGS(SUBARU_MAIN_BUS) SUBARU_COMMON_LONG_TX_MSGS(SUBARU_MAIN_BUS)
@@ -363,12 +371,6 @@ static safety_config subaru_init(uint16_t param) {
SUBARU_STOP_START_TX_MSGS(SUBARU_ALT_BUS) SUBARU_STOP_START_TX_MSGS(SUBARU_ALT_BUS)
}; };
static const CanMsg SUBARU_GEN2_LKAS_ANGLE_STOP_START_AVH_TX_MSGS[] = {
SUBARU_BASE_TX_MSGS(SUBARU_ALT_BUS, MSG_SUBARU_ES_LKAS_ANGLE)
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
SUBARU_STOP_START_AVH_TX_MSGS(SUBARU_ALT_BUS)
};
static const CanMsg SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS[] = { static const CanMsg SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS[] = {
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_MAIN_BUS) SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_MAIN_BUS)
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS) SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
@@ -380,12 +382,6 @@ static safety_config subaru_init(uint16_t param) {
SUBARU_STOP_START_TX_MSGS(SUBARU_ALT_BUS) SUBARU_STOP_START_TX_MSGS(SUBARU_ALT_BUS)
}; };
static const CanMsg SUBARU_D_PLATFORM_ANGLE_STOP_START_AVH_MAIN_TX_MSGS[] = {
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_MAIN_BUS)
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
SUBARU_STOP_START_AVH_TX_MSGS(SUBARU_ALT_BUS)
};
static const CanMsg SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS[] = { static const CanMsg SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS[] = {
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_CAM_BUS) SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_CAM_BUS)
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS) SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
@@ -433,8 +429,8 @@ static safety_config subaru_init(uint16_t param) {
const uint16_t SUBARU_PARAM_STOP_START_BUTTON = 256; const uint16_t SUBARU_PARAM_STOP_START_BUTTON = 256;
subaru_stop_start_button = GET_FLAG(param, SUBARU_PARAM_STOP_START_BUTTON); subaru_stop_start_button = GET_FLAG(param, SUBARU_PARAM_STOP_START_BUTTON);
const uint16_t SUBARU_PARAM_AVH_BUTTON = 512; const uint16_t SUBARU_PARAM_REDNECK_CRUISE = 512;
subaru_avh_button = GET_FLAG(param, SUBARU_PARAM_AVH_BUTTON); subaru_redneck_cruise = GET_FLAG(param, SUBARU_PARAM_REDNECK_CRUISE);
#ifdef ALLOW_DEBUG #ifdef ALLOW_DEBUG
const uint16_t SUBARU_PARAM_LONGITUDINAL = 2; const uint16_t SUBARU_PARAM_LONGITUDINAL = 2;
@@ -443,19 +439,19 @@ static safety_config subaru_init(uint16_t param) {
safety_config ret; safety_config ret;
if (subaru_lkas_angle) { if (subaru_lkas_angle) {
ret = subaru_d_platform ? (subaru_stop_start_button ? (subaru_avh_button ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_STOP_START_AVH_MAIN_TX_MSGS) : \ ret = subaru_d_platform ? (subaru_stop_start_button ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_STOP_START_MAIN_TX_MSGS) : \
BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_STOP_START_MAIN_TX_MSGS)) : \
(subaru_d_platform_camera ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS) : \ (subaru_d_platform_camera ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS) : \
BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS))) : \ BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS))) : \
subaru_gen2 ? (subaru_stop_start_button ? (subaru_avh_button ? BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_STOP_START_AVH_TX_MSGS) : \ subaru_gen2 ? (subaru_stop_start_button ? BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_STOP_START_TX_MSGS) : \
BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_STOP_START_TX_MSGS)) : \
BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_TX_MSGS)) : \ BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_TX_MSGS)) : \
BUILD_SAFETY_CFG(subaru_lkas_angle_rx_checks, SUBARU_LKAS_ANGLE_TX_MSGS); BUILD_SAFETY_CFG(subaru_lkas_angle_rx_checks, SUBARU_LKAS_ANGLE_TX_MSGS);
} else if (subaru_gen2) { } else if (subaru_gen2) {
ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_LONG_TX_MSGS) : \ ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_LONG_TX_MSGS) : \
BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_TX_MSGS); BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_TX_MSGS);
} else { } else {
ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_LONG_TX_MSGS) : \ ret = subaru_redneck_cruise ? (subaru_stop_and_go ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_REDNECK_STOP_AND_GO_TX_MSGS_CONFIG) : \
BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_REDNECK_TX_MSGS_CONFIG)) : \
subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_LONG_TX_MSGS) : \
subaru_stop_and_go ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_STOP_AND_GO_TX_MSGS) : \ subaru_stop_and_go ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_STOP_AND_GO_TX_MSGS) : \
BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_TX_MSGS); BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_TX_MSGS);
} }
@@ -224,10 +224,16 @@ static void tesla_preap_rx_hook(const CANPacket_t *msg) {
} }
if (msg->addr == 0x368U) { if (msg->addr == 0x368U) {
const int cruise_state = (msg->data[1] >> 4) & 0x07U; const int cruise_state = (msg->data[1] >> 4) & 0x0FU;
if (cruise_state == 3) { if (cruise_state == 3) {
vehicle_moving = false; vehicle_moving = false;
} }
acc_main_on = (cruise_state == 1) ||
(cruise_state == 2) ||
(cruise_state == 3) ||
(cruise_state == 4) ||
(cruise_state == 6) ||
(cruise_state == 7);
} }
if (msg->addr == 0x118U) { if (msg->addr == 0x118U) {
+12 -17
View File
@@ -2,6 +2,8 @@
#include "opendbc/safety/declarations.h" #include "opendbc/safety/declarations.h"
#define TOYOTA_AUTO_HOLD_ACCEL -1000 // -1.0 m/s^2 in ACC_CONTROL units
// Stock longitudinal // Stock longitudinal
#define TOYOTA_BASE_TX_MSGS \ #define TOYOTA_BASE_TX_MSGS \
{0x191, 0, 8, .check_relay = true}, {0x412, 0, 8, .check_relay = true}, {0x1D2, 0, 8, .check_relay = false}, /* LKAS + LTA + PCM cancel cmd */ \ {0x191, 0, 8, .check_relay = true}, {0x412, 0, 8, .check_relay = true}, {0x1D2, 0, 8, .check_relay = false}, /* LKAS + LTA + PCM cancel cmd */ \
@@ -277,7 +279,15 @@ static bool toyota_tx_hook(const CANPacket_t *msg) {
// SecOC cars move accel to 0x183. Only allow inactive accel on 0x343 to match stock behavior // SecOC cars move accel to 0x183. Only allow inactive accel on 0x343 to match stock behavior
violation = desired_accel != TOYOTA_LONG_LIMITS.inactive_accel; violation = desired_accel != TOYOTA_LONG_LIMITS.inactive_accel;
} }
violation |= longitudinal_accel_checks(desired_accel, TOYOTA_LONG_LIMITS);
bool toyota_auto_hold =
!toyota_stock_longitudinal &&
((alternative_experience & ALT_EXP_TOYOTA_AUTO_HOLD) != 0) &&
!vehicle_moving && !gas_pressed && acc_main_on &&
(desired_accel == TOYOTA_AUTO_HOLD_ACCEL) &&
GET_BIT(msg, 30U) && !GET_BIT(msg, 31U) && !GET_BIT(msg, 24U);
violation |= !toyota_auto_hold && longitudinal_accel_checks(desired_accel, TOYOTA_LONG_LIMITS);
// only ACC messages that cancel are allowed when openpilot is not controlling longitudinal // only ACC messages that cancel are allowed when openpilot is not controlling longitudinal
if (toyota_stock_longitudinal) { if (toyota_stock_longitudinal) {
@@ -394,12 +404,7 @@ static bool toyota_tx_hook(const CANPacket_t *msg) {
tx = false; tx = false;
} }
// Auto brake hold replaces the camera AEB message only while stopped. if ((msg->addr == 0x344U) && toyota_stock_longitudinal) {
if ((msg->addr == 0x344U) && ((alternative_experience & ALT_EXP_ALLOW_AEB) != 0)) {
if (vehicle_moving || gas_pressed || !acc_main_on) {
tx = false;
}
} else if ((msg->addr == 0x344U) && toyota_stock_longitudinal) {
tx = false; tx = false;
} }
} }
@@ -566,21 +571,11 @@ static safety_config toyota_init(uint16_t param) {
return ret; return ret;
} }
static bool toyota_fwd_hook(int bus_num, int addr) {
bool block_msg = false;
if (bus_num == 2) {
block_msg = (addr == 0x344) && ((alternative_experience & ALT_EXP_ALLOW_AEB) != 0) &&
!vehicle_moving && !gas_pressed && acc_main_on;
}
return block_msg;
}
const safety_hooks toyota_hooks = { const safety_hooks toyota_hooks = {
.init = toyota_init, .init = toyota_init,
.rx = toyota_rx_hook, .rx = toyota_rx_hook,
.rx_all = toyota_rx_all_hook, .rx_all = toyota_rx_all_hook,
.tx = toyota_tx_hook, .tx = toyota_tx_hook,
.fwd = toyota_fwd_hook,
.get_checksum = toyota_get_checksum, .get_checksum = toyota_get_checksum,
.compute_checksum = toyota_compute_checksum, .compute_checksum = toyota_compute_checksum,
.get_quality_flag_valid = toyota_get_quality_flag_valid, .get_quality_flag_valid = toyota_get_quality_flag_valid,
+118 -6
View File
@@ -2,9 +2,11 @@
#include "opendbc/safety/declarations.h" #include "opendbc/safety/declarations.h"
// safetyParam: 0 = CMA (XC40 Recharge), 1 = SPA (S60 Recharge, Polestar 2) // safetyParam: 0 = CMA (XC40 Recharge), 1 = SPA (S60 Recharge, Polestar 2),
// 2 = C1 (V40)
// Polestar 2 is technically CMA, but appears to use SPA DBC for CAN 1 bus // Polestar 2 is technically CMA, but appears to use SPA DBC for CAN 1 bus
#define VOLVO_FLAG_SPA 1U #define VOLVO_FLAG_SPA 1U
#define VOLVO_FLAG_C1 2U
// Volvo CAN message addresses shared between CMA and SPA // Volvo CAN message addresses shared between CMA and SPA
#define VOLVO_LCA_STEER 0x58U // TX from VCU1 to PSCM, LCA steering command (0x58) #define VOLVO_LCA_STEER 0x58U // TX from VCU1 to PSCM, LCA steering command (0x58)
@@ -24,6 +26,15 @@
#define VOLVO_LCA_6 0x97U // TX LCA_6 message #define VOLVO_LCA_6 0x97U // TX LCA_6 message
#define VOLVO_LCA_7 0x92U // TX LCA_7 message #define VOLVO_LCA_7 0x92U // TX LCA_7 message
// C1-specific addresses (V40). The V40 powertrain bus is bus 0 and its
// forward-camera bus is bus 2; bus 1 is unused by this port.
#define VOLVO_C1_BUTTONS 0x10U
#define VOLVO_C1_FSM_0 0x30U
#define VOLVO_C1_FSM_1 0xD0U
#define VOLVO_C1_PSCM_1 0x125U
#define VOLVO_C1_PEDAL_AND_BRAKE 0x55U
#define VOLVO_C1_SPEED 0x150U
// CMA-specific PT bus addresses // CMA-specific PT bus addresses
#define VOLVO_CMA_BUS1_SPEED 0x70U // RX vehicle speed #define VOLVO_CMA_BUS1_SPEED 0x70U // RX vehicle speed
#define VOLVO_CMA_ECM_1 0x250U // RX accelerator pedal position #define VOLVO_CMA_ECM_1 0x250U // RX accelerator pedal position
@@ -43,7 +54,10 @@
#define VOLVO_ANGLE_DEG_TO_CAN 17.869907f #define VOLVO_ANGLE_DEG_TO_CAN 17.869907f
#define VOLVO_MAX_ANGLE_CAN 9650 #define VOLVO_MAX_ANGLE_CAN 9650
#define VOLVO_RELAY_ANGLE_TOLERANCE 54 // approximately 3 degrees #define VOLVO_RELAY_ANGLE_TOLERANCE 54 // approximately 3 degrees
#define VOLVO_DRIVER_OVERRIDE 5
#define VOLVO_C1_ANGLE_DEG_TO_CAN 22.753128f
#define VOLVO_C1_MAX_ANGLE_CAN 8189
#define VOLVO_C1_RELAY_ANGLE_TOLERANCE 2
// CAN bus definitions for Volvo // CAN bus definitions for Volvo
@@ -55,6 +69,7 @@
// Runtime addresses set by volvo_init based on safetyParam // Runtime addresses set by volvo_init based on safetyParam
static uint16_t volvo_ecm_1_addr; static uint16_t volvo_ecm_1_addr;
static uint16_t volvo_bus1_cruise_control_addr; static uint16_t volvo_bus1_cruise_control_addr;
static bool volvo_c1;
static int volvo_be_15(const CANPacket_t *msg, uint8_t byte) { static int volvo_be_15(const CANPacket_t *msg, uint8_t byte) {
return (int)(((uint16_t)(msg->data[byte] & 0x7FU) << 8U) | msg->data[byte + 1U]); return (int)(((uint16_t)(msg->data[byte] & 0x7FU) << 8U) | msg->data[byte + 1U]);
@@ -68,6 +83,21 @@ static int volvo_lca_5_angle(const CANPacket_t *msg) {
return to_signed(volvo_be_15(msg, 6U), 15); return to_signed(volvo_be_15(msg, 6U), 15);
} }
static int volvo_c1_pscm_angle(const CANPacket_t *msg) {
return (int)(((uint16_t)msg->data[5] << 8U) | msg->data[6]) - 32768;
}
static int volvo_c1_fsm_angle(const CANPacket_t *msg) {
return (int)(((uint16_t)(msg->data[4] & 0x3FU) << 8U) | msg->data[5]) - 8192;
}
static uint8_t volvo_c1_fsm_checksum(const CANPacket_t *msg) {
const uint16_t angle_raw = ((uint16_t)(msg->data[4] & 0x3FU) << 8U) | msg->data[5];
const uint8_t direction = msg->data[7] & 0x3U;
const uint8_t checksum_sum = (msg->data[3] + direction + angle_raw + (angle_raw >> 8U)) & 0xFFU;
return checksum_sum ^ 0xFFU;
}
static const AngleSteeringLimits VOLVO_ANGLE_STEERING_LIMITS = { static const AngleSteeringLimits VOLVO_ANGLE_STEERING_LIMITS = {
.max_angle = VOLVO_MAX_ANGLE_CAN, .max_angle = VOLVO_MAX_ANGLE_CAN,
.angle_deg_to_can = VOLVO_ANGLE_DEG_TO_CAN, .angle_deg_to_can = VOLVO_ANGLE_DEG_TO_CAN,
@@ -82,9 +112,50 @@ static const AngleSteeringLimits VOLVO_ANGLE_STEERING_LIMITS = {
.frequency = 50U, .frequency = 50U,
}; };
static const AngleSteeringLimits VOLVO_C1_ANGLE_STEERING_LIMITS = {
.max_angle = VOLVO_C1_MAX_ANGLE_CAN,
.angle_deg_to_can = VOLVO_C1_ANGLE_DEG_TO_CAN,
.angle_rate_up_lookup = {
{7.0f, 17.0f, 36.0f},
{2.0f, 0.25f, 0.1f},
},
.angle_rate_down_lookup = {
{7.0f, 17.0f, 36.0f},
{2.0f, 0.25f, 0.1f},
},
.max_angle_error = 455, // 20 degrees
.angle_error_min_speed = 0.0f,
.frequency = 50U,
.enforce_angle_error = true,
};
static void volvo_rx_hook(const CANPacket_t *msg) { static void volvo_rx_hook(const CANPacket_t *msg) {
// Monitor the vehicle state required for cruise, disengagement, and angle
// safety. All steering TX frames are separately constrained in volvo_tx_hook. if (volvo_c1) {
if (msg->bus == VOLVO_MAIN_BUS) {
if (msg->addr == VOLVO_C1_PSCM_1) {
update_sample(&angle_meas, volvo_c1_pscm_angle(msg));
}
if (msg->addr == VOLVO_C1_SPEED) {
const uint16_t speed_raw = ((uint16_t)msg->data[6] << 8U) | msg->data[7];
const float speed = ((float)speed_raw * 0.01f) / 3.6f;
vehicle_moving = speed > 0.1f;
UPDATE_VEHICLE_SPEED(speed);
}
if (msg->addr == VOLVO_C1_PEDAL_AND_BRAKE) {
const uint16_t gas_raw = ((uint16_t)(msg->data[1] & 0x3U) << 8U) | msg->data[2];
gas_pressed = gas_raw > 50U; // DBC factor 0.1: greater than 5 percent
brake_pressed = GET_BIT(msg, 24U) || GET_BIT(msg, 38U);
}
}
if ((msg->bus == VOLVO_PARTY_BUS) && (msg->addr == VOLVO_C1_FSM_0)) {
pcm_cruise_check(GET_BIT(msg, 58U));
}
return;
}
// Main bus (bus 0) messages // Main bus (bus 0) messages
if (msg->bus == VOLVO_MAIN_BUS) { if (msg->bus == VOLVO_MAIN_BUS) {
@@ -148,13 +219,11 @@ static void volvo_rx_hook(const CANPacket_t *msg) {
// DRIVER_INPUT is the signal consumed by carstate.py for driver torque. // DRIVER_INPUT is the signal consumed by carstate.py for driver torque.
// The PSCM frame's DRIVER_INPUT_DEVIATION is a different signal and must // The PSCM frame's DRIVER_INPUT_DEVIATION is a different signal and must
// not be substituted here: doing so leaves the hardware disengage path blind.
if (msg->addr == VOLVO_DRIVER_INPUT) { if (msg->addr == VOLVO_DRIVER_INPUT) {
// STEERING_DRIVER_INPUT is a Motorola signal starting at bit 55. The // STEERING_DRIVER_INPUT is a Motorola signal starting at bit 55. The
// DBC also carries a +1 offset, so its raw byte is data[6]. // DBC also carries a +1 offset, so its raw byte is data[6].
const int driver_input = to_signed(msg->data[6], 8) + 1; const int driver_input = to_signed(msg->data[6], 8) + 1;
update_sample(&torque_driver, driver_input); update_sample(&torque_driver, driver_input);
steering_disengage = SAFETY_ABS(driver_input) > VOLVO_DRIVER_OVERRIDE;
} }
} }
@@ -163,6 +232,33 @@ static void volvo_rx_hook(const CANPacket_t *msg) {
static bool volvo_tx_hook(const CANPacket_t *msg) { static bool volvo_tx_hook(const CANPacket_t *msg) {
bool tx = true; bool tx = true;
if (volvo_c1) {
if (msg->addr == VOLVO_C1_FSM_1) {
const int desired_angle = volvo_c1_fsm_angle(msg);
const uint8_t direction = msg->data[7] & 0x3U;
const bool steer_control_enabled = direction != 0U;
tx &= SAFETY_ABS(desired_angle) <= VOLVO_C1_MAX_ANGLE_CAN;
tx &= !steer_angle_cmd_checks(desired_angle, steer_control_enabled, VOLVO_C1_ANGLE_STEERING_LIMITS);
tx &= (direction == 0U) || (direction == 3U);
tx &= (msg->data[0] == 0xE3U) && (msg->data[1] == 0xB4U) && (msg->data[2] == 0x08U);
tx &= (msg->data[3] == 0x80U) && ((msg->data[4] & 0xC0U) == 0x80U) && ((msg->data[7] & 0xFCU) == 0x94U);
tx &= msg->data[6] == volvo_c1_fsm_checksum(msg);
}
if (msg->addr == VOLVO_C1_PSCM_1) {
const int relayed_angle = volvo_c1_pscm_angle(msg);
const int measured_max = angle_meas.max + VOLVO_C1_RELAY_ANGLE_TOLERANCE;
const int measured_min = angle_meas.min - VOLVO_C1_RELAY_ANGLE_TOLERANCE;
tx &= !safety_max_limit_check(relayed_angle, measured_max, measured_min);
}
// Only ACC cancel (byte 7 bit 4) may be synthesized.
if (msg->addr == VOLVO_C1_BUTTONS) {
tx &= ((msg->data[7] & 0xEFU) == 0U) && (msg->data[6] == 0U);
}
return tx;
}
// LCA_5 carries the actual angle command used by the controller. The stock // LCA_5 carries the actual angle command used by the controller. The stock
// LCA frame also contains an angle-shaped field, but the imported controller // LCA frame also contains an angle-shaped field, but the imported controller
// deliberately leaves that field at the observed vehicle value. // deliberately leaves that field at the observed vehicle value.
@@ -260,6 +356,22 @@ static bool volvo_tx_hook(const CANPacket_t *msg) {
static safety_config volvo_init(uint16_t param) { static safety_config volvo_init(uint16_t param) {
bool spa = GET_FLAG(param, VOLVO_FLAG_SPA); bool spa = GET_FLAG(param, VOLVO_FLAG_SPA);
volvo_c1 = GET_FLAG(param, VOLVO_FLAG_C1);
if (volvo_c1) {
static const CanMsg VOLVO_C1_TX_MSGS[] = {
{VOLVO_C1_FSM_1, VOLVO_MAIN_BUS, 8, .check_relay = true},
{VOLVO_C1_PSCM_1, VOLVO_PARTY_BUS, 8, .check_relay = true},
{VOLVO_C1_BUTTONS, VOLVO_MAIN_BUS, 8, .check_relay = false},
};
static RxCheck volvo_c1_rx_checks[] = {
{.msg = {{VOLVO_C1_PSCM_1, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{VOLVO_C1_FSM_0, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{VOLVO_C1_PEDAL_AND_BRAKE, VOLVO_MAIN_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{VOLVO_C1_SPEED, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
};
return BUILD_SAFETY_CFG(volvo_c1_rx_checks, VOLVO_C1_TX_MSGS);
}
// Set PT bus addresses based on platform // Set PT bus addresses based on platform
volvo_ecm_1_addr = spa ? VOLVO_SPA_ECM_1 : VOLVO_CMA_ECM_1; volvo_ecm_1_addr = spa ? VOLVO_SPA_ECM_1 : VOLVO_CMA_ECM_1;
+24 -33
View File
@@ -170,6 +170,11 @@ class TestFordSafetyBase(common.CarSafetyTest):
} }
return self.packer.make_can_msg_safety("Lane_Assist_Data1", 0, values) return self.packer.make_can_msg_safety("Lane_Assist_Data1", 0, values)
def _extended_lka_msg(self, angle_mode=False):
msg = self._lkas_command_msg(0)
msg[0].data[4] |= 0x2 | int(angle_mode)
return msg
# LCA command # LCA command
def _lat_ctl_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float): def _lat_ctl_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float):
if self.STEER_MESSAGE == MSG_LateralMotionControl: if self.STEER_MESSAGE == MSG_LateralMotionControl:
@@ -376,6 +381,25 @@ class TestFordSafetyBase(common.CarSafetyTest):
should_tx |= self.LKA_STEERING and controls_allowed and action in (2, 4) should_tx |= self.LKA_STEERING and controls_allowed and action in (2, 4)
self.assertEqual(should_tx, self._tx(self._lkas_command_msg(action))) self.assertEqual(should_tx, self._tx(self._lkas_command_msg(action)))
def test_extended_angle_mode_rejected(self):
if self.LKA_STEERING:
return
self.assertTrue(self._tx(self._extended_lka_msg()))
self.assertFalse(self._tx(self._extended_lka_msg(angle_mode=True)))
def test_extended_curvature_signals(self):
if self.LKA_STEERING:
return
speed = 15.0
self.safety.set_controls_allowed(True)
self._reset_curvature_measurement(0.0, speed)
self.assertTrue(self._tx(self._extended_lka_msg()))
self.assertTrue(self._tx(self._lat_ctl_msg(True, 0.0, 0.0, 0.001, 0.0005)))
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0.1, 0.0, 0.001, 0.0005)))
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0.0, 0.02, 0.001, 0.0005)))
def test_acc_buttons(self): def test_acc_buttons(self):
for allowed in (0, 1): for allowed in (0, 1):
self.safety.set_controls_allowed(allowed) self.safety.set_controls_allowed(allowed)
@@ -443,39 +467,6 @@ class TestFordCANFDStockSafety(TestFordSafetyBase):
self.safety.set_safety_hooks(CarParams.SafetyModel.ford, FordSafetyFlags.CANFD) self.safety.set_safety_hooks(CarParams.SafetyModel.ford, FordSafetyFlags.CANFD)
self.safety.init_tests() self.safety.init_tests()
def _extended_lka_msg(self, angle_mode=False, shadow_curvature=0.0):
msg = self._lkas_command_msg(0)
raw_shadow = int(round(shadow_curvature / 1e-6)) & 0xFFFF
msg[0].data[4] |= 0x2 | int(angle_mode)
msg[0].data[5] = raw_shadow >> 8
msg[0].data[6] = raw_shadow & 0xFF
return msg
def test_extended_curvature_signals(self):
speed = 15.0
self.safety.set_controls_allowed(True)
self._reset_curvature_measurement(0.0, speed)
self.assertTrue(self._tx(self._extended_lka_msg()))
self.assertTrue(self._tx(self._lat_ctl_msg(True, 0.0, 0.0, 0.001, 0.0005)))
self.assertTrue(self._tx(self._extended_lka_msg()))
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0.1, 0.0, 0.001, 0.0005)))
def test_extended_angle_signals(self):
speed = 15.0
curvature = 0.005
self.safety.set_controls_allowed(True)
self._reset_curvature_measurement(curvature, speed)
self.assertTrue(self._tx(self._extended_lka_msg(angle_mode=True, shadow_curvature=curvature)))
self.assertTrue(self._tx(self._lat_ctl_msg(True, 0.0, 0.02, 0.0, 0.0)))
self.assertTrue(self._tx(self._extended_lka_msg(angle_mode=True, shadow_curvature=curvature)))
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0.0, 0.2, 0.0, 0.0)))
self.assertTrue(self._tx(self._extended_lka_msg(angle_mode=True, shadow_curvature=-curvature)))
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0.0, 0.01, 0.0, 0.0)))
class TestFordStockSafety(TestFordSafetyBase): class TestFordStockSafety(TestFordSafetyBase):
STEER_MESSAGE = MSG_LateralMotionControl STEER_MESSAGE = MSG_LateralMotionControl
STOCK_LONGITUDINAL = True STOCK_LONGITUDINAL = True
@@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Provenance: portions of HKG angle-safety tests are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
from parameterized import parameterized_class from parameterized import parameterized_class
import unittest import unittest
import numpy as np import numpy as np
@@ -37,7 +37,6 @@ class SubaruMsg(enum.IntEnum):
ES_STATIC_1 = 0x22a ES_STATIC_1 = 0x22a
ES_STATIC_2 = 0x325 ES_STATIC_2 = 0x325
Dashlights = 0x390 Dashlights = 0x390
AVH = 0x32b
SUBARU_MAIN_BUS = 0 SUBARU_MAIN_BUS = 0
@@ -386,20 +385,6 @@ class TestSubaruGen2FixedAngleStopStartSafety(TestSubaruGen2FixedAngleSafety):
self.assertFalse(self._tx(self._stop_start_msg(False))) self.assertFalse(self._tx(self._stop_start_msg(False)))
class TestSubaruGen2FixedAngleStopStartAvhSafety(TestSubaruGen2FixedAngleStopStartSafety):
FLAGS = TestSubaruGen2FixedAngleStopStartSafety.FLAGS | SubaruSafetyFlags.AVH_BUTTON
TX_MSGS = TestSubaruGen2FixedAngleStopStartSafety.TX_MSGS + [[SubaruMsg.AVH, SUBARU_ALT_BUS]]
def _avh_msg(self, pressed):
return self.packer.make_can_msg_safety(
"AVH", SUBARU_ALT_BUS, {"COUNTER": 0, "AVH": pressed},
)
def test_avh_tx_requires_pressed_bit(self):
self.assertTrue(self._tx(self._avh_msg(True)))
self.assertFalse(self._tx(self._avh_msg(False)))
class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruAngleSafetyBase): class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruAngleSafetyBase):
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM
ALT_MAIN_BUS = SUBARU_ALT_BUS ALT_MAIN_BUS = SUBARU_ALT_BUS
@@ -432,6 +417,18 @@ class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, Test
return self.packer.make_can_msg_safety("Steering_2", SUBARU_MAIN_BUS, {"Steering_Angle": angle}) return self.packer.make_can_msg_safety("Steering_2", SUBARU_MAIN_BUS, {"Steering_Angle": angle})
class TestSubaruDPlatformFixedAngleSafety(TestSubaruDPlatformAngleSafety):
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM | \
SubaruSafetyFlags.FIXED_ANGLE_LIMITS
STEER_ANGLE_MAX = 545
ANGLE_RATE_BP = [0., 5., 35.]
ANGLE_RATE_UP = [5., .8, .15]
ANGLE_RATE_DOWN = [5., .8, .15]
def test_rt_limits(self):
raise unittest.SkipTest("Breakpoint angle limits do not enforce a real-time message frequency")
class TestSubaruDPlatformStopStartSafety(TestSubaruDPlatformAngleSafety): class TestSubaruDPlatformStopStartSafety(TestSubaruDPlatformAngleSafety):
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM | \ FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM | \
SubaruSafetyFlags.STOP_START_BUTTON SubaruSafetyFlags.STOP_START_BUTTON
@@ -2,6 +2,7 @@
import unittest import unittest
from opendbc.car.tesla.preap.interface import SAFETY_TESLA_PREAP from opendbc.car.tesla.preap.interface import SAFETY_TESLA_PREAP
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from opendbc.safety.tests.libsafety import libsafety_py from opendbc.safety.tests.libsafety import libsafety_py
import opendbc.safety.tests.common as common import opendbc.safety.tests.common as common
@@ -36,6 +37,12 @@ class TestTeslaPreAPSafety(common.SafetyTestBase):
dat[0] = lever & 0x3F dat[0] = lever & 0x3F
return common.make_msg(0, 0x45, 8, dat) return common.make_msg(0, 0x45, 8, dat)
@staticmethod
def _di_state_msg(cruise_state: int):
dat = bytearray(8)
dat[1] = (cruise_state & 0x0F) << 4
return common.make_msg(0, 0x368, 8, dat)
@staticmethod @staticmethod
def _steering_status_msg(hands_on_level: int = 0, eac_status: int = 1, eac_error_code: int = 0, angle_tenths: int = 0): def _steering_status_msg(hands_on_level: int = 0, eac_status: int = 1, eac_error_code: int = 0, angle_tenths: int = 0):
raw_angle = angle_tenths + 8192 raw_angle = angle_tenths + 8192
@@ -123,6 +130,30 @@ class TestTeslaPreAPSafety(common.SafetyTestBase):
self.assertTrue(self._tx(self._epas_control_msg(1))) self.assertTrue(self._tx(self._epas_control_msg(1)))
self.assertFalse(self._tx(self._epas_control_msg(2))) self.assertFalse(self._tx(self._epas_control_msg(2)))
def test_always_on_lateral(self):
ENABLED, OFF = 2, 0
self.safety.set_controls_allowed(False)
self.safety.set_alternative_experience(0)
self.assertTrue(self._rx(self._di_state_msg(ENABLED)))
self.assertFalse(self._tx(self._steer_cmd_msg(0, 1)))
self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL)
self.assertTrue(self._rx(self._di_state_msg(OFF)))
self.assertFalse(self.safety.get_controls_allowed())
self.assertFalse(self._tx(self._steer_cmd_msg(0, 1)))
self.assertTrue(self._rx(self._di_state_msg(ENABLED)))
self.assertFalse(self.safety.get_controls_allowed())
self.assertTrue(self._tx(self._steer_cmd_msg(0, 1)))
self.assertTrue(self._rx(self._di_state_msg(OFF)))
self.assertFalse(self._tx(self._steer_cmd_msg(0, 1)))
self.assertTrue(self._rx(self._di_state_msg(9)))
self.assertFalse(self.safety.get_aol_allowed())
self.assertFalse(self._tx(self._steer_cmd_msg(0, 1)))
def test_aeb_is_blocked(self): def test_aeb_is_blocked(self):
self.safety.set_controls_allowed(True) self.safety.set_controls_allowed(True)
self.assertTrue(self._tx(self._long_msg(0))) self.assertTrue(self._tx(self._long_msg(0)))
@@ -97,29 +97,55 @@ class TestToyotaSafetyBase(common.CarSafetyTest, common.LongitudinalAccelSafetyT
msg = libsafety_py.make_CANPacket(0x283, 0, bytes(dat)) msg = libsafety_py.make_CANPacket(0x283, 0, bytes(dat))
self.assertEqual(not bad and not stock_longitudinal, self._tx(msg)) self.assertEqual(not bad and not stock_longitudinal, self._tx(msg))
def test_auto_brake_hold_aeb_replacement_only_at_standstill(self): def test_auto_hold_acc_control_is_narrowly_allowed_only_at_standstill(self):
self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.ALLOW_AEB) if (not self.LONGITUDINAL or
hold_msg = libsafety_py.make_CANPacket(0x344, 0, b"\xfd\x80\x00\x00\x00\x00\x00\xcc") self.safety.get_current_safety_param() & (ToyotaSafetyFlags.STOCK_LONGITUDINAL.value | ToyotaSafetyFlags.SECOC.value)):
raise unittest.SkipTest("Toyota Auto Hold requires non-SecOC openpilot longitudinal control")
self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD)
hold_msg = self.packer.make_can_msg_safety("ACC_CONTROL", 0, {
"ACCEL_CMD": -1.0,
"PERMIT_BRAKING": 1,
"RELEASE_STANDSTILL": 0,
"CANCEL_REQ": 0,
})
self._rx(self._speed_msg(0)) self._rx(self._speed_msg(0))
self._rx(self._toggle_aol(True)) self._rx(self._toggle_aol(True))
self._rx(self._user_gas_msg(False)) self._rx(self._user_gas_msg(False))
self.safety.set_controls_allowed(False)
self.assertTrue(self._tx(hold_msg)) self.assertTrue(self._tx(hold_msg))
self.assertEqual(-1, self.safety.safety_fwd_hook(2, 0x344))
self.assertFalse(self._tx(self.packer.make_can_msg_safety("ACC_CONTROL", 0, {
"ACCEL_CMD": -1.1,
"PERMIT_BRAKING": 1,
"RELEASE_STANDSTILL": 0,
})))
self._rx(self._speed_msg(1.0)) self._rx(self._speed_msg(1.0))
self.assertFalse(self._tx(hold_msg)) self.assertFalse(self._tx(hold_msg))
self.assertEqual(0, self.safety.safety_fwd_hook(2, 0x344))
self._rx(self._speed_msg(0)) self._rx(self._speed_msg(0))
self._rx(self._user_gas_msg(True)) self._rx(self._user_gas_msg(True))
self.assertFalse(self._tx(hold_msg)) self.assertFalse(self._tx(hold_msg))
self.assertEqual(0, self.safety.safety_fwd_hook(2, 0x344))
self._rx(self._user_gas_msg(False)) self._rx(self._user_gas_msg(False))
self._rx(self._toggle_aol(False)) self._rx(self._toggle_aol(False))
self.assertFalse(self._tx(hold_msg)) self.assertFalse(self._tx(hold_msg))
self.assertEqual(0, self.safety.safety_fwd_hook(2, 0x344))
def test_auto_hold_acc_control_is_blocked_without_toyota_hold_toggle(self):
hold_msg = self.packer.make_can_msg_safety("ACC_CONTROL", 0, {
"ACCEL_CMD": -1.0,
"PERMIT_BRAKING": 1,
"RELEASE_STANDSTILL": 0,
"CANCEL_REQ": 0,
})
self._rx(self._speed_msg(0))
self._rx(self._toggle_aol(True))
self._rx(self._user_gas_msg(False))
self.safety.set_controls_allowed(False)
self.safety.set_alternative_experience(0)
self.assertFalse(self._tx(hold_msg))
# Only allow LTA msgs with no actuation # Only allow LTA msgs with no actuation
def test_lta_steer_cmd(self): def test_lta_steer_cmd(self):
+135 -18
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Safety tests for Volvo CMA/SPA. Safety tests for Volvo C1/CMA/SPA.
The safety mode lives at ``opendbc/safety/modes/volvo.h`` and is parameterized The safety mode lives at ``opendbc/safety/modes/volvo.h`` and is parameterized
by ``safetyParam``: by ``safetyParam``:
@@ -8,14 +8,13 @@ by ``safetyParam``:
- ``safetyParam == 0`` CMA platform (Volvo XC40 Recharge) - ``safetyParam == 0`` CMA platform (Volvo XC40 Recharge)
- ``safetyParam == VOLVO_FLAG_SPA`` SPA platform (Volvo S60 Recharge, - ``safetyParam == VOLVO_FLAG_SPA`` SPA platform (Volvo S60 Recharge,
Polestar 2) Polestar 2)
- ``safetyParam == VOLVO_FLAG_C1`` C1 platform (Volvo V40)
The two platforms share LCA/PSCM/etc. addresses on the main and party buses CMA and SPA share LCA/PSCM/etc. addresses on the main and party buses but use
but use *different* PT-bus addresses and signal scales for ECM_1 and different PT-bus addresses and signal scales. C1 uses the V40's legacy CAN
BUS1_CRUISE_CONTROL. Vehicle speed is read from main-bus SPEED on both, so it layout and its own safety allowlist. The tests exercise all three through the
is not platform-dependent. This test file exercises both platforms through the generic ``CarSafetyTest`` harness so divergence between ``carstate.py`` and
same generic ``CarSafetyTest`` harness so that any future divergence between ``volvo.h`` is caught before running in a car.
``carstate.py`` and ``volvo.h`` e.g. a threshold drifting out of sync is
caught on a laptop instead of in the car.
Companion to: ``opendbc/car/volvo/carstate.py`` (must agree on thresholds). Companion to: ``opendbc/car/volvo/carstate.py`` (must agree on thresholds).
""" """
@@ -25,13 +24,16 @@ import re
import unittest import unittest
from opendbc.car.volvo.interface import SAFETY_VOLVO from opendbc.car.volvo.interface import SAFETY_VOLVO
from opendbc.car.volvo.values import VolvoSafetyFlags
from opendbc.car.volvo.volvocan import create_c1_checksum, create_c1_steering_control
from opendbc.safety.tests.libsafety import libsafety_py from opendbc.safety.tests.libsafety import libsafety_py
import opendbc.safety.tests.common as common import opendbc.safety.tests.common as common
from opendbc.safety.tests.common import CANPackerSafety from opendbc.safety.tests.common import CANPackerSafety
# Must match VOLVO_FLAG_SPA in opendbc/safety/modes/volvo.h # Must match the flags in opendbc/safety/modes/volvo.h
VOLVO_FLAG_SPA = 1 VOLVO_FLAG_SPA = VolvoSafetyFlags.SPA.value
VOLVO_FLAG_C1 = VolvoSafetyFlags.C1.value
# Must match VOLVO_SPEED_TO_MS in volvo.h and SPEED_TO_MS in carstate.py # Must match VOLVO_SPEED_TO_MS in volvo.h and SPEED_TO_MS in carstate.py
VOLVO_SPEED_TO_MS = 0.003977 VOLVO_SPEED_TO_MS = 0.003977
@@ -211,21 +213,17 @@ class TestVolvoSafetyBase(common.CarSafetyTest):
self.assertTrue(self._tx(valid)) self.assertTrue(self._tx(valid))
self.assertFalse(self._tx(invalid)) self.assertFalse(self._tx(invalid))
def test_driver_override_disengages_controls(self): def test_driver_input_is_a_normal_override(self):
def driver_input_msg(value): def driver_input_msg(value):
return self.mid_packer.make_can_msg_safety( return self.mid_packer.make_can_msg_safety(
"DRIVER_INPUT", VOLVO_PARTY_BUS, {"STEERING_DRIVER_INPUT": value}) "DRIVER_INPUT", VOLVO_PARTY_BUS, {"STEERING_DRIVER_INPUT": value})
for value in (2, 3, 5): for value in (2, 3, 5, 6, 20, -20):
self._rx(driver_input_msg(0)) self._rx(driver_input_msg(0))
self.safety.set_controls_allowed(True) self.safety.set_controls_allowed(True)
self._rx(driver_input_msg(value)) self._rx(driver_input_msg(value))
self.assertTrue(self.safety.get_controls_allowed(), f"unexpected disengage at {value=}") self.assertTrue(self.safety.get_controls_allowed(), f"unexpected safety disengage at {value=}")
self.assertFalse(self.safety.get_steering_disengage_prev())
self._rx(driver_input_msg(0))
self.safety.set_controls_allowed(True)
self._rx(driver_input_msg(6))
self.assertFalse(self.safety.get_controls_allowed())
# ---- Volvo-specific consistency tests ---- # ---- Volvo-specific consistency tests ----
@@ -336,5 +334,124 @@ class TestVolvoSPA(TestVolvoSafetyBase):
"BUS1_CRUISE_CONTROL", VOLVO_PT_BUS, values) "BUS1_CRUISE_CONTROL", VOLVO_PT_BUS, values)
class TestVolvoC1(common.CarSafetyTest, common.AngleSteeringSafetyTest):
TX_MSGS = [[0xD0, VOLVO_MAIN_BUS], [0x125, VOLVO_PARTY_BUS], [0x10, VOLVO_MAIN_BUS]]
RELAY_MALFUNCTION_ADDRS = {
VOLVO_MAIN_BUS: (0xD0,),
VOLVO_PARTY_BUS: (0x125,),
}
FWD_BLACKLISTED_ADDRS = {
VOLVO_MAIN_BUS: [0x125],
VOLVO_PARTY_BUS: [0xD0],
}
STANDSTILL_THRESHOLD = 0.1
GAS_PRESSED_THRESHOLD = 5.0
STEER_ANGLE_MAX = 359.9
STEER_ANGLE_TEST_MAX = 350.0
DEG_TO_CAN = 1 / 0.04395
ANGLE_RATE_BP = [7.0, 17.0, 36.0]
ANGLE_RATE_UP = [2.0, 0.25, 0.1]
ANGLE_RATE_DOWN = [2.0, 0.25, 0.1]
def setUp(self):
self.packer = CANPackerSafety("volvo_v40_2017_pt")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(SAFETY_VOLVO, VOLVO_FLAG_C1)
self.safety.init_tests()
def _angle_cmd_msg(self, angle: float, enabled: bool, increment_timer: bool = True):
values = {
"SET_X_E3": 0xE3,
"SET_X_B4": 0xB4,
"SET_X_08": 0x08,
"LKAAngleReq": angle,
"LKASteerDirection": 3 if enabled else 0,
"TrqLim": 0,
"SET_X_25": 0x25,
"SET_X_02": 0x02,
}
def fix_checksum(msg):
address, data, bus = msg
data = bytearray(data)
data[6] = create_c1_checksum(data)
return address, data, bus
return self.packer.make_can_msg_safety("FSM1", VOLVO_MAIN_BUS, values, fix_checksum)
def _angle_meas_msg(self, angle: float):
return self.packer.make_can_msg_safety(
"PSCM1", VOLVO_MAIN_BUS, {"SteeringAngleServo": angle})
def _speed_msg(self, speed):
return self.packer.make_can_msg_safety(
"VehicleSpeed1", VOLVO_MAIN_BUS, {"VehicleSpeed": speed * 3.6})
def _speed_msg_2(self, speed):
return None
def _user_brake_msg(self, brake):
return self.packer.make_can_msg_safety(
"PedalandBrake", VOLVO_MAIN_BUS, {"BrakePedalActive2": bool(brake)})
def _user_gas_msg(self, gas):
return self.packer.make_can_msg_safety(
"PedalandBrake", VOLVO_MAIN_BUS, {"AccPedal": gas})
def _pcm_status_msg(self, enable):
return self.packer.make_can_msg_safety(
"FSM0", VOLVO_PARTY_BUS, {"ACCStatusActive": bool(enable)})
def test_cancel_button_only(self):
allowed = self.packer.make_can_msg_safety(
"CCButtons", VOLVO_MAIN_BUS, {"ACCStopBtn": 1})
self.assertTrue(self._tx(allowed))
for signal in ("ACCOnOffBtn", "ACCSetBtn", "ACCResumeBtn", "ACCMinusBtn",
"TimeGapIncreaseBtn", "TimeGapDecreaseBtn"):
msg = self.packer.make_can_msg_safety("CCButtons", VOLVO_MAIN_BUS, {signal: 1})
self.assertFalse(self._tx(msg), signal)
def test_pscm_relay_cannot_invent_angle(self):
for _ in range(common.MAX_SAMPLE_VALS):
self._rx(self._angle_meas_msg(10))
valid = self.packer.make_can_msg_safety(
"PSCM1", VOLVO_PARTY_BUS, {"SteeringAngleServo": 10})
invalid = self.packer.make_can_msg_safety(
"PSCM1", VOLVO_PARTY_BUS, {"SteeringAngleServo": 20})
self.assertTrue(self._tx(valid))
self.assertFalse(self._tx(invalid))
def test_pscm_relay_preserves_full_lock_angle(self):
for angle in (-720, 500):
for _ in range(common.MAX_SAMPLE_VALS):
self._rx(self._angle_meas_msg(angle))
relayed = self.packer.make_can_msg_safety(
"PSCM1", VOLVO_PARTY_BUS, {"SteeringAngleServo": angle})
self.assertTrue(self._tx(relayed), angle)
def test_steering_static_fields_and_checksum(self):
self.safety.set_controls_allowed(True)
self._reset_angle_measurement(0)
self._reset_speed_measurement(10)
self._set_prev_desired_angle(0)
valid = self._angle_cmd_msg(0, True)
self.assertTrue(self._tx(valid))
for byte_index in (0, 1, 2, 3, 4, 6, 7):
invalid = self._angle_cmd_msg(0, True)
invalid[0].data[byte_index] ^= 0x4 if byte_index in (4, 7) else 0x1
self.assertFalse(self._tx(invalid), byte_index)
def test_controller_steering_message_is_allowed(self):
self.safety.set_controls_allowed(True)
self._reset_angle_measurement(0)
self._reset_speed_measurement(10)
self._set_prev_desired_angle(0)
address, data, bus = create_c1_steering_control(self.packer, 0, True)
self.assertTrue(self._tx(libsafety_py.make_CANPacket(address, bus, data)))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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