mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-10 10:13:45 +08:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95a7f3b398 | |||
| 22707891bd | |||
| 962d8b6719 | |||
| 0f3bdb34b3 | |||
| c2921c1a8f | |||
| a19beda327 | |||
| b7775991bf | |||
| eedd73e522 | |||
| 50e2c21dbd | |||
| 54c3fb13f3 | |||
| 7f3bd61292 | |||
| dec4a0884a | |||
| 4edc8ab86a | |||
| b3a14cb48d | |||
| f47322cbee | |||
| a08065f282 | |||
| a33bec1ca4 | |||
| ca3d8a3816 | |||
| 2360ff9b0f | |||
| 0976fd804d | |||
| 2504441a4e | |||
| 0b5ccb31e1 | |||
| b91ea3e1da | |||
| 1588f7041a | |||
| bcf152e6f7 | |||
| 249b03a3f5 | |||
| 9ae473355a | |||
| 2ab6195d9b | |||
| 5df8b59964 | |||
| c03d06b408 | |||
| a695335f21 | |||
| 9d1043ad01 | |||
| a5d7db3b29 | |||
| bb284d0bc4 | |||
| 67bf3adfd4 | |||
| 7a83f8f430 | |||
| 4b507c960a | |||
| 37908b698d | |||
| 759ff3107b | |||
| 422488562f | |||
| 7f1f926c47 | |||
| 9cb1b6b11d |
@@ -81,6 +81,7 @@ selfdrive/modeld/models/*.pkl
|
||||
# openpilot log files
|
||||
*.bz2
|
||||
*.zst
|
||||
!selfdrive/modeld/firmware/amdgpu/*.zst
|
||||
|
||||
build/
|
||||
|
||||
|
||||
+162
@@ -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,4 +1,5 @@
|
||||
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:
|
||||
|
||||
|
||||
@@ -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)
|
||||
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).
|
||||
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
|
||||
|
||||
## 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.
|
||||
* 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.
|
||||
|
||||
@@ -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.
@@ -774,6 +774,7 @@ struct ChestnutState {
|
||||
pcieLtssm @7 :UInt8;
|
||||
supplyVoltage @8 :UInt16; # mV
|
||||
supplyCurrent @9 :Int16; # mA
|
||||
supplyFault @10 :Bool;
|
||||
}
|
||||
|
||||
struct RadarState @0x9a185389d6fdd05f {
|
||||
|
||||
Binary file not shown.
+20
-7
@@ -18,6 +18,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"BootCount", {PERSISTENT, INT}},
|
||||
{"BluetoothAudioAddress", {PERSISTENT, STRING}},
|
||||
{"BluetoothAudioTestActive", {CLEAR_ON_MANAGER_START | DONT_LOG, BOOL}},
|
||||
{"BluetoothDisconnectControllersOffroad", {PERSISTENT, BOOL, "0"}},
|
||||
{"BluetoothEnabled", {PERSISTENT, BOOL, "0"}},
|
||||
{"CalibrationParams", {PERSISTENT, BYTES}},
|
||||
{"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}},
|
||||
@@ -109,10 +110,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"LocationFilterInitialState", {PERSISTENT, BYTES}},
|
||||
{"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
{"LongitudinalPersonality", {PERSISTENT, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}},
|
||||
{"LongitudinalPersonalityProfiles", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"NetworkMetered", {PERSISTENT, BOOL}},
|
||||
{"ObdMultiplexingChanged", {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_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_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
|
||||
@@ -310,6 +318,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
|
||||
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"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}},
|
||||
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 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"}},
|
||||
{"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"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}},
|
||||
{"ModelVersion", {PERSISTENT, STRING, "v15", "v15", 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}},
|
||||
{"FordLKASButtonControlMigrated", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"FordAngleBlend", {PERSISTENT, FLOAT, "0.5", "0.5", 2}},
|
||||
{"FordAngleHighSpeedDamping", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"FordAngleHighSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"FordAngleLaneChangeFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"FordAngleLowSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
// These Ford curvature tuning concepts descend from BluePilot bp-7.0. StarPilot's key names and
|
||||
// settings integration are local; see /CREDITS.md and /THIRD_PARTY_NOTICES.md for provenance.
|
||||
{"FordCurvatureBlendHigh", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
|
||||
{"FordCurvatureBlendLow", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
|
||||
{"FordCurvatureLaneChangeFactor", {PERSISTENT, FLOAT, "0.85", "0.85", 2}},
|
||||
{"FordHandsFreeCluster", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"FordHumanTurnDetection", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"FordLateralMode", {PERSISTENT, INT, "1", "1", 2}},
|
||||
{"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
|
||||
{"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"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}},
|
||||
{"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}},
|
||||
{"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}},
|
||||
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
|
||||
@@ -599,6 +611,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"RelaxedJerkSpeed", {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}},
|
||||
{"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
|
||||
Binary file not shown.
@@ -5,7 +5,7 @@ import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName
|
||||
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
|
||||
|
||||
class TestParams:
|
||||
def setup_method(self):
|
||||
@@ -128,6 +128,31 @@ class TestParams:
|
||||
assert self.params.get("LiveParameters") 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):
|
||||
# json
|
||||
self.params.put("ApiCache_DriveStats", {"a": 0})
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ fi
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="19.6.19"
|
||||
export AGNOS_VERSION="19.6.20"
|
||||
fi
|
||||
|
||||
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
|
||||
|
||||
@@ -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)
|
||||
elif dbc_name.startswith(("toyota_", "lexus_")):
|
||||
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)
|
||||
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)
|
||||
|
||||
@@ -10,6 +10,7 @@ from opendbc.car.carlog import carlog
|
||||
from opendbc.car.structs import CarParams, CarParamsT
|
||||
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.hyundai.values import kia_ray_ev_vin
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from opendbc.car.toyota.values import ToyotaSafetyFlags
|
||||
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)
|
||||
# VIN query only reliably works through OBDII
|
||||
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)
|
||||
car_fw = get_fw_versions_ordered(can_recv, can_send, set_obd_multiplexing, vin, ecu_rx_addrs, num_pandas=num_pandas)
|
||||
skip_fw_buses = {1} if kia_ray_ev_vin(vin) else set()
|
||||
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
|
||||
|
||||
exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import math
|
||||
import numpy as np
|
||||
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.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
|
||||
# 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.lateral import FordLateralController, FordLateralMode, FordLateralResult
|
||||
from openpilot.starpilot.car.ford.lateral import FordLateralController, FordLateralResult
|
||||
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
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))
|
||||
|
||||
|
||||
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):
|
||||
# No blending at low speed due to lack of torque wind-up and inaccurate current curvature
|
||||
if v_ego_raw > 9:
|
||||
@@ -93,7 +79,6 @@ class CarController(CarControllerBase):
|
||||
|
||||
self.apply_curvature_last = 0
|
||||
self.apply_angle_last = 0
|
||||
self.anti_overshoot_curvature_last = 0
|
||||
self.accel = 0.0
|
||||
self.gas = 0.0
|
||||
self.brake_request = False
|
||||
@@ -103,8 +88,7 @@ class CarController(CarControllerBase):
|
||||
self.lead_distance_bars_last = None
|
||||
self.distance_bar_frame = 0
|
||||
self.ford_lateral = None if CP.flags & FordFlags.LKA_STEERING else FordLateralController(CP)
|
||||
self.ford_shadow_curvature = 0.0
|
||||
self.ford_lateral_announced_mode = FordLateralMode.native
|
||||
self.ford_extended_lateral_announced = False
|
||||
self.stock_cruise_button = FordStockCruiseButton()
|
||||
|
||||
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,
|
||||
direction=direction, ramp_type=ramp_type, curvature=-self.apply_curvature_last))
|
||||
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 not lateral_mode_ready:
|
||||
self.apply_curvature_last = 0.0
|
||||
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)
|
||||
if (self.frame % CarControllerParams.STEER_STEP) == 0:
|
||||
lateral = self.ford_lateral.update(CC, CS, actuators) \
|
||||
if self.ford_extended_lateral_announced else FordLateralResult()
|
||||
|
||||
self.apply_curvature_last = lateral.curvature
|
||||
self.ford_shadow_curvature = lateral.shadow_curvature
|
||||
if self.CP.flags & FordFlags.CANFD:
|
||||
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
|
||||
can_sends.append(starpilot_fordcan.create_lat_ctl2_msg(
|
||||
self.packer, self.CAN, 1 if lateral.active else 0,
|
||||
lateral.ramp_type, lateral.precision_type,
|
||||
-lateral.path_offset, -lateral.path_angle,
|
||||
-lateral.curvature, -lateral.curvature_rate, counter))
|
||||
else:
|
||||
can_sends.append(starpilot_fordcan.create_lat_ctl_msg(
|
||||
self.packer, self.CAN, lateral.active,
|
||||
lateral.ramp_type, lateral.precision_type,
|
||||
-lateral.path_offset, -lateral.path_angle,
|
||||
-lateral.curvature, -lateral.curvature_rate))
|
||||
|
||||
if (self.frame % CarControllerParams.LKA_STEP) == 0:
|
||||
if lateral_mode == FordLateralMode.native:
|
||||
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN))
|
||||
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
|
||||
can_sends.append(starpilot_fordcan.create_lka_msg(self.packer, self.CAN))
|
||||
self.ford_extended_lateral_announced = True
|
||||
|
||||
### longitudinal control ###
|
||||
# send acc msg at 50Hz
|
||||
@@ -292,8 +232,7 @@ class CarController(CarControllerBase):
|
||||
show_distance_bars = self.frame - self.distance_bar_frame < 400
|
||||
hands_free_cluster = bool(
|
||||
self.ford_lateral is not None
|
||||
and self.ford_lateral.mode != FordLateralMode.native
|
||||
and self.ford_lateral.mode == self.ford_lateral_announced_mode
|
||||
and self.ford_extended_lateral_announced
|
||||
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,
|
||||
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 opendbc.can import CANDefine, CANParser
|
||||
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."""
|
||||
# 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.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
|
||||
from opendbc.car import Bus, get_safety_config, structs
|
||||
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
|
||||
from collections import deque
|
||||
from typing import cast
|
||||
|
||||
@@ -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 re
|
||||
from dataclasses import dataclass, field, replace
|
||||
|
||||
@@ -170,7 +170,9 @@ def match_fw_to_car(fw_versions: list[CarParams.CarFw], vin: str, allow_exact: b
|
||||
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: dict[bool, list[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:
|
||||
# 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
|
||||
|
||||
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,
|
||||
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"""
|
||||
|
||||
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]:
|
||||
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)
|
||||
|
||||
# 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,
|
||||
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()
|
||||
|
||||
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 brand, config, r in requests:
|
||||
# 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
|
||||
|
||||
# Toggle OBD multiplexing for each request
|
||||
|
||||
@@ -6,7 +6,7 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits
|
||||
from opendbc.car.gm import gmcan
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
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,
|
||||
)
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
@@ -309,7 +309,7 @@ def supports_volt_auto_hold(CP, auto_hold_enabled: bool):
|
||||
auto_hold_enabled and
|
||||
getattr(CP, "openpilotLongitudinalControl", False) and
|
||||
stock_hold_safety_ready and
|
||||
CP.carFingerprint in AUTO_HOLD_VOLT_CARS
|
||||
CP.carFingerprint in GM_AUTO_HOLD_CARS
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import copy
|
||||
import math
|
||||
from cereal import custom
|
||||
from opendbc.can import CANDefine, CANParser
|
||||
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
|
||||
AUTO_HOLD_MIN_DRIVE_TIME_S = 3.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,
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
def __init__(self, CP, FPCP):
|
||||
super().__init__(CP, FPCP)
|
||||
@@ -104,13 +124,18 @@ class CarState(CarStateBase):
|
||||
self.lkas_previously_enabled = 0
|
||||
self.lkas_enabled = 0
|
||||
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.car_gps_config = get_car_gps_config(CP)
|
||||
self.car_gps_supported = self.car_gps_config is not None
|
||||
self.car_gps = None
|
||||
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:
|
||||
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))
|
||||
if gps is not None:
|
||||
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_timestamp_nanos = timestamp_nanos
|
||||
|
||||
@@ -145,8 +189,6 @@ class CarState(CarStateBase):
|
||||
cam_cp = can_parsers[Bus.cam]
|
||||
loopback_cp = can_parsers[Bus.loopback]
|
||||
|
||||
self._update_car_gps(pt_cp)
|
||||
|
||||
ret = structs.CarState()
|
||||
|
||||
volt_like = {
|
||||
@@ -217,6 +259,8 @@ class CarState(CarStateBase):
|
||||
ret.standstill = abs(pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"]) <= STANDSTILL_THRESHOLD and \
|
||||
abs(pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"]) <= STANDSTILL_THRESHOLD
|
||||
|
||||
self._update_car_gps(pt_cp, ret.vEgo)
|
||||
|
||||
if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1:
|
||||
ret.gearShifter = self.parse_gear_shifter("T")
|
||||
else:
|
||||
@@ -327,8 +371,18 @@ class CarState(CarStateBase):
|
||||
|
||||
ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0
|
||||
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
|
||||
ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or
|
||||
pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1)
|
||||
acc_state = pt_cp.vl["AcceleratorPedal2"]["CruiseState"]
|
||||
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.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
|
||||
|
||||
@@ -31,6 +31,8 @@ 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):
|
||||
@@ -344,7 +346,15 @@ def _create_volt_cc_spam_command(CS, actuators, ms_convert):
|
||||
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
|
||||
ego_speed = CS.out.vEgo * ms_convert
|
||||
|
||||
if accel == 0.0:
|
||||
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:
|
||||
|
||||
@@ -15,6 +15,7 @@ from opendbc.car.gm.values import (
|
||||
CC_ONLY_CAR,
|
||||
CC_REGEN_PADDLE_CAR,
|
||||
EV_CAR,
|
||||
GM_AUTO_HOLD_CARS,
|
||||
SDGM_CAR,
|
||||
CarControllerParams,
|
||||
CanBus,
|
||||
@@ -408,7 +409,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning)
|
||||
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:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
@@ -710,18 +711,19 @@ class CarInterface(CarInterfaceBase):
|
||||
if remote_start_boots_comma:
|
||||
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
|
||||
(gm_auto_hold or volt_one_pedal_mode) and
|
||||
candidate in {
|
||||
CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019,
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
}
|
||||
(
|
||||
(gm_auto_hold and candidate in GM_AUTO_HOLD_CARS) or
|
||||
(volt_one_pedal_mode and candidate in {
|
||||
CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019,
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
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
|
||||
# longitudinal is configured but not currently active, so the bit must
|
||||
# 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,
|
||||
)
|
||||
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(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT,
|
||||
|
||||
@@ -8,7 +8,12 @@ from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, DT_CTRL, structs
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
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 (
|
||||
VisualAlert,
|
||||
get_acc_dashboard_always_one,
|
||||
@@ -88,6 +93,107 @@ class TestBoltGps:
|
||||
assert gps["latitude"] == 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)
|
||||
def test_gps_message_is_added_to_powertrain_parser(self, car_model):
|
||||
cp = SimpleNamespace(
|
||||
@@ -103,7 +209,78 @@ class TestBoltGps:
|
||||
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:
|
||||
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([
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
@@ -190,6 +367,14 @@ class TestGMInterface:
|
||||
|
||||
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([
|
||||
("interceptor", True),
|
||||
("ascm_int", False),
|
||||
@@ -385,6 +570,43 @@ class TestGMInterface:
|
||||
assert car_params.openpilotLongitudinalControl
|
||||
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):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
|
||||
fingerprint = _empty_fingerprint()
|
||||
@@ -711,6 +933,63 @@ class TestGMCarController:
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
@@ -533,6 +533,14 @@ EV_CAR = {
|
||||
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)
|
||||
CAMERA_ACC_CAR = {
|
||||
CAR.CHEVROLET_BOLT_ACC_2022_2023,
|
||||
|
||||
@@ -113,11 +113,11 @@ def parse_chevrolet_bolt_can_gps(position: Mapping[str, float]) -> CarGpsSample
|
||||
"altitude": 0.0,
|
||||
"speed": 0.0,
|
||||
"bearingDeg": 0.0,
|
||||
"horizontalAccuracy": 100.0,
|
||||
"horizontalAccuracy": 6.0,
|
||||
"unixTimestampMillis": int(datetime.now(UTC).timestamp() * 1000),
|
||||
"verticalAccuracy": 100.0,
|
||||
"verticalAccuracy": 10.0,
|
||||
"bearingAccuracyDeg": 180.0,
|
||||
"speedAccuracy": 100.0,
|
||||
"speedAccuracy": 0.5,
|
||||
"hasFix": coordinates_valid,
|
||||
"satelliteCount": 0,
|
||||
"vNED": [0.0, 0.0, 0.0],
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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
|
||||
from opendbc.can import CANPacker
|
||||
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.hyundai import hyundaicanfd, hyundaican
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
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
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
@@ -475,6 +477,11 @@ class CarController(CarControllerBase):
|
||||
self._dash_lat_disengage_init = False
|
||||
self._dash_prev_lat_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):
|
||||
if CC.latActive:
|
||||
@@ -748,6 +755,7 @@ class CarController(CarControllerBase):
|
||||
can_sends = []
|
||||
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)
|
||||
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
|
||||
|
||||
# HUD messages
|
||||
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
|
||||
@@ -756,6 +764,7 @@ class CarController(CarControllerBase):
|
||||
if blended_hda2:
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(
|
||||
self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0,
|
||||
longitudinal_active=longitudinal_active,
|
||||
))
|
||||
if self.long_active_ecu:
|
||||
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(
|
||||
@@ -830,7 +839,10 @@ class CarController(CarControllerBase):
|
||||
|
||||
# 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)):
|
||||
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon))
|
||||
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))
|
||||
|
||||
# 5 Hz ACC options
|
||||
if self.frame % 20 == 0 and self.long_active_ecu and not can_canfd_blended:
|
||||
@@ -847,7 +859,15 @@ class CarController(CarControllerBase):
|
||||
can_sends = []
|
||||
|
||||
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
|
||||
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)
|
||||
@@ -876,7 +896,7 @@ class CarController(CarControllerBase):
|
||||
if angle_lkas_alt:
|
||||
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)
|
||||
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))
|
||||
)
|
||||
preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint)
|
||||
@@ -885,7 +905,8 @@ class CarController(CarControllerBase):
|
||||
steering_msg_active, apply_torque, apply_angle,
|
||||
CS.stock_lfa_msg if preserve_stock_lfa_status 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
|
||||
inactive_steering_angle = float(np.clip(CS.angle_steering_angle,
|
||||
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
|
||||
@@ -977,7 +998,7 @@ class CarController(CarControllerBase):
|
||||
# The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
|
||||
# and stops publishing object tracks when it disappears.
|
||||
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,
|
||||
CS.out.brakePressed, CS.out.gasPressed,
|
||||
self.CP.carFingerprint))
|
||||
|
||||
@@ -2,6 +2,8 @@ from collections import deque
|
||||
import copy
|
||||
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 opendbc.can import CANDefine, CANParser
|
||||
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."""
|
||||
# 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.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)
|
||||
|
||||
|
||||
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,
|
||||
stopping, long_override, use_fca, CP):
|
||||
commands = []
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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
|
||||
from opendbc.car import CanBusBase, CanData
|
||||
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,
|
||||
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:
|
||||
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
|
||||
|
||||
@@ -193,7 +198,7 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
|
||||
ret = []
|
||||
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
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(lkas_msg, CAN.ACAN, lkas_values))
|
||||
else:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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.hyundai.hyundaicanfd import CanBus
|
||||
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_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_ECU_KEEPALIVE_CAR, \
|
||||
RADAR_LIVE_LONGITUDINAL_CAR, \
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
|
||||
LEGACY_LONGITUDINAL_CAR, \
|
||||
@@ -25,6 +28,15 @@ from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
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
|
||||
ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise)
|
||||
|
||||
@@ -209,9 +221,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.enableBsm = 0x58b in fingerprint[CAN.ECAN]
|
||||
|
||||
# Send LFA message on cars with HDA
|
||||
if 0x485 in fingerprint[CAN.CAM] and (
|
||||
candidate != CAR.KIA_RAY_EV or fingerprint[CAN.CAM][0x485] == 4
|
||||
):
|
||||
if 0x485 in fingerprint[CAN.CAM]:
|
||||
ret.flags |= HyundaiFlags.SEND_LFA.value
|
||||
|
||||
# These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
|
||||
@@ -226,6 +236,9 @@ class CarInterface(CarInterfaceBase):
|
||||
else:
|
||||
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:
|
||||
ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value
|
||||
if candidate in (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024):
|
||||
@@ -350,14 +363,7 @@ class CarInterface(CarInterfaceBase):
|
||||
params = Params()
|
||||
|
||||
if communication_control is None:
|
||||
if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR:
|
||||
# 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])
|
||||
communication_control = get_communication_control_request(CP.carFingerprint)
|
||||
|
||||
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
|
||||
MRR35_RADAR_START_ADDR = 0x3A5
|
||||
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)
|
||||
@@ -30,6 +33,7 @@ class RadarTrackConfig:
|
||||
frequency: int = 50
|
||||
parser_msg_count: int | None = None
|
||||
expected_length: int | None = None
|
||||
dbc_name: str | None = None
|
||||
|
||||
@property
|
||||
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:
|
||||
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)
|
||||
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)
|
||||
@@ -65,6 +73,10 @@ def radar_tracks_available(radar_config: RadarTrackConfig | None, fingerprint) -
|
||||
if radar_config is None:
|
||||
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)
|
||||
if msg_len is None:
|
||||
return False
|
||||
@@ -78,7 +90,8 @@ def get_radar_can_parser(CP, radar_config):
|
||||
|
||||
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)]
|
||||
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):
|
||||
@@ -223,6 +236,27 @@ class RadarInterface(RadarInterfaceBase):
|
||||
del self.pts[track_key]
|
||||
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":
|
||||
for i in ("1", "2"):
|
||||
track_key = addr * 2 + int(i) - 1
|
||||
|
||||
@@ -24,16 +24,17 @@ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY
|
||||
clear_ioniq_6_torque_when_request_inactive
|
||||
from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \
|
||||
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.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, \
|
||||
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, \
|
||||
HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, CANFD_FUZZY_WHITELIST, \
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \
|
||||
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
|
||||
from opendbc.car.hyundai.fingerprints import FW_VERSIONS
|
||||
@@ -129,6 +130,15 @@ def get_test_toggles() -> SimpleNamespace:
|
||||
|
||||
|
||||
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):
|
||||
CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [],
|
||||
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.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:
|
||||
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, None)
|
||||
assert bool(CP.flags & HyundaiFlags.NON_SCC)
|
||||
@@ -901,7 +947,21 @@ class TestHyundaiFingerprint:
|
||||
fingerprint[2][0x485] = 8
|
||||
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):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
@@ -1038,9 +1098,12 @@ class TestHyundaiFingerprint:
|
||||
ret = update(0, 3)
|
||||
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
|
||||
|
||||
ret = update(2, 4)
|
||||
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,
|
||||
@@ -2467,10 +2530,11 @@ class TestHyundaiFingerprint:
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
|
||||
CP.openpilotLongitudinalControl = False
|
||||
CP.openpilotLongitudinalControl = True
|
||||
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.frame = 1
|
||||
controller.long_active_ecu = True
|
||||
can_bus = CanBus(CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN)
|
||||
stock_lkas = {
|
||||
@@ -2512,7 +2576,6 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["LKAS"]["STEER_MODE"] == 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_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)]
|
||||
@@ -2520,6 +2583,46 @@ class TestHyundaiFingerprint:
|
||||
assert lfa_parser.can_valid
|
||||
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):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
|
||||
@@ -2651,6 +2754,36 @@ class TestHyundaiFingerprint:
|
||||
get_test_toggles(), lka_icon=1, lfa_icon=1)
|
||||
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):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV9
|
||||
|
||||
@@ -2,6 +2,8 @@ import re
|
||||
from dataclasses import dataclass, field
|
||||
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.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL
|
||||
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_RAY_EV_VIN_VDS_PREFIXES = frozenset({
|
||||
"CG81A",
|
||||
})
|
||||
|
||||
|
||||
ALT_BUS_LDA_BUTTON_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)
|
||||
|
||||
|
||||
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]]:
|
||||
# Returns unique, platform-specific identification codes for a set of versions
|
||||
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_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.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 | {
|
||||
CAR.HYUNDAI_IONIQ,
|
||||
CAR.HYUNDAI_KONA_EV_2022,
|
||||
|
||||
@@ -77,7 +77,7 @@ class CarController(CarControllerBase):
|
||||
self.angle_bus = CanBus.angle_for_cp(CP)
|
||||
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.prev_close_distance = 0
|
||||
@@ -302,7 +302,7 @@ class CarController(CarControllerBase):
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
steer_target = self._legacy_2025_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
|
||||
@@ -325,14 +325,14 @@ class CarController(CarControllerBase):
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
if lkas_active and not self.angle_lkas_active:
|
||||
self.apply_steer_last = CS.out.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(
|
||||
steer_target,
|
||||
self.apply_steer_last,
|
||||
@@ -361,7 +361,7 @@ class CarController(CarControllerBase):
|
||||
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 \
|
||||
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
|
||||
if lat_active and not self.angle_lkas_active:
|
||||
self.apply_steer_last = CS.out.steeringAngleDeg
|
||||
|
||||
@@ -85,14 +85,14 @@ class CarState(CarStateBase):
|
||||
|
||||
if self.CP.flags & SubaruFlags.LKAS_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:
|
||||
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):
|
||||
# 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.steeringTorqueEps = cp_angle.vl["Steering_Torque"]["Steer_Torque_Output"]
|
||||
|
||||
@@ -42,7 +42,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value
|
||||
if candidate in SUBARU_STOP_START_CARS:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
|
||||
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023):
|
||||
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
|
||||
@@ -244,7 +244,7 @@ def test_outback_2023_uses_d_platform_bus_layout():
|
||||
assert CP.flags & SubaruFlags.D_PLATFORM
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
|
||||
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.LEGACY_2025_ANGLE_LIMITS)
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
|
||||
assert CanBus.main_for_cp(CP) == CanBus.alt
|
||||
assert CanBus.angle_for_cp(CP) == CanBus.main
|
||||
assert parsers[Bus.pt].bus == CanBus.alt
|
||||
@@ -546,6 +546,25 @@ def test_ascent_2023_uses_gen2_angle_bus_layout():
|
||||
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():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
|
||||
parsers = CarState.get_can_parsers(CP)
|
||||
@@ -622,8 +641,9 @@ def test_angle_controller_blocks_low_speed_mads_engagement():
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
|
||||
|
||||
def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
@pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
|
||||
def test_angle_controller_uses_fixed_angle_rate_limits(platform):
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-14.88))
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
@@ -706,11 +726,20 @@ def test_ascent_angle_controller_blocks_parking_lot_aol_engagement():
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
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
|
||||
|
||||
CS.out.gearShifter = structs.CarState.GearShifter.reverse
|
||||
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_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ non_tested_cars = [
|
||||
TOYOTA.TOYOTA_RAV4H,
|
||||
|
||||
# No recorded routes yet
|
||||
VOLVO.VOLVO_V40,
|
||||
VOLVO.VOLVO_XC40_RECHARGE,
|
||||
VOLVO.VOLVO_S60_RECHARGE,
|
||||
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]
|
||||
"VOLVO_XC40_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
|
||||
"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.values import CAR, MIN_ACC_SPEED, NO_STOP_TIMER_CAR, PEDAL_TRANSITION, TSS2_CAR, \
|
||||
CarControllerParams, ToyotaFlags, \
|
||||
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, RADAR_ACC_CAR, SECOC_CAR
|
||||
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS
|
||||
from opendbc.can import CANPacker
|
||||
|
||||
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_INTERCEPTOR_COMFORT_TARGET_ACCEL = 2.0 # m/s^2
|
||||
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
|
||||
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
|
||||
@@ -49,8 +54,6 @@ MAX_USER_TORQUE = 500
|
||||
PARK = structs.CarState.GearShifter.park
|
||||
REVERSE = structs.CarState.GearShifter.reverse
|
||||
|
||||
TOYOTA_AUTO_HOLD_CARS = TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR
|
||||
|
||||
# Lock / unlock door commands - Credit goes to AlexandreSato!
|
||||
LOCK_CMD = b"\x40\x05\x30\x11\x00\x80\x00\x00"
|
||||
UNLOCK_CMD = b"\x40\x05\x30\x11\x00\x40\x00\x00"
|
||||
@@ -82,6 +85,14 @@ def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
|
||||
)
|
||||
|
||||
|
||||
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):
|
||||
kiBP = [2., 5.]
|
||||
kiV = [0.5, 0.25]
|
||||
@@ -265,7 +276,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
max_interceptor_gas = 0.5
|
||||
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):
|
||||
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:
|
||||
@@ -300,23 +311,24 @@ class CarController(CarControllerBase):
|
||||
|
||||
self.last_standstill = CS.out.standstill
|
||||
|
||||
def create_auto_brake_hold_messages(self, CS: structs.CarState, brake_hold_allowed_timer: int = 100):
|
||||
can_sends = []
|
||||
brake_hold_allowed = (CS.out.standstill and CS.out.cruiseState.available and
|
||||
def update_auto_hold_state(self, CS: structs.CarState, cancel_requested: bool = False,
|
||||
activation_frames: int = TOYOTA_AUTO_HOLD_ACTIVATION_FRAMES):
|
||||
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
|
||||
CS.out.gearShifter not in (PARK, REVERSE))
|
||||
|
||||
if brake_hold_allowed and not self.brake_hold_active and CS.out.brakePressed:
|
||||
self._brake_hold_counter += 1
|
||||
self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer
|
||||
self.brake_hold_active = self._brake_hold_counter > activation_frames
|
||||
elif not brake_hold_allowed:
|
||||
self._brake_hold_counter = 0
|
||||
self.brake_hold_active = False
|
||||
|
||||
if self.frame % 2 == 0:
|
||||
can_sends.append(toyotacan.create_brake_hold_command(self.packer, self.frame, CS.pre_collision_2, self.brake_hold_active))
|
||||
return 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):
|
||||
actuators = CC.actuators
|
||||
@@ -414,10 +426,9 @@ class CarController(CarControllerBase):
|
||||
|
||||
self._update_standstill_request(CC, CS, actuators, starpilot_toggles)
|
||||
if supports_toyota_auto_hold(self.CP, getattr(starpilot_toggles, "toyota_auto_hold", False)):
|
||||
can_sends.extend(self.create_auto_brake_hold_messages(CS))
|
||||
elif self.brake_hold_active:
|
||||
self._brake_hold_counter = 0
|
||||
self.brake_hold_active = False
|
||||
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)
|
||||
|
||||
@@ -525,6 +536,11 @@ class CarController(CarControllerBase):
|
||||
|
||||
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
|
||||
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,
|
||||
|
||||
@@ -90,8 +90,6 @@ class CarState(CarStateBase):
|
||||
self.has_can_filter = self.FPCP.flags & ToyotaStarPilotFlags.RADAR_CAN_FILTER.value
|
||||
self.has_SDSU = self.FPCP.flags & ToyotaStarPilotFlags.SMART_DSU.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:
|
||||
cp = can_parsers[Bus.pt]
|
||||
@@ -227,9 +225,6 @@ class CarState(CarStateBase):
|
||||
if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V:
|
||||
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:
|
||||
self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"]
|
||||
|
||||
|
||||
@@ -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.carcontroller import CarController
|
||||
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, \
|
||||
ToyotaSafetyFlags, LEGACY_PRIUS_CAR
|
||||
ToyotaSafetyFlags, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS
|
||||
from opendbc.car.disable_ecu import disable_ecu
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
@@ -164,8 +164,8 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.GAS_INTERCEPTOR.value
|
||||
|
||||
toyota_auto_hold = Params(return_defaults=True).get_bool("ToyotaAutoHold")
|
||||
if toyota_auto_hold and candidate in (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR):
|
||||
ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
|
||||
if toyota_auto_hold and ret.openpilotLongitudinalControl and candidate in TOYOTA_AUTO_HOLD_CARS:
|
||||
ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
|
||||
ret.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value
|
||||
|
||||
if not ret.openpilotLongitudinalControl:
|
||||
|
||||
@@ -10,6 +10,7 @@ from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
|
||||
from opendbc.car.toyota import toyotacan
|
||||
from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \
|
||||
get_prius_positive_feedforward_scale, \
|
||||
get_rav4_interceptor_pedal_scale, \
|
||||
limit_interceptor_pcm_accel, \
|
||||
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
|
||||
limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
|
||||
@@ -18,9 +19,10 @@ from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_int
|
||||
from opendbc.car.toyota.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.toyota.interface import CarInterface
|
||||
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, \
|
||||
ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, get_platform_codes
|
||||
ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, TOYOTA_AUTO_HOLD_CARS, \
|
||||
get_platform_codes
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.common.params import Params
|
||||
|
||||
@@ -187,13 +189,15 @@ class TestToyotaInterfaces:
|
||||
if car_model in TSS2_CAR and car_model not in SECOC_CAR:
|
||||
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()
|
||||
try:
|
||||
params.put_bool("ToyotaAutoHold", True)
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.TOYOTA_CAMRY_TSS2,
|
||||
{bus: {} for bus in range(8)},
|
||||
candidate,
|
||||
{bus: ({0x2FF: 8} if candidate in (CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H) and bus == 0 else {})
|
||||
for bus in range(8)},
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
@@ -204,13 +208,20 @@ class TestToyotaInterfaces:
|
||||
params.remove("ToyotaAutoHold")
|
||||
|
||||
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
|
||||
|
||||
def test_auto_hold_is_disabled_by_default(self):
|
||||
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(
|
||||
CAR.TOYOTA_CAMRY_TSS2,
|
||||
candidate,
|
||||
{bus: {} for bus in range(8)},
|
||||
[],
|
||||
alpha_long=False,
|
||||
@@ -220,7 +231,7 @@ class TestToyotaInterfaces:
|
||||
)
|
||||
|
||||
assert not car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
|
||||
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
|
||||
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
|
||||
|
||||
def test_prius_openpilot_long_uses_hybrid_long_defaults(self):
|
||||
car_params = CarInterface.get_params(
|
||||
@@ -735,6 +746,8 @@ class TestToyotaCarController:
|
||||
controller.standstill_req = standstill_req
|
||||
controller.last_standstill = last_standstill
|
||||
controller.accel = 0.0
|
||||
controller.brake_hold_active = False
|
||||
controller._brake_hold_counter = 0
|
||||
return controller
|
||||
|
||||
@staticmethod
|
||||
@@ -774,6 +787,14 @@ class TestToyotaCarController:
|
||||
)
|
||||
|
||||
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,
|
||||
@@ -784,12 +805,11 @@ class TestToyotaCarController:
|
||||
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])
|
||||
controller.frame = 0
|
||||
controller.brake_hold_active = False
|
||||
controller._brake_hold_counter = 0
|
||||
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
@@ -799,28 +819,22 @@ class TestToyotaCarController:
|
||||
brakePressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
pre_collision_2={},
|
||||
)
|
||||
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
controller.update_auto_hold_state(cs, activation_frames=0)
|
||||
assert controller.brake_hold_active
|
||||
|
||||
cs.out.brakePressed = False
|
||||
controller.frame = 2
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
controller.update_auto_hold_state(cs, activation_frames=0)
|
||||
assert controller.brake_hold_active
|
||||
|
||||
cs.out.gasPressed = True
|
||||
controller.frame = 4
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
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])
|
||||
controller.frame = 0
|
||||
controller.brake_hold_active = False
|
||||
controller._brake_hold_counter = 0
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
@@ -829,10 +843,9 @@ class TestToyotaCarController:
|
||||
brakePressed=False,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
pre_collision_2={},
|
||||
)
|
||||
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
controller.update_auto_hold_state(cs, activation_frames=0)
|
||||
assert not controller.brake_hold_active
|
||||
|
||||
def test_prius_resume_request_releases_standstill_latch(self):
|
||||
@@ -972,12 +985,9 @@ class TestToyotaCarController:
|
||||
assert parser.can_valid
|
||||
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.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
|
||||
controller.frame = 0
|
||||
controller.brake_hold_active = False
|
||||
controller._brake_hold_counter = 0
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
@@ -986,16 +996,19 @@ class TestToyotaCarController:
|
||||
brakePressed=True,
|
||||
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)])
|
||||
assert controller.brake_hold_active
|
||||
assert parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -1.0
|
||||
assert parser.vl["PRE_COLLISION_2"]["PBRTRGR"] == 1
|
||||
assert parser.vl["ACC_CONTROL"]["ACCEL_CMD"] == -1.0
|
||||
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):
|
||||
controller = self._make_controller()
|
||||
@@ -1023,6 +1036,27 @@ class TestToyotaCarController:
|
||||
|
||||
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):
|
||||
controller = self._make_controller()
|
||||
controller.CP.enableGasInterceptorDEPRECATED = True
|
||||
|
||||
@@ -89,38 +89,6 @@ def create_pcs_commands(packer, accel, active, mass):
|
||||
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):
|
||||
values = {
|
||||
"GAS_RELEASED": 0,
|
||||
|
||||
@@ -624,6 +624,11 @@ ANGLE_CONTROL_CAR = CAR.with_flags(ToyotaFlags.ANGLE_CONTROL)
|
||||
|
||||
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_STOP_TIMER_CAR = CAR.with_flags(ToyotaFlags.NO_STOP_TIMER)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from collections import deque
|
||||
|
||||
import numpy as np
|
||||
|
||||
from opendbc.can.packer import CANPacker
|
||||
@@ -5,16 +7,20 @@ from opendbc.car import Bus
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.lateral import apply_std_steer_angle_limits
|
||||
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,
|
||||
create_lca_5_message, create_lca_6_message, create_lca_7_message, create_pscm_related_message)
|
||||
from opendbc.car.volvo.values import CarControllerParams
|
||||
from opendbc.car.volvo.volvocan import (create_c1_cancel, create_c1_pscm_message, create_c1_steering_control, create_lca_message,
|
||||
create_pscm_message, create_lca_3_message, create_lca_2_message, create_lca_4_message, create_lca_5_message,
|
||||
create_lca_6_message, create_lca_7_message, create_pscm_related_message)
|
||||
from opendbc.car.volvo.values import CAR, CarControllerParams, VolvoC1PlatformConfig
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, 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.c1_torque_samples = deque(maxlen=CarControllerParams.C1_N_ZERO_TORQUE)
|
||||
self.c1_recovery_until = -1
|
||||
|
||||
self.gear_acc = 60
|
||||
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
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
if self.is_c1:
|
||||
return self._update_c1(CC, CS)
|
||||
|
||||
can_sends = []
|
||||
actuators = CC.actuators
|
||||
|
||||
@@ -285,3 +294,50 @@ class CarController(CarControllerBase):
|
||||
self.frame += 1
|
||||
self.last_lat_active = CC.latActive
|
||||
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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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.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.volvo.values import CAR, DBC, VolvoC1PlatformConfig, VolvoSPAPlatformConfig
|
||||
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
TransmissionType = structs.CarParams.TransmissionType
|
||||
@@ -16,6 +17,7 @@ STEERING_PRESSED_THRESHOLD = 2
|
||||
class CarState(CarStateBase):
|
||||
def __init__(self, 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.gas_pressed_prev = False
|
||||
self.dispatch_lca_2_msg = False
|
||||
@@ -34,8 +36,22 @@ class CarState(CarStateBase):
|
||||
self.msg_lca_4 = {}
|
||||
self.msg_lca_6 = {}
|
||||
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:
|
||||
if self.is_c1:
|
||||
return self._update_c1(can_parsers)
|
||||
|
||||
cp_main = can_parsers[Bus.main]
|
||||
cp_pt = can_parsers[Bus.pt]
|
||||
cp_party = can_parsers[Bus.party]
|
||||
@@ -137,8 +153,83 @@ class CarState(CarStateBase):
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
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
|
||||
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 {
|
||||
Bus.main: CANParser(DBC[CP.carFingerprint][Bus.main], [], 0),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 1),
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
from opendbc.car.volvo.values import CAR
|
||||
|
||||
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: [{
|
||||
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
|
||||
}],
|
||||
|
||||
@@ -2,11 +2,10 @@ from opendbc.car import structs, get_safety_config
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.volvo.carcontroller import CarController
|
||||
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
|
||||
|
||||
VOLVO_FLAG_SPA = 1
|
||||
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:
|
||||
ret.brand = 'volvo'
|
||||
|
||||
platform = CAR(candidate).config
|
||||
safety_param = 0
|
||||
if isinstance(CAR(candidate).config, VolvoSPAPlatformConfig):
|
||||
safety_param = VOLVO_FLAG_SPA
|
||||
if isinstance(platform, VolvoSPAPlatformConfig):
|
||||
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(structs.CarParams.SafetyModel.noOutput)]
|
||||
|
||||
ret.dashcamOnly = False
|
||||
|
||||
ret.steerActuatorDelay = 0.3
|
||||
ret.steerActuatorDelay = 0.2 if isinstance(platform, VolvoC1PlatformConfig) else 0.3
|
||||
ret.steerLimitTimer = 0.1
|
||||
ret.steerAtStandstill = True
|
||||
ret.steerAtStandstill = not isinstance(platform, VolvoC1PlatformConfig)
|
||||
|
||||
# Use angle-based steering control for Volvo CMA platform
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
@@ -39,4 +41,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
ret.pcmCruise = True
|
||||
|
||||
if isinstance(platform, VolvoC1PlatformConfig):
|
||||
ret.transmissionType = TransmissionType.automatic
|
||||
|
||||
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.helpers import checksum_lca_5_message
|
||||
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():
|
||||
@@ -67,3 +68,70 @@ def test_controller_relays_stock_lca5_angle_when_inactive():
|
||||
if raw & (1 << 14):
|
||||
raw -= 1 << 15
|
||||
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
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import IntFlag
|
||||
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms
|
||||
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.fw_query_definitions import FwQueryConfig
|
||||
|
||||
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:
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
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):
|
||||
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(
|
||||
[VolvoCarDocs("Volvo XC40 Recharge 2021-23")],
|
||||
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)
|
||||
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,
|
||||
authority_pos: int = 614, authority_neg: int = -614,
|
||||
overrides: dict | None = None):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1497,7 +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_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_ RAY_LKAS_BTN : 0|2@1+ (1,0) [0|3] "" XXX
|
||||
SG_ RAY_LKAS_BTN : 4|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 1426 LABEL11: 8 XXX
|
||||
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
|
||||
|
||||
@@ -1497,7 +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_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_ RAY_LKAS_BTN : 0|2@1+ (1,0) [0|3] "" XXX
|
||||
SG_ RAY_LKAS_BTN : 4|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 1426 LABEL11: 8 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
@@ -11,3 +11,4 @@ class ALTERNATIVE_EXPERIENCE:
|
||||
|
||||
ALWAYS_ON_LATERAL = 32
|
||||
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_GM_REMAP_CANCEL_TO_DISTANCE 64
|
||||
#define ALT_EXP_TOYOTA_AUTO_HOLD 128
|
||||
|
||||
extern int alternative_experience;
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
#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.
|
||||
#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
|
||||
@@ -88,10 +94,8 @@ static bool ford_get_quality_flag_valid(const CANPacket_t *msg) {
|
||||
|
||||
static bool ford_lka_steering = false;
|
||||
static bool ford_extended_lateral = false;
|
||||
static bool ford_angle_mode = false;
|
||||
static bool ford_longitudinal = false;
|
||||
static bool ford_cancel_resume_button = false;
|
||||
static int16_t ford_shadow_curvature = 0;
|
||||
|
||||
// Curvature rate limits
|
||||
#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 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) {
|
||||
if (msg->bus == FORD_MAIN_BUS) {
|
||||
// Update in motion state from standstill signal
|
||||
@@ -300,10 +272,8 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
if (!ford_lka_steering) {
|
||||
ford_angle_mode = (msg->data[4] & 0x1U) != 0U;
|
||||
ford_extended_lateral = (msg->data[4] & 0x2U) != 0U;
|
||||
ford_shadow_curvature = (int16_t)((msg->data[5] << 8) | msg->data[6]);
|
||||
if (ford_angle_mode && !ford_extended_lateral) {
|
||||
if ((msg->data[4] & 0x1U) != 0U) {
|
||||
tx = false;
|
||||
}
|
||||
}
|
||||
@@ -327,20 +297,9 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
|
||||
if (ford_extended_lateral) {
|
||||
violation |= desired_path_offset != 0;
|
||||
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 |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
|
||||
FORD_EXTENDED_STEERING_LIMITS);
|
||||
}
|
||||
violation |= desired_path_angle != 0;
|
||||
violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
|
||||
FORD_EXTENDED_STEERING_LIMITS);
|
||||
if (!steer_control_enabled) {
|
||||
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) {
|
||||
violation |= desired_path_offset != 0;
|
||||
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 |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
|
||||
FORD_CANFD_EXTENDED_STEERING_LIMITS);
|
||||
}
|
||||
violation |= desired_path_angle != 0;
|
||||
violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
|
||||
FORD_CANFD_EXTENDED_STEERING_LIMITS);
|
||||
if (!steer_control_enabled) {
|
||||
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);
|
||||
ford_lka_steering = GET_FLAG(param, FORD_PARAM_LKA_STEERING);
|
||||
ford_extended_lateral = false;
|
||||
ford_angle_mode = false;
|
||||
ford_cancel_resume_button = false;
|
||||
ford_shadow_curvature = 0;
|
||||
ford_desired_path_angle_last = 0;
|
||||
|
||||
ford_longitudinal = false;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#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/modes/hyundai_common.h"
|
||||
|
||||
|
||||
@@ -224,10 +224,16 @@ static void tesla_preap_rx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
|
||||
#define TOYOTA_AUTO_HOLD_ACCEL -1000 // -1.0 m/s^2 in ACC_CONTROL units
|
||||
|
||||
// Stock longitudinal
|
||||
#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 */ \
|
||||
@@ -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
|
||||
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
|
||||
if (toyota_stock_longitudinal) {
|
||||
@@ -394,12 +404,7 @@ static bool toyota_tx_hook(const CANPacket_t *msg) {
|
||||
tx = false;
|
||||
}
|
||||
|
||||
// Auto brake hold replaces the camera AEB message only while stopped.
|
||||
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) {
|
||||
if ((msg->addr == 0x344U) && toyota_stock_longitudinal) {
|
||||
tx = false;
|
||||
}
|
||||
}
|
||||
@@ -566,21 +571,11 @@ static safety_config toyota_init(uint16_t param) {
|
||||
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 = {
|
||||
.init = toyota_init,
|
||||
.rx = toyota_rx_hook,
|
||||
.rx_all = toyota_rx_all_hook,
|
||||
.tx = toyota_tx_hook,
|
||||
.fwd = toyota_fwd_hook,
|
||||
.get_checksum = toyota_get_checksum,
|
||||
.compute_checksum = toyota_compute_checksum,
|
||||
.get_quality_flag_valid = toyota_get_quality_flag_valid,
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
#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
|
||||
#define VOLVO_FLAG_SPA 1U
|
||||
#define VOLVO_FLAG_C1 2U
|
||||
|
||||
// Volvo CAN message addresses shared between CMA and SPA
|
||||
#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_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
|
||||
#define VOLVO_CMA_BUS1_SPEED 0x70U // RX vehicle speed
|
||||
#define VOLVO_CMA_ECM_1 0x250U // RX accelerator pedal position
|
||||
@@ -44,6 +55,10 @@
|
||||
#define VOLVO_MAX_ANGLE_CAN 9650
|
||||
#define VOLVO_RELAY_ANGLE_TOLERANCE 54 // approximately 3 degrees
|
||||
|
||||
#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
|
||||
// Using same naming as carstate.py for consistency: main, pt, party
|
||||
@@ -54,6 +69,7 @@
|
||||
// Runtime addresses set by volvo_init based on safetyParam
|
||||
static uint16_t volvo_ecm_1_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) {
|
||||
return (int)(((uint16_t)(msg->data[byte] & 0x7FU) << 8U) | msg->data[byte + 1U]);
|
||||
@@ -67,6 +83,21 @@ static int volvo_lca_5_angle(const CANPacket_t *msg) {
|
||||
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 = {
|
||||
.max_angle = VOLVO_MAX_ANGLE_CAN,
|
||||
.angle_deg_to_can = VOLVO_ANGLE_DEG_TO_CAN,
|
||||
@@ -81,8 +112,51 @@ static const AngleSteeringLimits VOLVO_ANGLE_STEERING_LIMITS = {
|
||||
.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) {
|
||||
|
||||
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
|
||||
if (msg->bus == VOLVO_MAIN_BUS) {
|
||||
// Update brake pedal and cruise state from BCM2
|
||||
@@ -158,6 +232,33 @@ static void volvo_rx_hook(const CANPacket_t *msg) {
|
||||
static bool volvo_tx_hook(const CANPacket_t *msg) {
|
||||
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 frame also contains an angle-shaped field, but the imported controller
|
||||
// deliberately leaves that field at the observed vehicle value.
|
||||
@@ -255,6 +356,22 @@ static bool volvo_tx_hook(const CANPacket_t *msg) {
|
||||
|
||||
static safety_config volvo_init(uint16_t param) {
|
||||
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
|
||||
volvo_ecm_1_addr = spa ? VOLVO_SPA_ECM_1 : VOLVO_CMA_ECM_1;
|
||||
|
||||
@@ -170,6 +170,11 @@ class TestFordSafetyBase(common.CarSafetyTest):
|
||||
}
|
||||
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
|
||||
def _lat_ctl_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float):
|
||||
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)
|
||||
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):
|
||||
for allowed in (0, 1):
|
||||
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.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):
|
||||
STEER_MESSAGE = MSG_LateralMotionControl
|
||||
STOCK_LONGITUDINAL = True
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/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
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
@@ -417,6 +417,18 @@ class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, Test
|
||||
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):
|
||||
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM | \
|
||||
SubaruSafetyFlags.STOP_START_BUTTON
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import unittest
|
||||
|
||||
from opendbc.car.tesla.preap.interface import SAFETY_TESLA_PREAP
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
import opendbc.safety.tests.common as common
|
||||
|
||||
@@ -36,6 +37,12 @@ class TestTeslaPreAPSafety(common.SafetyTestBase):
|
||||
dat[0] = lever & 0x3F
|
||||
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
|
||||
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
|
||||
@@ -123,6 +130,30 @@ class TestTeslaPreAPSafety(common.SafetyTestBase):
|
||||
self.assertTrue(self._tx(self._epas_control_msg(1)))
|
||||
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):
|
||||
self.safety.set_controls_allowed(True)
|
||||
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))
|
||||
self.assertEqual(not bad and not stock_longitudinal, self._tx(msg))
|
||||
|
||||
def test_auto_brake_hold_aeb_replacement_only_at_standstill(self):
|
||||
self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.ALLOW_AEB)
|
||||
hold_msg = libsafety_py.make_CANPacket(0x344, 0, b"\xfd\x80\x00\x00\x00\x00\x00\xcc")
|
||||
def test_auto_hold_acc_control_is_narrowly_allowed_only_at_standstill(self):
|
||||
if (not self.LONGITUDINAL or
|
||||
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._toggle_aol(True))
|
||||
self._rx(self._user_gas_msg(False))
|
||||
self.safety.set_controls_allowed(False)
|
||||
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.assertFalse(self._tx(hold_msg))
|
||||
self.assertEqual(0, self.safety.safety_fwd_hook(2, 0x344))
|
||||
|
||||
self._rx(self._speed_msg(0))
|
||||
self._rx(self._user_gas_msg(True))
|
||||
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._toggle_aol(False))
|
||||
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
|
||||
def test_lta_steer_cmd(self):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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
|
||||
by ``safetyParam``:
|
||||
@@ -8,14 +8,13 @@ by ``safetyParam``:
|
||||
- ``safetyParam == 0`` → CMA platform (Volvo XC40 Recharge)
|
||||
- ``safetyParam == VOLVO_FLAG_SPA`` → SPA platform (Volvo S60 Recharge,
|
||||
Polestar 2)
|
||||
- ``safetyParam == VOLVO_FLAG_C1`` → C1 platform (Volvo V40)
|
||||
|
||||
The two platforms share LCA/PSCM/etc. addresses on the main and party buses
|
||||
but use *different* PT-bus addresses and signal scales for ECM_1 and
|
||||
BUS1_CRUISE_CONTROL. Vehicle speed is read from main-bus SPEED on both, so it
|
||||
is not platform-dependent. This test file exercises both platforms through the
|
||||
same generic ``CarSafetyTest`` harness so that any future divergence between
|
||||
``carstate.py`` and ``volvo.h`` — e.g. a threshold drifting out of sync — is
|
||||
caught on a laptop instead of in the car.
|
||||
CMA and SPA share LCA/PSCM/etc. addresses on the main and party buses but use
|
||||
different PT-bus addresses and signal scales. C1 uses the V40's legacy CAN
|
||||
layout and its own safety allowlist. The tests exercise all three through the
|
||||
generic ``CarSafetyTest`` harness so divergence between ``carstate.py`` and
|
||||
``volvo.h`` is caught before running in a car.
|
||||
|
||||
Companion to: ``opendbc/car/volvo/carstate.py`` (must agree on thresholds).
|
||||
"""
|
||||
@@ -25,13 +24,16 @@ import re
|
||||
import unittest
|
||||
|
||||
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
|
||||
import opendbc.safety.tests.common as common
|
||||
from opendbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
|
||||
# Must match VOLVO_FLAG_SPA in opendbc/safety/modes/volvo.h
|
||||
VOLVO_FLAG_SPA = 1
|
||||
# Must match the flags in opendbc/safety/modes/volvo.h
|
||||
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
|
||||
VOLVO_SPEED_TO_MS = 0.003977
|
||||
@@ -332,5 +334,124 @@ class TestVolvoSPA(TestVolvoSafetyBase):
|
||||
"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__":
|
||||
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
extern const uint8_t gitversion[19];
|
||||
const uint8_t gitversion[19] = "DEV-cec1a0fb-DEBUG";
|
||||
const uint8_t gitversion[19] = "DEV-eedd73e5-DEBUG";
|
||||
|
||||
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
Reference in New Issue
Block a user