mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-01 20:03:51 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a3d96fbae | |||
| 5f95574f02 |
@@ -81,7 +81,6 @@ selfdrive/modeld/models/*.pkl
|
||||
# openpilot log files
|
||||
*.bz2
|
||||
*.zst
|
||||
!selfdrive/modeld/firmware/amdgpu/*.zst
|
||||
|
||||
build/
|
||||
|
||||
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
# 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,5 +1,4 @@
|
||||
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,20 +21,6 @@ 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!
|
||||
|
||||
@@ -91,9 +77,4 @@ 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.
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
# 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.
|
||||
@@ -27,10 +27,6 @@ add_panda_targets() {
|
||||
panda_h7_remote_can_ignition_only
|
||||
panda_hkg_remote_can_ignition_only
|
||||
panda_h7_hkg_remote_can_ignition_only
|
||||
panda_tesla_wake
|
||||
panda_h7_tesla_wake
|
||||
panda_tesla_wake_can_ignition_only
|
||||
panda_h7_tesla_wake_can_ignition_only
|
||||
panda_jungle_h7
|
||||
body_h7
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ using Car = import "car.capnp";
|
||||
|
||||
struct StarPilotCarControl @0x81c2f05a394cf4af {
|
||||
hudControl @0 :HUDControl;
|
||||
steeringLimitInfo @1 :SteeringLimitInfo;
|
||||
|
||||
struct HUDControl {
|
||||
audibleAlert @0 :AudibleAlert;
|
||||
@@ -50,16 +49,6 @@ struct StarPilotCarControl @0x81c2f05a394cf4af {
|
||||
uwu @22;
|
||||
}
|
||||
}
|
||||
|
||||
struct SteeringLimitInfo {
|
||||
valid @0 :Bool;
|
||||
modelLimitErrorDeg @1 :Float32;
|
||||
resumeLimitErrorDeg @2 :Float32;
|
||||
cooperativeLimitErrorDeg @3 :Float32;
|
||||
cooperativeOffsetDeg @4 :Float32;
|
||||
monoTime @5 :UInt64;
|
||||
combinedLimitErrorDeg @6 :Float32;
|
||||
}
|
||||
}
|
||||
|
||||
struct StarPilotCarParams @0xaedffd8f31e7b55d {
|
||||
|
||||
Binary file not shown.
@@ -774,7 +774,6 @@ struct ChestnutState {
|
||||
pcieLtssm @7 :UInt8;
|
||||
supplyVoltage @8 :UInt16; # mV
|
||||
supplyCurrent @9 :Int16; # mA
|
||||
supplyFault @10 :Bool;
|
||||
}
|
||||
|
||||
struct RadarState @0x9a185389d6fdd05f {
|
||||
|
||||
@@ -152,8 +152,7 @@ class FrequencyTracker:
|
||||
class SubMaster:
|
||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
||||
ignore_valid: Optional[list[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None,
|
||||
drain_services: list[str] | None = None):
|
||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
||||
self.frame = -1
|
||||
self.services = services
|
||||
self.seen = {s: False for s in services}
|
||||
@@ -161,9 +160,6 @@ class SubMaster:
|
||||
self.recv_time = {s: 0. for s in services}
|
||||
self.recv_frame = {s: 0 for s in services}
|
||||
self.sock = {}
|
||||
self.drained = {s: [] for s in (drain_services or [])}
|
||||
if not self.drained.keys() <= set(services):
|
||||
raise ValueError("Drained services must be subscribed")
|
||||
self.data = {}
|
||||
self.logMonoTime = {s: 0 for s in services}
|
||||
|
||||
@@ -191,7 +187,7 @@ class SubMaster:
|
||||
|
||||
for s in services:
|
||||
p = self.poller if s not in self.non_polled_services else None
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=s not in self.drained)
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
|
||||
|
||||
try:
|
||||
data = new_message(s)
|
||||
@@ -211,28 +207,14 @@ class SubMaster:
|
||||
def _check_avg_freq(self, s: str) -> bool:
|
||||
return SERVICE_LIST[s].frequency > 0.99 and (s not in self.ignore_average_freq) and (s not in self.ignore_alive)
|
||||
|
||||
def _recv_socket(self, sock):
|
||||
message = recv_one_or_none(sock)
|
||||
if not self.drained or message is None:
|
||||
return message
|
||||
# Native Poller returns fresh socket wrappers; identify the service by data.
|
||||
service = message.which()
|
||||
if service not in self.drained:
|
||||
return message
|
||||
# Preserve event edges for observers, but update state/frequency only once.
|
||||
self.drained[service] = [message, *drain_sock(sock)]
|
||||
return self.drained[service][-1]
|
||||
|
||||
def update(self, timeout: int = 100) -> None:
|
||||
for service in self.drained:
|
||||
self.drained[service] = []
|
||||
msgs = []
|
||||
for sock in self.poller.poll(timeout):
|
||||
msgs.append(self._recv_socket(sock))
|
||||
msgs.append(recv_one_or_none(sock))
|
||||
|
||||
# non-blocking receive for non-polled sockets
|
||||
for s in self.non_polled_services:
|
||||
msgs.append(self._recv_socket(self.sock[s]))
|
||||
msgs.append(recv_one_or_none(self.sock[s]))
|
||||
self.update_msgs(time.monotonic(), msgs)
|
||||
|
||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||
@@ -280,7 +262,6 @@ class SubMaster:
|
||||
ignore_valid=self.ignore_valid,
|
||||
addr=self.addr,
|
||||
frequency=None if self.poll is not None else self.update_freq,
|
||||
drain_services=list(self.drained),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import random
|
||||
import time
|
||||
import pytest
|
||||
from typing import Sized, cast
|
||||
|
||||
import cereal.messaging as messaging
|
||||
@@ -17,29 +16,6 @@ class TestSubMaster:
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep(3)
|
||||
|
||||
@pytest.mark.parametrize("poll", [None, "deviceState"])
|
||||
def test_drain_preserves_short_events_with_native_socket_wrappers(self, poll):
|
||||
pub = messaging.PubMaster(["carState", "deviceState"])
|
||||
sm = messaging.SubMaster(["carState", "deviceState"], poll=poll, drain_services=["carState"])
|
||||
zmq_sleep()
|
||||
pressed = messaging.new_message("carState", valid=True)
|
||||
button = pressed.carState.init("buttonEvents", 1)[0]
|
||||
button.type, button.pressed = "accelCruise", True
|
||||
pub.send("carState", pressed)
|
||||
latest = messaging.new_message("carState", valid=True)
|
||||
latest.carState.vEgo = 12.0
|
||||
pub.send("carState", latest)
|
||||
pub.send("deviceState", messaging.new_message("deviceState", valid=True))
|
||||
sm.update(1000)
|
||||
assert len(sm.drained["carState"]) == 2
|
||||
assert sm.drained["carState"][0].carState.buttonEvents[0].pressed
|
||||
assert sm["carState"].vEgo == 12.0 and not sm["carState"].buttonEvents
|
||||
assert sm.logMonoTime["carState"] == latest.logMonoTime
|
||||
assert sm.frame == 0 and all(sm.updated.values())
|
||||
sm.update(0)
|
||||
assert sm.drained["carState"] == []
|
||||
assert sm.frame == 1 and not any(sm.updated.values())
|
||||
|
||||
def test_init(self):
|
||||
sm = messaging.SubMaster(events)
|
||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||
|
||||
Binary file not shown.
+11
-82
@@ -16,10 +16,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"AthenadUploadQueue", {PERSISTENT, JSON}},
|
||||
{"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}},
|
||||
{"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}},
|
||||
{"CameraDebugExpTime", {CLEAR_ON_MANAGER_START, STRING}},
|
||||
@@ -88,7 +84,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
{"JoystickControlDevice", {PERSISTENT, STRING}},
|
||||
{"LanguageSetting", {PERSISTENT, STRING, "main_en"}},
|
||||
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
|
||||
{"LastGPSPosition", {PERSISTENT, STRING}},
|
||||
@@ -110,17 +105,10 @@ 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}},
|
||||
@@ -254,6 +242,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CommunityFavorites", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"ConditionalChill", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"ConditionalExperimental", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"HybridExpBias", {PERSISTENT, FLOAT, "0", "0", 1}},
|
||||
{"HybridExperimental", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"HybridVisionBrakeSensitivity", {PERSISTENT, FLOAT, "1", "1", 1}},
|
||||
{"HEMExpDominant", {CLEAR_ON_MANAGER_START, BOOL, "0", "0", 2}},
|
||||
{"CurvatureData", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"CurveSpeedController", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CurveSpeedControllerNoLead", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
@@ -267,32 +259,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CustomAccelProfile45MPH", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
{"CustomAccelProfile56MPH", {PERSISTENT, FLOAT, "0.8", "0.8", 3}},
|
||||
{"CustomAccelProfile89MPH", {PERSISTENT, FLOAT, "0.6", "0.6", 3}},
|
||||
{"CustomAccelProfileBreakpointsInitialized", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"CustomAccelProfilePointCount", {PERSISTENT, INT, "7", "7", 3}},
|
||||
{"CustomAccelProfileBreakpoint1MPH", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"CustomAccelProfileBreakpoint2MPH", {PERSISTENT, FLOAT, "11.184681", "11.184681", 3}},
|
||||
{"CustomAccelProfileBreakpoint3MPH", {PERSISTENT, FLOAT, "22.369363", "22.369363", 3}},
|
||||
{"CustomAccelProfileBreakpoint4MPH", {PERSISTENT, FLOAT, "33.554044", "33.554044", 3}},
|
||||
{"CustomAccelProfileBreakpoint5MPH", {PERSISTENT, FLOAT, "44.738726", "44.738726", 3}},
|
||||
{"CustomAccelProfileBreakpoint6MPH", {PERSISTENT, FLOAT, "55.923407", "55.923407", 3}},
|
||||
{"CustomAccelProfileBreakpoint7MPH", {PERSISTENT, FLOAT, "89.477452", "89.477452", 3}},
|
||||
{"CustomAccelProfileBreakpoint8MPH", {PERSISTENT, FLOAT, "100.662133", "100.662133", 3}},
|
||||
{"CustomAccelProfileBreakpoint9MPH", {PERSISTENT, FLOAT, "111.846815", "111.846815", 3}},
|
||||
{"CustomAccelProfileBreakpoint10MPH", {PERSISTENT, FLOAT, "123.031496", "123.031496", 3}},
|
||||
{"CustomAccelProfileBreakpoint11MPH", {PERSISTENT, FLOAT, "134.216178", "134.216178", 3}},
|
||||
{"CustomAccelProfileBreakpoint12MPH", {PERSISTENT, FLOAT, "145.400859", "145.400859", 3}},
|
||||
{"CustomAccelProfilePoint1Accel", {PERSISTENT, FLOAT, "3.0", "3.0", 3}},
|
||||
{"CustomAccelProfilePoint2Accel", {PERSISTENT, FLOAT, "2.5", "2.5", 3}},
|
||||
{"CustomAccelProfilePoint3Accel", {PERSISTENT, FLOAT, "2.0", "2.0", 3}},
|
||||
{"CustomAccelProfilePoint4Accel", {PERSISTENT, FLOAT, "1.5", "1.5", 3}},
|
||||
{"CustomAccelProfilePoint5Accel", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
{"CustomAccelProfilePoint6Accel", {PERSISTENT, FLOAT, "0.8", "0.8", 3}},
|
||||
{"CustomAccelProfilePoint7Accel", {PERSISTENT, FLOAT, "0.6", "0.6", 3}},
|
||||
{"CustomAccelProfilePoint8Accel", {PERSISTENT, FLOAT, "0.55", "0.55", 3}},
|
||||
{"CustomAccelProfilePoint9Accel", {PERSISTENT, FLOAT, "0.5", "0.5", 3}},
|
||||
{"CustomAccelProfilePoint10Accel", {PERSISTENT, FLOAT, "0.45", "0.45", 3}},
|
||||
{"CustomAccelProfilePoint11Accel", {PERSISTENT, FLOAT, "0.4", "0.4", 3}},
|
||||
{"CustomAccelProfilePoint12Accel", {PERSISTENT, FLOAT, "0.35", "0.35", 3}},
|
||||
{"CustomCruise", {PERSISTENT, FLOAT, "1.0", "1.0", 2, SETTINGS_SIMPLE}},
|
||||
{"CustomCruiseLong", {PERSISTENT, FLOAT, "5.0", "5.0", 2, SETTINGS_SIMPLE}},
|
||||
{"CustomPersonalities", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
@@ -318,8 +284,6 @@ 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}},
|
||||
{"GalaxyDeviceName", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"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}},
|
||||
@@ -340,12 +304,6 @@ 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}},
|
||||
@@ -353,7 +311,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DrivingModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}},
|
||||
{"DynamicPathWidth", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"DynamicPedalsOnUI", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"GpuModelReadySound", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"EngageVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"EVTuning", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"Fahrenheit", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
@@ -364,7 +321,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"IgnoreIgnitionLine", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"LongPitch", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"TeslaWakeOnCAN", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPAdaptiveAccel", {PERSISTENT, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPFollowDistance", {PERSISTENT, INT, "4", "4"}},
|
||||
@@ -390,13 +346,17 @@ 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}},
|
||||
// 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.
|
||||
{"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}},
|
||||
{"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, "{}", "{}"}},
|
||||
@@ -409,12 +369,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"StarPilotCarParamsPersistent", {PERSISTENT, BYTES, "", ""}},
|
||||
{"StarPilotDongleId", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"StarPilotFavoriteSlots", {PERSISTENT, JSON, "[]", "[]", 1}},
|
||||
{"ControllerActionSlots", {PERSISTENT, JSON, "[]", "[]", 1}},
|
||||
{"WheelControlLearnSlot", {CLEAR_ON_MANAGER_START | DONT_LOG, INT}},
|
||||
{"WheelControlMappings", {PERSISTENT, JSON, "[]", "[]", 1}},
|
||||
{"WheelControlStatus", {CLEAR_ON_MANAGER_START | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"WheelControlTestActive", {CLEAR_ON_MANAGER_START | DONT_LOG, BOOL}},
|
||||
{"WheelControlsEnabled", {PERSISTENT, BOOL, "0"}},
|
||||
{"StarPilotStats", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"StarPilotTogglesUpdated", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"GoatScream", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
@@ -445,7 +399,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"AggressiveCoolingEnabled", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseThermalLimits", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"IssueReported", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
|
||||
{"KonikDongleId", {PERSISTENT, STRING, "", "", 0}},
|
||||
@@ -469,8 +422,7 @@ 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, "0", "0", 3}},
|
||||
{"LeadInfoMode", {PERSISTENT, INT, "2", "2", 3}},
|
||||
{"LeadInfo", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
|
||||
@@ -526,9 +478,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"ModelDrivesAndScores", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"ModelLabConfig", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"ModelLabModelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"ModelLabRuntime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}},
|
||||
{"ModelReleasedDates", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"ModelRandomizer", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"LatSmoothSeconds", {PERSISTENT, FLOAT, "0.1", "0.1", 3}},
|
||||
@@ -562,11 +511,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"FavoriteVirtualDecelCruiseCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"FavoriteTrafficModeCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"WheelButtonBookmarkCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"WheelControlAOLCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"WheelControlDisengageCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"WheelControlEngageCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"WheelControlForceCoastCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"WheelControlPulseGlideCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"OverpassRequests", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"PathColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}},
|
||||
@@ -615,7 +559,6 @@ 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"}},
|
||||
@@ -625,10 +568,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"RotatingWheel", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightness", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOnroad", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOnroadManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOnroadOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ScreenRecorder", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenTimeout", {PERSISTENT, INT, "30", "30", 2, SETTINGS_SIMPLE}},
|
||||
@@ -638,7 +577,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowBrakeStatus", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ShowCPU", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
@@ -700,14 +638,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"StandardJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandardJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandbyMode", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeButton", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyButtonPressTime", {CLEAR_ON_MANAGER_START | DONT_LOG, INT, "0", "0"}},
|
||||
{"StandbyWakeEngage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeDisengage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeInfoAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeWarningAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeCriticalAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeTurnSignal", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"StartAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StartAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StartupMessageBottom", {PERSISTENT, STRING, "Always keep hands on wheel and eyes on road", "Always keep hands on wheel and eyes on road", 0}},
|
||||
@@ -740,7 +670,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruRedneckCruise", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
|
||||
Binary file not shown.
@@ -1,44 +0,0 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR
|
||||
|
||||
from openpilot.starpilot.common.lateral_only_experimental import (
|
||||
experimental_mode_available,
|
||||
lateral_only_experimental_available,
|
||||
)
|
||||
|
||||
|
||||
def test_telluride_platform_allows_lateral_only_experimental_mode():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=HYUNDAI_CAR.HYUNDAI_PALISADE_2023,
|
||||
openpilotLongitudinalControl=False,
|
||||
)
|
||||
|
||||
assert lateral_only_experimental_available(CP)
|
||||
assert experimental_mode_available(CP)
|
||||
|
||||
|
||||
def test_lateral_only_mode_does_not_expand_other_stock_acc_cars():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=HYUNDAI_CAR.HYUNDAI_SONATA,
|
||||
openpilotLongitudinalControl=False,
|
||||
)
|
||||
|
||||
assert not lateral_only_experimental_available(CP)
|
||||
assert not experimental_mode_available(CP)
|
||||
|
||||
old_palisade = SimpleNamespace(
|
||||
carFingerprint=HYUNDAI_CAR.HYUNDAI_PALISADE,
|
||||
openpilotLongitudinalControl=False,
|
||||
)
|
||||
assert not lateral_only_experimental_available(old_palisade)
|
||||
|
||||
|
||||
def test_normal_experimental_mode_remains_available_with_openpilot_long():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=HYUNDAI_CAR.HYUNDAI_SONATA,
|
||||
openpilotLongitudinalControl=True,
|
||||
)
|
||||
|
||||
assert not lateral_only_experimental_available(CP)
|
||||
assert experimental_mode_available(CP)
|
||||
@@ -5,7 +5,7 @@ import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
|
||||
from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName
|
||||
|
||||
class TestParams:
|
||||
def setup_method(self):
|
||||
@@ -128,31 +128,6 @@ 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})
|
||||
|
||||
+4
-5
@@ -4,7 +4,7 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||
|
||||
# 453 Supported Cars
|
||||
# 452 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -76,7 +76,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|Stock|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM SDGM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt 2019">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt ASCM Harness 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/QeMCN_4TFfQ" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|openpilot available[<sup>1</sup>](#footnotes)|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt Camera Harness 2017-18">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Volt No-ACC 2016-18 (OBD Harness)|Redneck ACC|openpilot|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-II connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt No-ACC 2016-18 (OBD Harness)">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Volt No-ACC 2017-18|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt No-ACC 2017-18">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2019-20">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2021-23">Buy Here</a></sub></details>|||
|
||||
@@ -465,7 +465,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|
||||
These additional vehicle ports are maintained by StarPilot rather than upstream openpilot.
|
||||
|
||||
# 17 Community Cars
|
||||
# 16 Community Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -478,7 +478,6 @@ These additional vehicle ports are maintained by StarPilot rather than upstream
|
||||
|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed Plug-in Hybrid Non-SCC 2022">Buy Here</a></sub></details>|||
|
||||
|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2019">Buy Here</a></sub></details>|||
|
||||
|Kia|Forte Non-SCC 2021|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2021">Buy Here</a></sub></details>|||
|
||||
|Kia|Ray EV 2025|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ray EV 2025">Buy Here</a></sub></details>|||
|
||||
|Kia|Seltos Non-SCC 2023-24|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Seltos Non-SCC 2023-24">Buy Here</a></sub></details>|||
|
||||
|Tesla|Model S (Pre-AP) 2012-14|All|Stock|0 mph|0 mph|[](##)|[](##)|None|||
|
||||
|Toyota|Matrix Retrofit 2005|Custom retrofit|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Matrix Retrofit 2005">Buy Here</a></sub></details>|||
|
||||
@@ -546,4 +545,4 @@ openpilot does not yet support these Toyota models due to a new message authenti
|
||||
* Toyota Camry 2025+
|
||||
* Lexus NX 2022+
|
||||
* Toyota bZ4x 2023+
|
||||
* Subaru Solterra 2023+
|
||||
* Subaru Solterra 2023+
|
||||
+161
-125
@@ -1,128 +1,146 @@
|
||||
# StarPilot Model Rebuild
|
||||
# StarPilot Unified Model Rebuild
|
||||
|
||||
This is the supported workflow for changing the vendored tinygrad revision and
|
||||
releasing a new model manifest generation. A manifest generation represents one
|
||||
tinygrad ABI. Model behavior versions (`v8` through `v16`) are independent and
|
||||
must remain unchanged when only tinygrad changes.
|
||||
This workflow rebuilds StarPilot driving and driver-monitoring artifacts for the vendored tinygrad revision. Driving-model behavior versions remain manifest metadata; every runtime driving artifact uses the `tinygrad_single_v1` layout.
|
||||
|
||||
The current generation is **v25**, pinned to tinygrad
|
||||
`e837e367aac9e1a66e689f4f32ce20ca9367df13`. The supported compiler is
|
||||
`comma@192.168.3.110`; never substitute another comma without explicit approval.
|
||||
## Safety
|
||||
|
||||
## Release Contract
|
||||
- The supported build device is `comma@192.168.3.109`.
|
||||
- Never run these commands against `192.168.3.110`.
|
||||
- Normal artifacts target QCOM. External-GPU artifacts must be compiled explicitly and tagged in the manifest.
|
||||
- Keep source ONNX files and compiled PKLs on the T5 workspace, not the comma.
|
||||
|
||||
- Keep every existing StarPilot model ID stable across manifest generations.
|
||||
- Store HF artifacts under `models/v25/<model-id>/`.
|
||||
- Store GitHub fallback artifacts on the `Models` branch under `v25/<model-id>/`.
|
||||
- Name every logical artifact `<model-id>_driving_tinygrad.pkl`.
|
||||
- Publish native chunks as `.chunkNNofNN` plus `.chunkmanifest`.
|
||||
- Serialize every v25 driving artifact out-of-band; this applies to normal QCOM
|
||||
models as well as external-GPU models.
|
||||
- Include `artifact_sha256` and `artifact_chunk_count` in the manifest.
|
||||
- Set `uses_external_gpu: true` only for models compiled for Chestnut.
|
||||
- Do not rename an artifact from another tinygrad revision. PKLs must either use
|
||||
the exact v25 pin or be rebuilt with it.
|
||||
- Do not add models absent from the existing StarPilot catalog unless the
|
||||
release explicitly requests them.
|
||||
## Workspace
|
||||
|
||||
The downloader checks Hugging Face first and GitHub second. There is no GitLab
|
||||
fallback. The HF manifest lives only at `manifests/model_names_v25.json`, old
|
||||
artifacts live under `models/v24/`, and current artifacts live under
|
||||
`models/v25/`. The v25 downloader never probes unversioned or v24 artifact
|
||||
paths; missing v25 artifacts fail safely instead of loading an incompatible
|
||||
pickle.
|
||||
|
||||
## Tinygrad Bump
|
||||
|
||||
1. Record the exact tinygrad commit used by the compatible source catalog.
|
||||
2. Replace `tinygrad_repo/` from that commit, excluding nested Git metadata.
|
||||
3. Write the full SHA to `tinygrad_repo/TINYGRAD_COMMIT`.
|
||||
4. Review upstream `modeld`, compiler, parser, and camera-warp changes. Merge
|
||||
required ABI changes into StarPilot's existing multi-model runtime; never
|
||||
replace StarPilot `modeld.py` wholesale.
|
||||
5. Increment `MANIFEST_CANDIDATES` to a new single version. Do not fall back to
|
||||
the prior manifest because its PKLs target a different tinygrad ABI.
|
||||
6. Sync the exact tree to the compiler before building anything:
|
||||
|
||||
```bash
|
||||
./dev sync
|
||||
rsync -az --delete --exclude=.git --exclude=__pycache__ -e ssh \
|
||||
tinygrad_repo/ comma@192.168.3.110:/data/openpilot/tinygrad_repo/
|
||||
rsync -az -e ssh selfdrive/modeld/ \
|
||||
comma@192.168.3.110:/data/openpilot/selfdrive/modeld/
|
||||
rsync -az -e ssh scripts/model_compiler.py \
|
||||
comma@192.168.3.110:/data/openpilot/scripts/model_compiler.py
|
||||
rsync -az -e ssh models comma@192.168.3.110:/data/openpilot/models
|
||||
```
|
||||
|
||||
Confirm the device marker before compiling:
|
||||
|
||||
```bash
|
||||
ssh comma@192.168.3.110 \
|
||||
'cat /data/openpilot/tinygrad_repo/TINYGRAD_COMMIT'
|
||||
```
|
||||
|
||||
## Reuse Compatible Artifacts
|
||||
|
||||
Reusing an artifact is preferred when its catalog records the exact same
|
||||
tinygrad SHA and exact same source-model commit. Display names and release dates
|
||||
are not sufficient proof. Copy compatible chunks server-side so the Mac never
|
||||
stores a second multi-gigabyte artifact, but rename every destination chunk to
|
||||
the stable StarPilot model ID.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
hf buckets cp \
|
||||
'hf://datasets/<source>/<path>/<source-file>.chunk01of02' \
|
||||
'hf://buckets/StarPilot-Driving/StarPilot-Resources/models/v25/pop223/pop223_driving_tinygrad.pkl.chunk01of02'
|
||||
```
|
||||
|
||||
Write `2` to `pop223_driving_tinygrad.pkl.chunkmanifest`, upload it last, and
|
||||
put the source artifact's full SHA-256 and chunk count into the v25 manifest.
|
||||
Upload the manifest only after every listed artifact directory is complete.
|
||||
|
||||
## Compile Missing Models
|
||||
|
||||
Archived sources live under:
|
||||
The default workspace is:
|
||||
|
||||
```text
|
||||
hf://buckets/StarPilot-Driving/StarPilot-Resources/onnx/<source-id>/
|
||||
/Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/
|
||||
```
|
||||
|
||||
Stage one model at a time in `/data/openpilot/uncompiledmodels`; this avoids
|
||||
filling the comma and prevents `./models` from selecting stale input files.
|
||||
Important directories:
|
||||
|
||||
- `onnx/<model-id>/`: ID-prefixed source ONNX files.
|
||||
- `compiled/`: completed unified driving PKLs.
|
||||
- `driver-monitoring/`: DM ONNX, model PKL, metadata, and camera warps.
|
||||
- `ready-for-resources/`: flat repository-upload handoff.
|
||||
- Oversized models are represented by repository-safe `.p00`, `.p01`, and `.sha256` files in `ready-for-resources/`.
|
||||
- `logs/`: one remote compilation log per model.
|
||||
- `results/`: source and artifact checksum records.
|
||||
- `manifests/`: source `model_names_v22.json` and namespaced release `model_names_v23.json`.
|
||||
- The v23 manifest and compiled artifacts are published together in the resource repository's `Models` branch.
|
||||
|
||||
## Initialize And Extract
|
||||
|
||||
```bash
|
||||
./models --<model-id> --version <behavior-version>
|
||||
./models --<gpu-model-id> --version v16 --gpu
|
||||
python3 scripts/model_rebuild_pipeline.py init
|
||||
python3 scripts/model_rebuild_pipeline.py extract \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
The default input is a single supercombo ONNX. For legacy sources use:
|
||||
Extraction streams Git blobs directly to disk. LFS pointers are resolved from the local object cache or fetched by object ID, then checked against the pointer SHA-256 and size. Binary ONNX data is never stored in a shell variable.
|
||||
|
||||
To retry one source:
|
||||
|
||||
```bash
|
||||
./models --<model-id> --input-format split --version <behavior-version>
|
||||
python3 scripts/model_rebuild_pipeline.py extract \
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
Every non-local release build emits an OOB artifact as native chunks and removes
|
||||
the temporary full PKL. `./models --local-<id>` intentionally keeps one OOB PKL
|
||||
for local use.
|
||||
The original catalog sources are defined in `scripts/model_source_map_v22.json`.
|
||||
Recovered late-model and supercombo sources, including RDF2, are defined in
|
||||
`scripts/model_source_map_v23.json`. The v23 map is intentionally separate so
|
||||
adding a recovered iteration cannot alter the older model source history.
|
||||
|
||||
The resumable bulk helper is:
|
||||
## Compile
|
||||
|
||||
Compile one model:
|
||||
|
||||
```bash
|
||||
STAR_PILOT_MODEL_REMOTE=comma@192.168.3.110 \
|
||||
python3 scripts/model_rebuild_pipeline.py compile \
|
||||
--workspace /Volumes/T5/StarPilot-Model-Rebuild \
|
||||
--source-map scripts/model_source_map_v25.json \
|
||||
--base-manifest ~/StarPilot-Resources/model_names_v25.json
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
Failures are recorded under `results/`; rerun the same command to resume.
|
||||
Compile or resume the full catalog:
|
||||
|
||||
## Driver Monitoring And Default
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py compile \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
Driver monitoring is built once per tinygrad generation:
|
||||
Existing artifacts are skipped unless `--force` is passed. Each model is staged in its own remote input directory, compiled on `.109`, copied back to the T5, hashed, and copied into `ready-for-resources/`. Failures are written to `results/<id>_failure.json`; rerunning the same command resumes incomplete models.
|
||||
|
||||
Validate one or all completed artifacts with synthetic camera inputs on QCOM:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py validate \
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
The lower-level device compiler also supports direct use:
|
||||
|
||||
```bash
|
||||
./models --model pop22 --input-format split --version v11
|
||||
./models --model deeprl3v2 --input-format supercombo --version v15
|
||||
```
|
||||
|
||||
For a model that cannot run on the device GPU, compile with the USB AMD GPU attached:
|
||||
|
||||
```bash
|
||||
./models --lebowski --gpu
|
||||
```
|
||||
|
||||
The ASM2464PD bridge must run the current tinygrad custom firmware from
|
||||
https://github.com/tinygrad/asm2464pd-firmware. Its USB product string starts
|
||||
with `custom`; the legacy `USB 3.2 PCIe TinyEnclosure` patch is not compatible
|
||||
with comma's current external-GPU runtime. Firmware flashing is a separate,
|
||||
explicit hardware setup step and StarPilot never performs it automatically.
|
||||
|
||||
The dynamic flag (`--lebowski` above) sets the output and manifest model ID;
|
||||
when only one source model is staged, its ONNX filename does not need to match
|
||||
that ID. Input format and behavior version are inferred. `--external-gpu`
|
||||
remains available as a compatibility alias for `--gpu`.
|
||||
|
||||
This emits a streaming out-of-band pickle and keeps QCOM available for camera warps. Its manifest entry must include:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "lebowski",
|
||||
"uses_external_gpu": true
|
||||
}
|
||||
```
|
||||
|
||||
Only tagged models activate the external GPU. If the GPU or artifact is unavailable, runtime falls back to the built-in model; all untagged models retain the existing QCOM path.
|
||||
|
||||
`--version` records behavioral semantics only. It does not change artifact layout.
|
||||
|
||||
If the compiled PKL exceeds 100 MiB, `./models` automatically keeps the full
|
||||
local PKL and creates 95 MiB upload parts beside it:
|
||||
|
||||
```text
|
||||
deeprl3v2_driving_tinygrad.pkl
|
||||
deeprl3v2_driving_tinygrad.pkl.p00
|
||||
deeprl3v2_driving_tinygrad.pkl.p01
|
||||
deeprl3v2_driving_tinygrad.pkl.sha256
|
||||
```
|
||||
|
||||
To split an already compiled artifact:
|
||||
|
||||
```bash
|
||||
./models --split-artifact /path/to/deeprl3v2_driving_tinygrad.pkl \
|
||||
--output-dir /path/to/upload-ready
|
||||
```
|
||||
|
||||
Upload only the numbered parts and checksum when the full PKL exceeds the
|
||||
repository limit. The downloader reassembles into a temporary file, verifies
|
||||
the companion SHA-256, and atomically installs the final PKL. No manifest field
|
||||
is required for multipart artifacts.
|
||||
|
||||
## Driver Monitoring
|
||||
|
||||
Stage the current DM ONNX in `uncompiledmodels`, then run:
|
||||
|
||||
```bash
|
||||
./models --dm \
|
||||
@@ -130,43 +148,61 @@ Driver monitoring is built once per tinygrad generation:
|
||||
--output-dir /tmp/dm_artifacts
|
||||
```
|
||||
|
||||
Replace these four files together:
|
||||
This builds:
|
||||
|
||||
- `dmonitoring_model_tinygrad.pkl`
|
||||
- `dmonitoring_model_metadata.pkl`
|
||||
- `dm_warp_1928x1208_tinygrad.pkl`
|
||||
- `dm_warp_1344x760_tinygrad.pkl`
|
||||
|
||||
Recompile RDF V4 with the same pin and replace the built-in
|
||||
`selfdrive/modeld/models/driving_tinygrad.pkl` native chunk set. Never commit a
|
||||
full built-in PKL over the repository limit.
|
||||
All four files must be updated together.
|
||||
|
||||
## Validation
|
||||
## Manifest
|
||||
|
||||
Run repository tests first:
|
||||
Generate the base manifest after compilation, then namespace the release artifacts as v23:
|
||||
|
||||
```bash
|
||||
./dev sync
|
||||
./.venv/bin/pytest -q -n0 \
|
||||
starpilot/assets/tests/test_model_pipeline.py \
|
||||
common/tests/test_file_chunker.py \
|
||||
scripts/tests/test_model_release.py
|
||||
python3 scripts/model_rebuild_pipeline.py manifest \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
For representative v8, v11, v12, v15, v16, and GPU artifacts, validate both
|
||||
camera resolutions on real QCOM and require finite plan, lane-line, road-edge,
|
||||
lead, pose, and action outputs. Then start `modeld` and confirm stable
|
||||
`modelV2` publication. Validate DM `driverStateV2` at both resolutions.
|
||||
```bash
|
||||
python3 scripts/namespace_model_artifacts.py \
|
||||
--workspace /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22 \
|
||||
--base-manifest /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/manifests/model_names_v22.json \
|
||||
--manifest-version v23 --suffix 3
|
||||
```
|
||||
|
||||
## Device Migration
|
||||
The namespace command changes IDs such as `tr1422` to `tr14223`, renames the
|
||||
compiled and upload-ready files, and writes an ID map. It preserves display
|
||||
names and behavioral versions. The current model manager requests v23 only;
|
||||
the manifest is fetched from `Models/model_names_v23.json`, while v22 remains
|
||||
available for devices that have not updated yet.
|
||||
|
||||
When `ModelManifestVersion` changes, the model manager retains the selected
|
||||
model ID but deletes every non-local downloaded driving artifact from the old
|
||||
generation, including full PKLs, `.pNN` parts, native chunks, and chunk
|
||||
manifests. It then downloads that ID's v25 chunks. Local models and DM files are
|
||||
not deleted. If the selected v25 artifact cannot be downloaded and verified,
|
||||
the manager selects the built-in RDF V4 model.
|
||||
After importing newly compiled sources, normalize the release namespace before
|
||||
copying files into either resource repository:
|
||||
|
||||
Test this explicitly before release by starting with a v24 selected model and
|
||||
checking that no v24 driving artifact remains under `/data/models` after the
|
||||
v25 manifest is applied.
|
||||
```bash
|
||||
python3 scripts/reconcile_v23_artifacts.py \
|
||||
--workspace /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22
|
||||
```
|
||||
|
||||
This maps recovered source IDs to their v23 release IDs, removes duplicate
|
||||
macOS metadata files, and adds `rdf23` for Regret Driven Framework V2. It does
|
||||
not overwrite a conflicting artifact.
|
||||
Repository-hosted multipart files are discovered by naming convention, so no
|
||||
size, hash, format, or part-count metadata is required.
|
||||
|
||||
`uses_external_gpu` is optional and defaults to `false`.
|
||||
|
||||
## Runtime Verification
|
||||
|
||||
Compilation validates JIT capture/replay, pickle round-trip, finite outputs, metadata slices, and both camera warps. Before release:
|
||||
|
||||
1. Select representative v8, v11, v12, v15, and supercombo models.
|
||||
2. Confirm `modeld` stays running.
|
||||
3. Confirm finite `modelV2` path, lane-line, lead, pose, and action data.
|
||||
4. Confirm `driverStateV2` on both supported camera resolutions.
|
||||
5. Test download, selection, deletion, randomization, migration, and fallback in both device UIs and Galaxy.
|
||||
|
||||
The built-in RDF artifact is `selfdrive/modeld/models/driving_tinygrad.pkl`. If migration cannot download the selected v23 artifact, StarPilot switches to that built-in model.
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
# Custom personality graphs
|
||||
|
||||
Each personality keeps its own Custom acceleration, braking and following curve.
|
||||
Selecting a named preset changes the active selection without deleting Custom
|
||||
points. Selecting Custom again restores those points, including after a reload
|
||||
or restart. If a category has never had Custom points, it is initialized from
|
||||
the current selection, as before.
|
||||
|
||||
The existing **Reset to default** button, below each Custom graph's numeric
|
||||
points in New Galaxy's Advanced section, replaces only that category's Custom
|
||||
curve. It leaves the category set to Custom. The server resolves the reset
|
||||
values; the dashed **Dom default** line uses the same resolver.
|
||||
|
||||
Defaults are Dom's configured base curves sampled at the editor's 10 mph
|
||||
points. They include Traffic's dedicated acceleration and braking, following
|
||||
settings, global tuning switches and powertrain overrides. Where gear mapping
|
||||
is enabled, the reference uses normal gear. Live Eco/Sport gear, weather,
|
||||
lead/stop and overspeed adjustments remain on the existing controller paths.
|
||||
Sampling cannot reproduce every native breakpoint or between-point value;
|
||||
resetting a Custom graph is not the same as delegating to the Dom-default
|
||||
runtime path.
|
||||
|
||||
Dom-default points outside the ordinary editor range (such as Traffic braking
|
||||
at 0.35 m/s², configured Traffic following at 0.5 seconds or truck acceleration
|
||||
at 6 m/s²) remain visible and are preserved when another point is edited.
|
||||
New point edits still use the existing authoring bounds. This does not expand
|
||||
braking authority or change acceleration/braking preset definitions.
|
||||
|
||||
## Following presets
|
||||
|
||||
Named following presets now match Dom's factory following settings with custom
|
||||
personalities enabled. Close follows Aggressive, Medium follows Standard and
|
||||
Far follows Relaxed. The presets are available in every personality.
|
||||
|
||||
| Preset | Previous curve | Revised curve |
|
||||
| --- | --- | --- |
|
||||
| Close | 1.25 s at every speed | 1.25 s through 45 mph, falling to 1.0 s at 70 mph |
|
||||
| Medium | 1.45 s at every speed | 1.45 s through 45 mph, falling to 1.2 s at 70 mph |
|
||||
| Far | 1.75 s at every speed | 1.6 s through 45 mph, falling to 1.4 s at 70 mph |
|
||||
| Traffic | No named preset | 0.75 s at rest, rising to 1.6 s at 25 m/s (55.92 mph) |
|
||||
|
||||
Interpolation is linear between the stated breakpoints and constant outside
|
||||
them. Named presets use the exact native speed axes at runtime. First-use
|
||||
Custom conversion samples them onto the existing 10 mph editor grid.
|
||||
|
||||
Existing v1/v2 Close, Medium and Far selections keep their old fixed headways
|
||||
as `legacy_close`, `legacy_medium` and `legacy_far`. Both Galaxy pickers show
|
||||
the selected compatibility entry as **Previous Close**, **Previous Medium**
|
||||
or **Previous Far**. Explicitly selecting a current preset adopts its new curve.
|
||||
The previous entry disappears when it is no longer selected.
|
||||
|
||||
Existing `dom_default` selections continue to inherit configured settings;
|
||||
they are not silently converted to fixed named presets. Fresh profiles also
|
||||
retain this inheritance. The named curves match untouched factory settings;
|
||||
users' changed global following values can still differ from them.
|
||||
|
||||
Acceleration and braking presets are unchanged. Standard acceleration and Eco
|
||||
braking match the normal factory defaults for Aggressive, Standard and Relaxed
|
||||
when named-preset and global powertrain tuning agree. Named presets use detected
|
||||
EV/truck tuning; the Dom-default resolver respects the global tuning switches,
|
||||
so these can differ. Traffic retains its dedicated acceleration/braking defaults;
|
||||
this change adds only its named following preset.
|
||||
|
||||
## Storage compatibility
|
||||
|
||||
Profile document version 3 retains `curve` and optional `legacyCurve` while
|
||||
`preset` is a named preset or `dom_default`. These retained values are dormant;
|
||||
only Custom uses them. An actual graph edit or reset retires preserved v1
|
||||
interpolation for that category; a preset switch or unchanged submission does
|
||||
not.
|
||||
|
||||
Valid v2 documents retain their runtime meaning and are upgraded on the next
|
||||
normal write, including the fixed following compatibility names above.
|
||||
Version 1 keeps its existing explicit, verified migration flow. Reads never
|
||||
rewrite Params. Category conflict detection, off-road checks and atomic profile
|
||||
document writes still apply to edits and resets.
|
||||
|
||||
Older builds do not understand v3 documents. Retain a compatible settings
|
||||
backup before rolling back to one of those builds. Curves discarded before
|
||||
this change cannot be recovered automatically.
|
||||
Binary file not shown.
+1
-1
@@ -21,7 +21,7 @@ fi
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="19.6.20"
|
||||
export AGNOS_VERSION="19.6.10"
|
||||
fi
|
||||
|
||||
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
exec python3 "$DIR/scripts/model_release.py" "$@"
|
||||
@@ -1,4 +1,3 @@
|
||||
include opendbc/car/car.capnp
|
||||
include opendbc/car/include/c++.capnp
|
||||
include opendbc/dbc/hyundai_kia_ray_pedal.dbc
|
||||
recursive-include opendbc/safety *.h
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt No-ACC 2016-18 (OBD Harness)|Redneck ACC|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt No-ACC 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chrysler|Pacifica 2021-23|All|[Upstream](#upstream)|
|
||||
@@ -578,4 +578,4 @@ Toyota, and the GM Global B platform.
|
||||
All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a
|
||||
CAN bus isn't the only way that the computers in your car can communicate. Most, if not all, vehicles from the following
|
||||
manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars
|
||||
may one day be supported, but we have no immediate plans to support FlexRay.
|
||||
may one day be supported, but we have no immediate plans to support FlexRay.
|
||||
@@ -13,7 +13,7 @@ from opendbc.car.subaru.subarucan import subaru_checksum
|
||||
from opendbc.car.chrysler.chryslercan import chrysler_checksum, fca_giorgio_checksum
|
||||
from opendbc.car.hyundai.hyundaicanfd import hkg_can_fd_checksum
|
||||
from opendbc.car.volkswagen.mlbcan import volkswagen_mlb_checksum
|
||||
from opendbc.car.volkswagen.mqbcan import volkswagen_meb_alt_crc_checksum, volkswagen_mqb_meb_checksum, xor_checksum
|
||||
from opendbc.car.volkswagen.mqbcan import volkswagen_mqb_meb_checksum, xor_checksum
|
||||
from opendbc.car.tesla.teslacan import tesla_checksum
|
||||
from opendbc.car.body.bodycan import body_checksum
|
||||
from opendbc.car.psa.psacan import psa_checksum
|
||||
@@ -194,10 +194,8 @@ 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", "hyundai_radar_210_21f_generated")):
|
||||
elif dbc_name.startswith("hyundai_canfd_generated"):
|
||||
return ChecksumState(16, -1, 0, -1, True, SignalType.HKG_CAN_FD_CHECKSUM, hkg_can_fd_checksum)
|
||||
elif dbc_name.startswith("vw_meb_2024"):
|
||||
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_meb_alt_crc_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)
|
||||
elif dbc_name.startswith("vw_mlb"):
|
||||
|
||||
@@ -132,7 +132,6 @@ class CANParser:
|
||||
self.dbc: DBC = DBC(dbc_name)
|
||||
|
||||
self.vl: dict[int | str, dict[str, float]] = VLDict(self)
|
||||
self.vl_raw: dict[int | str, bytes] = {}
|
||||
self.vl_all: dict[int | str, dict[str, list[float]]] = {}
|
||||
self.ts_nanos: dict[int | str, dict[str, int]] = {}
|
||||
self.addresses: set[int] = set()
|
||||
@@ -167,8 +166,6 @@ class CANParser:
|
||||
signals_dict = {s: 0.0 for s in signal_names}
|
||||
dict.__setitem__(self.vl, msg.address, signals_dict)
|
||||
dict.__setitem__(self.vl, msg.name, signals_dict)
|
||||
self.vl_raw[msg.address] = bytes(msg.size)
|
||||
self.vl_raw[msg.name] = bytes(msg.size)
|
||||
self.vl_all[msg.address] = defaultdict(list)
|
||||
self.vl_all[msg.name] = self.vl_all[msg.address]
|
||||
self.ts_nanos[msg.address] = {s: 0 for s in signal_names}
|
||||
@@ -250,8 +247,6 @@ class CANParser:
|
||||
vl_addr[sig.name] = state.vals[i]
|
||||
vl_all_addr[sig.name] = state.all_vals[i]
|
||||
ts_addr[sig.name] = state.timestamps[-1]
|
||||
self.vl_raw[address] = bytes(dat)
|
||||
self.vl_raw[state.name] = bytes(dat)
|
||||
|
||||
if not bus_empty:
|
||||
self.last_nonempty_nanos = t
|
||||
|
||||
@@ -90,7 +90,6 @@ class Bus(StrEnum):
|
||||
main = auto()
|
||||
party = auto()
|
||||
ap_party = auto()
|
||||
ap_pt = auto()
|
||||
|
||||
|
||||
def rate_limit(new_value, last_value, dw_step, up_step):
|
||||
|
||||
@@ -644,8 +644,6 @@ struct CarParams {
|
||||
fcaGiorgio @32;
|
||||
rivian @33;
|
||||
volkswagenMeb @34;
|
||||
teslaPreAP @35;
|
||||
volvo @36;
|
||||
}
|
||||
|
||||
enum SteerControlType {
|
||||
|
||||
@@ -10,7 +10,6 @@ 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
|
||||
@@ -56,20 +55,6 @@ GM_CANDIDATE_PREFIXES = ("CHEVROLET_", "GMC_", "CADILLAC_", "BUICK_", "HOLDEN_")
|
||||
GM_CORE_FINGERPRINT_MSGS = frozenset((190, 201, 209, 211, 241))
|
||||
GM_CAMERA_BUS = 2
|
||||
GM_VOLT_CAMERA_MSG = 0x320
|
||||
GM_SUBURBAN_CAMERA_VIN_PREFIX = "1GNSKJKJ"
|
||||
GM_SUBURBAN_CAMERA_PT_SIGNATURE = {
|
||||
190: 6,
|
||||
201: 8,
|
||||
209: 7,
|
||||
211: 2,
|
||||
241: 6,
|
||||
304: 1,
|
||||
320: 3,
|
||||
}
|
||||
GM_CAMERA_DIAGNOSTIC_MESSAGES = {
|
||||
0x24b: 8,
|
||||
0x64b: 8,
|
||||
}
|
||||
|
||||
|
||||
def _normalize_forced_candidate(candidate: str | None) -> str | None:
|
||||
@@ -166,24 +151,6 @@ def _normalize_gm_volt_candidate(candidate: str | None, fingerprints: dict[int,
|
||||
return candidate
|
||||
|
||||
|
||||
def _normalize_gm_suburban_camera_candidate(candidate: str | None, fingerprints: dict[int, dict], vin: str | None) -> str | None:
|
||||
"""Resolve the 2019 Suburban camera-harness variant when CAN is shared with Yukon."""
|
||||
if candidate not in (None, "GMC_YUKON", "GMC_YUKON_CC"):
|
||||
return candidate
|
||||
|
||||
if not isinstance(vin, str) or not vin.startswith(GM_SUBURBAN_CAMERA_VIN_PREFIX):
|
||||
return candidate
|
||||
|
||||
powertrain = fingerprints.get(0, {})
|
||||
camera = fingerprints.get(GM_CAMERA_BUS, {})
|
||||
if not all(powertrain.get(address) == length for address, length in GM_SUBURBAN_CAMERA_PT_SIGNATURE.items()):
|
||||
return candidate
|
||||
if not all(camera.get(address) == length for address, length in GM_CAMERA_DIAGNOSTIC_MESSAGES.items()):
|
||||
return candidate
|
||||
|
||||
return "CHEVROLET_SUBURBAN_CAMERA"
|
||||
|
||||
|
||||
def _is_gm_candidate(candidate: str | None) -> bool:
|
||||
return isinstance(candidate, str) and candidate.startswith(GM_CANDIDATE_PREFIXES)
|
||||
|
||||
@@ -279,13 +246,8 @@ 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))
|
||||
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)
|
||||
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)
|
||||
cached = False
|
||||
|
||||
exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin)
|
||||
@@ -339,10 +301,6 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip
|
||||
stored_candidate = _normalize_forced_candidate(params.get("CarModel"))
|
||||
cached_candidate = _normalize_forced_candidate(getattr(cached_params, "carFingerprint", None))
|
||||
|
||||
if candidate is None and stored_candidate is None and cached_candidate is None:
|
||||
candidate = _normalize_gm_suburban_camera_candidate(candidate, fingerprints, vin)
|
||||
fingerprinted_candidate = candidate
|
||||
|
||||
if candidate is None:
|
||||
gm_fallback_candidate = _get_gm_stored_candidate_fallback(fingerprints, stored_candidate, cached_candidate)
|
||||
if gm_fallback_candidate is not None:
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import math
|
||||
import numpy as np
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, structs
|
||||
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, apply_hysteresis, 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
|
||||
from opendbc.car.ford.values import CarControllerParams, FordFlags, CAR
|
||||
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, FordLateralResult
|
||||
from openpilot.starpilot.car.ford.lateral import FordLateralController, FordLateralMode, FordLateralResult
|
||||
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
@@ -20,31 +18,27 @@ AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll
|
||||
MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL - (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~2.4 m/s^2
|
||||
|
||||
|
||||
class FordStockCruiseButton:
|
||||
"""Resolve Ford's context-sensitive cancel/resume switch for stock ACC."""
|
||||
|
||||
def __init__(self):
|
||||
self.pressed = False
|
||||
self.cancel = False
|
||||
self.resume = False
|
||||
|
||||
def update(self, pressed: bool, cruise_available: bool, cruise_enabled: bool) -> tuple[bool, bool]:
|
||||
if pressed and not self.pressed:
|
||||
self.cancel = cruise_available and cruise_enabled
|
||||
self.resume = cruise_available and not cruise_enabled
|
||||
elif not pressed:
|
||||
self.cancel = False
|
||||
self.resume = False
|
||||
|
||||
self.pressed = pressed
|
||||
return self.cancel, self.resume
|
||||
|
||||
|
||||
def apply_ford_angle(desired_angle_deg: float, current_angle_deg: float) -> float:
|
||||
relative_angle = desired_angle_deg - current_angle_deg
|
||||
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:
|
||||
@@ -79,6 +73,7 @@ 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
|
||||
@@ -88,8 +83,8 @@ 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_extended_lateral_announced = False
|
||||
self.stock_cruise_button = FordStockCruiseButton()
|
||||
self.ford_shadow_curvature = 0.0
|
||||
self.ford_lateral_announced_mode = FordLateralMode.native
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
can_sends = []
|
||||
@@ -105,23 +100,9 @@ class CarController(CarControllerBase):
|
||||
self.ford_lateral.update_inputs()
|
||||
|
||||
### acc buttons ###
|
||||
stock_cancel = False
|
||||
stock_resume = False
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
stock_cancel, stock_resume = self.stock_cruise_button.update(
|
||||
bool(CS.buttons_stock_values["CcAslButtnCnclResPress"]),
|
||||
CS.out.cruiseState.available,
|
||||
CS.out.cruiseState.enabled,
|
||||
)
|
||||
|
||||
if CC.cruiseControl.cancel:
|
||||
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=True))
|
||||
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, cancel=True))
|
||||
elif (stock_cancel or stock_resume) and (self.frame % CarControllerParams.BUTTONS_STEP) == 0:
|
||||
can_sends.append(fordcan.create_button_msg(
|
||||
self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=stock_cancel, resume=stock_resume))
|
||||
can_sends.append(fordcan.create_button_msg(
|
||||
self.packer, self.CAN.main, CS.buttons_stock_values, cancel=stock_cancel, resume=stock_resume))
|
||||
elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0:
|
||||
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, resume=True))
|
||||
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, resume=True))
|
||||
@@ -155,26 +136,70 @@ 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:
|
||||
if (self.frame % CarControllerParams.STEER_STEP) == 0:
|
||||
lateral = self.ford_lateral.update(CC, CS, actuators) \
|
||||
if self.ford_extended_lateral_announced else FordLateralResult()
|
||||
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)
|
||||
|
||||
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:
|
||||
can_sends.append(starpilot_fordcan.create_lka_msg(self.packer, self.CAN))
|
||||
self.ford_extended_lateral_announced = True
|
||||
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
|
||||
|
||||
### longitudinal control ###
|
||||
# send acc msg at 50Hz
|
||||
@@ -232,7 +257,8 @@ 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_extended_lateral_announced
|
||||
and self.ford_lateral.mode != FordLateralMode.native
|
||||
and self.ford_lateral.mode == self.ford_lateral_announced_mode
|
||||
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,6 +1,3 @@
|
||||
# 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
|
||||
@@ -212,79 +209,7 @@ class CarState(CarStateBase):
|
||||
def get_can_parsers(CP):
|
||||
gps_config = get_car_gps_config(CP)
|
||||
gps_messages = [(name, 0) for name in gps_config.messages] if gps_config is not None else []
|
||||
|
||||
pt_messages = [
|
||||
("BrakeSysFeatures", 50),
|
||||
("Yaw_Data_FD1", 100),
|
||||
("DesiredTorqBrk", 50),
|
||||
("EngVehicleSpThrottle", 100),
|
||||
("EngVehicleSpThrottle2", 50),
|
||||
("BrakeSnData_4", 50),
|
||||
("EngBrakeData", 10),
|
||||
("EPAS_INFO", 50),
|
||||
("Cluster_Info1_FD1", 10),
|
||||
("Steering_Data_FD1", 10),
|
||||
("BodyInfo_3_FD1", 2),
|
||||
("RCMStatusMessage2_FD1", 10),
|
||||
("BCM_Lamp_Stat_FD1", 0),
|
||||
*gps_messages,
|
||||
]
|
||||
|
||||
if CP.flags & FordFlags.ALT_STEER_ANGLE:
|
||||
pt_messages += [
|
||||
("SteeringPinion_Data_Alt", 100),
|
||||
("ParkAid_Data", 50),
|
||||
]
|
||||
else:
|
||||
pt_messages += [("SteeringPinion_Data", 100)]
|
||||
|
||||
if CP.flags & FordFlags.CANFD:
|
||||
pt_messages += [
|
||||
("Lane_Assist_Data3_FD1", 33),
|
||||
("Cluster_Info_3_FD1", 10),
|
||||
]
|
||||
else:
|
||||
pt_messages += [("INSTRUMENT_PANEL", 1)]
|
||||
|
||||
if CP.transmissionType == TransmissionType.automatic:
|
||||
if CP.flags & FordFlags.CANFD:
|
||||
pt_messages += [("Gear_Shift_by_Wire_FD1", 10)]
|
||||
elif CP.flags & FordFlags.ALT_STEER_ANGLE:
|
||||
pt_messages += [("TransGearData", 10)]
|
||||
else:
|
||||
pt_messages += [("PowertrainData_10", 10)]
|
||||
|
||||
if CP.enableBsm and not (CP.flags & FordFlags.CANFD):
|
||||
pt_messages += [
|
||||
("Side_Detect_L_Stat", 5),
|
||||
("Side_Detect_R_Stat", 5),
|
||||
]
|
||||
|
||||
cam_messages = [
|
||||
("ACCDATA", 50),
|
||||
("ACCDATA_2", 50),
|
||||
("ACCDATA_3", 5),
|
||||
("IPMA_Data", 1),
|
||||
]
|
||||
|
||||
if CP.flags & FordFlags.CANFD:
|
||||
cam_messages += [
|
||||
("Traffic_RecognitnData", 1),
|
||||
("IPMA_Data2", 1),
|
||||
]
|
||||
else:
|
||||
cam_messages += [("Traffic_RecognitnData", 0)]
|
||||
|
||||
if CP.enableBsm and CP.flags & FordFlags.CANFD:
|
||||
cam_messages += [
|
||||
("Side_Detect_L_Stat", 5),
|
||||
("Side_Detect_R_Stat", 5),
|
||||
]
|
||||
|
||||
if CP.flags & FordFlags.LKA_STEERING:
|
||||
cam_messages += [("LateralMotionControl", 20)]
|
||||
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).main),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CanBus(CP).camera),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], gps_messages, CanBus(CP).main),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).camera),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
""" 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,5 +1,3 @@
|
||||
# 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,5 +1,3 @@
|
||||
# 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
|
||||
|
||||
@@ -4,12 +4,10 @@ from types import SimpleNamespace
|
||||
|
||||
from hypothesis import settings, given, strategies as st
|
||||
from parameterized import parameterized
|
||||
import pytest
|
||||
|
||||
from opendbc.car import Bus, gen_empty_fingerprint
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car.ford import fordcan
|
||||
from opendbc.car.ford.carcontroller import FordStockCruiseButton
|
||||
from opendbc.car.gps import FORD_MACH_E_GPS_MESSAGES, get_car_gps_config, parse_ford_can_gps
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.fw_versions import build_fw_dict
|
||||
@@ -20,24 +18,6 @@ from opendbc.car.ford.fingerprints import FW_VERSIONS
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
|
||||
def test_stock_cruise_button_latches_context_until_release():
|
||||
button = FordStockCruiseButton()
|
||||
|
||||
assert button.update(True, cruise_available=True, cruise_enabled=True) == (True, False)
|
||||
assert button.update(True, cruise_available=True, cruise_enabled=False) == (True, False)
|
||||
assert button.update(False, cruise_available=True, cruise_enabled=False) == (False, False)
|
||||
|
||||
assert button.update(True, cruise_available=True, cruise_enabled=False) == (False, True)
|
||||
assert button.update(True, cruise_available=True, cruise_enabled=True) == (False, True)
|
||||
assert button.update(False, cruise_available=True, cruise_enabled=True) == (False, False)
|
||||
|
||||
|
||||
def test_stock_cruise_button_ignores_press_with_cruise_master_off():
|
||||
button = FordStockCruiseButton()
|
||||
|
||||
assert button.update(True, cruise_available=False, cruise_enabled=False) == (False, False)
|
||||
|
||||
|
||||
ECU_ADDRESSES = {
|
||||
Ecu.eps: 0x730, # Power Steering Control Module (PSCM)
|
||||
Ecu.abs: 0x760, # Anti-Lock Brake System (ABS)
|
||||
@@ -294,23 +274,6 @@ def test_mach_e_can_gps_messages_are_optional_main_bus_inputs():
|
||||
assert all(parser.message_states[address].ignore_alive for address in (0x462, 0x463, 0x464))
|
||||
|
||||
|
||||
def test_lightning_low_rate_camera_messages_use_declared_frequencies():
|
||||
cp = CarInterface.get_params(CAR.FORD_F_150_LIGHTNING_MK1, gen_empty_fingerprint(), [], True, False, False, None)
|
||||
cp.enableBsm = True
|
||||
parser = CarInterface.CarState.get_can_parsers(cp)[Bus.cam]
|
||||
|
||||
expected_frequencies = {
|
||||
"IPMA_Data": 1,
|
||||
"Traffic_RecognitnData": 1,
|
||||
"Side_Detect_L_Stat": 5,
|
||||
"Side_Detect_R_Stat": 5,
|
||||
}
|
||||
for message, frequency in expected_frequencies.items():
|
||||
state = parser.message_states[parser.dbc.name_to_msg[message].address]
|
||||
assert state.frequency == frequency
|
||||
assert state.timeout_threshold == pytest.approx(10e9 / frequency)
|
||||
|
||||
|
||||
def test_hands_free_cluster_status_is_opt_in():
|
||||
packer = CANPacker("ford_lincoln_base_pt")
|
||||
CAN = SimpleNamespace(main=0)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# 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,9 +170,7 @@ 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, skip_buses: set[int] | None = None) -> set[EcuAddrBusType]:
|
||||
skip_buses = skip_buses or set()
|
||||
def get_present_ecus(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, num_pandas: int = 1) -> set[EcuAddrBusType]:
|
||||
# queries are split by OBD multiplexing mode
|
||||
queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []}
|
||||
parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []}
|
||||
@@ -180,7 +178,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 or r.bus in skip_buses:
|
||||
if r.bus > num_pandas * 4 - 1:
|
||||
continue
|
||||
|
||||
for ecu_type, addr, sub_addr in config.get_all_ecus(VERSIONS[brand]):
|
||||
@@ -237,8 +235,7 @@ 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, skip_buses: set[int] | None = None) -> list[CarParams.CarFw]:
|
||||
ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1, progress: bool = False) -> list[CarParams.CarFw]:
|
||||
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
|
||||
|
||||
all_car_fw = []
|
||||
@@ -251,8 +248,7 @@ 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, skip_buses=skip_buses)
|
||||
car_fw = get_fw_versions(can_recv, can_send, set_obd_multiplexing, query_brand=brand, timeout=timeout, num_pandas=num_pandas, progress=progress)
|
||||
all_car_fw.extend(car_fw)
|
||||
|
||||
# If there is a match using this brand's FW alone, finish querying early
|
||||
@@ -264,9 +260,7 @@ 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,
|
||||
skip_buses: set[int] | None = None) -> list[CarParams.CarFw]:
|
||||
skip_buses = skip_buses or set()
|
||||
extra: OfflineFwVersions = None, timeout: float = 0.1, num_pandas: int = 1, progress: bool = False) -> list[CarParams.CarFw]:
|
||||
versions = VERSIONS.copy()
|
||||
|
||||
if query_brand is not None:
|
||||
@@ -304,7 +298,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 or r.bus in skip_buses:
|
||||
if r.bus > num_pandas * 4 - 1:
|
||||
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, GM_AUTO_HOLD_CARS, SDGM_CAR, AccState, CanBus, CarControllerParams,
|
||||
ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, CC_REGEN_PADDLE_CAR, DBC, EV_CAR, SDGM_CAR, AccState, CanBus, CarControllerParams,
|
||||
CruiseButtons, GMFlags, GMSafetyFlags,
|
||||
)
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
@@ -172,7 +172,7 @@ def should_send_cc_button_spam(CP, CC, CS):
|
||||
return (
|
||||
bool(CP.flags & GMFlags.CC_LONG.value) and
|
||||
CC.longActive and
|
||||
CS.out.vEgo >= CP.minEnableSpeed
|
||||
CS.out.vEgo > CP.minEnableSpeed
|
||||
)
|
||||
|
||||
|
||||
@@ -256,17 +256,6 @@ def shape_truck_pitch_accel(pitch_accel: float, v_ego: float, enabled: bool) ->
|
||||
return pitch_accel * scale
|
||||
|
||||
|
||||
MAX_UPHILL_GRADE_FF = 0.20
|
||||
|
||||
|
||||
def limit_grade_feedforward(planner_accel: float, pitch_accel: float) -> float:
|
||||
if pitch_accel > 0.0 and planner_accel > 0.0:
|
||||
return 0.0
|
||||
if pitch_accel > MAX_UPHILL_GRADE_FF:
|
||||
return MAX_UPHILL_GRADE_FF
|
||||
return pitch_accel
|
||||
|
||||
|
||||
def shape_truck_friction_brake(apply_brake: int, accel_cmd: float, stopping: bool, active: bool) -> tuple[int, bool]:
|
||||
if apply_brake <= 0:
|
||||
return 0, False
|
||||
@@ -309,7 +298,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 GM_AUTO_HOLD_CARS
|
||||
CP.carFingerprint in AUTO_HOLD_VOLT_CARS
|
||||
)
|
||||
|
||||
|
||||
@@ -1014,13 +1003,14 @@ class CarController(CarControllerBase):
|
||||
self.truck_follow_accel = 0.0
|
||||
else:
|
||||
long_pitch_enabled = bool(getattr(starpilot_toggles, "long_pitch", True))
|
||||
pedal_long_path = bool(self.CP.enableGasInterceptorDEPRECATED and (self.CP.flags & GMFlags.PEDAL_LONG.value))
|
||||
long_pitch_for_powertrain = long_pitch_enabled or pedal_long_path
|
||||
|
||||
if self.is_volt:
|
||||
if long_pitch_enabled and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
|
||||
if long_pitch_for_powertrain and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
|
||||
volt_pitch_accel = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY
|
||||
else:
|
||||
volt_pitch_accel = 0.0
|
||||
volt_pitch_accel = limit_grade_feedforward(accel, volt_pitch_accel)
|
||||
|
||||
aero_drag_accel = (0.5 * self.coeffDrag * self.frontalArea * self.airDensity * CS.out.vEgo ** 2) / self.mass
|
||||
accel_cmd = float(np.clip(accel + aero_drag_accel + volt_pitch_accel, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
|
||||
@@ -1041,7 +1031,7 @@ class CarController(CarControllerBase):
|
||||
if self.apply_brake > 0:
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
else:
|
||||
if long_pitch_enabled and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
|
||||
if long_pitch_for_powertrain and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
|
||||
accel_due_to_pitch = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY
|
||||
else:
|
||||
accel_due_to_pitch = 0.0
|
||||
@@ -1058,7 +1048,6 @@ class CarController(CarControllerBase):
|
||||
not self.CP.enableGasInterceptorDEPRECATED
|
||||
)
|
||||
accel_due_to_pitch = shape_truck_pitch_accel(accel_due_to_pitch, CS.out.vEgo, truck_long_smoothing)
|
||||
accel_due_to_pitch = limit_grade_feedforward(actuators.accel, accel_due_to_pitch)
|
||||
accel_input = actuators.accel + accel_due_to_pitch
|
||||
if truck_long_smoothing:
|
||||
accel_input = shape_truck_positive_accel(
|
||||
@@ -1159,13 +1148,7 @@ class CarController(CarControllerBase):
|
||||
if should_send_cc_button_spam(self.CP, CC, CS):
|
||||
if self.CP.carFingerprint != CAR.CADILLAC_XT4_CC:
|
||||
# Using extend instead of append since the message is only sent intermittently
|
||||
longitudinal_adjustment_active = bool(getattr(
|
||||
CS, "openpilot_longitudinal_adjustment_active", CC.hudControl.leadVisible,
|
||||
))
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(
|
||||
self.packer_pt, self, CS, actuators, starpilot_toggles,
|
||||
longitudinal_adjustment_active=longitudinal_adjustment_active,
|
||||
))
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
|
||||
else:
|
||||
if (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
|
||||
CS.cruise_buttons == CruiseButtons.UNPRESS and CS.out.gasPressed and CS.out.cruiseState.speed < CS.out.vEgo < hud_v_cruise):
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import copy
|
||||
import math
|
||||
from cereal import custom
|
||||
from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, create_button_events, structs
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.gps import get_car_gps_config
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.gm.values import (
|
||||
ALT_ACCS,
|
||||
@@ -18,7 +16,6 @@ from opendbc.car.gm.values import (
|
||||
AccState,
|
||||
CanBus,
|
||||
CruiseButtons,
|
||||
GM_AUTO_HOLD_CARS,
|
||||
GMFlags,
|
||||
SDGM_CAR,
|
||||
STEER_THRESHOLD,
|
||||
@@ -33,7 +30,6 @@ 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}
|
||||
@@ -69,36 +65,6 @@ 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 is_gm_auto_hold_active(car_fingerprint: str, auto_hold_engaged: bool, in_drive_for_hold: bool,
|
||||
cruise_available: bool, standstill: bool, gas_pressed: bool) -> bool:
|
||||
return (
|
||||
auto_hold_engaged and
|
||||
car_fingerprint in GM_AUTO_HOLD_CARS and
|
||||
in_drive_for_hold and
|
||||
cruise_available and
|
||||
standstill and
|
||||
not gas_pressed
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
@@ -137,56 +103,7 @@ 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, v_ego: float = 0.0) -> None:
|
||||
if self.car_gps_config is None:
|
||||
return
|
||||
|
||||
timestamps = [max(cp.ts_nanos[name].values(), default=0) for name in self.car_gps_config.messages]
|
||||
if not all(timestamps) or max(timestamps) - min(timestamps) > 2_000_000_000:
|
||||
return
|
||||
|
||||
timestamp_nanos = max(timestamps)
|
||||
if timestamp_nanos <= self._car_gps_timestamp_nanos:
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
def get_car_gps(self):
|
||||
return self.car_gps
|
||||
|
||||
def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]):
|
||||
if not self.CP.pcmCruise:
|
||||
@@ -272,8 +189,6 @@ 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:
|
||||
@@ -384,18 +299,8 @@ class CarState(CarStateBase):
|
||||
|
||||
ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0
|
||||
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 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.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or
|
||||
pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1)
|
||||
|
||||
ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF
|
||||
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
|
||||
@@ -444,11 +349,6 @@ class CarState(CarStateBase):
|
||||
self.auto_hold_fault_suppression_timer = max(self.auto_hold_fault_suppression_timer - DT_CTRL, 0.0)
|
||||
ret.accFaulted = False
|
||||
|
||||
ret.brakeHoldActive = is_gm_auto_hold_active(
|
||||
self.CP.carFingerprint, self.auto_hold_engaged, in_drive_for_hold,
|
||||
ret.cruiseState.available, ret.standstill, ret.gasPressed,
|
||||
)
|
||||
|
||||
if self.CP.enableBsm and not sdgm_non_volt:
|
||||
ret.leftBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["LeftBSM"] == 1
|
||||
ret.rightBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["RightBSM"] == 1
|
||||
@@ -528,9 +428,6 @@ class CarState(CarStateBase):
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
gps_config = get_car_gps_config(CP)
|
||||
gps_messages = [(name, 0) for name in gps_config.messages] if gps_config is not None else []
|
||||
|
||||
volt_like = {
|
||||
CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019,
|
||||
@@ -554,7 +451,6 @@ class CarState(CarStateBase):
|
||||
("PSCMSteeringAngle", 100),
|
||||
("ECMAcceleratorPos", 80),
|
||||
("SportMode", 0),
|
||||
*gps_messages,
|
||||
]
|
||||
|
||||
prndl2_rate = 10 if CP.carFingerprint in kaofui_state_cars else 40
|
||||
|
||||
@@ -30,20 +30,20 @@ FINGERPRINTS = {
|
||||
CAR.BUICK_LACROSSE: [{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8
|
||||
}],
|
||||
CAR.CHEVROLET_VOLT_CC: [
|
||||
# Captured no-ACC Volt fingerprints for OBD-C/L&P harness installations
|
||||
# Volt Premier w/o ACC 2016
|
||||
{
|
||||
170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 8, 308: 4, 309: 8, 311: 8, 313: 8, 320: 8, 328: 1, 352: 5, 368: 8, 381: 6, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 4, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 8, 563: 5, 564: 5, 565: 8, 566: 5, 567: 3, 568: 1, 577: 8, 578: 8, 594: 8, 647: 3, 707: 8, 711: 6, 717: 5, 761: 7, 800: 6, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 6, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1618: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 1930: 7, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2024: 8, 2025: 8, 2028: 8
|
||||
},
|
||||
{
|
||||
201: 8, 493: 8, 495: 4, 193: 8, 197: 8, 209: 7, 171: 8, 456: 8, 199: 4, 489: 8, 211: 2, 499: 3, 390: 7, 532: 6, 568: 1, 761: 7, 381: 6, 485: 8, 189: 7, 479: 3, 711: 6, 501: 8, 241: 6, 717: 5, 869: 4, 389: 2, 454: 8, 170: 8, 190: 6, 497: 8, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 500: 6, 508: 8, 528: 4, 647: 3, 1105: 6, 1005: 6, 481: 7, 844: 8, 866: 4, 564: 5, 969: 8, 388: 8, 352: 5, 562: 8, 961: 8, 386: 8, 707: 8, 977: 8, 979: 7, 298: 8, 840: 5, 842: 5, 988: 6, 1001: 8, 560: 8, 546: 7, 558: 8, 309: 8, 995: 7, 311: 8, 566: 5, 567:3, 989: 8, 384: 4, 800: 6, 1033: 7, 1034: 7, 313: 8, 554: 3, 810: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 1223: 3, 1233: 8, 1227: 4, 1417: 8, 1009: 8, 1221: 5, 1275: 3, 1225: 7, 289: 8, 550: 8, 1273: 3, 1928: 7, 1187: 4, 1265: 8, 1927: 7, 1267: 1, 1906: 7, 288: 5, 304: 1, 328: 1, 1912: 7, 320: 3, 1910: 7, 563: 5, 1249: 8, 1930: 7, 1257: 6, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 565: 5, 1280: 4, 1907: 7
|
||||
},
|
||||
# Volt Premier w/o ACC 2018 + Pedal
|
||||
{
|
||||
189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7, 1930: 7
|
||||
}
|
||||
],
|
||||
# CAR.CHEVROLET_VOLT_CC: [
|
||||
# FIXME: Need a message to distinguish flashed from non-flashed
|
||||
# Volt Premier w/o acc 2016
|
||||
# {
|
||||
# 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 8, 308: 4, 309: 8, 311: 8, 313: 8, 320: 8, 328: 1, 352: 5, 368: 8, 381: 6, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 4, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 8, 563: 5, 564: 5, 565: 8, 566: 5, 567: 3, 568: 1, 577: 8, 578: 8, 594: 8, 647: 3, 707: 8, 711: 6, 717: 5, 761: 7, 800: 6, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 6, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1618: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 1930: 7, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2024: 8, 2025: 8, 2028: 8
|
||||
# },
|
||||
# {
|
||||
# 201: 8, 493: 8, 495: 4, 193: 8, 197: 8, 209: 7, 171: 8, 456: 8, 199: 4, 489: 8, 211: 2, 499: 3, 390: 7, 532: 6, 568: 1, 761: 7, 381: 6, 485: 8, 189: 7, 479: 3, 711: 6, 501: 8, 241: 6, 717: 5, 869: 4, 389: 2, 454: 8, 170: 8, 190: 6, 497: 8, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 500: 6, 508: 8, 528: 4, 647: 3, 1105: 6, 1005: 6, 481: 7, 844: 8, 866: 4, 564: 5, 969: 8, 388: 8, 352: 5, 562: 8, 961: 8, 386: 8, 707: 8, 977: 8, 979: 7, 298: 8, 840: 5, 842: 5, 988: 6, 1001: 8, 560: 8, 546: 7, 558: 8, 309: 8, 995: 7, 311: 8, 566: 5, 567:3, 989: 8, 384: 4, 800: 6, 1033: 7, 1034: 7, 313: 8, 554: 3, 810: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 1223: 3, 1233: 8, 1227: 4, 1417: 8, 1009: 8, 1221: 5, 1275: 3, 1225: 7, 289: 8, 550: 8, 1273: 3, 1928: 7, 1187: 4, 1265: 8, 1927: 7, 1267: 1, 1906: 7, 288: 5, 304: 1, 328: 1, 1912: 7, 320: 3, 1910: 7, 563: 5, 1249: 8, 1930: 7, 1257: 6, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 565: 5, 1280: 4, 1907: 7
|
||||
# },
|
||||
# # Volt Premier w/o ACC 2018 + Pedal
|
||||
# {
|
||||
# 189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7, 1930: 7
|
||||
# }
|
||||
# ],
|
||||
CAR.BUICK_REGAL: [{
|
||||
190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 8, 419: 8, 422: 4, 426: 8, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 463: 3, 479: 8, 481: 7, 485: 8, 487: 8, 489: 8, 495: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 884: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 8, 969: 8, 977: 8, 979: 8, 985: 8, 1001: 8, 1005: 6, 1009: 8, 1011: 8, 1013: 3, 1017: 8, 1020: 8, 1024: 8, 1025: 8, 1026: 8, 1027: 8, 1028: 8, 1029: 8, 1030: 8, 1031: 8, 1032: 2, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 8, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 8, 1263: 8, 1265: 8, 1267: 8, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1603: 7, 1611: 8, 1618: 8, 1906: 8, 1907: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1930: 7, 2016: 8, 2018: 8, 2019: 8, 2024: 8, 2026: 8
|
||||
}],
|
||||
@@ -207,15 +207,12 @@ FINGERPRINTS = {
|
||||
FINGERPRINTS.update({
|
||||
CAR.CHEVROLET_VOLT_ASCM: FINGERPRINTS[CAR.CHEVROLET_VOLT],
|
||||
CAR.CHEVROLET_VOLT_CAMERA: [{**fp, CAMERA_DIAGNOSTIC_ADDRESS: 8, CAMERA_DIAGNOSTIC_RX_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CHEVROLET_VOLT]],
|
||||
CAR.CHEVROLET_VOLT_CC: FINGERPRINTS[CAR.CHEVROLET_VOLT],
|
||||
CAR.GMC_ACADIA_ASCM: FINGERPRINTS[CAR.GMC_ACADIA],
|
||||
CAR.CHEVROLET_MALIBU_ASCM: FINGERPRINTS[CAR.CHEVROLET_MALIBU],
|
||||
CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE],
|
||||
CAR.CADILLAC_ESCALADE_ESV_2019_ASCM: [{**fp, SASCM_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CADILLAC_ESCALADE_ESV_2019]],
|
||||
CAR.CHEVROLET_SUBURBAN: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
|
||||
CAR.CHEVROLET_SUBURBAN_ASCM: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
|
||||
# The camera-harness Suburban shares the observed CAN map with the 2019 Yukon;
|
||||
# VIN and camera-bus diagnostics disambiguate it during live fingerprinting.
|
||||
CAR.CHEVROLET_SUBURBAN_CAMERA: FINGERPRINTS[CAR.GMC_YUKON],
|
||||
CAR.GMC_YUKON_CC: FINGERPRINTS[CAR.GMC_YUKON],
|
||||
CAR.CADILLAC_XT6: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
CAR.CADILLAC_XT5: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from opendbc.car import DT_CTRL, structs
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car.can_definitions import CanData
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.gm.values import CAR, CanBus, CruiseButtons, GMFlags
|
||||
@@ -28,11 +28,6 @@ BOLT_CC_BUTTON_CARS = {
|
||||
BOLT_CC_TARGET_DEADBAND_MPH = 0.75
|
||||
BOLT_CC_REVERSE_CONFIRM_S = 0.6
|
||||
BOLT_CC_DIRECTION_MEMORY_S = 1.5
|
||||
VOLT_CC_CARS = {
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
}
|
||||
VOLT_CC_FREE_REQUEST_DEADBAND_MPH = 5.0
|
||||
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH = 2.0
|
||||
|
||||
|
||||
def malibu_phase_map_for_button(button):
|
||||
@@ -341,54 +336,7 @@ def stabilize_bolt_cc_button(controller, CP, requested_button):
|
||||
return requested_button
|
||||
|
||||
|
||||
def _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active):
|
||||
accel = float(actuators.accel)
|
||||
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
|
||||
ego_speed = CS.out.vEgo * ms_convert
|
||||
requested_setpoint = (CS.out.vEgo * 1.01 + 3 * accel) * ms_convert
|
||||
deadband_mph = (
|
||||
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH if longitudinal_adjustment_active
|
||||
else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
|
||||
)
|
||||
request_deadband = deadband_mph * (CV.MPH_TO_KPH if ms_convert == CV.MS_TO_KPH else 1.0)
|
||||
|
||||
target_setpoint = None
|
||||
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 = int(round(v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH))
|
||||
|
||||
moving_toward_target = target_setpoint is not None and (
|
||||
(accel > 0.0 and speed_setpoint < target_setpoint) or
|
||||
(accel < 0.0 and speed_setpoint > target_setpoint)
|
||||
)
|
||||
if target_setpoint is not None and accel > 0.0 and speed_setpoint >= target_setpoint:
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
if (target_setpoint is not None and accel < 0.0 and speed_setpoint <= target_setpoint and
|
||||
not longitudinal_adjustment_active):
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
|
||||
if not moving_toward_target and abs(requested_setpoint - speed_setpoint) <= request_deadband:
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
|
||||
if accel == 0.0:
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
|
||||
if accel < 0.0:
|
||||
if speed_setpoint > ego_speed + 3.0:
|
||||
rate = 0.2
|
||||
else:
|
||||
rate = max(1.0 / (-accel * ms_convert), 0.2)
|
||||
return CruiseButtons.DECEL_SET, rate
|
||||
|
||||
if speed_setpoint < ego_speed - 3.0:
|
||||
rate = 0.2
|
||||
else:
|
||||
rate = max(1.0 / (accel * ms_convert), 0.2)
|
||||
return CruiseButtons.RES_ACCEL, rate
|
||||
|
||||
|
||||
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles, longitudinal_adjustment_active=False):
|
||||
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles):
|
||||
accel = actuators.accel
|
||||
v_ego = CS.out.vEgo
|
||||
cruise_btn = CruiseButtons.INIT
|
||||
@@ -402,15 +350,12 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
|
||||
target_deadband = BOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0) if bolt_cc else 0.0
|
||||
comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint
|
||||
|
||||
if CS.CP.carFingerprint in VOLT_CC_CARS:
|
||||
cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active)
|
||||
else:
|
||||
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
|
||||
cruise_btn = CruiseButtons.CANCEL
|
||||
elif comparison_setpoint < speed_setpoint - target_deadband and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1:
|
||||
cruise_btn = CruiseButtons.DECEL_SET
|
||||
elif comparison_setpoint > speed_setpoint + target_deadband:
|
||||
cruise_btn = CruiseButtons.RES_ACCEL
|
||||
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
|
||||
cruise_btn = CruiseButtons.CANCEL
|
||||
elif comparison_setpoint < speed_setpoint - target_deadband and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1:
|
||||
cruise_btn = CruiseButtons.DECEL_SET
|
||||
elif comparison_setpoint > speed_setpoint + target_deadband:
|
||||
cruise_btn = CruiseButtons.RES_ACCEL
|
||||
|
||||
cruise_btn = stabilize_bolt_cc_button(controller, CS.CP, cruise_btn)
|
||||
if cruise_btn == CruiseButtons.CANCEL:
|
||||
@@ -475,11 +420,9 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
|
||||
idx = (CS.buttons_counter + 1) % 4 # Need to predict the next idx for '22-23 EUV
|
||||
msgs = [create_buttons(packer, CanBus.POWERTRAIN, idx, cruise_btn)]
|
||||
|
||||
# A camera-forward Volt CC install needs the button spoof on both sides.
|
||||
# The OBD-C/L&P gateway variant has no camera bus and remains PT-only.
|
||||
if (CS.CP.carFingerprint == CAR.CHEVROLET_VOLT_CC and
|
||||
getattr(CS.CP, "networkLocation", None) == structs.CarParams.NetworkLocation.fwdCamera and
|
||||
not (CS.CP.flags & GMFlags.NO_CAMERA.value)):
|
||||
# Flashed camera-forward Volt CC installs also need the button spoof on the
|
||||
# camera side. Removed-camera installs set NO_CAMERA and keep this PT-only.
|
||||
if CS.CP.carFingerprint == CAR.CHEVROLET_VOLT_CC and not (CS.CP.flags & GMFlags.NO_CAMERA.value):
|
||||
msgs.append(create_buttons(packer, CanBus.CAMERA, idx, cruise_btn))
|
||||
return msgs
|
||||
else:
|
||||
|
||||
@@ -15,7 +15,6 @@ from opendbc.car.gm.values import (
|
||||
CC_ONLY_CAR,
|
||||
CC_REGEN_PADDLE_CAR,
|
||||
EV_CAR,
|
||||
GM_AUTO_HOLD_CARS,
|
||||
SDGM_CAR,
|
||||
CarControllerParams,
|
||||
CanBus,
|
||||
@@ -274,6 +273,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
kaofui_camera_cars = {
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
}
|
||||
bolt_cc_camera_cars = {
|
||||
@@ -306,7 +306,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.HW_CAM_LONG.value
|
||||
|
||||
elif is_camera_acc:
|
||||
ret.alphaLongitudinalAvailable = candidate not in (CC_ONLY_CAR | ALT_ACCS) and not ret.enableGasInterceptorDEPRECATED
|
||||
ret.alphaLongitudinalAvailable = (candidate not in CC_ONLY_CAR) and not ret.enableGasInterceptorDEPRECATED
|
||||
ret.networkLocation = NetworkLocation.fwdCamera
|
||||
ret.radarUnavailable = True
|
||||
ret.pcmCruise = not ret.enableGasInterceptorDEPRECATED
|
||||
@@ -409,7 +409,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
ret.radarTimeStepDEPRECATED = 0.15 if candidate == CAR.BUICK_LACROSSE else 0.0667
|
||||
ret.radarTimeStepDEPRECATED = 0.0667 # GM radar runs at 15Hz instead of the standard 20Hz
|
||||
ret.longitudinalActuatorDelay = 0.5 # large delay to initially start braking
|
||||
|
||||
if candidate in (
|
||||
@@ -441,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 = 28 * CV.MPH_TO_MS
|
||||
ret.minSteerSpeed = 27 * CV.MPH_TO_MS
|
||||
|
||||
elif candidate == CAR.CADILLAC_ESCALADE:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
@@ -501,7 +501,9 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.flags |= GMFlags.PEDAL_LONG.value
|
||||
|
||||
elif candidate in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC):
|
||||
ret.minEnableSpeed = 0.
|
||||
# On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop
|
||||
# with foot on brake to allow engagement, but this platform only has that check in the camera.
|
||||
# TODO: check if this is split by EV/ICE with more platforms in the future
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.CHEVROLET_EQUINOX, CAR.CHEVROLET_EQUINOX_CC):
|
||||
@@ -522,7 +524,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_ASCM, CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.CHEVROLET_SUBURBAN_CC):
|
||||
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_CC):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
@@ -665,8 +667,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
ret.openpilotLongitudinalControl = not disable_openpilot_long
|
||||
ret.pcmCruise = False
|
||||
if candidate not in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC):
|
||||
ret.minEnableSpeed = 24 * CV.MPH_TO_MS
|
||||
ret.minEnableSpeed = 24 * CV.MPH_TO_MS
|
||||
ret.radarUnavailable = True
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_CC_LONG.value
|
||||
|
||||
@@ -684,8 +685,6 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if candidate in CC_ONLY_CAR:
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_ACC.value
|
||||
if candidate == CAR.CHEVROLET_VOLT_CC and ret.networkLocation == NetworkLocation.gateway:
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY.value
|
||||
|
||||
if candidate in SDGM_CAR and ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
|
||||
ret.flags |= GMFlags.FORCE_BRAKE_C9.value
|
||||
@@ -699,7 +698,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
|
||||
ret.flags |= GMFlags.NO_ACCELERATOR_POS_MSG.value
|
||||
if candidate in (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_CC) and ret.networkLocation == NetworkLocation.gateway:
|
||||
if candidate == CAR.CHEVROLET_VOLT and ret.networkLocation == NetworkLocation.gateway:
|
||||
# Reuse the no-camera safety bit as an ASCM Volt selector for the alternate EBCM brake path.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
@@ -711,19 +710,18 @@ class CarInterface(CarInterfaceBase):
|
||||
if remote_start_boots_comma:
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value
|
||||
|
||||
gm_stock_friction_brake_safety = (
|
||||
volt_stock_friction_brake_safety = (
|
||||
ret.openpilotLongitudinalControl and
|
||||
(
|
||||
(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,
|
||||
})
|
||||
)
|
||||
(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,
|
||||
}
|
||||
)
|
||||
if gm_stock_friction_brake_safety:
|
||||
if volt_stock_friction_brake_safety:
|
||||
# Reuse the paddle-scheduler safety bit as a Volt stock friction-brake
|
||||
# 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
|
||||
|
||||
@@ -53,7 +53,6 @@ from opendbc.car.gm.carcontroller import (
|
||||
get_testing_ground_1_brake_switch_bias,
|
||||
get_acc_dashboard_status_active,
|
||||
get_stock_cc_active_for_cancel,
|
||||
limit_grade_feedforward,
|
||||
shape_bolt_acc_pedal_low_speed_friction,
|
||||
shape_truck_friction_brake,
|
||||
shape_truck_pitch_accel,
|
||||
@@ -431,15 +430,6 @@ 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,
|
||||
@@ -905,20 +895,6 @@ def test_shape_truck_pitch_accel_is_inactive_without_truck_tuning():
|
||||
assert shape_truck_pitch_accel(-0.30, 30.0, False) == pytest.approx(-0.30)
|
||||
|
||||
|
||||
def test_limit_grade_feedforward_does_not_stack_on_positive_planner():
|
||||
assert limit_grade_feedforward(0.40, 0.50) == 0.0
|
||||
|
||||
|
||||
def test_limit_grade_feedforward_caps_uphill_hold():
|
||||
assert limit_grade_feedforward(0.0, 0.50) == pytest.approx(0.20)
|
||||
assert limit_grade_feedforward(-0.10, 0.50) == pytest.approx(0.20)
|
||||
|
||||
|
||||
def test_limit_grade_feedforward_keeps_downhill_help():
|
||||
assert limit_grade_feedforward(0.40, -0.30) == pytest.approx(-0.30)
|
||||
assert limit_grade_feedforward(-0.20, -0.30) == pytest.approx(-0.30)
|
||||
|
||||
|
||||
def test_shape_truck_friction_brake_suppresses_boundary_chatter():
|
||||
assert shape_truck_friction_brake(14, -0.3, False, False) == (0, False)
|
||||
|
||||
|
||||
@@ -8,13 +8,7 @@ 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,
|
||||
is_gm_auto_hold_active,
|
||||
update_auto_hold_drive_timers,
|
||||
update_startup_acc_fault_suppression,
|
||||
)
|
||||
from opendbc.car.gm.carstate import CarState as GMCarState, get_hard_cruise_buttons, update_auto_hold_drive_timers
|
||||
from opendbc.car.gm.carcontroller import (
|
||||
VisualAlert,
|
||||
get_acc_dashboard_always_one,
|
||||
@@ -26,9 +20,8 @@ from opendbc.car.gm.carcontroller import (
|
||||
)
|
||||
import opendbc.car.gm.interface as gm_interface
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.gps import CHEVROLET_BOLT_GPS_CARS, CHEVROLET_BOLT_GPS_MESSAGES, get_car_gps_config, parse_chevrolet_bolt_can_gps
|
||||
from opendbc.car.gm.fingerprints import FINGERPRINTS
|
||||
from opendbc.car.gm.values import ALT_ACCS, ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
|
||||
from opendbc.car.gm.values import ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.common.params import Params
|
||||
|
||||
@@ -72,317 +65,7 @@ class TestGMFingerprint:
|
||||
assert finger.get(required_addr) == 8, required_addr
|
||||
|
||||
|
||||
class TestBoltGps:
|
||||
@parameterized.expand(CHEVROLET_BOLT_GPS_CARS)
|
||||
def test_all_bolt_generations_are_registered(self, car_model):
|
||||
config = get_car_gps_config(SimpleNamespace(carFingerprint=car_model, brand="gm"))
|
||||
assert config is not None
|
||||
assert config.messages == CHEVROLET_BOLT_GPS_MESSAGES
|
||||
gps = parse_chevrolet_bolt_can_gps({
|
||||
"GPSLatitude": 145292743.0,
|
||||
"GPSLongitude": -267520892.0,
|
||||
})
|
||||
assert gps is not None
|
||||
assert gps["hasFix"]
|
||||
assert gps["latitude"] == pytest.approx(40.3590953)
|
||||
assert gps["longitude"] == pytest.approx(-74.3113589)
|
||||
|
||||
def test_invalid_bolt_position_does_not_become_a_fix(self):
|
||||
gps = parse_chevrolet_bolt_can_gps({"GPSLatitude": 0.0, "GPSLongitude": -2147483648.0})
|
||||
assert gps is not None
|
||||
assert not gps["hasFix"]
|
||||
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(
|
||||
brand="gm",
|
||||
carFingerprint=car_model,
|
||||
flags=0,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
enableBsm=False,
|
||||
enableGasInterceptorDEPRECATED=False,
|
||||
)
|
||||
parsers = GMCarState.get_can_parsers(cp)
|
||||
assert all(message in parsers[Bus.pt].vl for message in CHEVROLET_BOLT_GPS_MESSAGES)
|
||||
|
||||
|
||||
class TestGMCarState:
|
||||
@parameterized.expand([
|
||||
(CAR.BUICK_LACROSSE, True, True, True, True, False, True),
|
||||
(CAR.CHEVROLET_VOLT, True, True, True, True, False, True),
|
||||
(CAR.CHEVROLET_BOLT_CC_2017, True, True, True, True, False, False),
|
||||
(CAR.BUICK_LACROSSE, True, True, True, False, False, False),
|
||||
(CAR.BUICK_LACROSSE, True, True, True, True, True, False),
|
||||
])
|
||||
def test_auto_hold_alert_state_requires_supported_complete_stop(self, car_fingerprint, engaged, in_drive,
|
||||
cruise_available, standstill, gas_pressed, expected):
|
||||
assert is_gm_auto_hold_active(
|
||||
car_fingerprint, engaged, in_drive, cruise_available, standstill, gas_pressed,
|
||||
) is expected
|
||||
|
||||
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_suburban_obd_and_ascm_integrations_remain_separate(self):
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
|
||||
|
||||
assert CAR.CHEVROLET_SUBURBAN_ASCM in ASCM_INT
|
||||
assert FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_ASCM] == FINGERPRINTS[CAR.CHEVROLET_SUBURBAN]
|
||||
|
||||
obd_params = interfaces[CAR.CHEVROLET_SUBURBAN].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
ascm_params = interfaces[CAR.CHEVROLET_SUBURBAN_ASCM].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN_ASCM,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert obd_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert obd_params.openpilotLongitudinalControl
|
||||
assert not obd_params.pcmCruise
|
||||
assert obd_params.safetyConfigs[0].safetyParam == 0
|
||||
|
||||
assert ascm_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
|
||||
assert not ascm_params.flags & GMFlags.SASCM.value
|
||||
assert not ascm_params.alphaLongitudinalAvailable
|
||||
assert not ascm_params.openpilotLongitudinalControl
|
||||
assert ascm_params.pcmCruise
|
||||
assert ascm_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value | GMSafetyFlags.HW_ASCM_INT.value
|
||||
assert ascm_params.lateralTuning.torque.latAccelFactor == pytest.approx(obd_params.lateralTuning.torque.latAccelFactor)
|
||||
assert ascm_params.lateralTuning.torque.friction == pytest.approx(obd_params.lateralTuning.torque.friction)
|
||||
|
||||
def test_suburban_camera_harness_preserves_stock_acc(self):
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
|
||||
fingerprint[2] = fingerprint[0].copy()
|
||||
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA in CAMERA_ACC_CAR
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA in ALT_ACCS
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in CC_ONLY_CAR
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in ASCM_INT
|
||||
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
|
||||
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
|
||||
|
||||
camera_params = interfaces[CAR.CHEVROLET_SUBURBAN_CAMERA].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN_CAMERA,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert camera_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
|
||||
assert camera_params.pcmCruise
|
||||
assert not camera_params.alphaLongitudinalAvailable
|
||||
assert not camera_params.openpilotLongitudinalControl
|
||||
assert camera_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value
|
||||
|
||||
def test_suburban_cc_remains_no_acc_gateway_profile(self):
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC][0].copy()
|
||||
|
||||
cc_params = interfaces[CAR.CHEVROLET_SUBURBAN_CC].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN_CC,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert cc_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert cc_params.openpilotLongitudinalControl
|
||||
assert not cc_params.pcmCruise
|
||||
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_CC_LONG.value
|
||||
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
|
||||
|
||||
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,
|
||||
@@ -469,14 +152,6 @@ 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),
|
||||
@@ -526,45 +201,6 @@ class TestGMInterface:
|
||||
assert car_params.flags & GMFlags.NO_CAMERA.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
def test_volt_cc_obd_gateway_uses_cc_long_no_camera_path(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_CC]
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
_empty_fingerprint(),
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert car_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert car_params.flags & GMFlags.CC_LONG.value
|
||||
assert car_params.flags & GMFlags.NO_CAMERA.value
|
||||
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_CAM.value)
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_CC_LONG.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
parsers = CarInterface.CarState.get_can_parsers(car_params)
|
||||
assert "ECMCruiseControl" in parsers[Bus.pt].vl
|
||||
assert not parsers[Bus.cam].vl
|
||||
|
||||
def test_other_cc_only_gateway_does_not_use_volt_cc_safety_path(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO_CC]
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.CHEVROLET_SILVERADO_CC,
|
||||
_empty_fingerprint(),
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert car_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY.value)
|
||||
|
||||
def test_volt_ascm_sparse_fingerprint_without_camera_does_not_set_no_camera(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
|
||||
fingerprint = {
|
||||
@@ -590,36 +226,11 @@ class TestGMInterface:
|
||||
|
||||
assert car_params.openpilotLongitudinalControl
|
||||
assert not car_params.enableGasInterceptorDEPRECATED
|
||||
assert car_params.minEnableSpeed == pytest.approx(0.0)
|
||||
assert list(car_params.longitudinalTuning.kpBP) == pytest.approx([0.0, 5.0, 15.0, 35.0])
|
||||
assert list(car_params.longitudinalTuning.kpV) == pytest.approx([0.02, 0.03, 0.028, 0.022])
|
||||
assert list(car_params.longitudinalTuning.kiBP) == pytest.approx([0.0, 5.0, 15.0, 35.0])
|
||||
assert list(car_params.longitudinalTuning.kiV) == pytest.approx([0.20, 0.18, 0.13, 0.08])
|
||||
|
||||
def test_silverado_camera_acc_allows_engage_from_stop(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SILVERADO][0].copy()
|
||||
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_SILVERADO, fingerprint, [], alpha_long=False, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert car_params.minEnableSpeed == pytest.approx(0.0)
|
||||
|
||||
def test_silverado_cc_allows_engage_from_stop(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO_CC]
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.CHEVROLET_SILVERADO_CC,
|
||||
_empty_fingerprint(),
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert car_params.minEnableSpeed == pytest.approx(0.0)
|
||||
|
||||
def test_blazer_uses_softer_low_speed_stop_hold_tune(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_BLAZER]
|
||||
fingerprint = _empty_fingerprint()
|
||||
@@ -672,43 +283,6 @@ 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()
|
||||
@@ -952,13 +526,6 @@ class TestGMCarController:
|
||||
assert not should_send_cc_button_spam(SimpleNamespace(flags=GMFlags.CC_LONG.value, minEnableSpeed=10.0), cc, cs)
|
||||
assert not should_send_cc_button_spam(SimpleNamespace(flags=0, minEnableSpeed=10.0), cc, cs)
|
||||
|
||||
def test_cc_button_spam_allows_standstill_when_min_enable_is_zero(self):
|
||||
cp = SimpleNamespace(flags=GMFlags.CC_LONG.value, minEnableSpeed=0.0)
|
||||
cc = SimpleNamespace(longActive=True)
|
||||
cs = SimpleNamespace(out=SimpleNamespace(vEgo=0.0, cruiseState=SimpleNamespace(enabled=False)))
|
||||
|
||||
assert should_send_cc_button_spam(cp, cc, cs)
|
||||
|
||||
def test_volt_cc_redneck_spam_is_mirrored_to_camera_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)
|
||||
@@ -966,7 +533,6 @@ class TestGMCarController:
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=0,
|
||||
networkLocation=structs.CarParams.NetworkLocation.fwdCamera,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=2,
|
||||
@@ -981,267 +547,6 @@ class TestGMCarController:
|
||||
|
||||
assert [msg[2] for msg in msgs] == [0, 2]
|
||||
|
||||
def test_volt_cc_redneck_holds_setpoint_without_planner_acceleration(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=60.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=60.0 * CV.KPH_TO_MS),
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.0), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 60
|
||||
|
||||
def test_volt_cc_redneck_rate_limits_setpoint_changes_by_planner_acceleration(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(0.2 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=60.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=60.0 * CV.KPH_TO_MS),
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=1.0), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
|
||||
controller.frame = int(0.3 / DT_CTRL)
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=1.0), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
|
||||
def test_volt_cc_redneck_does_not_raise_stock_setpoint_above_max(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=100.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 == 100
|
||||
|
||||
def test_volt_cc_redneck_tracks_max_inside_request_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.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=52.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=52.0 * CV.KPH_TO_MS),
|
||||
vCruise=60.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 53
|
||||
|
||||
def test_volt_cc_redneck_tracks_max_down_inside_request_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.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=68.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=68.0 * CV.KPH_TO_MS),
|
||||
vCruise=60.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 67
|
||||
|
||||
def test_volt_cc_redneck_holds_small_decel_request_at_max(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.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=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_holds_strong_decel_request_at_max_during_free_cruise(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.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=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-1.2), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_brakes_for_active_lead_inside_free_road_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.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=100.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), longitudinal_adjustment_active=True,
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 99
|
||||
|
||||
def test_volt_cc_redneck_accelerates_when_pseudo_speed_request_exceeds_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(0.1 / 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=44.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=44.0 * CV.KPH_TO_MS),
|
||||
vCruise=50.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=2.0), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
|
||||
controller.frame = int(0.3 / DT_CTRL)
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=2.0), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 45
|
||||
|
||||
def test_volt_cc_redneck_brakes_when_pseudo_speed_request_exceeds_deadband(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)
|
||||
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=50.7 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=49.0 * CV.KPH_TO_MS),
|
||||
vCruise=49.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-1.36), SimpleNamespace(is_metric=True), longitudinal_adjustment_active=True,
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 48
|
||||
|
||||
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)
|
||||
@@ -1249,7 +554,6 @@ class TestGMCarController:
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=2,
|
||||
|
||||
@@ -175,7 +175,6 @@ class GMSafetyFlags(IntFlag):
|
||||
FLAG_GM_REMOTE_START_BOOTS_COMMA = 8192
|
||||
FLAG_GM_PANDA_3D1_SCHED = 16384
|
||||
FLAG_GM_PANDA_PADDLE_SCHED = 32768
|
||||
FLAG_GM_VOLT_CC_GATEWAY = 16384
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
@@ -248,7 +247,7 @@ class CAR(Platforms):
|
||||
dbc_dict=CHEVROLET_VOLT.dbc_dict,
|
||||
)
|
||||
CHEVROLET_VOLT_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Volt No-ACC 2016-18 (OBD Harness)", "Redneck ACC", min_enable_speed=0)],
|
||||
[GMCarDocs("Chevrolet Volt No-ACC 2017-18", min_enable_speed=0)],
|
||||
CHEVROLET_VOLT.specs,
|
||||
dbc_dict=CHEVROLET_VOLT.dbc_dict,
|
||||
)
|
||||
@@ -357,14 +356,6 @@ class CAR(Platforms):
|
||||
[GMCarDocs("Chevrolet Suburban Premier 2016-20")],
|
||||
CarSpecs(mass=2731, wheelbase=3.302, steerRatio=17.3, centerToFrontRatio=0.49),
|
||||
)
|
||||
CHEVROLET_SUBURBAN_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Suburban Premier ASCM Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
CHEVROLET_SUBURBAN.specs,
|
||||
)
|
||||
CHEVROLET_SUBURBAN_CAMERA = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Suburban Premier Camera Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
CHEVROLET_SUBURBAN.specs,
|
||||
)
|
||||
GMC_YUKON_CC = GMPlatformConfig(
|
||||
[GMCarDocs("GMC Yukon No-ACC 2019-20")],
|
||||
CarSpecs(mass=2541, wheelbase=2.95, steerRatio=16.3, centerToFrontRatio=0.4),
|
||||
@@ -541,21 +532,12 @@ 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,
|
||||
CAR.CHEVROLET_SILVERADO,
|
||||
CAR.CHEVROLET_EQUINOX,
|
||||
CAR.CHEVROLET_TRAILBLAZER,
|
||||
CAR.CHEVROLET_SUBURBAN_CAMERA,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
CAR.CHEVROLET_BLAZER,
|
||||
CAR.CHEVROLET_TRAX,
|
||||
@@ -563,7 +545,7 @@ CAMERA_ACC_CAR = {
|
||||
}
|
||||
|
||||
# Alt ASCMActiveCruiseControlStatus
|
||||
ALT_ACCS = {CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
|
||||
ALT_ACCS = {CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
|
||||
|
||||
# We're integrated at the Safety Data Gateway Module on these cars
|
||||
SDGM_CAR = {
|
||||
@@ -602,10 +584,9 @@ CC_REGEN_PADDLE_CAR = {
|
||||
}
|
||||
CAMERA_ACC_CAR.update(CC_ONLY_CAR)
|
||||
|
||||
# ASCM-intercept variants preserve stock ACC. SASCM (0x2FF) enables alpha-long where supported.
|
||||
# ASCM-INT paths are only enabled when SASCM (0x2FF) is detected at runtime
|
||||
ASCM_INT = {
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_SUBURBAN_ASCM,
|
||||
CAR.GMC_ACADIA_ASCM,
|
||||
CAR.CHEVROLET_MALIBU_ASCM,
|
||||
CAR.CADILLAC_ESCALADE_ASCM,
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import Any
|
||||
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.ford.values import CAR as FORD_CAR
|
||||
from opendbc.car.gm.values import CAR as GM_CAR
|
||||
|
||||
|
||||
CarGpsSample = dict[str, Any]
|
||||
@@ -91,53 +90,11 @@ def parse_ford_can_gps(nav1: Mapping[str, float], nav2: Mapping[str, float], nav
|
||||
}
|
||||
|
||||
|
||||
def parse_chevrolet_bolt_can_gps(position: Mapping[str, float]) -> CarGpsSample | None:
|
||||
"""Decode the Bolt's OnStar GPS position message."""
|
||||
try:
|
||||
latitude = float(position["GPSLatitude"]) / 3_600_000.0
|
||||
longitude = float(position["GPSLongitude"]) / 3_600_000.0
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return None
|
||||
|
||||
coordinates_valid = (
|
||||
math.isfinite(latitude) and math.isfinite(longitude) and
|
||||
-90.0 <= latitude <= 90.0 and -180.0 <= longitude <= 180.0 and
|
||||
(latitude != 0.0 or longitude != 0.0)
|
||||
)
|
||||
if not coordinates_valid:
|
||||
latitude = longitude = 0.0
|
||||
|
||||
return {
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"altitude": 0.0,
|
||||
"speed": 0.0,
|
||||
"bearingDeg": 0.0,
|
||||
"horizontalAccuracy": 6.0,
|
||||
"unixTimestampMillis": int(datetime.now(UTC).timestamp() * 1000),
|
||||
"verticalAccuracy": 10.0,
|
||||
"bearingAccuracyDeg": 180.0,
|
||||
"speedAccuracy": 0.5,
|
||||
"hasFix": coordinates_valid,
|
||||
"satelliteCount": 0,
|
||||
"vNED": [0.0, 0.0, 0.0],
|
||||
}
|
||||
|
||||
|
||||
FORD_MACH_E_GPS_MESSAGES = (
|
||||
"APIMGPS_Data_Nav_1_FD1",
|
||||
"APIMGPS_Data_Nav_2_FD1",
|
||||
"APIMGPS_Data_Nav_3_FD1",
|
||||
)
|
||||
CHEVROLET_BOLT_GPS_MESSAGES = ("TCICOnStarGPSPosition",)
|
||||
|
||||
CHEVROLET_BOLT_GPS_CARS = (
|
||||
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023,
|
||||
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
GM_CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
GM_CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
GM_CAR.CHEVROLET_BOLT_CC_2017,
|
||||
)
|
||||
|
||||
|
||||
CAR_GPS_CONFIGS: dict[str, CarGpsConfig] = {
|
||||
@@ -146,14 +103,6 @@ CAR_GPS_CONFIGS: dict[str, CarGpsConfig] = {
|
||||
messages=FORD_MACH_E_GPS_MESSAGES,
|
||||
decoder=parse_ford_can_gps,
|
||||
),
|
||||
**{
|
||||
car: CarGpsConfig(
|
||||
brand="gm",
|
||||
messages=CHEVROLET_BOLT_GPS_MESSAGES,
|
||||
decoder=parse_chevrolet_bolt_can_gps,
|
||||
)
|
||||
for car in CHEVROLET_BOLT_GPS_CARS
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,20 +23,6 @@ from openpilot.common.params import Params
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
|
||||
BOSCH_BRAKE_FORCE_ON = -0.12
|
||||
BOSCH_BRAKE_FORCE_RELEASE = -0.02
|
||||
|
||||
|
||||
def update_honda_bosch_braking(braking: bool, gas_pedal_force: float, stopping: bool, long_active: bool) -> bool:
|
||||
"""Select Bosch brake mode from the same road-load-adjusted force used for gas."""
|
||||
if not long_active:
|
||||
return False
|
||||
if stopping:
|
||||
return True
|
||||
if braking:
|
||||
return gas_pedal_force <= BOSCH_BRAKE_FORCE_RELEASE
|
||||
return gas_pedal_force < BOSCH_BRAKE_FORCE_ON
|
||||
|
||||
|
||||
def get_civic_bosch_modified_torque_lpf_tau(torque_cmd: float, prev_torque_cmd: float, v_ego: float) -> float:
|
||||
torque_delta = abs(float(torque_cmd) - float(prev_torque_cmd))
|
||||
@@ -252,7 +238,6 @@ class CarController(CarControllerBase):
|
||||
self.steering_pressed_filter_s = 0.0
|
||||
self.steering_pressed_robust_prev = False
|
||||
self.bosch_last_gas = 0.0
|
||||
self.bosch_braking = False
|
||||
self.bosch_gas_factor = self.param_store.get_float("HondaGasFactorParams", default=1.0)
|
||||
self.bosch_wind_factor = self.param_store.get_float("HondaWindFactorParams", default=1.0)
|
||||
self.bosch_wind_factor_before_brake = self.bosch_wind_factor
|
||||
@@ -487,16 +472,12 @@ class CarController(CarControllerBase):
|
||||
self.bosch_last_gas = self.gas
|
||||
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
bosch_braking = None
|
||||
if not self.mvl_accord_mode:
|
||||
self.bosch_braking = update_honda_bosch_braking(self.bosch_braking, gas_pedal_force, stopping, CC.longActive)
|
||||
bosch_braking = self.bosch_braking
|
||||
self.stopping_counter = self.stopping_counter + 1 if stopping else 0
|
||||
if not self.mvl_accord_mode or mvl_radar_owned:
|
||||
can_sends.extend(
|
||||
hondacan.create_acc_commands(
|
||||
self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas, self.stopping_counter, self.CP,
|
||||
gas_force=gas_pedal_force, braking=bosch_braking,
|
||||
gas_force=gas_pedal_force if self.mvl_accord_mode else None,
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -71,18 +71,16 @@ def create_brake_command(packer, CAN, apply_brake, pump_on, pcm_override, pcm_ca
|
||||
return packer.make_can_msg("BRAKE_COMMAND", CAN.pt, values)
|
||||
|
||||
|
||||
def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_counter, CP, gas_force=None, braking=None):
|
||||
def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_counter, CP, gas_force=None):
|
||||
commands = []
|
||||
min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0]
|
||||
|
||||
control_on = 5 if enabled else 0
|
||||
if gas_force is None:
|
||||
gas_force = accel
|
||||
if braking is None:
|
||||
braking = gas_force < min_gas_accel
|
||||
braking = int(active and braking)
|
||||
gas_command = gas if active and gas_force > min_gas_accel and not braking else -30000
|
||||
gas_command = gas if active and gas_force > min_gas_accel else -30000
|
||||
accel_command = accel if active else 0
|
||||
braking = 1 if active and gas_force < min_gas_accel else 0
|
||||
standstill = 1 if active and stopping_counter > 0 else 0
|
||||
standstill_release = 1 if active and stopping_counter == 0 else 0
|
||||
|
||||
|
||||
@@ -1107,14 +1107,6 @@ def test_crv_5g_bosch_a_radar_dbc_wired_for_parser_unit_tests():
|
||||
assert ri.rcp.bus == CanBus(cp).camera
|
||||
|
||||
|
||||
def test_accord_bosch_a_radar_stays_disabled_until_validated():
|
||||
cp = CarInterface.get_non_essential_params(CAR.HONDA_ACCORD)
|
||||
assert cp.radarUnavailable is True
|
||||
ri = CarInterface.RadarInterface(cp)
|
||||
assert ri.bosch_a_radar is False
|
||||
assert ri.rcp is None
|
||||
|
||||
|
||||
def test_civic_bosch_object_feed_uses_camera_side_acc_can():
|
||||
ri = make_radar_interface()
|
||||
can = CanBus(CP)
|
||||
@@ -1188,6 +1180,5 @@ def test_bosch_a_toggle_defaults_on_but_allowlist_still_gates_platforms():
|
||||
Params().remove("HondaBoschARadar")
|
||||
assert CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH).radarUnavailable is False
|
||||
assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD).radarUnavailable is True
|
||||
assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD_11G).radarUnavailable is True
|
||||
finally:
|
||||
Params().put_bool("HondaBoschARadar", original)
|
||||
|
||||
@@ -7,16 +7,13 @@ from opendbc.car.structs import CarParams
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.honda.interface import CarInterface
|
||||
from opendbc.car.honda.carcontroller import (
|
||||
BOSCH_BRAKE_FORCE_ON,
|
||||
BOSCH_BRAKE_FORCE_RELEASE,
|
||||
CarController,
|
||||
get_civic_bosch_modified_steering_pressed,
|
||||
get_civic_bosch_modified_torque_lpf_tau,
|
||||
get_honda_bosch_wind_brake_mps2,
|
||||
update_honda_bosch_braking,
|
||||
update_honda_bosch_live_learning,
|
||||
)
|
||||
from opendbc.car.honda.hondacan import create_acc_commands, create_lkas_hud
|
||||
from opendbc.car.honda.hondacan import create_lkas_hud
|
||||
from opendbc.car.honda.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.honda.values import CAR, DBC, HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL, CarControllerParams, HondaFlags, HondaSafetyFlags, \
|
||||
HondaStarPilotFlags
|
||||
@@ -29,67 +26,6 @@ def get_test_toggles() -> SimpleNamespace:
|
||||
|
||||
|
||||
class TestHondaFingerprint:
|
||||
@staticmethod
|
||||
def _acc_control_values(active, accel, gas=500, gas_force=0.5, braking=False):
|
||||
class FakePacker:
|
||||
@staticmethod
|
||||
def make_can_msg(name, bus, values):
|
||||
return name, bus, values
|
||||
|
||||
can = SimpleNamespace(pt=1)
|
||||
cp = SimpleNamespace(carFingerprint=CAR.HONDA_CRV_5G)
|
||||
commands = create_acc_commands(FakePacker(), can, True, active, accel, gas, 0, cp, gas_force, braking)
|
||||
assert commands[-1][0] == "ACC_CONTROL"
|
||||
return commands[-1][2]
|
||||
|
||||
def test_bosch_acc_commands_reject_fault_route_gas_brake_conflict(self):
|
||||
braking = update_honda_bosch_braking(False, 0.2, False, True)
|
||||
values = self._acc_control_values(True, -0.27, gas=160, gas_force=0.2, braking=braking)
|
||||
|
||||
assert values["GAS_COMMAND"] == 160
|
||||
assert values["ACCEL_COMMAND"] == pytest.approx(-0.27)
|
||||
assert values["BRAKE_REQUEST"] == 0
|
||||
assert values["BRAKE_LIGHTS"] == 0
|
||||
|
||||
@pytest.mark.parametrize("active", [False, True])
|
||||
@pytest.mark.parametrize("accel", [-3.5, -0.27, -0.2, -0.1, 0.0, 0.01, 2.0])
|
||||
@pytest.mark.parametrize("gas_force", [-0.5, 0.0, 0.5])
|
||||
@pytest.mark.parametrize("braking", [False, True])
|
||||
def test_bosch_acc_commands_never_request_gas_and_braking_together(self, active, accel, gas_force, braking):
|
||||
values = self._acc_control_values(active, accel, gas_force=gas_force, braking=braking)
|
||||
|
||||
assert not (values["GAS_COMMAND"] > 0 and values["BRAKE_REQUEST"] == 1)
|
||||
assert not (values["GAS_COMMAND"] > 0 and values["BRAKE_LIGHTS"] == 1)
|
||||
if values["GAS_COMMAND"] > 0:
|
||||
assert active
|
||||
|
||||
def test_bosch_acc_commands_preserve_road_load_gas_above_brake_threshold(self):
|
||||
values = self._acc_control_values(True, -0.27, gas=500, gas_force=0.3)
|
||||
|
||||
assert values["GAS_COMMAND"] == 500
|
||||
assert values["ACCEL_COMMAND"] == pytest.approx(-0.27)
|
||||
assert values["BRAKE_REQUEST"] == 0
|
||||
assert values["BRAKE_LIGHTS"] == 0
|
||||
|
||||
def test_bosch_acc_commands_do_not_send_gas_without_positive_force(self):
|
||||
values = self._acc_control_values(True, 0.2, gas=500, gas_force=-0.4)
|
||||
|
||||
assert values["GAS_COMMAND"] == -30000
|
||||
|
||||
def test_bosch_braking_uses_force_hysteresis(self):
|
||||
braking = update_honda_bosch_braking(False, BOSCH_BRAKE_FORCE_ON - 0.01, False, True)
|
||||
assert braking
|
||||
|
||||
braking = update_honda_bosch_braking(braking, -0.05, False, True)
|
||||
assert braking
|
||||
|
||||
braking = update_honda_bosch_braking(braking, BOSCH_BRAKE_FORCE_RELEASE + 0.01, False, True)
|
||||
assert not braking
|
||||
|
||||
def test_bosch_braking_preserves_stopping_and_resets_inactive(self):
|
||||
assert update_honda_bosch_braking(False, 0.5, True, True)
|
||||
assert not update_honda_bosch_braking(True, -1.0, False, False)
|
||||
|
||||
def test_honda_lkas_hud_shows_lane_lines_when_lateral_only_is_active(self):
|
||||
class FakePacker:
|
||||
@staticmethod
|
||||
|
||||
@@ -533,6 +533,9 @@ HONDA_BOSCH_ALT_RADAR = CAR.with_flags(HondaFlags.BOSCH_ALT_RADAR)
|
||||
# HondaBoschARadar. This describes hardware compatibility only; it is deliberately separate from the
|
||||
# verified set below so a newly supported model cannot start using unvalidated radar data by accident.
|
||||
HONDA_BOSCH_A = HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR
|
||||
# Add individual CAR entries only after the exact platform has a real capture and decoder replay
|
||||
# validation. The Civic and CR-V 5G captures both exercise the plain Bosch-A object bank; every
|
||||
# other Bosch-A variant remains disabled until it gets the same verification.
|
||||
HONDA_BOSCH_A_RADAR_VERIFIED = frozenset({CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G})
|
||||
HONDA_BOSCH_TJA_CONTROL = CAR.with_flags(HondaFlags.BOSCH_TJA_CONTROL)
|
||||
HONDA_CAMERA_MESSAGE_CARS = {
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
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, create_gas_interceptor_command, make_tester_present_msg, rate_limit, structs
|
||||
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs
|
||||
from opendbc.car.common.filter_simple import FirstOrderFilter
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_angle_limits_vm, common_fault_avoidance, get_max_angle_delta_vm, get_max_angle_vm
|
||||
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.lead_data import CanLeadDataState
|
||||
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, \
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_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
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_hyundai_canfd_scc_jerk_limits, shape_hyundai_canfd_scc_accel
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
@@ -28,9 +24,6 @@ LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
MAX_ANGLE = 85
|
||||
MAX_ANGLE_FRAMES = 89
|
||||
MAX_ANGLE_CONSECUTIVE_FRAMES = 2
|
||||
|
||||
CANCEL_BUTTON_DELAY_FRAMES = 10
|
||||
|
||||
CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000
|
||||
CANFD_CAMERA_LEAD_STALE_NS = 300_000_000
|
||||
CANFD_LEAD_MIN_DISTANCE = 0.1
|
||||
@@ -42,9 +35,6 @@ IONIQ_6_RESPONSE_MULTIPLIER = 1.2
|
||||
IONIQ_6_CANFD_SCC_ACCEL_STEP = (6.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
IONIQ_6_CANFD_SCC_DECEL_STEP = (15.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
EV9_CANFD_SCC_DECEL_STEP = 10.0 / 50.0
|
||||
RAY_PEDAL_COMMAND_CAP = 0.35 # Ray firmware voltage scaling is route-derived; validate before raising.
|
||||
RAY_PEDAL_RATE_UP = 0.012 # per 25 Hz command (0.30 normalized pedal per second)
|
||||
RAY_PEDAL_RATE_DOWN = 0.06
|
||||
GENESIS_G90_STOP_HOLD_SPEED_BP = [0.0, 0.03, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
|
||||
GENESIS_G90_STOP_HOLD_ACCEL_V = [-0.10, -0.10, -0.12, -0.18, -0.30, -0.50, -0.75, -1.00, -1.40, -1.80]
|
||||
GENESIS_G90_STOP_HOLD_RELAX_SPEED_BP = [0.0, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
|
||||
@@ -190,6 +180,13 @@ def should_use_ev6_gt_line_stop_direct_tracking(ev6_gt_line: bool, stopping: boo
|
||||
return bool(ev6_gt_line and stopping and v_ego > EV6_GT_LINE_STOP_BRAKE_CAP_MAX_SPEED and accel_cmd < actual_accel)
|
||||
|
||||
|
||||
def apply_carnival_steering_override(car_fingerprint, steering_pressed: bool,
|
||||
apply_steer_req: bool, apply_torque: int) -> tuple[bool, int]:
|
||||
if car_fingerprint == CAR.KIA_CARNIVAL_2025 and steering_pressed:
|
||||
return False, 0
|
||||
return apply_steer_req, apply_torque
|
||||
|
||||
|
||||
def update_ev9_longitudinal_tuning(state: EV9LongitudinalTuningState, enabled: bool,
|
||||
stopping: bool, v_ego: float) -> EV9LongitudinalTuningState:
|
||||
if not enabled:
|
||||
@@ -441,12 +438,6 @@ def suppress_redundant_gv70_brake_cancel(CP, brake_pressed: bool, lat_active: bo
|
||||
)
|
||||
|
||||
|
||||
def clear_ioniq_6_torque_when_request_inactive(CP, apply_torque: int, apply_steer_req: bool) -> int:
|
||||
if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and not apply_steer_req:
|
||||
return 0
|
||||
return apply_torque
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_names, CP):
|
||||
super().__init__(dbc_names, CP)
|
||||
@@ -464,7 +455,6 @@ class CarController(CarControllerBase):
|
||||
self.apply_angle_last = 0.0
|
||||
self.car_fingerprint = CP.carFingerprint
|
||||
self.last_button_frame = 0
|
||||
self.cancel_counter = 0
|
||||
self.redneck_button_frame = 0
|
||||
self.ecu_disable_failed = False
|
||||
self._ecu_disable_checked = False
|
||||
@@ -478,19 +468,9 @@ class CarController(CarControllerBase):
|
||||
self._ioniq_6_lane_change_ui_frames = 0
|
||||
self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState()
|
||||
self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState()
|
||||
self._can_lead_data = CanLeadDataState()
|
||||
self._dash_lat_disengage_blink_frame = 0
|
||||
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
|
||||
self._ray_pedal = CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED
|
||||
self._ray_pedal_packer = CANPacker("hyundai_kia_ray_pedal") if self._ray_pedal else None
|
||||
self._ray_pedal_gas_last = 0.0
|
||||
|
||||
def _update_dash_icon_state(self, CC):
|
||||
if CC.latActive:
|
||||
@@ -511,9 +491,7 @@ class CarController(CarControllerBase):
|
||||
return lka_icon, lfa_icon
|
||||
|
||||
def _get_canfd_scc_lead_state(self, CC, CS, now_nanos):
|
||||
openpilot_lead_visible = bool(
|
||||
getattr(CS, "openpilot_lead_visible", False) or getattr(CC.hudControl, "leadVisible", False)
|
||||
)
|
||||
openpilot_lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or CC.hudControl.leadVisible)
|
||||
openpilot_lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
|
||||
openpilot_lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -16.4, 34.7))
|
||||
stock_camera_lead_fresh = now_nanos - getattr(CS, "stock_camera_lead_ts", 0) <= CANFD_CAMERA_LEAD_STALE_NS
|
||||
@@ -642,7 +620,9 @@ class CarController(CarControllerBase):
|
||||
if not CC.latActive:
|
||||
apply_torque = 0
|
||||
|
||||
apply_torque = clear_ioniq_6_torque_when_request_inactive(self.CP, apply_torque, apply_steer_req)
|
||||
apply_steer_req, apply_torque = apply_carnival_steering_override(
|
||||
self.CP.carFingerprint, CS.out.steeringPressed, apply_steer_req, apply_torque,
|
||||
)
|
||||
|
||||
# Hold torque with induced temporary fault when cutting the actuation bit
|
||||
# FIXME: we don't use this with CAN FD?
|
||||
@@ -739,8 +719,6 @@ class CarController(CarControllerBase):
|
||||
if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS:
|
||||
can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True))
|
||||
|
||||
self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0
|
||||
|
||||
# *** CAN/CAN FD specific ***
|
||||
if self.CP.flags & HyundaiFlags.CANFD:
|
||||
can_sends.extend(self.create_canfd_msgs(now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel,
|
||||
@@ -766,14 +744,6 @@ 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))
|
||||
lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or getattr(hud_control, "leadVisible", False))
|
||||
lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
|
||||
lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -170.0, 239.5))
|
||||
if lead_visible and lead_distance <= CANFD_LEAD_MIN_DISTANCE:
|
||||
lead_distance = CANFD_FALLBACK_LEAD_DISTANCE
|
||||
lead_rel_speed = 0.0
|
||||
lead_data = self._can_lead_data.update(lead_distance, lead_rel_speed, lead_visible)
|
||||
|
||||
# HUD messages
|
||||
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
|
||||
@@ -782,8 +752,6 @@ 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,
|
||||
lka_icon=lka_icon,
|
||||
longitudinal_active=longitudinal_active,
|
||||
))
|
||||
if self.long_active_ecu:
|
||||
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(
|
||||
@@ -793,7 +761,6 @@ class CarController(CarControllerBase):
|
||||
left_lane_warning, right_lane_warning, CS.msg_364,
|
||||
include_alerts=False,
|
||||
counter_mod=0xF,
|
||||
fcw_opt_usm=2 if apply_steer_req or lka_icon == 3 else 1,
|
||||
))
|
||||
if self.frame % 5 == 0:
|
||||
can_sends.append(hyundaicanfd.create_suppress_lfa(
|
||||
@@ -805,25 +772,16 @@ class CarController(CarControllerBase):
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
left_lane_warning, right_lane_warning, CS.msg_364))
|
||||
else:
|
||||
if self.CP.carFingerprint != CAR.KIA_RAY_EV or self._ray_lkas11_active:
|
||||
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req,
|
||||
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
left_lane_warning, right_lane_warning, lka_icon))
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
self._ray_lkas11_active = True
|
||||
if getattr(self.FPCP, "flags", 0) & HyundaiStarPilotFlags.HAS_LKAS12:
|
||||
can_sends.append(hyundaican.create_lkas12(self.packer, CS.lkas12))
|
||||
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req,
|
||||
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
left_lane_warning, right_lane_warning, lka_icon))
|
||||
|
||||
# Button messages
|
||||
if not self.long_active_ecu:
|
||||
if self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
|
||||
if CC.cruiseControl.cancel:
|
||||
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
|
||||
elif self._ray_pedal and CC.longActive and CS.out.cruiseState.enabled:
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
|
||||
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
|
||||
self.last_button_frame = self.frame
|
||||
elif CC.cruiseControl.resume and not self._ray_pedal:
|
||||
elif CC.cruiseControl.resume:
|
||||
# send resume at a max freq of 10Hz
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
|
||||
# send 25 messages at a time to increases the likelihood of resume being accepted
|
||||
@@ -831,24 +789,7 @@ class CarController(CarControllerBase):
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
|
||||
self.last_button_frame = self.frame
|
||||
else:
|
||||
if not self._ray_pedal:
|
||||
can_sends.extend(self._create_can_redneck_button_messages(CS))
|
||||
|
||||
if self._ray_pedal and self.frame % 4 == 0:
|
||||
pedal_ready = CS.ray_pedal_valid and CS.ray_pedal_state == 0
|
||||
pedal_active = (CC.longActive and pedal_ready and not CC.cruiseControl.override and
|
||||
not CS.out.gasPressed and not CS.out.brakePressed and
|
||||
not CS.out.cruiseState.enabled and CS.out.vEgo >= self.CP.minEnableSpeed)
|
||||
if pedal_active:
|
||||
target = float(np.clip(accel / CarControllerParams.ACCEL_MAX * RAY_PEDAL_COMMAND_CAP,
|
||||
0.0, RAY_PEDAL_COMMAND_CAP))
|
||||
self._ray_pedal_gas_last = rate_limit(
|
||||
target, self._ray_pedal_gas_last, -RAY_PEDAL_RATE_DOWN, RAY_PEDAL_RATE_UP,
|
||||
)
|
||||
else:
|
||||
self._ray_pedal_gas_last = 0.0
|
||||
can_sends.append(create_gas_interceptor_command(
|
||||
self._ray_pedal_packer, self._ray_pedal_gas_last, (self.frame // 4) & 0xF))
|
||||
can_sends.extend(self._create_can_redneck_button_messages(CS))
|
||||
|
||||
if self.long_active_ecu and can_canfd_blended:
|
||||
if blended_hda2:
|
||||
@@ -876,14 +817,11 @@ class CarController(CarControllerBase):
|
||||
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
|
||||
hud_control, set_speed_in_units, stopping,
|
||||
CC.cruiseControl.override, use_fca, self.CP,
|
||||
main_cruise_enabled, lead_data))
|
||||
main_cruise_enabled))
|
||||
|
||||
# 20 Hz LFA MFA message
|
||||
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)):
|
||||
if self._ray_lfa_8byte:
|
||||
can_sends.append(hyundaican.create_ray_lfahda_mfc(self._ray_lfa_packer, CC.latActive, lfa_icon))
|
||||
else:
|
||||
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon))
|
||||
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon))
|
||||
|
||||
# 5 Hz ACC options
|
||||
if self.frame % 20 == 0 and self.long_active_ecu and not can_canfd_blended:
|
||||
@@ -900,14 +838,7 @@ class CarController(CarControllerBase):
|
||||
can_sends = []
|
||||
|
||||
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
|
||||
persistent_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 persistent_lfa_status_cars else self.long_active_ecu
|
||||
lka_steering_long = lka_steering and lfa_longitudinal_active
|
||||
lka_steering_long = lka_steering and self.long_active_ecu
|
||||
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)
|
||||
@@ -918,6 +849,9 @@ class CarController(CarControllerBase):
|
||||
)
|
||||
|
||||
# steering control
|
||||
# The first-generation Electrified GV70 expects the synthesized LKAS status
|
||||
# payload. Forwarding its stock status bits leaves lane-safety state asserted
|
||||
# while StarPilot is suppressing the stock LFA path.
|
||||
preserve_stock_lkas = bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) and \
|
||||
not self.long_active_ecu and self.CP.carFingerprint != CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and \
|
||||
preserve_stock_canfd_lkas_status(self.CP.carFingerprint)
|
||||
@@ -933,10 +867,10 @@ class CarController(CarControllerBase):
|
||||
|
||||
gear = getattr(getattr(CS, "out", None), "gearShifter", None)
|
||||
drive_gear = gear == structs.CarState.GearShifter.drive
|
||||
if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026:
|
||||
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 = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and angle_lkas_alt and (
|
||||
forward_stock_lkas = 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)
|
||||
@@ -945,8 +879,7 @@ 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,
|
||||
longitudinal_active=lfa_longitudinal_active))
|
||||
lka_icon=lka_icon))
|
||||
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,
|
||||
@@ -963,7 +896,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
# prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU
|
||||
suppress_lfa = bool(lka_steering)
|
||||
if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026:
|
||||
if angle_lkas_alt:
|
||||
suppress_lfa = bool(lka_steering and drive_gear and (CC.latActive or (ccnc_angle_long and CC.enabled)))
|
||||
if self.frame % 5 == 0 and suppress_lfa:
|
||||
can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg,
|
||||
@@ -1033,14 +966,12 @@ class CarController(CarControllerBase):
|
||||
left_sound_active=left_warning.sound_active, right_sound_active=right_warning.sound_active,
|
||||
)
|
||||
else:
|
||||
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame,
|
||||
car_fingerprint=self.CP.carFingerprint,
|
||||
drive_gear=drive_gear)
|
||||
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)
|
||||
can_sends.extend(adrv_messages)
|
||||
# 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_ECU_KEEPALIVE_CAR and self.frame % radar_heartbeat_step == 0:
|
||||
if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_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))
|
||||
@@ -1064,23 +995,10 @@ class CarController(CarControllerBase):
|
||||
CC.leftBlinker,
|
||||
CC.rightBlinker))
|
||||
if self.frame % 2 == 0:
|
||||
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
|
||||
if self.CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN:
|
||||
scc_jerk_limits = get_hyundai_canfd_scc_jerk_limits(self.CP, stopping, accel)
|
||||
raw_accel = accel
|
||||
accel = shape_hyundai_canfd_scc_accel(
|
||||
self.CP, CC.enabled, CC.cruiseControl.override, stopping, accel, self.accel_last,
|
||||
)
|
||||
acc_kwargs = {
|
||||
"direct_accel": True,
|
||||
"raw_accel": raw_accel,
|
||||
"jerk_upper": scc_jerk_limits[0],
|
||||
"jerk_lower": scc_jerk_limits[1],
|
||||
"lead_distance": lead_distance,
|
||||
"lead_rel_speed": lead_rel_speed,
|
||||
"lead_visible": lead_visible,
|
||||
}
|
||||
acc_kwargs = {}
|
||||
else:
|
||||
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
|
||||
acc_kwargs = {
|
||||
"main_mode_acc": int(CS.out.cruiseState.available),
|
||||
"direct_accel": True,
|
||||
@@ -1128,7 +1046,7 @@ class CarController(CarControllerBase):
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
|
||||
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info))
|
||||
self.last_button_frame = self.frame
|
||||
elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
|
||||
else:
|
||||
for _ in range(20):
|
||||
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL))
|
||||
self.last_button_frame = self.frame
|
||||
|
||||
@@ -2,8 +2,6 @@ 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
|
||||
@@ -38,8 +36,6 @@ CLASSIC_MEDIA_BUTTON_CARS = frozenset({
|
||||
|
||||
|
||||
def get_non_scc_cruise_signals(CP) -> tuple[str, str, str, str, str, str]:
|
||||
if CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
return "LABEL11", "CC_React", "LABEL11", "CC_Engaged", "E_EMS11", "Cruise_Limit_Target"
|
||||
if CP.flags & HyundaiFlags.EV:
|
||||
return "LABEL11", "CC_React", "EMS12", "ACC_ACT", "E_EMS11", "Cruise_Limit_Target"
|
||||
if CP.flags & HyundaiFlags.HYBRID:
|
||||
@@ -138,9 +134,6 @@ class CarState(CarStateBase):
|
||||
self.buttons_counter = 0
|
||||
self.main_cruise_on = False
|
||||
self.main_cruise_tracking = bool(getattr(FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
|
||||
if CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
self.ray_pedal_state = 5
|
||||
self.ray_pedal_valid = False
|
||||
|
||||
self.cruise_info = {}
|
||||
self.msg_161 = {}
|
||||
@@ -149,7 +142,6 @@ class CarState(CarStateBase):
|
||||
self.msg_364 = {}
|
||||
self.lfa_block_msg = {}
|
||||
self.stock_lkas_msg = {}
|
||||
self.lkas12 = {}
|
||||
self.stock_lfa_msg = {}
|
||||
self.stock_lfahda_cluster_msg = {}
|
||||
self.stock_camera_lead_visible = False
|
||||
@@ -181,10 +173,8 @@ class CarState(CarStateBase):
|
||||
# Main button also can trigger an engagement on these cars
|
||||
return any(btn in ENABLE_BUTTONS for btn in self.cruise_buttons) or any(self.main_buttons)
|
||||
|
||||
def update_main_cruise(self, ret: structs.CarState,
|
||||
button_events: list[structs.CarState.ButtonEvent] | None = None) -> bool:
|
||||
button_events = ret.buttonEvents if button_events is None else button_events
|
||||
if any(be.type == ButtonType.mainCruise and be.pressed for be in button_events):
|
||||
def update_main_cruise(self, ret: structs.CarState) -> bool:
|
||||
if any(be.type == ButtonType.mainCruise and be.pressed for be in ret.buttonEvents):
|
||||
self.main_cruise_on = not self.main_cruise_on
|
||||
|
||||
return bool(ret.cruiseState.available and self.main_cruise_on)
|
||||
@@ -250,20 +240,10 @@ class CarState(CarStateBase):
|
||||
return button_events
|
||||
|
||||
def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]:
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
self.lda_button = int(cp.vl["BCM_PO_11"]["RAY_LKAS_BTN"] != 0) \
|
||||
if cp.ts_nanos["BCM_PO_11"]["RAY_LKAS_BTN"] > 0 else 0
|
||||
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
|
||||
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0
|
||||
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
|
||||
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
|
||||
elif self.CP.carFingerprint == CAR.HYUNDAI_ELANTRA_HEV_2024:
|
||||
lda_samples = [
|
||||
*cp.vl_all["CLU13"]["CF_Clu_LdwsLkasSW"],
|
||||
*cp.vl_all["BCM_PO_11"]["LDA_BTN"],
|
||||
]
|
||||
if lda_samples:
|
||||
self.lda_button = int(any(lda_samples))
|
||||
else:
|
||||
source_states = (
|
||||
int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"]) if cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0 else 0,
|
||||
@@ -303,7 +283,6 @@ class CarState(CarStateBase):
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
cp_alt = can_parsers.get(Bus.alt)
|
||||
cp_pedal = can_parsers.get(Bus.party)
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CANFD:
|
||||
return self.update_canfd(can_parsers)
|
||||
@@ -348,15 +327,6 @@ class CarState(CarStateBase):
|
||||
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5)
|
||||
ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0
|
||||
|
||||
prev_cruise_buttons = self.cruise_buttons[-1]
|
||||
prev_main_buttons = self.main_buttons[-1]
|
||||
prev_lda_button = self.lda_button
|
||||
main_button_events = []
|
||||
if self.main_cruise_tracking:
|
||||
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
|
||||
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
|
||||
main_button_events = create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise})
|
||||
|
||||
# cruise state
|
||||
no_scc = bool(self.CP.flags & HyundaiFlags.NON_SCC)
|
||||
if no_scc:
|
||||
@@ -380,9 +350,6 @@ class CarState(CarStateBase):
|
||||
ret.cruiseState.nonAdaptive = cp_cruise.vl[scc_msg]["SCCInfoDisplay"] == 2. # Shows 'Cruise Control' on dash
|
||||
ret.cruiseState.speed = cp_cruise.vl[scc_msg]["VSetDis"] * speed_conv
|
||||
|
||||
if self.CP.openpilotLongitudinalControl and self.main_cruise_tracking:
|
||||
ret.cruiseState.available = self.update_main_cruise(ret, main_button_events)
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
self.lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x2a4"])
|
||||
@@ -397,11 +364,6 @@ class CarState(CarStateBase):
|
||||
ret.espDisabled = cp.vl["TCS11"]["TCS_PAS"] == 1
|
||||
ret.espActive = cp.vl["TCS11"]["ABS_ACT"] == 1
|
||||
ret.accFaulted = False if no_scc else cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED:
|
||||
self.ray_pedal_valid = bool(cp_pedal is not None and cp_pedal.can_valid and
|
||||
cp_pedal.ts_nanos["GAS_SENSOR"]["STATE"] > 0)
|
||||
self.ray_pedal_state = int(cp_pedal.vl["GAS_SENSOR"]["STATE"]) if cp_pedal is not None else 5
|
||||
ret.accFaulted = not self.ray_pedal_valid or self.ray_pedal_state != 0
|
||||
|
||||
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV | HyundaiFlags.FCEV):
|
||||
if self.CP.flags & HyundaiFlags.FCEV:
|
||||
@@ -413,12 +375,6 @@ class CarState(CarStateBase):
|
||||
else:
|
||||
ret.gasPressed = bool(cp.vl["EMS16"]["CF_Ems_AclAct"])
|
||||
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED and self.ray_pedal_valid:
|
||||
driver_pedal = cp_pedal.vl_raw["GAS_SENSOR"]
|
||||
track1 = int.from_bytes(driver_pedal[:2], "big")
|
||||
track2 = int.from_bytes(driver_pedal[2:4], "big")
|
||||
ret.gasPressed = track1 > 272 or track2 > 513
|
||||
|
||||
# Gear Selection via Cluster - For those Kia/Hyundai which are not fully discovered, we can use the Cluster Indicator for Gear Selection,
|
||||
# as this seems to be standard over all cars, but is not the preferred method.
|
||||
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV):
|
||||
@@ -461,22 +417,21 @@ class CarState(CarStateBase):
|
||||
self.lkas11 = {}
|
||||
else:
|
||||
self.lkas11 = copy.copy(cp_cam.vl["LKAS11"])
|
||||
if getattr(self.FPCP, "flags", 0) & HyundaiStarPilotFlags.HAS_LKAS12:
|
||||
self.lkas12 = copy.copy(cp_cam.vl["LKAS12"])
|
||||
self.clu11 = copy.copy(cp.vl["CLU11"])
|
||||
self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE
|
||||
if not self.main_cruise_tracking:
|
||||
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
|
||||
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
|
||||
main_button_events = create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise})
|
||||
prev_cruise_buttons = self.cruise_buttons[-1]
|
||||
prev_main_buttons = self.main_buttons[-1]
|
||||
prev_lda_button = self.lda_button
|
||||
lkas_button_events = []
|
||||
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
|
||||
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
|
||||
if self.CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS and cp_alt is not None and self.get_alt_bus_lda_button_raw_state(cp_alt)[1] > 0:
|
||||
lkas_button_events = self.create_alt_bus_lda_button_events(cp_alt)
|
||||
else:
|
||||
lkas_button_events = self.create_lkas_button_events(cp, prev_lda_button)
|
||||
|
||||
ret.buttonEvents = [*self.create_cruise_button_events(self.cruise_buttons[-1], prev_cruise_buttons),
|
||||
*main_button_events,
|
||||
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
|
||||
*lkas_button_events]
|
||||
|
||||
ret.blockPcmEnable = not self.recent_button_interaction()
|
||||
@@ -745,12 +700,6 @@ class CarState(CarStateBase):
|
||||
("BCM_PO_11", 0),
|
||||
("CLU13", 0),
|
||||
]
|
||||
if CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
msgs += [
|
||||
("LABEL11", 10),
|
||||
("E_EMS11", 100),
|
||||
("ELECT_GEAR", 100),
|
||||
]
|
||||
if CP.carFingerprint in CLASSIC_MEDIA_BUTTON_CARS:
|
||||
# Steering-wheel media switches are event-driven on the refresh Elantra.
|
||||
msgs.append(("GW_SWRC_PE", 0))
|
||||
@@ -759,10 +708,8 @@ class CarState(CarStateBase):
|
||||
|
||||
parsers = {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], msgs, 0),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS12", 0)], 2),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2),
|
||||
}
|
||||
if CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS:
|
||||
parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1)
|
||||
if CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
|
||||
parsers[Bus.party] = CANParser("hyundai_kia_ray_pedal", [("GAS_SENSOR", 50)], 0)
|
||||
return parsers
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
""" 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
|
||||
|
||||
@@ -185,7 +183,6 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00DN ESC \x01 102\x19\x04\x13 58910-L1300',
|
||||
b'\xf1\x00DN ESC \x01 107 \x07\x03 58910-L1300',
|
||||
b'\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300',
|
||||
b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100',
|
||||
b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100',
|
||||
@@ -209,7 +206,6 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102',
|
||||
b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1010 4DNDC103',
|
||||
b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1030 4DNDC103',
|
||||
b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1210 4DNDC103',
|
||||
b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100',
|
||||
b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101',
|
||||
b'\xf1\x00DN8 MDPS R 1.00 1.02 57700-L1000 4DNDP105',
|
||||
@@ -217,7 +213,6 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422',
|
||||
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.04 99211-L1000 191016',
|
||||
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.06 99211-L1000 210325',
|
||||
b'\xf1\x00DN8 MFC AT RUS LHD 1.00 1.03 99211-L1000 190705',
|
||||
b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.00 99211-L0000 190716',
|
||||
b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.01 99211-L0000 191016',
|
||||
@@ -1701,9 +1696,4 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00BC3 LKA AT EUR LHD 1.00 1.01 99211-Q0100 261',
|
||||
],
|
||||
},
|
||||
CAR.KIA_RAY_EV: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00TAM MFC AT KOR LHD 1.00 1.02 99211-E2000 230901',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import crcmod
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.lead_data import CanLeadData
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
|
||||
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
|
||||
@@ -41,7 +40,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
CAR.HYUNDAI_ELANTRA_HEV_2021, CAR.HYUNDAI_SONATA_HYBRID, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022,
|
||||
CAR.HYUNDAI_SANTA_FE_2022, CAR.KIA_K5_2021, CAR.HYUNDAI_IONIQ_HEV_2022, CAR.HYUNDAI_SANTA_FE_HEV_2022,
|
||||
CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, CAR.KIA_XCEED_PHEV,
|
||||
CAR.HYUNDAI_AZERA_6TH_GEN, CAR.HYUNDAI_AZERA_HEV_6TH_GEN, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.HYUNDAI_KONA_2022, CAR.KIA_RAY_EV,
|
||||
CAR.HYUNDAI_AZERA_6TH_GEN, CAR.HYUNDAI_AZERA_HEV_6TH_GEN, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.HYUNDAI_KONA_2022,
|
||||
CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024):
|
||||
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
|
||||
values["CF_Lkas_LdwsOpt_USM"] = 2
|
||||
@@ -52,7 +51,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
# FcwOpt_USM 2 = Green car + lanes
|
||||
# FcwOpt_USM 1 = White car + lanes
|
||||
# FcwOpt_USM 0 = No car + lanes
|
||||
values["CF_Lkas_FcwOpt_USM"] = lka_icon
|
||||
values["CF_Lkas_FcwOpt_USM"] = lka_icon if CP.carFingerprint == CAR.GENESIS_G70_2020 else 2 if enabled else 1
|
||||
|
||||
# SysWarning 4 = keep hands on wheel
|
||||
# SysWarning 5 = keep hands on wheel (red)
|
||||
@@ -61,7 +60,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
|
||||
|
||||
# Likely cars lacking the ability to show individual lane lines in the dash
|
||||
elif CP.carFingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.HYUNDAI_KONA_NON_SCC):
|
||||
elif CP.carFingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL):
|
||||
# SysWarning 4 = keep hands on wheel + beep
|
||||
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
|
||||
|
||||
@@ -69,7 +68,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
# SysState 1-2 = white car + lanes
|
||||
# SysState 3 = green car + lanes, green steering wheel
|
||||
# SysState 4 = green car + lanes
|
||||
values["CF_Lkas_LdwsSysState"] = lka_icon if CP.carFingerprint == CAR.HYUNDAI_KONA_NON_SCC else 3 if enabled else 1
|
||||
values["CF_Lkas_LdwsSysState"] = 3 if enabled else 1
|
||||
values["CF_Lkas_LdwsOpt_USM"] = 2 # non-2 changes above SysState definition
|
||||
|
||||
# these have no effect
|
||||
@@ -81,14 +80,6 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
# Genesis and Optima fault when forwarding while engaged
|
||||
values["CF_Lkas_LdwsActivemode"] = 2
|
||||
|
||||
if CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
if not enabled:
|
||||
values["CF_Lkas_LdwsActivemode"] = lkas11["CF_Lkas_LdwsActivemode"]
|
||||
values["CF_Lkas_LdwsSysState"] = lkas11["CF_Lkas_LdwsSysState"]
|
||||
values["CF_Lkas_FcwOpt_USM"] = lkas11["CF_Lkas_FcwOpt_USM"]
|
||||
values["CF_Lkas_LdwsOpt_USM"] = 0
|
||||
values["CF_Lkas_Chksum"] = 0
|
||||
|
||||
dat = packer.make_can_msg("LKAS11", 0, values)[1]
|
||||
|
||||
if CP.flags & HyundaiFlags.CHECKSUM_CRC8:
|
||||
@@ -107,19 +98,6 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
return packer.make_can_msg("LKAS11", 0, values)
|
||||
|
||||
|
||||
def create_lkas12(packer, lkas12):
|
||||
values = {s: lkas12[s] for s in (
|
||||
"CF_Lkas_TsrSlifOpt",
|
||||
"CF_LkasTsrStatus",
|
||||
"CF_Lkas_TsrSpeed_Display_Clu",
|
||||
"CF_LkasTsrSpeed_Display_Navi",
|
||||
"CF_Lkas_TsrAddinfo_Display",
|
||||
"CF_Lkas_Daw_USM",
|
||||
) if s in lkas12}
|
||||
values["CF_LkasDawStatus"] = 0
|
||||
return packer.make_can_msg("LKAS12", 0, values)
|
||||
|
||||
|
||||
def create_checksum_can_canfd_blended(packer, bus, addr, values):
|
||||
dat = packer.make_can_msg(addr, bus, values)[1]
|
||||
return hyundai_checksum(dat[1:8])
|
||||
@@ -129,13 +107,13 @@ def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
|
||||
torque_fault, lkas11, sys_warning, sys_state, enabled,
|
||||
left_lane, right_lane,
|
||||
left_lane_depart, right_lane_depart, msg_364,
|
||||
include_alerts=True, counter_mod=0x10, fcw_opt_usm=None):
|
||||
include_alerts=True, counter_mod=0x10):
|
||||
bus = CanBus(CP).ECAN
|
||||
values = {
|
||||
"CF_Lkas_LdwsActivemode": int(left_lane) + (int(right_lane) << 1),
|
||||
"CF_Lkas_LdwsLHWarning": left_lane_depart,
|
||||
"CF_Lkas_LdwsRHWarning": right_lane_depart,
|
||||
"CF_Lkas_FcwOpt_USM": (2 if enabled else 1) if fcw_opt_usm is None else fcw_opt_usm,
|
||||
"CF_Lkas_FcwOpt_USM": 2 if enabled else 1,
|
||||
"CR_Lkas_StrToqReq": apply_steer,
|
||||
"CF_Lkas_ActToi": steer_req,
|
||||
"CF_Lkas_ToiFlt": torque_fault,
|
||||
@@ -204,17 +182,6 @@ 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 = []
|
||||
@@ -318,20 +285,19 @@ def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_las
|
||||
|
||||
|
||||
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP,
|
||||
main_cruise_enabled=True, lead_data: CanLeadData | None = None):
|
||||
main_cruise_enabled=True):
|
||||
commands = []
|
||||
lead_data = lead_data or CanLeadData()
|
||||
|
||||
scc11_values = {
|
||||
"MainMode_ACC": int(bool(main_cruise_enabled)),
|
||||
"TauGapSet": hud_control.leadDistanceBars,
|
||||
"VSetDis": set_speed if enabled else 0,
|
||||
"AliveCounterACC": idx % 0x10,
|
||||
"ObjValid": int(lead_data.lead_visible),
|
||||
"ACC_ObjStatus": int(lead_data.lead_visible),
|
||||
"ObjValid": 1, # close lead makes controls tighter
|
||||
"ACC_ObjStatus": 1, # close lead makes controls tighter
|
||||
"ACC_ObjLatPos": 0,
|
||||
"ACC_ObjRelSpd": lead_data.lead_rel_speed,
|
||||
"ACC_ObjDist": int(lead_data.lead_distance),
|
||||
"ACC_ObjRelSpd": 0,
|
||||
"ACC_ObjDist": 1, # close lead makes controls tighter
|
||||
}
|
||||
commands.append(packer.make_can_msg("SCC11", 0, scc11_values))
|
||||
|
||||
@@ -359,8 +325,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
|
||||
"JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values
|
||||
"JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values
|
||||
"ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
|
||||
"ObjGap": lead_data.object_gap, # 5: >30 m, 4: 25-30 m, 3: 20-25 m, 2: <20 m, 0: no lead
|
||||
"ObjDistStat": lead_data.object_rel_gap,
|
||||
"ObjGap": 2 if hud_control.leadVisible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
|
||||
}
|
||||
commands.append(packer.make_can_msg("SCC14", 0, scc14_values))
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
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
|
||||
@@ -8,34 +6,6 @@ from opendbc.car.crc import CRC16_XMODEM
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CANFD_ALT_BUTTONS_RESUME_CAR
|
||||
|
||||
|
||||
_adrv_0x51_templates: dict[CAR, bytes] = {}
|
||||
|
||||
|
||||
def cache_adrv_0x51_template(car_fingerprint: CAR, dat: bytes | None) -> None:
|
||||
if car_fingerprint != CAR.KIA_EV6:
|
||||
return
|
||||
|
||||
if dat is None:
|
||||
_adrv_0x51_templates.pop(car_fingerprint, None)
|
||||
elif len(dat) == 32 and any(dat[3:]):
|
||||
_adrv_0x51_templates[car_fingerprint] = bytes(dat)
|
||||
|
||||
|
||||
def create_adrv_0x51(packer, CAN, frame: int, car_fingerprint: CAR | None = None, drive_gear: bool = False):
|
||||
template = _adrv_0x51_templates.get(car_fingerprint)
|
||||
if template is None:
|
||||
return packer.make_can_msg("ADRV_0x51", CAN.ACAN, {})
|
||||
|
||||
# EV6 MRR30 tracks stop when the ADAS takeover replaces this platform payload with zeros.
|
||||
dat = bytearray(template)
|
||||
dat[2] = (template[2] + frame + 1) & 0xFF
|
||||
dat[3] = (dat[3] & ~0x1) | int(drive_gear)
|
||||
crc = hkg_can_fd_checksum(0x51, None, dat)
|
||||
dat[0] = crc & 0xFF
|
||||
dat[1] = (crc >> 8) & 0xFF
|
||||
return CanData(0x51, bytes(dat), CAN.ACAN)
|
||||
|
||||
|
||||
def _set_value(msg: bytearray, sig, ival: int) -> None:
|
||||
i = sig.lsb // 8
|
||||
bits = sig.size
|
||||
@@ -93,6 +63,61 @@ def _update_checksum(packer, address: int, dat: bytearray) -> None:
|
||||
_set_value(dat, sig_checksum, checksum)
|
||||
|
||||
|
||||
def _set_little_endian_bits(dat: bytearray, lsb: int, size: int, value: int) -> None:
|
||||
"""Write the legacy HDA-II field layout without changing the generated DBC aliases."""
|
||||
value &= (1 << size) - 1
|
||||
bit = lsb
|
||||
remaining = size
|
||||
while remaining:
|
||||
byte = bit // 8
|
||||
shift = bit % 8
|
||||
chunk_size = min(remaining, 8 - shift)
|
||||
mask = ((1 << chunk_size) - 1) << shift
|
||||
dat[byte] = (dat[byte] & ~mask) | ((value & ((1 << chunk_size) - 1)) << shift)
|
||||
value >>= chunk_size
|
||||
bit += chunk_size
|
||||
remaining -= chunk_size
|
||||
|
||||
|
||||
def _create_gv70_lka_status_msg(packer, CAN, message_name: str, bus: int, enabled: bool,
|
||||
lat_active: bool, apply_torque: int):
|
||||
values = {
|
||||
"LKA_MODE": 2,
|
||||
"LKA_ICON": 2 if enabled else 1,
|
||||
"TORQUE_REQUEST": apply_torque,
|
||||
"STEER_REQ": 1 if lat_active else 0,
|
||||
"LKA_ASSIST": 0,
|
||||
"STEER_MODE": 0,
|
||||
"DAMP_FACTOR": 100,
|
||||
}
|
||||
address, raw, _ = packer.make_can_msg(message_name, bus, values)
|
||||
dat = bytearray(raw)
|
||||
|
||||
legacy_fields = (
|
||||
(24, 3, 2),
|
||||
(27, 3, 0),
|
||||
(30, 2, 0),
|
||||
(32, 2, 0),
|
||||
(34, 2, 0),
|
||||
(36, 2, 0),
|
||||
(38, 3, 2 if enabled else 1),
|
||||
(52, 2, 1 if lat_active else 0),
|
||||
(54, 2, 0),
|
||||
(56, 1, 0),
|
||||
(60, 4, 0),
|
||||
(80, 2, 0),
|
||||
)
|
||||
for lsb, size, value in legacy_fields:
|
||||
_set_little_endian_bits(dat, lsb, size, value)
|
||||
|
||||
_set_little_endian_bits(dat, 64 if message_name == "LKAS" else 104, 8, 100)
|
||||
if message_name == "LKAS":
|
||||
_set_little_endian_bits(dat, 84, 3, 0)
|
||||
|
||||
_update_checksum(packer, address, dat)
|
||||
return address, bytes(dat), bus
|
||||
|
||||
|
||||
def _create_angle_lfa_msg(packer, CAN, values, apply_angle: float, lat_active: bool, torque_reduction_gain: float):
|
||||
address = packer.dbc.name_to_msg["LFA"].address
|
||||
dat = packer.pack(address, values)
|
||||
@@ -127,12 +152,16 @@ 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,
|
||||
longitudinal_active=None):
|
||||
lfa_base_values=None, lkas_base_values=None, lka_icon=None):
|
||||
if lka_icon is None:
|
||||
lka_icon = 2 if enabled else 1
|
||||
if longitudinal_active is None:
|
||||
longitudinal_active = CP.openpilotLongitudinalControl
|
||||
|
||||
if CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
ret = []
|
||||
if CP.openpilotLongitudinalControl:
|
||||
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LFA", CAN.ECAN, enabled, lat_active, apply_torque))
|
||||
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LKAS", CAN.ACAN, enabled, lat_active, apply_torque))
|
||||
return ret
|
||||
|
||||
angle_lkas_alt = CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
|
||||
|
||||
@@ -151,12 +180,7 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
|
||||
else:
|
||||
lkas_values = copy.copy(control_values)
|
||||
lkas_values["LKA_AVAILABLE"] = 0
|
||||
if CP.carFingerprint in (
|
||||
CAR.KIA_CARNIVAL_4TH_GEN,
|
||||
CAR.KIA_CARNIVAL_2025,
|
||||
CAR.KIA_CARNIVAL_HEV_4TH_GEN,
|
||||
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
|
||||
):
|
||||
if CP.carFingerprint in (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN):
|
||||
lkas_values["DAMP_FACTOR"] = 100
|
||||
|
||||
if lfa_base_values:
|
||||
@@ -175,21 +199,7 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
|
||||
lkas_values["LKAS_ANGLE_ACTIVE"] = 2 if lat_active else 1
|
||||
lkas_values["ADAS_ACIAnglTqRedcGainVal"] = apply_torque if lat_active else 0.0
|
||||
if angle_lkas_alt:
|
||||
if CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026:
|
||||
lkas_values = {
|
||||
"LKA_OptUsmSta": 0,
|
||||
"LKA_SysIndReq": 2 if enabled else 1,
|
||||
"StrTqReqVal": 0,
|
||||
"LKA_SysWrn": 0,
|
||||
"ActToiSta": 0,
|
||||
"LKA_UsmMod": 0,
|
||||
"LKA_RcgSta": 3 if lat_active else 0,
|
||||
"Damping_Gain": 100,
|
||||
"ADAS_StrAnglReqVal": apply_angle,
|
||||
"LKAS_ANGLE_ACTIVE": 2 if lat_active else 1,
|
||||
"ADAS_ACIAnglTqRedcGainVal": apply_torque if lat_active else 0.0,
|
||||
}
|
||||
elif lat_active:
|
||||
if lat_active:
|
||||
lkas_values = {
|
||||
"LKA_OptUsmSta": 0,
|
||||
"LKA_RcgSta": 3,
|
||||
@@ -245,7 +255,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 longitudinal_active and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
if CP.openpilotLongitudinalControl 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:
|
||||
@@ -732,13 +742,13 @@ def create_ioniq_6_cluster_lane_change_messages(CAN, frame, side=None):
|
||||
|
||||
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control,
|
||||
main_mode_acc=1, jerk_lower=None, jerk_upper=None, direct_accel=False,
|
||||
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None, raw_accel=None):
|
||||
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None):
|
||||
jerk = 5
|
||||
jn = jerk / 50
|
||||
if not enabled or gas_override:
|
||||
a_val, a_raw = 0, 0
|
||||
elif direct_accel:
|
||||
a_raw = accel if raw_accel is None else raw_accel
|
||||
a_raw = accel
|
||||
a_val = accel
|
||||
else:
|
||||
a_raw = accel
|
||||
@@ -816,13 +826,15 @@ def create_fca_warning_light(packer, CAN, frame):
|
||||
return ret
|
||||
|
||||
|
||||
def create_adrv_messages(packer, CAN, frame, blended_hda2=False, car_fingerprint=None, drive_gear=False):
|
||||
def create_adrv_messages(packer, CAN, frame, blended_hda2=False):
|
||||
# messages needed to car happy after disabling
|
||||
# the ADAS Driving ECU to do longitudinal control
|
||||
|
||||
ret = []
|
||||
|
||||
ret.append(create_adrv_0x51(packer, CAN, frame, car_fingerprint, drive_gear))
|
||||
values = {
|
||||
}
|
||||
ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values))
|
||||
|
||||
if blended_hda2:
|
||||
return ret
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
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 import hyundaicanfd
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
|
||||
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \
|
||||
CANFD_SECURITYACCESS_CAR, \
|
||||
CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_ECU_KEEPALIVE_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
|
||||
RADAR_LIVE_LONGITUDINAL_CAR, \
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
|
||||
LEGACY_LONGITUDINAL_CAR, \
|
||||
@@ -28,15 +25,6 @@ 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)
|
||||
|
||||
@@ -44,7 +32,6 @@ ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.can
|
||||
ECU_DISABLE_TIMESTAMP = 0.0
|
||||
KONA_NON_SCC_FCA_RADAR_ADDR = 0x602
|
||||
KIA_EV9_ACCEL_MAX = 2.2
|
||||
RAY_PEDAL_SENSOR_ADDR = 0x201
|
||||
|
||||
|
||||
def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
@@ -61,7 +48,7 @@ def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
|
||||
def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
ret.startAccel = 1.4
|
||||
ret.longitudinalActuatorDelay = 0.5
|
||||
ret.longitudinalActuatorDelay = 0.35
|
||||
ret.vEgoStarting = 0.5
|
||||
|
||||
|
||||
@@ -237,9 +224,6 @@ 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):
|
||||
@@ -304,18 +288,6 @@ class CarInterface(CarInterfaceBase):
|
||||
elif ret.flags & HyundaiFlags.FCEV:
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.FCEV_GAS.value
|
||||
|
||||
if (candidate == CAR.KIA_RAY_EV and fingerprint[0].get(RAY_PEDAL_SENSOR_ADDR) == 6 and
|
||||
ret.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS and
|
||||
ret.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON):
|
||||
ret.enableGasInterceptorDEPRECATED = True
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.pcmCruise = False
|
||||
ret.radarUnavailable = True
|
||||
ret.autoResumeSng = False
|
||||
ret.minEnableSpeed = 5.0 # pedal-only: no commanded friction brake/standstill hold
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.LONG.value
|
||||
|
||||
# Car specific configuration overrides
|
||||
|
||||
if candidate == CAR.GENESIS_G90:
|
||||
@@ -376,7 +348,14 @@ class CarInterface(CarInterfaceBase):
|
||||
params = Params()
|
||||
|
||||
if communication_control is None:
|
||||
communication_control = get_communication_control_request(CP.carFingerprint)
|
||||
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])
|
||||
|
||||
ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===")
|
||||
|
||||
@@ -392,25 +371,11 @@ class CarInterface(CarInterfaceBase):
|
||||
skip_disable_ecu = True
|
||||
|
||||
if not skip_disable_ecu:
|
||||
disable_can_recv = can_recv
|
||||
if CP.carFingerprint == CAR.KIA_EV6 and can_recv is not None:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, None)
|
||||
base_can_recv = can_recv
|
||||
adrv_bus = CanBus(CP).ACAN
|
||||
|
||||
def disable_can_recv(*args, **kwargs):
|
||||
packets = base_can_recv(*args, **kwargs)
|
||||
for packet in packets or []:
|
||||
for msg in packet:
|
||||
if msg.src == adrv_bus and msg.address == 0x51:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, msg.dat)
|
||||
return packets
|
||||
|
||||
# Try ECU disable. If it succeeds (IGN-ON mode), enable longitudinal.
|
||||
# If it fails (READY mode returns NRC 0x22, or timeout), strip LONG safety flag
|
||||
# so panda forwards stock SCC messages normally (lateral-only mode).
|
||||
ecu_log(f"=== ECU DISABLE attempt: addr=0x{addr:x}, bus={bus} ===")
|
||||
ecu_disabled = disable_ecu(disable_can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
|
||||
ecu_disabled = disable_ecu(can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
|
||||
reset=bool(CP.flags & HyundaiFlags.CAN_CANFD_BLENDED))
|
||||
|
||||
if CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE):
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanLeadData:
|
||||
object_gap: int = 0
|
||||
lead_distance: float = 0.0
|
||||
lead_rel_speed: float = 0.0
|
||||
lead_visible: bool = False
|
||||
|
||||
@property
|
||||
def object_rel_gap(self) -> int:
|
||||
return 0 if self.lead_distance == 0 else 2 if self.lead_rel_speed < -0.2 else 1
|
||||
|
||||
|
||||
def _hysteresis_update(current, new_value, counter, threshold):
|
||||
if new_value == current:
|
||||
return current, 0
|
||||
|
||||
counter += 1
|
||||
return (new_value, 0) if counter >= threshold else (current, counter)
|
||||
|
||||
|
||||
class CanLeadDataState:
|
||||
LEAD_HYSTERESIS_FRAMES = 50
|
||||
|
||||
def __init__(self):
|
||||
self._lead_on_counter = 0
|
||||
self._lead_off_counter = 0
|
||||
self._gap_counter = 0
|
||||
self._lead_visible = False
|
||||
self._object_gap = 0
|
||||
|
||||
@staticmethod
|
||||
def _get_object_gap(lead_distance: float) -> int:
|
||||
if lead_distance == 0:
|
||||
return 0
|
||||
if lead_distance < 20:
|
||||
return 2
|
||||
if lead_distance < 25:
|
||||
return 3
|
||||
if lead_distance < 30:
|
||||
return 4
|
||||
return 5
|
||||
|
||||
def update(self, lead_distance: float, lead_rel_speed: float, lead_visible: bool) -> CanLeadData:
|
||||
counter = self._lead_on_counter if lead_visible else self._lead_off_counter
|
||||
self._lead_visible, counter = _hysteresis_update(
|
||||
self._lead_visible, lead_visible, counter, self.LEAD_HYSTERESIS_FRAMES,
|
||||
)
|
||||
if lead_visible:
|
||||
self._lead_on_counter = counter
|
||||
self._lead_off_counter = 0
|
||||
else:
|
||||
self._lead_off_counter = counter
|
||||
self._lead_on_counter = 0
|
||||
|
||||
object_gap = self._get_object_gap(lead_distance)
|
||||
self._object_gap, self._gap_counter = _hysteresis_update(
|
||||
self._object_gap, object_gap, self._gap_counter, self.LEAD_HYSTERESIS_FRAMES,
|
||||
)
|
||||
|
||||
return CanLeadData(self._object_gap, lead_distance, lead_rel_speed, self._lead_visible)
|
||||
@@ -19,9 +19,6 @@ 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)
|
||||
@@ -33,7 +30,6 @@ 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:
|
||||
@@ -51,10 +47,6 @@ 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)
|
||||
@@ -73,10 +65,6 @@ 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
|
||||
@@ -90,8 +78,7 @@ 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)]
|
||||
dbc_name = radar_config.dbc_name or DBC[CP.carFingerprint][Bus.radar]
|
||||
return CANParser(dbc_name, messages, radar_config.bus)
|
||||
return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, radar_config.bus)
|
||||
|
||||
|
||||
class RadarInterface(RadarInterfaceBase):
|
||||
@@ -236,27 +223,6 @@ 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
|
||||
|
||||
@@ -7,7 +7,7 @@ from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
|
||||
from opendbc.car.structs import CarControl, CarParams
|
||||
from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
|
||||
from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
|
||||
from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
|
||||
EV9LongitudinalTuningState, update_ev9_longitudinal_tuning, \
|
||||
BlindspotWarningState, update_blindspot_warning, \
|
||||
reset_egmp_longitudinal_tuning, \
|
||||
@@ -20,22 +20,20 @@ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY
|
||||
should_track_stop_accel_directly_for_car, \
|
||||
preserve_stock_canfd_lfa_status, \
|
||||
preserve_stock_canfd_lkas_status, \
|
||||
suppress_redundant_gv70_brake_cancel, \
|
||||
clear_ioniq_6_torque_when_request_inactive
|
||||
apply_carnival_steering_override, \
|
||||
suppress_redundant_gv70_brake_cancel
|
||||
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, get_communication_control_request
|
||||
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX
|
||||
from opendbc.car.hyundai import hyundaican, hyundaicanfd
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
|
||||
from opendbc.car.hyundai.lead_data import CanLeadData, CanLeadDataState
|
||||
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
|
||||
RADAR_START_ADDR, RadarInterface, get_radar_track_config, radar_tracks_available
|
||||
RADAR_START_ADDR, get_radar_track_config
|
||||
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, \
|
||||
CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning
|
||||
HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, Buttons, CarControllerParams, kia_ev6_gt_line_longitudinal_tuning
|
||||
|
||||
LongCtrlState = CarControl.Actuators.LongControlState
|
||||
from opendbc.car.hyundai.fingerprints import FW_VERSIONS
|
||||
@@ -81,7 +79,6 @@ HYUNDAI_NON_SCC_CARS = (
|
||||
CAR.HYUNDAI_ELANTRA_HEV_2022_NON_SCC,
|
||||
CAR.HYUNDAI_KONA_NON_SCC,
|
||||
CAR.HYUNDAI_KONA_EV_NON_SCC,
|
||||
CAR.KIA_RAY_EV,
|
||||
CAR.KIA_CEED_PHEV_2022_NON_SCC,
|
||||
CAR.KIA_FORTE_2019_NON_SCC,
|
||||
CAR.KIA_FORTE_2021_NON_SCC,
|
||||
@@ -131,92 +128,6 @@ def get_test_toggles() -> SimpleNamespace:
|
||||
|
||||
|
||||
class TestHyundaiFingerprint:
|
||||
def test_egmp_communication_control_paths(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 CAR.HYUNDAI_IONIQ_5 in CANFD_RADAR_LIVE_LONGITUDINAL_CAR
|
||||
assert CAR.HYUNDAI_IONIQ_5 not in CANFD_RADAR_ECU_KEEPALIVE_CAR
|
||||
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_5) == stock_request
|
||||
|
||||
assert CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN in CANFD_RADAR_LIVE_LONGITUDINAL_CAR
|
||||
assert CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN not in CANFD_RADAR_ECU_KEEPALIVE_CAR
|
||||
assert get_communication_control_request(CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN) == stock_request
|
||||
|
||||
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request
|
||||
|
||||
def test_ev6_adrv_0x51_replays_factory_payload(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV6
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.EV)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
can_bus = CanBus(CP)
|
||||
factory = bytes.fromhex("88ed2e091700ffff5e0d0000012006ff021c2200000000000800000010000000")
|
||||
|
||||
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, factory)
|
||||
try:
|
||||
address, dat, bus = hyundaicanfd.create_adrv_0x51(packer, can_bus, 7, CAR.KIA_EV6, drive_gear=True)
|
||||
_, parked_dat, _ = hyundaicanfd.create_adrv_0x51(packer, can_bus, 8, CAR.KIA_EV6, drive_gear=False)
|
||||
_, other_dat, _ = hyundaicanfd.create_adrv_0x51(packer, can_bus, 7, CAR.HYUNDAI_IONIQ_6)
|
||||
finally:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, None)
|
||||
|
||||
assert address == 0x51
|
||||
assert bus == can_bus.ACAN
|
||||
assert dat[2] == (factory[2] + 8) & 0xFF
|
||||
assert dat[3:] == factory[3:]
|
||||
assert int.from_bytes(dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(dat))
|
||||
assert parked_dat[3] == factory[3] & ~0x1
|
||||
assert parked_dat[4:] == factory[4:]
|
||||
assert int.from_bytes(parked_dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(parked_dat))
|
||||
assert other_dat[3:] == bytes(29)
|
||||
|
||||
def test_ev6_init_captures_factory_adrv_0x51(self, monkeypatch):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[CanBus(None, fingerprint).CAM][0x50] = 16
|
||||
radar_config = get_radar_track_config(CAR.KIA_EV6)
|
||||
fingerprint[radar_config.bus][radar_config.start_addr] = radar_config.expected_length
|
||||
car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, fingerprint, car_fw, True, False, False, get_test_toggles())
|
||||
factory = bytes.fromhex("6b657d090900e1ff000000000020ffff00000000000000000800000010000000")
|
||||
|
||||
def can_recv(*, wait_for_one=True):
|
||||
msg = SimpleNamespace(address=0x51, src=CanBus(CP).ACAN, dat=factory)
|
||||
return [[msg]]
|
||||
|
||||
def fake_disable_ecu(capturing_can_recv, *_args, **_kwargs):
|
||||
capturing_can_recv(wait_for_one=True)
|
||||
return True
|
||||
|
||||
monkeypatch.setattr("opendbc.car.hyundai.interface.disable_ecu", fake_disable_ecu)
|
||||
CarInterface.init(CP, can_recv, None)
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
try:
|
||||
_, dat, _ = hyundaicanfd.create_adrv_0x51(packer, CanBus(CP), 0, CAR.KIA_EV6, drive_gear=True)
|
||||
finally:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, None)
|
||||
|
||||
assert dat[3:] == factory[3:]
|
||||
|
||||
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)
|
||||
carnival_2025_cp = CarInterface.get_params(CAR.KIA_CARNIVAL_2025, gen_empty_fingerprint(), [],
|
||||
False, False, False, None)
|
||||
|
||||
low_speed = CarControllerParams(CP, 10.0)
|
||||
high_speed = CarControllerParams(CP, 20.0)
|
||||
carnival_2025_low_speed = CarControllerParams(carnival_2025_cp, 10.0)
|
||||
|
||||
assert (low_speed.STEER_DELTA_UP, low_speed.STEER_DELTA_DOWN) == (2, 3)
|
||||
assert (high_speed.STEER_DELTA_UP, high_speed.STEER_DELTA_DOWN) == (2, 3)
|
||||
assert (carnival_2025_low_speed.STEER_DELTA_UP, carnival_2025_low_speed.STEER_DELTA_DOWN) == (10, 8)
|
||||
|
||||
@pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN))
|
||||
def test_carnival_uses_clean_canfd_lfa_status(self, candidate):
|
||||
assert not preserve_stock_canfd_lfa_status(candidate)
|
||||
@@ -298,6 +209,12 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["LKAS_ALT"]["TORQUE_REQUEST"] == 123
|
||||
assert parser.vl["LKAS_ALT"]["STEER_REQ"] == 1
|
||||
|
||||
def test_carnival_steering_override_is_scoped_to_2025_platform(self):
|
||||
assert apply_carnival_steering_override(CAR.KIA_CARNIVAL_2025, True, True, 123) == (False, 0)
|
||||
assert apply_carnival_steering_override(CAR.KIA_CARNIVAL_2025, False, True, 123) == (True, 123)
|
||||
assert apply_carnival_steering_override(CAR.KIA_CARNIVAL_HEV_4TH_GEN, True, True, 123) == (True, 123)
|
||||
assert apply_carnival_steering_override(CAR.HYUNDAI_IONIQ_6, True, True, 123) == (True, 123)
|
||||
|
||||
def test_canfd_torque_bsm_parser_registers_rear_blindspots(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
|
||||
@@ -500,42 +417,6 @@ 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)
|
||||
@@ -680,14 +561,6 @@ class TestHyundaiFingerprint:
|
||||
assert not (CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
|
||||
assert bool(CP.flags & HyundaiFlags.CANFD_CAMERA_SCC)
|
||||
|
||||
def test_ioniq_6_clears_torque_with_inactive_safety_request(self):
|
||||
ioniq_6_cp = SimpleNamespace(carFingerprint=CAR.HYUNDAI_IONIQ_6)
|
||||
other_cp = SimpleNamespace(carFingerprint=CAR.KIA_EV6)
|
||||
|
||||
assert clear_ioniq_6_torque_when_request_inactive(ioniq_6_cp, -409, False) == 0
|
||||
assert clear_ioniq_6_torque_when_request_inactive(ioniq_6_cp, -409, True) == -409
|
||||
assert clear_ioniq_6_torque_when_request_inactive(other_cp, -409, False) == -409
|
||||
|
||||
palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None)
|
||||
assert palisade_2023.flags & HyundaiFlags.CAN_CANFD_BLENDED
|
||||
assert DBC[palisade_2023.carFingerprint][Bus.pt] == "hyundai_palisade_2023_generated"
|
||||
@@ -781,45 +654,6 @@ class TestHyundaiFingerprint:
|
||||
} <= msg_addrs_buses
|
||||
assert (0x364, 1) not in msg_addrs_buses
|
||||
|
||||
def test_palisade_telluride_hda2_aol_keeps_lkas_status_after_long_cancel(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x50] = 16
|
||||
car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, fingerprint, car_fw, True, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.frame = 1
|
||||
|
||||
hud_control = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True,
|
||||
rightLaneVisible=True,
|
||||
leftLaneDepart=False,
|
||||
rightLaneDepart=False,
|
||||
leadDistanceBars=3,
|
||||
leadVisible=False,
|
||||
)
|
||||
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 24) if i != 7}
|
||||
lfa_block_msg["COUNTER"] = 0
|
||||
CS = SimpleNamespace(lfa_block_msg=lfa_block_msg, redneck_send_button=Buttons.NONE, lkas11={}, msg_364={},
|
||||
out=SimpleNamespace(vEgoRaw=5.0))
|
||||
CC = SimpleNamespace(enabled=False, latActive=True, longActive=False,
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False))
|
||||
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
|
||||
adas_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], 0)
|
||||
ecan_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 1)
|
||||
|
||||
for steering_requested, icon, expected_status in ((True, 2, 2), (False, 1, 1)):
|
||||
msgs = controller.create_can_msgs(steering_requested, 16 if steering_requested else 0, False,
|
||||
0.0, 0.0, False, hud_control, actuators, CS, CC, icon, icon)
|
||||
adas_parser.update([(1, [msg for msg in msgs if msg[0] == 0x50])])
|
||||
ecan_parser.update([(1, [msg for msg in msgs if msg[0] == 0x340])])
|
||||
|
||||
assert adas_parser.vl["LKAS"]["LKA_ICON"] == icon
|
||||
assert adas_parser.vl["LKAS"]["STEER_REQ"] == int(steering_requested)
|
||||
assert ecan_parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == expected_status
|
||||
assert ecan_parser.vl["LKAS11"]["CF_Lkas_ActToi"] == int(steering_requested)
|
||||
assert not any(msg[0] == 0x364 for msg in msgs)
|
||||
|
||||
def test_g70_aol_uses_active_lkas_icon(self):
|
||||
CP = CarInterface.get_params(CAR.GENESIS_G70_2020, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
@@ -842,168 +676,6 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "expected_status"), (
|
||||
(CAR.KIA_NIRO_PHEV_2022, 2),
|
||||
(CAR.KIA_NIRO_HEV_2021, 2),
|
||||
))
|
||||
def test_classic_niro_aol_keeps_active_lkas_status(self, candidate, expected_status):
|
||||
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, get_test_toggles())
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.frame = 1
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
|
||||
|
||||
hud_control = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True,
|
||||
rightLaneVisible=True,
|
||||
leftLaneDepart=False,
|
||||
rightLaneDepart=False,
|
||||
leadVisible=False,
|
||||
)
|
||||
CS = SimpleNamespace(lkas11=parser.vl["LKAS11"])
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=True,
|
||||
longActive=False,
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False),
|
||||
)
|
||||
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
|
||||
|
||||
msgs = controller.create_can_msgs(True, 156, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0)
|
||||
lkas11 = next(msg for msg in msgs if msg[0] == 0x340)
|
||||
parser.update([(1, [lkas11])])
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_ActToi"] == 1
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == expected_status
|
||||
|
||||
CC.latActive = False
|
||||
msgs = controller.create_can_msgs(False, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 1, 0)
|
||||
lkas11 = next(msg for msg in msgs if msg[0] == 0x340)
|
||||
parser.update([(2, [lkas11])])
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_ActToi"] == 0
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 1
|
||||
|
||||
def test_kona_non_scc_uses_no_individual_lane_lkas_status(self):
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_KONA_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
|
||||
|
||||
lkas11 = parser.vl["LKAS11"]
|
||||
msg = hyundaican.create_lkas11(
|
||||
packer, 0, CP, 0, True, False, lkas11, False, 4, False,
|
||||
True, True, 0, 0, 2,
|
||||
)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsSysState"] == 2
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsOpt_USM"] == 2
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsActivemode"] == 0
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 0
|
||||
|
||||
def test_kia_ray_ev_preserves_stock_inactive_lkas_status(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x485] = 4
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
|
||||
assert CP.flags & HyundaiFlags.SEND_LFA
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
|
||||
|
||||
lkas11 = parser.vl["LKAS11"]
|
||||
lkas11.update({
|
||||
"CF_Lkas_LdwsActivemode": 0,
|
||||
"CF_Lkas_LdwsSysState": 1,
|
||||
"CF_Lkas_FcwOpt_USM": 1,
|
||||
})
|
||||
msg = hyundaican.create_lkas11(
|
||||
packer, 0, CP, 0, True, False, lkas11, False, 4, False,
|
||||
True, True, 0, 0, 2,
|
||||
)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsActivemode"] == 0
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsSysState"] == 1
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsOpt_USM"] == 0
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 1
|
||||
|
||||
def test_kia_ray_ev_uses_active_lkas_status_when_enabled(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x485] = 4
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
|
||||
|
||||
lkas11 = parser.vl["LKAS11"]
|
||||
lkas11.update({
|
||||
"CF_Lkas_LdwsActivemode": 0,
|
||||
"CF_Lkas_LdwsSysState": 1,
|
||||
"CF_Lkas_FcwOpt_USM": 1,
|
||||
})
|
||||
msg = hyundaican.create_lkas11(
|
||||
packer, 0, CP, 0, True, False, lkas11, False, 4, True,
|
||||
True, True, 0, 0, 2,
|
||||
)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsActivemode"] == 3
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsSysState"] == 4
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsOpt_USM"] == 0
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
|
||||
|
||||
def test_kia_ray_ev_delays_first_lkas11(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x485] = 4
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
|
||||
|
||||
hud_control = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True,
|
||||
rightLaneVisible=True,
|
||||
leftLaneDepart=False,
|
||||
rightLaneDepart=False,
|
||||
)
|
||||
CS = SimpleNamespace(lkas11=parser.vl["LKAS11"], redneck_send_button=Buttons.NONE)
|
||||
CC = SimpleNamespace(enabled=False, cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False))
|
||||
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
|
||||
|
||||
first = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0)
|
||||
second = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0)
|
||||
|
||||
assert not any(addr == 0x340 for addr, _, _ in first)
|
||||
assert any(addr == 0x340 for addr, _, _ in second)
|
||||
|
||||
def test_stock_scc_cancel_waits_for_factory_disengagement(self):
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SANTA_FE_2022, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
|
||||
|
||||
hud_control = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True,
|
||||
rightLaneVisible=True,
|
||||
leftLaneDepart=False,
|
||||
rightLaneDepart=False,
|
||||
)
|
||||
CS = SimpleNamespace(
|
||||
lkas11=parser.vl["LKAS11"],
|
||||
clu11=parser.vl["CLU11"],
|
||||
redneck_send_button=Buttons.NONE,
|
||||
is_metric=False,
|
||||
)
|
||||
CC = SimpleNamespace(enabled=False, cruiseControl=SimpleNamespace(cancel=True, resume=False))
|
||||
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
|
||||
|
||||
for counter in range(1, CANCEL_BUTTON_DELAY_FRAMES + 1):
|
||||
controller.cancel_counter = counter
|
||||
msgs = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2)
|
||||
assert not any(addr == 0x4F1 for addr, _, _ in msgs)
|
||||
|
||||
controller.cancel_counter = CANCEL_BUTTON_DELAY_FRAMES + 1
|
||||
msgs = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2)
|
||||
assert any(addr == 0x4F1 for addr, _, _ in msgs)
|
||||
|
||||
@pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024))
|
||||
def test_hyundai_can_refresh_platforms_use_refresh_dbc_and_safety_param(self, candidate):
|
||||
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
@@ -1048,74 +720,6 @@ class TestHyundaiFingerprint:
|
||||
palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None)
|
||||
assert palisade_2023.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON
|
||||
|
||||
def test_lkas12_da_warning_is_filtered_for_camera_fingerprint(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x53E] = 6
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], False, False, False, None)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], CP, get_test_toggles())
|
||||
assert FPCP.flags & HyundaiStarPilotFlags.HAS_LKAS12
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
stock = {
|
||||
"CF_Lkas_TsrSlifOpt": 3,
|
||||
"CF_LkasTsrStatus": 2,
|
||||
"CF_Lkas_TsrSpeed_Display_Clu": 80,
|
||||
"CF_LkasTsrSpeed_Display_Navi": 70,
|
||||
"CF_Lkas_TsrAddinfo_Display": 1,
|
||||
"CF_Lkas_Daw_USM": 0,
|
||||
"CF_LkasDawStatus": 1,
|
||||
}
|
||||
msg = hyundaican.create_lkas12(packer, stock)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS12", 0)], 0)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
assert parser.can_valid
|
||||
assert parser.vl["LKAS12"]["CF_LkasDawStatus"] == 0
|
||||
assert parser.vl["LKAS12"]["CF_Lkas_TsrSpeed_Display_Clu"] == 80
|
||||
|
||||
no_lkas12 = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
no_lkas12_fpcp = CarInterface.get_starpilot_params(
|
||||
CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], no_lkas12, get_test_toggles(),
|
||||
)
|
||||
assert not (no_lkas12_fpcp.flags & HyundaiStarPilotFlags.HAS_LKAS12)
|
||||
|
||||
def test_ray_ev_does_not_treat_eight_byte_53e_as_lkas12(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x53E] = 8
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, fingerprint, [], CP, get_test_toggles())
|
||||
|
||||
assert not (FPCP.flags & HyundaiStarPilotFlags.HAS_LKAS12)
|
||||
|
||||
def test_ray_ev_does_not_treat_eight_byte_485_as_lfa(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x485] = 8
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
|
||||
|
||||
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()
|
||||
fingerprint[2][0x53E] = 8
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], False, False, False, None)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], CP, get_test_toggles())
|
||||
|
||||
assert FPCP.flags & HyundaiStarPilotFlags.HAS_LKAS12
|
||||
|
||||
def test_carnival_lka_button_does_not_enable_angle_steering_safety(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x391] = 8
|
||||
@@ -1133,7 +737,7 @@ class TestHyundaiFingerprint:
|
||||
|
||||
@pytest.mark.parametrize("candidate, tracks_main_cruise", (
|
||||
(CAR.HYUNDAI_ELANTRA_2021, False),
|
||||
(CAR.HYUNDAI_ELANTRA_HEV_2024, True),
|
||||
(CAR.HYUNDAI_ELANTRA_HEV_2024, False),
|
||||
(CAR.HYUNDAI_SONATA_HYBRID, False),
|
||||
))
|
||||
def test_legacy_hyundai_long_main_cruise_tracking_is_vehicle_specific(self, candidate, tracks_main_cruise):
|
||||
@@ -1184,7 +788,6 @@ class TestHyundaiFingerprint:
|
||||
(CAR.HYUNDAI_ELANTRA_2022_NON_SCC, ("EMS16", "LVR12"), ()),
|
||||
(CAR.HYUNDAI_ELANTRA_HEV_2022_NON_SCC, ("E_CRUISE_CONTROL", "ELECT_GEAR"), ("EMS16",)),
|
||||
(CAR.HYUNDAI_KONA_EV_NON_SCC, ("LABEL11", "EMS12", "E_EMS11"), ()),
|
||||
(CAR.KIA_RAY_EV, ("LABEL11", "E_EMS11", "ELECT_GEAR"), ("EMS12", "SCC11", "SCC12")),
|
||||
])
|
||||
def test_non_scc_cruise_message_selection(self, candidate, expected_msgs, unexpected_msgs):
|
||||
toggles = get_test_toggles()
|
||||
@@ -1203,69 +806,6 @@ class TestHyundaiFingerprint:
|
||||
assert not ret.cruiseState.enabled
|
||||
assert ret.cruiseState.speed == 0
|
||||
|
||||
def test_kia_ray_ev_decodes_cruise_state(self):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], CP, toggles)
|
||||
car_state = CarState(CP, FPCP)
|
||||
can_parsers = car_state.get_can_parsers(CP)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
|
||||
can_parsers[Bus.pt].update([(1_000_000_000, [
|
||||
packer.make_can_msg("LABEL11", 0, {"CC_React": 1, "CC_Engaged": 1}),
|
||||
packer.make_can_msg("E_EMS11", 0, {"Cruise_Limit_Target": 10, "Accel_Pedal_Pos": 0}),
|
||||
packer.make_can_msg("ELECT_GEAR", 0, {"Elect_Gear_Shifter": 5}),
|
||||
])])
|
||||
|
||||
ret, _ = car_state.update(can_parsers, toggles)
|
||||
|
||||
assert ret.cruiseState.available
|
||||
assert ret.cruiseState.enabled
|
||||
assert ret.cruiseState.speed == pytest.approx(10 * 0.2777778)
|
||||
|
||||
def test_kia_ray_ev_decodes_bcm_lkas_button_pulse(self):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], CP, toggles)
|
||||
car_state = CarState(CP, FPCP)
|
||||
can_parsers = car_state.get_can_parsers(CP)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
|
||||
def update(ray_lkas_button: int, frame: int):
|
||||
msg = packer.make_can_msg("BCM_PO_11", 0, {"RAY_LKAS_BTN": ray_lkas_button})
|
||||
can_parsers[Bus.pt].update([(frame, [msg])])
|
||||
return car_state.update(can_parsers, toggles)[0]
|
||||
|
||||
update(0, 1)
|
||||
ret = update(1, 2)
|
||||
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
|
||||
|
||||
ret = update(0, 3)
|
||||
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
|
||||
|
||||
ret = update(1, 4)
|
||||
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
|
||||
|
||||
raw_button_msg = packer.make_can_msg("BCM_PO_11", 0, {"RAY_LKAS_BTN": 1})
|
||||
assert raw_button_msg[1][0] == 0x10
|
||||
|
||||
def test_non_ray_does_not_use_ray_lkas_signal(self):
|
||||
CP = CarInterface.get_params(CAR.KIA_FORTE_2021_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
car_state = CarState(CP, CarInterface.get_starpilot_params(CAR.KIA_FORTE_2021_NON_SCC,
|
||||
gen_empty_fingerprint(), [], CP, get_test_toggles()))
|
||||
parser_cycle = SimpleNamespace(
|
||||
vl={
|
||||
"CLU13": {"CF_Clu_LdwsLkasSW": 0},
|
||||
"BCM_PO_11": {"LDA_BTN": 0, "RAY_LKAS_BTN": 1},
|
||||
},
|
||||
ts_nanos={
|
||||
"CLU13": {"CF_Clu_LdwsLkasSW": 1},
|
||||
"BCM_PO_11": {"LDA_BTN": 1, "RAY_LKAS_BTN": 1},
|
||||
},
|
||||
)
|
||||
|
||||
assert not car_state.create_lkas_button_events(parser_cycle, 0)
|
||||
|
||||
def test_hyundai_redneck_cruise_availability(self, monkeypatch):
|
||||
class FakeParams:
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -1559,7 +1099,7 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert CP.startAccel == pytest.approx(1.4)
|
||||
assert CP.vEgoStarting == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.35)
|
||||
assert CP.vEgoStopping == pytest.approx(0.3)
|
||||
assert CP.stoppingDecelRate == pytest.approx(0.4)
|
||||
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin)
|
||||
@@ -1588,7 +1128,7 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert CP.startAccel == pytest.approx(1.4)
|
||||
assert CP.vEgoStarting == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.35)
|
||||
|
||||
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, testing_ground_active=True)
|
||||
assert not kia_ev6_gt_line_longitudinal_tuning(CAR.KIA_EV6_2025, CP.carVin, testing_ground_active=True)
|
||||
@@ -1936,20 +1476,6 @@ class TestHyundaiFingerprint:
|
||||
ret = update(0, 3)
|
||||
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
|
||||
|
||||
def test_elantra_hev_lkas_button_keeps_a_short_parser_cycle_edge(self):
|
||||
car_state = CarState.__new__(CarState)
|
||||
car_state.CP = SimpleNamespace(carFingerprint=CAR.HYUNDAI_ELANTRA_HEV_2024)
|
||||
car_state.lda_button = 0
|
||||
parser_cycle = SimpleNamespace(vl_all={
|
||||
"CLU13": {"CF_Clu_LdwsLkasSW": [0]},
|
||||
"BCM_PO_11": {"LDA_BTN": [1, 0]},
|
||||
})
|
||||
|
||||
events = car_state.create_lkas_button_events(parser_cycle, 0)
|
||||
|
||||
assert any(be.type == ButtonType.lkas and be.pressed for be in events)
|
||||
assert car_state.lda_button == 1
|
||||
|
||||
def test_sonata_hybrid_uses_main_bus_lkas_parser(self):
|
||||
toggles = get_test_toggles()
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
@@ -2671,15 +2197,14 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.0)
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(8.5)
|
||||
|
||||
def test_gv70_electrified_uses_clean_damped_lkas_status_payload(self):
|
||||
def test_gv70_electrified_synthesizes_lkas_status_payload(self):
|
||||
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 = True
|
||||
CP.openpilotLongitudinalControl = False
|
||||
|
||||
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 = {
|
||||
@@ -2700,7 +2225,6 @@ class TestHyundaiFingerprint:
|
||||
"DAMP_FACTOR": 100,
|
||||
}
|
||||
cc = SimpleNamespace(enabled=True, latActive=True,
|
||||
longActive=False,
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
leftBlinker=False, rightBlinker=False,
|
||||
hudControl=SimpleNamespace())
|
||||
@@ -2719,9 +2243,8 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 100
|
||||
assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 0
|
||||
assert parser.vl["LKAS"]["STEER_REQ"] == 1
|
||||
assert parser.vl["LKAS"]["STEER_MODE"] == 2
|
||||
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 3
|
||||
|
||||
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)]
|
||||
@@ -2729,103 +2252,6 @@ 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)]
|
||||
|
||||
def test_gv70_electrified_stock_long_uses_damped_lkas_request(self):
|
||||
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
|
||||
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
can_bus = CanBus(CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN)
|
||||
msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 123, 0.0)
|
||||
|
||||
assert [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [("LKAS", can_bus.ACAN)]
|
||||
parser.update([(1, msgs)])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 123
|
||||
assert parser.vl["LKAS"]["STEER_REQ"] == 1
|
||||
assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0
|
||||
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 100
|
||||
assert parser.vl["LKAS"]["STEER_MODE"] == 2
|
||||
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 3
|
||||
|
||||
@pytest.mark.parametrize(("car", "powertrain_flag"), [
|
||||
(CAR.HYUNDAI_IONIQ_5, HyundaiFlags.EV),
|
||||
(CAR.HYUNDAI_IONIQ_6, HyundaiFlags.EV),
|
||||
(CAR.KIA_EV6, HyundaiFlags.EV),
|
||||
(CAR.KIA_CARNIVAL_2025, 0),
|
||||
(CAR.KIA_CARNIVAL_HEV_4TH_GEN, HyundaiFlags.HYBRID),
|
||||
(CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, HyundaiFlags.EV),
|
||||
])
|
||||
def test_hda2_keeps_lfa_status_when_longitudinal_is_inactive(self, car, powertrain_flag):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = car
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | powertrain_flag)
|
||||
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
|
||||
controller.long_active_ecu = 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)
|
||||
|
||||
@pytest.mark.parametrize("car", [
|
||||
CAR.HYUNDAI_IONIQ_6,
|
||||
CAR.KIA_EV6,
|
||||
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
|
||||
])
|
||||
def test_egmp_persistent_lfa_status_survives_ecu_fallback_state(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)
|
||||
controller.frame = 1
|
||||
controller.long_active_ecu = False
|
||||
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),
|
||||
)
|
||||
|
||||
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
|
||||
@@ -2841,11 +2267,10 @@ class TestHyundaiFingerprint:
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.pid),
|
||||
cruiseControl=SimpleNamespace(override=False, cancel=False, resume=False),
|
||||
leftBlinker=False, rightBlinker=False,
|
||||
hudControl=SimpleNamespace(leadDistanceBars=3, leadVisible=True),
|
||||
hudControl=SimpleNamespace(leadDistanceBars=3),
|
||||
)
|
||||
cs = SimpleNamespace(
|
||||
stock_lfa_msg=None, stock_lkas_msg=None,
|
||||
openpilot_lead_visible=True, openpilot_lead_distance=37.5, openpilot_lead_rel_speed=-1.3,
|
||||
out=SimpleNamespace(steeringAngleDeg=0.0, gearShifter=structs.CarState.GearShifter.drive),
|
||||
)
|
||||
|
||||
@@ -2858,8 +2283,7 @@ class TestHyundaiFingerprint:
|
||||
parser.update([(1, scc_msgs)])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["SCC_CONTROL"]["MainMode_ACC"] == 1
|
||||
assert parser.vl["SCC_CONTROL"]["ACC_ObjDist"] == pytest.approx(37.5)
|
||||
assert parser.vl["SCC_CONTROL"]["ACC_ObjRelSpd"] == pytest.approx(-1.3)
|
||||
assert parser.vl["SCC_CONTROL"]["ACC_ObjDist"] == pytest.approx(1.0)
|
||||
assert parser.vl["SCC_CONTROL"]["ObjValid"] == 0
|
||||
assert parser.vl["SCC_CONTROL"]["aReqValue"] == pytest.approx(-0.1)
|
||||
assert parser.vl["SCC_CONTROL"]["aReqRaw"] == pytest.approx(-1.0)
|
||||
@@ -2959,77 +2383,6 @@ 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_keeps_status_and_suppression_alive(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)
|
||||
controller.frame = 5
|
||||
can_bus = CanBus(CP)
|
||||
cc = SimpleNamespace(enabled=False, latActive=False,
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
|
||||
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7}
|
||||
lfa_block_msg["COUNTER"] = 0
|
||||
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg=lfa_block_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
|
||||
assert len([msg for msg in msgs if msg[0] == 0x362]) == 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_OptUsmSta"] == 0
|
||||
assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 1
|
||||
assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 0
|
||||
assert parser.vl["LKAS_ALT"]["Damping_Gain"] == 100
|
||||
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == 0.0
|
||||
|
||||
def test_sportage_angle_lkas_alt_active_status_matches_vehicle_contract(self):
|
||||
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)
|
||||
controller.frame = 5
|
||||
can_bus = CanBus(CP)
|
||||
cc = SimpleNamespace(enabled=True, latActive=True,
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
|
||||
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7}
|
||||
lfa_block_msg["COUNTER"] = 0
|
||||
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg=lfa_block_msg,
|
||||
out=SimpleNamespace(standstill=False, steeringAngleDeg=10.0,
|
||||
gearShifter=structs.CarState.GearShifter.drive))
|
||||
|
||||
msgs = controller.create_canfd_msgs(0, True, 0.4, 12.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
|
||||
get_test_toggles(), lka_icon=2, lfa_icon=2)
|
||||
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
|
||||
assert len(lkas_msgs) == 1
|
||||
assert len([msg for msg in msgs if msg[0] == 0x362]) == 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_OptUsmSta"] == 0
|
||||
assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 2
|
||||
assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 3
|
||||
assert parser.vl["LKAS_ALT"]["Damping_Gain"] == 100
|
||||
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 2
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.4)
|
||||
|
||||
def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV9
|
||||
@@ -3154,50 +2507,6 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["SCC14"]["ComfortBandUpper"] == pytest.approx(0.0)
|
||||
assert parser.vl["SCC14"]["ComfortBandLower"] == pytest.approx(0.0)
|
||||
assert parser.vl["SCC14"]["JerkLowerLimit"] == pytest.approx(5.0)
|
||||
assert parser.vl["SCC11"]["ObjValid"] == 0
|
||||
assert parser.vl["SCC11"]["ACC_ObjDist"] == 0
|
||||
assert parser.vl["SCC14"]["ObjGap"] == 0
|
||||
assert parser.vl["SCC14"]["ObjDistStat"] == 0
|
||||
|
||||
def test_can_acc_commands_show_approaching_lead(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.HYUNDAI_ELANTRA_2021
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("SCC11", 0), ("SCC14", 0)], 0)
|
||||
lead_data = CanLeadData(object_gap=4, lead_distance=27.5, lead_rel_speed=-1.3, lead_visible=True)
|
||||
|
||||
msgs = hyundaican.create_acc_commands(packer, enabled=True, accel=0.0, upper_jerk=1.0, idx=3,
|
||||
hud_control=SimpleNamespace(leadDistanceBars=3), set_speed=42,
|
||||
stopping=False, long_override=False, use_fca=False, CP=CP,
|
||||
lead_data=lead_data)
|
||||
parser.update([(1, msgs)])
|
||||
|
||||
assert parser.can_valid
|
||||
assert parser.vl["SCC11"]["ObjValid"] == 1
|
||||
assert parser.vl["SCC11"]["ACC_ObjStatus"] == 1
|
||||
assert parser.vl["SCC11"]["ACC_ObjDist"] == pytest.approx(27.0)
|
||||
assert parser.vl["SCC11"]["ACC_ObjRelSpd"] == pytest.approx(-1.3)
|
||||
assert parser.vl["SCC14"]["ObjGap"] == 4
|
||||
assert parser.vl["SCC14"]["ObjDistStat"] == 2
|
||||
|
||||
def test_can_lead_data_hysteresis_and_distance_bands(self):
|
||||
state = CanLeadDataState()
|
||||
|
||||
for _ in range(state.LEAD_HYSTERESIS_FRAMES - 1):
|
||||
lead_data = state.update(18.0, -0.5, True)
|
||||
assert not lead_data.lead_visible
|
||||
assert lead_data.object_gap == 0
|
||||
|
||||
lead_data = state.update(18.0, -0.5, True)
|
||||
assert lead_data.lead_visible
|
||||
assert lead_data.object_gap == 2
|
||||
assert lead_data.object_rel_gap == 2
|
||||
|
||||
for _ in range(state.LEAD_HYSTERESIS_FRAMES):
|
||||
lead_data = state.update(32.0, 0.5, True)
|
||||
assert lead_data.object_gap == 5
|
||||
assert lead_data.object_rel_gap == 1
|
||||
|
||||
def test_can_acc_commands_follow_sonata_main_cruise_state(self):
|
||||
CP = CarParams.new_message()
|
||||
@@ -4169,9 +3478,7 @@ class TestHyundaiFingerprint:
|
||||
def test_platform_code_ecus_available(self, subtests):
|
||||
# TODO: add queries for these non-CAN FD cars to get EPS
|
||||
no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H,
|
||||
CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80,
|
||||
CAR.HYUNDAI_ELANTRA, CAR.KIA_RAY_EV}
|
||||
no_fwd_radar_platforms = {CAR.KIA_RAY_EV}
|
||||
CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.HYUNDAI_ELANTRA}
|
||||
|
||||
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
@@ -4179,8 +3486,6 @@ class TestHyundaiFingerprint:
|
||||
for platform_code_ecu in PLATFORM_CODE_ECUS:
|
||||
if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS:
|
||||
continue
|
||||
if platform_code_ecu == Ecu.fwdRadar and car_model in no_fwd_radar_platforms:
|
||||
continue
|
||||
if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms:
|
||||
continue
|
||||
assert platform_code_ecu in [e[0] for e in ecus]
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, gen_empty_fingerprint
|
||||
from opendbc.car.hyundai.carcontroller import CarController
|
||||
from opendbc.car.hyundai.carstate import CarState
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.values import CAR, DBC, HyundaiSafetyFlags
|
||||
from opendbc.car.structs import CarControl
|
||||
|
||||
|
||||
def ray_fingerprint(sensor_length=6, lfa_length=8):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x201] = sensor_length
|
||||
fingerprint[0][0x391] = 8
|
||||
fingerprint[2][0x485] = lfa_length
|
||||
return fingerprint
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "fingerprint", "has_pedal"), [
|
||||
(CAR.KIA_RAY_EV, ray_fingerprint(), True),
|
||||
(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), False),
|
||||
(CAR.KIA_RAY_EV, ray_fingerprint(lfa_length=4), False),
|
||||
(CAR.HYUNDAI_KONA_EV_NON_SCC, ray_fingerprint(), False),
|
||||
])
|
||||
def test_ray_pedal_fingerprint_isolation(candidate, fingerprint, has_pedal):
|
||||
CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False, None)
|
||||
assert CP.enableGasInterceptorDEPRECATED is has_pedal
|
||||
assert CP.openpilotLongitudinalControl is has_pedal
|
||||
if has_pedal:
|
||||
assert not CP.pcmCruise
|
||||
assert CP.safetyConfigs[-1].safetyParam == 0x9405
|
||||
assert CP.minEnableSpeed == 5.0
|
||||
assert not CP.autoResumeSng
|
||||
FPCP = CarInterface.get_starpilot_params(candidate, fingerprint, [], CP, SimpleNamespace())
|
||||
assert FPCP.canUsePedal
|
||||
assert not FPCP.pcmCruiseSpeed
|
||||
assert not FPCP.redneckCruiseAvailable
|
||||
else:
|
||||
assert CP.pcmCruise
|
||||
assert not (CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG)
|
||||
|
||||
|
||||
def test_ray_pedal_safety_signature_is_unique_across_hyundai_platforms():
|
||||
for candidate in CAR:
|
||||
for alpha_long in (False, True):
|
||||
CP = CarInterface.get_params(candidate, ray_fingerprint(), [],
|
||||
alpha_long, False, False, None)
|
||||
has_ray_signature = (CP.safetyConfigs[-1].safetyParam & ~(32 | 128 | 2048)) == 0x9405
|
||||
assert has_ray_signature is (candidate == CAR.KIA_RAY_EV)
|
||||
assert CP.enableGasInterceptorDEPRECATED is (candidate == CAR.KIA_RAY_EV)
|
||||
|
||||
|
||||
def test_ray_pedal_parser_validates_actual_route_frames():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
parser = CarState(CP, None).get_can_parsers(CP)[Bus.party]
|
||||
assert parser.dbc_name == "hyundai_kia_ray_pedal"
|
||||
# Consecutive bus-0 GAS_SENSOR frames from Sept. 15 Ray rlog segment 4.
|
||||
samples = [bytes.fromhex(s) for s in (
|
||||
"01f403d55de8", "01f603d55ef1", "01f403d55f51", "01f603d3503f",
|
||||
"01f903d551ab", "01f903d552a4", "01f703d55370",
|
||||
)]
|
||||
for idx, dat in enumerate(samples):
|
||||
parser.update([(1_000_000_000 + idx * 20_000_000, [(0x201, dat, 0)])])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["GAS_SENSOR"]["STATE"] == 5 # FAULT_TIMEOUT: no 0x200 was sent
|
||||
|
||||
prior = parser.vl_raw["GAS_SENSOR"]
|
||||
bad = bytearray(samples[-1])
|
||||
bad[-1] ^= 1
|
||||
parser.update([(1_160_000_000, [(0x201, bytes(bad), 0)])])
|
||||
assert parser.vl_raw["GAS_SENSOR"] == prior
|
||||
|
||||
|
||||
def test_ray_pedal_fault_clears_only_with_healthy_sensor_state():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
state = CarState(CP, None)
|
||||
parsers = state.get_can_parsers(CP)
|
||||
packer = CANPacker("hyundai_kia_ray_pedal")
|
||||
sensor = packer.make_can_msg("GAS_SENSOR", 0, {
|
||||
"INTERCEPTOR_GAS": 0, "INTERCEPTOR_GAS2": 0,
|
||||
"STATE": 0, "COUNTER_PEDAL": 1,
|
||||
})
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_000_000_000, [sensor])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert state.ray_pedal_valid
|
||||
assert state.ray_pedal_state == 0
|
||||
assert not ret.accFaulted
|
||||
|
||||
|
||||
def test_ray_driver_override_uses_physical_interceptor_tracks():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
state = CarState(CP, None)
|
||||
parsers = state.get_can_parsers(CP)
|
||||
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
|
||||
physical_rest = (0x201, bytes.fromhex("010801f30cef"), 0)
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_000_000_000, [native_gas, physical_rest])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert state.ray_pedal_valid
|
||||
assert not ret.gasPressed
|
||||
|
||||
packer = CANPacker("hyundai_kia_ray_pedal")
|
||||
physical_press = packer.make_can_msg("GAS_SENSOR", 0, {
|
||||
"INTERCEPTOR_GAS": (310 - 264) * 0.672,
|
||||
"INTERCEPTOR_GAS2": (593 - 497) * 0.332,
|
||||
"STATE": 0, "COUNTER_PEDAL": 13,
|
||||
})
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_020_000_000, [physical_press])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert ret.gasPressed
|
||||
|
||||
|
||||
def test_ray_without_pedal_keeps_native_gas_detection():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), [], False, False, False, None)
|
||||
assert not CP.enableGasInterceptorDEPRECATED
|
||||
state = CarState(CP, None)
|
||||
parsers = state.get_can_parsers(CP)
|
||||
assert Bus.party not in parsers
|
||||
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_000_000_000, [native_gas])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert ret.gasPressed
|
||||
|
||||
|
||||
def test_ray_controller_heartbeats_and_only_actuates_when_ready():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
|
||||
CS = SimpleNamespace(
|
||||
lkas11=parser.vl["LKAS11"], clu11=parser.vl["CLU11"],
|
||||
out=SimpleNamespace(vEgo=12.0, gasPressed=False, brakePressed=False,
|
||||
cruiseState=SimpleNamespace(enabled=False)),
|
||||
ray_pedal_valid=True, ray_pedal_state=5, is_metric=True,
|
||||
)
|
||||
CC = SimpleNamespace(
|
||||
enabled=True, longActive=True, latActive=True,
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False),
|
||||
)
|
||||
hud = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True, rightLaneVisible=True,
|
||||
leftLaneDepart=False, rightLaneDepart=False,
|
||||
)
|
||||
actuators = SimpleNamespace(longControlState=CarControl.Actuators.LongControlState.pid)
|
||||
|
||||
def pedal_msg(accel, frame):
|
||||
controller.frame = frame
|
||||
messages = controller.create_can_msgs(True, 0, False, 0.0, accel, False,
|
||||
hud, actuators, CS, CC, 2, 0)
|
||||
return next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)
|
||||
|
||||
assert pedal_msg(2.0, 0)[:4] == bytes(4) # fault timeout: heartbeat only
|
||||
CS.ray_pedal_state = 0
|
||||
assert pedal_msg(2.0, 4)[:4] != bytes(4)
|
||||
CS.out.gasPressed = True
|
||||
assert pedal_msg(2.0, 8)[:4] == bytes(4)
|
||||
CS.out.gasPressed = False
|
||||
assert pedal_msg(-1.0, 12)[:4] == bytes(4) # decel = EV lift/regen, not gas
|
||||
CS.out.cruiseState.enabled = True
|
||||
controller.frame = 16
|
||||
messages = controller.create_can_msgs(True, 0, False, 0.0, 2.0, False,
|
||||
hud, actuators, CS, CC, 2, 0)
|
||||
assert next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)[:4] == bytes(4)
|
||||
assert any(addr == 0x4F1 and bus == 0 for addr, _, bus in messages) # cancel stock CC
|
||||
@@ -2,8 +2,6 @@ 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
|
||||
@@ -47,12 +45,8 @@ class CarControllerParams:
|
||||
self.STEER_DRIVER_MULTIPLIER = 2
|
||||
self.STEER_THRESHOLD = 100
|
||||
if vEgoRaw < 15.0: # below ~34 mph - more aggressive for tight turns
|
||||
if CP.carFingerprint == CAR.KIA_CARNIVAL_HEV_4TH_GEN:
|
||||
self.STEER_DELTA_UP = 2
|
||||
self.STEER_DELTA_DOWN = 3
|
||||
else:
|
||||
self.STEER_DELTA_UP = 10
|
||||
self.STEER_DELTA_DOWN = 8
|
||||
self.STEER_DELTA_UP = 10
|
||||
self.STEER_DELTA_DOWN = 8
|
||||
else:
|
||||
self.STEER_DELTA_UP = 2
|
||||
self.STEER_DELTA_DOWN = 3
|
||||
@@ -117,7 +111,6 @@ class HyundaiSafetyFlags(IntFlag):
|
||||
|
||||
|
||||
class HyundaiStarPilotSafetyFlags(IntFlag):
|
||||
AOL_MAIN_LKAS_ON_ENGAGE = 128
|
||||
AOL_MAIN_LKAS_SYNC = 32
|
||||
HAS_LDA_BUTTON = 1024
|
||||
AOL_LKAS_ON_ENGAGE = 2048
|
||||
@@ -126,7 +119,6 @@ class HyundaiStarPilotSafetyFlags(IntFlag):
|
||||
class HyundaiStarPilotFlags(IntFlag):
|
||||
SPEED_LIMIT_AVAILABLE = 1
|
||||
MAIN_CRUISE_STATE_TRACKING = 2 ** 2
|
||||
HAS_LKAS12 = 2 ** 9
|
||||
|
||||
|
||||
class HyundaiFlags(IntFlag):
|
||||
@@ -948,11 +940,6 @@ class CAR(Platforms):
|
||||
HYUNDAI_KONA_EV.specs,
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS,
|
||||
)
|
||||
KIA_RAY_EV = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Kia Ray EV 2025", car_parts=CarParts.common([CarHarness.hyundai_h]))],
|
||||
CarSpecs(mass=1295, wheelbase=2.52, steerRatio=14.5),
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
KIA_CEED_PHEV_2022_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Kia Ceed Plug-in Hybrid Non-SCC 2022", car_parts=CarParts.common([CarHarness.hyundai_i]))],
|
||||
CarSpecs(mass=1650, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5),
|
||||
@@ -1000,10 +987,6 @@ 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()
|
||||
@@ -1018,10 +1001,6 @@ 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)
|
||||
@@ -1218,13 +1197,6 @@ 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 = {
|
||||
CAR.HYUNDAI_IONIQ_5_PE,
|
||||
CAR.HYUNDAI_IONIQ_6,
|
||||
CAR.KIA_EV9,
|
||||
CAR.GENESIS_GV60_EV_1ST_GEN,
|
||||
}
|
||||
RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | {
|
||||
CAR.HYUNDAI_IONIQ,
|
||||
|
||||
@@ -22,7 +22,7 @@ from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HondaFlags, Hond
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, ALT_BUS_LDA_BUTTON_CARS
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from opendbc.car.subaru.values import CAR as SUBARU, SUBARU_REDNECK_CRUISE_CARS, SubaruSafetyFlags
|
||||
from opendbc.car.subaru.values import CAR as SUBARU, SubaruSafetyFlags
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags
|
||||
from opendbc.car.values import PLATFORMS
|
||||
from opendbc.can import CANParser
|
||||
@@ -109,7 +109,6 @@ class RadarInterfaceBase(ABC):
|
||||
self.CP = CP
|
||||
self.rcp = None
|
||||
self.pts: dict[int, structs.RadarData.RadarPoint] = {}
|
||||
self.track_id: int = 0
|
||||
self.frame = 0
|
||||
|
||||
def update(self, can_packets: list[tuple[int, list[CanData]]]) -> structs.RadarDataT | None:
|
||||
@@ -233,9 +232,6 @@ class CarInterfaceBase(ABC):
|
||||
fp_ret.flags |= int(HondaStarPilotFlags.HAS_CAMERA_MESSAGES)
|
||||
|
||||
elif platform in HYUNDAI:
|
||||
if candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
|
||||
fp_ret.canUsePedal = True
|
||||
fp_ret.pcmCruiseSpeed = False
|
||||
if candidate in CANFD_CAR:
|
||||
hda2 = Ecu.adas in [fw.ecu for fw in car_fw]
|
||||
CAN = CanBus(None, fingerprint, bool(CP.flags & HyundaiFlags.CANFD_LKA_STEERING))
|
||||
@@ -244,19 +240,11 @@ class CarInterfaceBase(ABC):
|
||||
if 0x1FA in fingerprint[CAN.ECAN]:
|
||||
fp_ret.flags |= HyundaiStarPilotFlags.SPEED_LIMIT_AVAILABLE.value
|
||||
|
||||
if candidate != HYUNDAI.KIA_RAY_EV and not (CP.flags & HyundaiFlags.CANFD) and 0x53E in fingerprint[2]:
|
||||
fp_ret.flags |= HyundaiStarPilotFlags.HAS_LKAS12.value
|
||||
|
||||
fp_ret.redneckCruiseAvailable = (bool(CP.flags & HyundaiFlags.NON_SCC) and
|
||||
not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS) and
|
||||
not (candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED))
|
||||
fp_ret.redneckCruiseAvailable = bool(CP.flags & HyundaiFlags.NON_SCC) and not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
|
||||
if fp_ret.redneckCruiseAvailable and params.get_bool("RedneckCruise"):
|
||||
fp_ret.pcmCruiseSpeed = False
|
||||
CP.openpilotLongitudinalControl = True
|
||||
|
||||
if candidate == HYUNDAI.HYUNDAI_ELANTRA_HEV_2024 and CP.openpilotLongitudinalControl:
|
||||
fp_ret.flags |= HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING.value
|
||||
|
||||
hyundai_has_lda_button = not (CP.flags & HyundaiFlags.CANFD) and (
|
||||
0x391 in fingerprint[0] or
|
||||
0x50C in fingerprint[0] or
|
||||
@@ -269,16 +257,8 @@ class CarInterfaceBase(ABC):
|
||||
if getattr(starpilot_toggles, "always_on_lateral_lkas", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
|
||||
if candidate in (HYUNDAI.HYUNDAI_ELANTRA_HEV_2024, HYUNDAI.HYUNDAI_SONATA_HYBRID) and \
|
||||
getattr(starpilot_toggles, "always_on_lateral_main", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
|
||||
if candidate == HYUNDAI.HYUNDAI_SONATA_HYBRID:
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
|
||||
# The refresh Elantra's safety mapping comes from the resolved Galaxy
|
||||
# toggle above, not from this legacy persisted-parameter fallback.
|
||||
if candidate != HYUNDAI.HYUNDAI_ELANTRA_HEV_2024 and \
|
||||
params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9:
|
||||
# LKASButtonControl == 9 means BUTTON_FUNCTIONS["AOL_TOGGLE"] in starpilot_variables.
|
||||
if params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9:
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
|
||||
if candidate == HYUNDAI.HYUNDAI_SONATA_HYBRID and getattr(starpilot_toggles, "always_on_lateral_lkas", False) and \
|
||||
@@ -306,14 +286,6 @@ class CarInterfaceBase(ABC):
|
||||
if getattr(starpilot_toggles, "subaru_sng", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value
|
||||
|
||||
fp_ret.redneckCruiseAvailable = candidate in SUBARU_REDNECK_CRUISE_CARS
|
||||
if fp_ret.redneckCruiseAvailable and params.get_bool("SubaruRedneckCruise") and \
|
||||
not CP.openpilotLongitudinalControl:
|
||||
fp_ret.pcmCruiseSpeed = False
|
||||
CP.openpilotLongitudinalControl = True
|
||||
CP.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
|
||||
|
||||
return fp_ret
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -4,7 +4,7 @@ from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, structs
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_std_steer_angle_limits, apply_steer_angle_limits_vm, common_fault_avoidance
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.subaru import subarucan
|
||||
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, CanBus, CarControllerParams, SubaruFlags
|
||||
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
|
||||
@@ -16,19 +16,24 @@ _SNG_ACC_MIN_DIST = 3
|
||||
_SNG_ACC_MAX_DIST = 4.5
|
||||
_LEGACY_2025_MADS_MIN_SPEED = 0.44704
|
||||
_LEGACY_2025_MADS_MAX_STEER_ANGLE = 120.0
|
||||
_ANGLE_OVERRIDE_CONFIRM_FRAMES = 2
|
||||
_LEGACY_2025_OVERRIDE_HOLD_FRAMES = 10
|
||||
_LEGACY_2025_REENGAGE_SETTLE_FRAMES = 8
|
||||
_LEGACY_2025_REENGAGE_MAX_STEER_RATE = 2.0
|
||||
_LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA = 1.0
|
||||
_LEGACY_2025_RECLAIM_FRAMES = 36
|
||||
_LEGACY_2025_RECLAIM_EXPONENT = 2.5
|
||||
_ANGLE_OVERRIDE_HOLD_FRAMES = 10
|
||||
_ANGLE_REENGAGE_SETTLE_FRAMES = 8
|
||||
_ANGLE_REENGAGE_MAX_STEER_RATE = 2.0
|
||||
_ANGLE_REENGAGE_MAX_ANGLE_DELTA = 1.0
|
||||
_ANGLE_RECLAIM_FRAMES = 36
|
||||
_ANGLE_RECLAIM_EXPONENT = 2.5
|
||||
_ANGLE_MADS_MIN_SPEED = 0.44704
|
||||
_ANGLE_MADS_MAX_STEER_ANGLE = 120.0
|
||||
_ASCENT_AOL_ARM_FRAMES = 30
|
||||
_STOP_START_STARTUP_DELAY_FRAMES = 100
|
||||
# StarPilot's first populated toggle message can arrive several seconds after
|
||||
# the car controller starts while fingerprinting and settings settle.
|
||||
_STOP_START_STARTUP_DEADLINE_FRAMES = 1000
|
||||
_STOP_START_STARTUP_DEADLINE_FRAMES = 300
|
||||
_STOP_START_PULSE_FRAMES = 30
|
||||
_STOP_START_PULSE_PERIOD_FRAMES = 5
|
||||
_REDNECK_BUTTON_INTERVAL_FRAMES = 10
|
||||
_REDNECK_BUTTON_COPIES = 2
|
||||
|
||||
|
||||
def get_safety_CP():
|
||||
@@ -42,10 +47,20 @@ class CarController(CarControllerBase):
|
||||
self.apply_torque_last = 0
|
||||
self.apply_steer_last = 0
|
||||
self.driver_override = False
|
||||
self.angle_override_confirm_frames = 0
|
||||
self.legacy_2025_lkas_active = False
|
||||
self.legacy_2025_handoff_active = False
|
||||
self.legacy_2025_override_hold_frames = 0
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
self.legacy_2025_reengage_reference_angle = 0.0
|
||||
self.legacy_2025_reclaim_frames = 0
|
||||
self.legacy_2025_reclaim_start_angle = 0.0
|
||||
self.angle_lkas_active = False
|
||||
self.angle_handoff_active = False
|
||||
self.ascent_aol_arm_frames = 0
|
||||
self.angle_override_hold_frames = 0
|
||||
self.angle_reengage_settle_frames = 0
|
||||
self.angle_reengage_reference_angle = 0.0
|
||||
self.angle_reclaim_frames = 0
|
||||
self.angle_reclaim_start_angle = 0.0
|
||||
|
||||
self.cruise_button_prev = 0
|
||||
self.steer_rate_counter = 0
|
||||
@@ -56,7 +71,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 and CP.carFingerprint != CAR.SUBARU_OUTBACK_2023:
|
||||
if CP.flags & SubaruFlags.LKAS_ANGLE:
|
||||
self.VM = VehicleModel(get_safety_CP())
|
||||
|
||||
self.prev_close_distance = 0
|
||||
@@ -68,15 +83,14 @@ class CarController(CarControllerBase):
|
||||
self.stop_start_initial_state = None
|
||||
self.stop_start_counter = 0
|
||||
self.stop_start_acknowledged = False
|
||||
self.last_redneck_button_frame = 0
|
||||
|
||||
def _stop_start_off_request(self, CC, CS, starpilot_toggles):
|
||||
"""Send one bounded Subaru Stop/Start OFF request after ignition.
|
||||
"""Send one bounded Outback Stop/Start OFF request after ignition.
|
||||
|
||||
This is intentionally opt-in and limited to a stationary vehicle in
|
||||
Park/Neutral. A single ignition session gets at most one attempt.
|
||||
"""
|
||||
if self.CP.carFingerprint not in SUBARU_STOP_START_CARS or \
|
||||
if self.CP.carFingerprint != CAR.SUBARU_OUTBACK_2023 or \
|
||||
not getattr(starpilot_toggles, "subaru_stop_start_off", False) or self.stop_start_attempted:
|
||||
return None
|
||||
|
||||
@@ -119,64 +133,136 @@ class CarController(CarControllerBase):
|
||||
return None
|
||||
|
||||
msg = subarucan.create_stop_start_control(
|
||||
self.packer, dashlights_msg, raw_dat=getattr(CS, "dashlights_dat", None),
|
||||
counter=self.stop_start_counter, bus=CanBus.alt_for_cp(self.CP),
|
||||
self.packer, dashlights_msg, counter=self.stop_start_counter, bus=self.main_bus,
|
||||
)
|
||||
self.stop_start_counter = (self.stop_start_counter + 1) % 0x10
|
||||
return msg
|
||||
|
||||
def _reset_legacy_2025_handoff(self):
|
||||
self.legacy_2025_handoff_active = False
|
||||
self.legacy_2025_override_hold_frames = 0
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
self.legacy_2025_reengage_reference_angle = 0.0
|
||||
self.legacy_2025_reclaim_frames = 0
|
||||
self.legacy_2025_reclaim_start_angle = 0.0
|
||||
|
||||
def _legacy_2025_manual_handoff(self, CS, lkas_available):
|
||||
if not lkas_available:
|
||||
self._reset_legacy_2025_handoff()
|
||||
return False
|
||||
|
||||
if getattr(CS.out, "steeringPressed", False):
|
||||
self.legacy_2025_handoff_active = True
|
||||
self.legacy_2025_override_hold_frames = _LEGACY_2025_OVERRIDE_HOLD_FRAMES
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
self.legacy_2025_reclaim_frames = 0
|
||||
return True
|
||||
|
||||
if not self.legacy_2025_handoff_active and not self.legacy_2025_lkas_active and \
|
||||
abs(getattr(CS.out, "steeringRateDeg", 0.0)) > _LEGACY_2025_REENGAGE_MAX_STEER_RATE:
|
||||
self.legacy_2025_handoff_active = True
|
||||
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
if not self.legacy_2025_handoff_active:
|
||||
return False
|
||||
|
||||
if self.legacy_2025_override_hold_frames > 0:
|
||||
self.legacy_2025_override_hold_frames -= 1
|
||||
if self.legacy_2025_override_hold_frames == 0:
|
||||
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
wheel_stable = abs(getattr(CS.out, "steeringRateDeg", 0.0)) <= _LEGACY_2025_REENGAGE_MAX_STEER_RATE and \
|
||||
abs(CS.out.steeringAngleDeg - self.legacy_2025_reengage_reference_angle) <= _LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA
|
||||
if wheel_stable:
|
||||
self.legacy_2025_reengage_settle_frames += 1
|
||||
else:
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
if self.legacy_2025_reengage_settle_frames < _LEGACY_2025_REENGAGE_SETTLE_FRAMES:
|
||||
return True
|
||||
|
||||
self.legacy_2025_handoff_active = False
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
self.legacy_2025_reclaim_frames = _LEGACY_2025_RECLAIM_FRAMES
|
||||
self.legacy_2025_reclaim_start_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
def _legacy_2025_reclaim_target(self, target_angle):
|
||||
if self.legacy_2025_reclaim_frames <= 0:
|
||||
return target_angle
|
||||
|
||||
progress = (_LEGACY_2025_RECLAIM_FRAMES - self.legacy_2025_reclaim_frames + 1) / _LEGACY_2025_RECLAIM_FRAMES
|
||||
eased_progress = progress ** _LEGACY_2025_RECLAIM_EXPONENT
|
||||
target_angle = self.legacy_2025_reclaim_start_angle + eased_progress * \
|
||||
(target_angle - self.legacy_2025_reclaim_start_angle)
|
||||
self.legacy_2025_reclaim_frames -= 1
|
||||
return target_angle
|
||||
|
||||
def _reset_angle_handoff(self):
|
||||
self.driver_override = False
|
||||
self.angle_override_confirm_frames = 0
|
||||
self.angle_handoff_active = False
|
||||
self.angle_override_hold_frames = 0
|
||||
self.angle_reengage_settle_frames = 0
|
||||
self.angle_reengage_reference_angle = 0.0
|
||||
self.angle_reclaim_frames = 0
|
||||
self.angle_reclaim_start_angle = 0.0
|
||||
|
||||
def _angle_manual_handoff(self, CS, lat_active):
|
||||
if not lat_active:
|
||||
self._reset_angle_handoff()
|
||||
return False
|
||||
|
||||
driver_override = self._update_angle_driver_override(CS)
|
||||
steering_rate = abs(getattr(CS.out, "steeringRateDeg", 0.0))
|
||||
if driver_override:
|
||||
if getattr(CS.out, "steeringPressed", False):
|
||||
self.angle_handoff_active = True
|
||||
self.angle_override_hold_frames = _ANGLE_OVERRIDE_HOLD_FRAMES
|
||||
self.angle_reengage_settle_frames = 0
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
self.angle_reclaim_frames = 0
|
||||
return True
|
||||
|
||||
if self.angle_handoff_active:
|
||||
if steering_rate > _ANGLE_REENGAGE_MAX_STEER_RATE:
|
||||
return True
|
||||
|
||||
self.angle_handoff_active = False
|
||||
return True
|
||||
|
||||
if not self.angle_lkas_active and steering_rate > _ANGLE_REENGAGE_MAX_STEER_RATE:
|
||||
if not self.angle_handoff_active and not self.angle_lkas_active and \
|
||||
abs(getattr(CS.out, "steeringRateDeg", 0.0)) > _ANGLE_REENGAGE_MAX_STEER_RATE:
|
||||
self.angle_handoff_active = True
|
||||
return True
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
return False
|
||||
|
||||
def _update_angle_driver_override(self, CS):
|
||||
"""Debounce the higher-confidence raw torque override signal for angle cars."""
|
||||
abs_torque = abs(getattr(CS.out, "steeringTorque", 0.0))
|
||||
if self.driver_override:
|
||||
if abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
|
||||
self.driver_override = False
|
||||
elif abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
|
||||
self.angle_override_confirm_frames += 1
|
||||
if self.angle_override_confirm_frames >= _ANGLE_OVERRIDE_CONFIRM_FRAMES:
|
||||
self.driver_override = True
|
||||
self.angle_override_confirm_frames = 0
|
||||
else:
|
||||
self.angle_override_confirm_frames = 0
|
||||
|
||||
return self.driver_override
|
||||
|
||||
def _ascent_aol_ready(self, ready):
|
||||
if not ready:
|
||||
self.ascent_aol_arm_frames = 0
|
||||
if not self.angle_handoff_active:
|
||||
return False
|
||||
|
||||
self.ascent_aol_arm_frames = min(self.ascent_aol_arm_frames + 1, _ASCENT_AOL_ARM_FRAMES)
|
||||
return self.ascent_aol_arm_frames >= _ASCENT_AOL_ARM_FRAMES
|
||||
if self.angle_override_hold_frames > 0:
|
||||
self.angle_override_hold_frames -= 1
|
||||
if self.angle_override_hold_frames == 0:
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
wheel_stable = abs(getattr(CS.out, "steeringRateDeg", 0.0)) <= _ANGLE_REENGAGE_MAX_STEER_RATE and \
|
||||
abs(CS.out.steeringAngleDeg - self.angle_reengage_reference_angle) <= _ANGLE_REENGAGE_MAX_ANGLE_DELTA
|
||||
if wheel_stable:
|
||||
self.angle_reengage_settle_frames += 1
|
||||
else:
|
||||
self.angle_reengage_settle_frames = 0
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
if self.angle_reengage_settle_frames < _ANGLE_REENGAGE_SETTLE_FRAMES:
|
||||
return True
|
||||
|
||||
self.angle_handoff_active = False
|
||||
self.angle_reengage_settle_frames = 0
|
||||
self.angle_reclaim_frames = _ANGLE_RECLAIM_FRAMES
|
||||
self.angle_reclaim_start_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
def _angle_reclaim_target(self, target_angle):
|
||||
if self.angle_reclaim_frames <= 0:
|
||||
return target_angle
|
||||
|
||||
progress = (_ANGLE_RECLAIM_FRAMES - self.angle_reclaim_frames + 1) / _ANGLE_RECLAIM_FRAMES
|
||||
eased_progress = progress ** _ANGLE_RECLAIM_EXPONENT
|
||||
target_angle = self.angle_reclaim_start_angle + eased_progress * \
|
||||
(target_angle - self.angle_reclaim_start_angle)
|
||||
self.angle_reclaim_frames -= 1
|
||||
return target_angle
|
||||
|
||||
def lateral_angle(self, CC, CS):
|
||||
if self.CP.carFingerprint == CAR.SUBARU_LEGACY_2025:
|
||||
@@ -186,11 +272,12 @@ 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._legacy_2025_manual_handoff(CS, lkas_available)
|
||||
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
|
||||
apply_steer = apply_std_steer_angle_limits(
|
||||
CC.actuators.steeringAngleDeg,
|
||||
steer_target,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
@@ -198,7 +285,7 @@ class CarController(CarControllerBase):
|
||||
self.p.LEGACY_2025_ANGLE_LIMITS,
|
||||
)
|
||||
self.apply_steer_last = apply_steer
|
||||
self.angle_lkas_active = lkas_active
|
||||
self.legacy_2025_lkas_active = lkas_active
|
||||
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
|
||||
|
||||
if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
|
||||
@@ -207,34 +294,43 @@ class CarController(CarControllerBase):
|
||||
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
|
||||
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
|
||||
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023:
|
||||
if mads_only:
|
||||
cruise_available = getattr(getattr(CS.out, "cruiseState", None), "available", True)
|
||||
lkas_available = self._ascent_aol_ready(lkas_available and cruise_available)
|
||||
else:
|
||||
self.ascent_aol_arm_frames = _ASCENT_AOL_ARM_FRAMES if lkas_available else 0
|
||||
|
||||
if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023:
|
||||
manual_handoff = False
|
||||
else:
|
||||
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
|
||||
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
|
||||
lkas_active = lkas_available and not manual_handoff
|
||||
|
||||
if lkas_active and not self.angle_lkas_active:
|
||||
self.apply_steer_last = CS.out.steeringAngleDeg
|
||||
|
||||
apply_steer = apply_std_steer_angle_limits(
|
||||
CC.actuators.steeringAngleDeg,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
lkas_active,
|
||||
self.p.FIXED_ANGLE_LIMITS,
|
||||
)
|
||||
steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
|
||||
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023:
|
||||
apply_steer = apply_std_steer_angle_limits(
|
||||
steer_target,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
lkas_active,
|
||||
self.p.FIXED_ANGLE_LIMITS,
|
||||
)
|
||||
else:
|
||||
apply_steer = apply_steer_angle_limits_vm(
|
||||
steer_target,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
lkas_active,
|
||||
self.p,
|
||||
self.VM,
|
||||
)
|
||||
self.apply_steer_last = apply_steer
|
||||
self.angle_lkas_active = lkas_active
|
||||
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
|
||||
|
||||
abs_torque = abs(CS.out.steeringTorque)
|
||||
if abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
|
||||
self.driver_override = True
|
||||
elif abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
|
||||
self.driver_override = False
|
||||
|
||||
mads_only = CC.latActive and not getattr(CC, "enabled", False)
|
||||
mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \
|
||||
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
|
||||
@@ -245,8 +341,9 @@ class CarController(CarControllerBase):
|
||||
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
|
||||
steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lat_active else CC.actuators.steeringAngleDeg
|
||||
apply_steer = apply_steer_angle_limits_vm(
|
||||
CC.actuators.steeringAngleDeg,
|
||||
steer_target,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
@@ -287,19 +384,10 @@ class CarController(CarControllerBase):
|
||||
|
||||
return subarucan.create_steering_control(self.packer, apply_torque, apply_steer_req)
|
||||
|
||||
def _lkas_status_active(self, CC):
|
||||
if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
|
||||
return self.angle_lkas_active
|
||||
return CC.latActive
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
actuators = CC.actuators
|
||||
hud_control = CC.hudControl
|
||||
pcm_cancel_cmd = CC.cruiseControl.cancel
|
||||
subaru_redneck_cruise = bool(
|
||||
self.CP.carFingerprint == CAR.SUBARU_IMPREZA_2020 and
|
||||
getattr(starpilot_toggles, "subaru_redneck_cruise", False)
|
||||
)
|
||||
|
||||
can_sends = []
|
||||
|
||||
@@ -364,15 +452,12 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled,
|
||||
self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise,
|
||||
CC.longActive, hud_control.leadVisible,
|
||||
self.CP.openpilotLongitudinalControl, CC.longActive, hud_control.leadVisible,
|
||||
self.status_bus))
|
||||
|
||||
can_sends.append(subarucan.create_es_lkas_state(
|
||||
self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert,
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus,
|
||||
))
|
||||
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive, hud_control.visualAlert,
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus))
|
||||
|
||||
if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT:
|
||||
can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg,
|
||||
@@ -385,7 +470,7 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg,
|
||||
speed_cmd, pcm_cancel_cmd))
|
||||
|
||||
if self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise:
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
if self.frame % 5 == 0:
|
||||
can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg,
|
||||
self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm))
|
||||
@@ -401,20 +486,6 @@ class CarController(CarControllerBase):
|
||||
bus = CanBus.alt_for_cp(self.CP) if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else self.main_bus
|
||||
can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd))
|
||||
|
||||
if subaru_redneck_cruise:
|
||||
redneck_button = {
|
||||
1: subarucan.CRUISE_BUTTON_RESUME,
|
||||
2: subarucan.CRUISE_BUTTON_SET,
|
||||
}.get(getattr(CS, "redneck_send_button", 0))
|
||||
cruise_buttons_msg = getattr(CS, "cruise_buttons_msg", None)
|
||||
if redneck_button and cruise_buttons_msg and self.frame - self.last_redneck_button_frame >= _REDNECK_BUTTON_INTERVAL_FRAMES:
|
||||
counter = (int(cruise_buttons_msg["COUNTER"]) + 1) % 0x10
|
||||
for copy_idx in range(_REDNECK_BUTTON_COPIES):
|
||||
can_sends.append(subarucan.create_cruise_buttons(
|
||||
self.packer, counter + copy_idx, cruise_buttons_msg, redneck_button, self.main_bus,
|
||||
))
|
||||
self.last_redneck_button_frame = self.frame
|
||||
|
||||
if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT:
|
||||
# Tester present (keeps eyesight disabled)
|
||||
if self.frame % 100 == 0:
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import copy
|
||||
from cereal import custom
|
||||
from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, create_button_events, structs
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.subaru.values import DBC, CanBus, SUBARU_REDNECK_CRUISE_CARS, SUBARU_STOP_START_CARS, SubaruFlags
|
||||
from opendbc.car.subaru.values import CAR, DBC, CanBus, SubaruFlags
|
||||
from opendbc.car import CanSignalRateCalculator
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
|
||||
SUBARU_CRUISE_BUTTONS = {
|
||||
"Main": ButtonType.mainCruise,
|
||||
"Set": ButtonType.decelCruise,
|
||||
"Resume": ButtonType.accelCruise,
|
||||
}
|
||||
|
||||
|
||||
class CarState(CarStateBase):
|
||||
def __init__(self, CP, FPCP):
|
||||
@@ -24,10 +16,7 @@ class CarState(CarStateBase):
|
||||
|
||||
self.angle_rate_calulator = CanSignalRateCalculator(50)
|
||||
self.dashlights_msg = {}
|
||||
self.dashlights_dat = b""
|
||||
self.stop_start_state = 0
|
||||
self.cruise_buttons_msg = {}
|
||||
self.cruise_buttons = {button: 0 for button in SUBARU_CRUISE_BUTTONS}
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
@@ -37,11 +26,9 @@ class CarState(CarStateBase):
|
||||
cp_angle = cp_main if self.CP.flags & SubaruFlags.D_PLATFORM else cp
|
||||
ret = structs.CarState()
|
||||
|
||||
if self.CP.carFingerprint in SUBARU_STOP_START_CARS:
|
||||
stop_start_cp = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
|
||||
self.dashlights_msg = copy.copy(stop_start_cp.vl["Dashlights"])
|
||||
self.dashlights_dat = stop_start_cp.vl_raw["Dashlights"]
|
||||
self.stop_start_state = stop_start_cp.vl["Engine_Stop_Start"]["STOP_START_STATE"]
|
||||
if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023:
|
||||
self.dashlights_msg = copy.copy(cp.vl["Dashlights"])
|
||||
self.stop_start_state = cp.vl["Engine_Stop_Start"]["STOP_START_STATE"]
|
||||
|
||||
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"]
|
||||
ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5
|
||||
@@ -85,14 +72,14 @@ class CarState(CarStateBase):
|
||||
|
||||
if self.CP.flags & SubaruFlags.LKAS_ANGLE:
|
||||
ret.steeringAngleDeg = cp_angle.vl["Steering_2"]["Steering_Angle"]
|
||||
steering_counter = cp_angle.vl["Steering_2"]["COUNTER"]
|
||||
steering_updated = len(cp_angle.vl_all["Steering_2"]["Steering_Angle"]) > 0
|
||||
else:
|
||||
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]
|
||||
steering_counter = cp.vl["Steering_Torque"].get("COUNTER", 0)
|
||||
steering_updated = len(cp.vl_all["Steering_Torque"]["Steering_Angle"]) > 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_counter)
|
||||
ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, steering_updated)
|
||||
|
||||
ret.steeringTorque = cp_angle.vl["Steering_Torque"]["Steer_Torque_Sensor"]
|
||||
ret.steeringTorqueEps = cp_angle.vl["Steering_Torque"]["Steer_Torque_Output"]
|
||||
@@ -146,17 +133,6 @@ class CarState(CarStateBase):
|
||||
self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"])
|
||||
self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"])
|
||||
|
||||
if self.CP.carFingerprint in SUBARU_REDNECK_CRUISE_CARS:
|
||||
cruise_buttons = cp.vl["Cruise_Buttons"]
|
||||
if getattr(starpilot_toggles, "subaru_redneck_cruise", False):
|
||||
ret.buttonEvents = []
|
||||
for button, button_type in SUBARU_CRUISE_BUTTONS.items():
|
||||
ret.buttonEvents.extend(create_button_events(
|
||||
int(bool(cruise_buttons[button])), self.cruise_buttons[button], {1: button_type},
|
||||
))
|
||||
self.cruise_buttons = {button: int(bool(cruise_buttons[button])) for button in SUBARU_CRUISE_BUTTONS}
|
||||
self.cruise_buttons_msg = copy.copy(cruise_buttons)
|
||||
|
||||
if not (self.CP.flags & SubaruFlags.HYBRID):
|
||||
self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from opendbc.car.disable_ecu import disable_ecu
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.subaru.carcontroller import CarController
|
||||
from opendbc.car.subaru.carstate import CarState
|
||||
from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, SubaruFlags, SubaruSafetyFlags
|
||||
from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SubaruFlags, SubaruSafetyFlags
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@@ -40,9 +40,9 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM.value
|
||||
if ret.flags & SubaruFlags.D_PLATFORM_CAMERA:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value
|
||||
if candidate in SUBARU_STOP_START_CARS:
|
||||
if candidate == CAR.SUBARU_OUTBACK_2023:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
|
||||
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
|
||||
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
|
||||
@@ -3,10 +3,6 @@ from opendbc.car.subaru.values import CanBus
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
|
||||
CRUISE_BUTTON_MAIN = 1
|
||||
CRUISE_BUTTON_SET = 2
|
||||
CRUISE_BUTTON_RESUME = 3
|
||||
|
||||
|
||||
def create_steering_control(packer, apply_torque, steer_req):
|
||||
values = {
|
||||
@@ -71,19 +67,6 @@ def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long
|
||||
return packer.make_can_msg("ES_Distance", bus, values)
|
||||
|
||||
|
||||
def create_cruise_buttons(packer, frame, cruise_buttons_msg, button, bus=CanBus.main):
|
||||
values = {s: cruise_buttons_msg[s] for s in [
|
||||
"CHECKSUM",
|
||||
"Signal1",
|
||||
"Signal2",
|
||||
]}
|
||||
values["COUNTER"] = frame % 0x10
|
||||
values["Main"] = button == CRUISE_BUTTON_MAIN
|
||||
values["Set"] = button == CRUISE_BUTTON_SET
|
||||
values["Resume"] = button == CRUISE_BUTTON_RESUME
|
||||
return packer.make_can_msg("Cruise_Buttons", bus, values)
|
||||
|
||||
|
||||
def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart,
|
||||
bus=CanBus.main):
|
||||
values = {s: es_lkas_state_msg[s] for s in [
|
||||
@@ -199,24 +182,12 @@ def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, l
|
||||
return packer.make_can_msg("ES_DashStatus", bus, values)
|
||||
|
||||
|
||||
def create_stop_start_control(packer, dashlights_msg, raw_dat=None, counter=None, bus=CanBus.alt):
|
||||
"""Create the supported Subaru momentary Stop/Start button request.
|
||||
def create_stop_start_control(packer, dashlights_msg, counter=None, bus=CanBus.alt):
|
||||
"""Create the Outback 2023-24 momentary Stop/Start button request.
|
||||
|
||||
Dashlights is a stock periodic message, so preserve the live frame and only
|
||||
change the counter, event bit, and checksum. The raw frame is needed because
|
||||
the DBC does not describe every byte in this message.
|
||||
change the event bit. CANPacker calculates the Subaru checksum for us.
|
||||
"""
|
||||
if raw_dat:
|
||||
dat = bytearray(raw_dat)
|
||||
if len(dat) != 8:
|
||||
raise ValueError(f"Dashlights frame must be 8 bytes, got {len(dat)}")
|
||||
if counter is None:
|
||||
counter = (int(dashlights_msg.get("COUNTER", 0)) + 1) % 0x10
|
||||
dat[1] = (dat[1] & 0xF0) | (counter % 0x10)
|
||||
dat[6] |= 0x40 # STOP_START, big-endian bit 54
|
||||
dat[0] = ((0x390 & 0xFF) + ((0x390 >> 8) & 0xFF) + sum(dat[1:])) & 0xFF
|
||||
return 0x390, bytes(dat), bus
|
||||
|
||||
values = dict(dashlights_msg)
|
||||
if counter is None:
|
||||
counter = (int(values.get("COUNTER", 0)) + 1) % 0x10
|
||||
|
||||
@@ -5,10 +5,10 @@ from types import SimpleNamespace
|
||||
import pytest
|
||||
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, fw_versions, gen_empty_fingerprint, structs
|
||||
from opendbc.car import Bus, fw_versions, structs
|
||||
from opendbc.car.fw_query_definitions import StdQueries
|
||||
from opendbc.car.subaru import subarucan
|
||||
from opendbc.car.subaru.carcontroller import CarController, _ASCENT_AOL_ARM_FRAMES
|
||||
from opendbc.car.subaru.carcontroller import CarController
|
||||
from opendbc.car.subaru.carstate import CarState
|
||||
from opendbc.car.subaru.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.fw_versions import match_fw_to_car
|
||||
@@ -67,56 +67,6 @@ def test_preglobal_sng_does_not_send_standstill_keepalive_without_manual_toggle(
|
||||
assert speed_cmd is False
|
||||
|
||||
|
||||
def test_redneck_cruise_buttons_use_resume_for_increase_and_set_for_decrease():
|
||||
dbc = DBC[CAR.SUBARU_IMPREZA_2020][Bus.pt]
|
||||
packer = CANPacker(dbc)
|
||||
parser = CANParser(dbc, [("Cruise_Buttons", 0)], CanBus.main)
|
||||
stock_buttons = defaultdict(int)
|
||||
|
||||
resume_msg = subarucan.create_cruise_buttons(
|
||||
packer, 1, stock_buttons, subarucan.CRUISE_BUTTON_RESUME, CanBus.main,
|
||||
)
|
||||
parser.update([(1, [resume_msg])])
|
||||
assert parser.vl["Cruise_Buttons"]["Resume"] == 1
|
||||
assert parser.vl["Cruise_Buttons"]["Set"] == 0
|
||||
|
||||
set_msg = subarucan.create_cruise_buttons(
|
||||
packer, 2, stock_buttons, subarucan.CRUISE_BUTTON_SET, CanBus.main,
|
||||
)
|
||||
parser.update([(2, [set_msg])])
|
||||
assert parser.vl["Cruise_Buttons"]["Resume"] == 0
|
||||
assert parser.vl["Cruise_Buttons"]["Set"] == 1
|
||||
|
||||
|
||||
def test_redneck_cruise_is_only_available_on_the_experimental_impreza(monkeypatch):
|
||||
class FakeParams:
|
||||
def __init__(self, **_kwargs):
|
||||
pass
|
||||
|
||||
def get_bool(self, key):
|
||||
return key == "SubaruRedneckCruise"
|
||||
|
||||
monkeypatch.setattr("opendbc.car.interfaces.Params", FakeParams)
|
||||
toggles = SimpleNamespace(subaru_sng=False)
|
||||
|
||||
impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA_2020)
|
||||
impreza_fpcp = CarInterface.get_starpilot_params(
|
||||
CAR.SUBARU_IMPREZA_2020, gen_empty_fingerprint(), [], impreza_cp, toggles,
|
||||
)
|
||||
assert impreza_fpcp.redneckCruiseAvailable
|
||||
assert not impreza_fpcp.pcmCruiseSpeed
|
||||
assert impreza_cp.openpilotLongitudinalControl
|
||||
assert impreza_cp.safetyConfigs[0].safetyParam & SubaruSafetyFlags.REDNECK_CRUISE
|
||||
|
||||
old_impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA)
|
||||
old_impreza_fpcp = CarInterface.get_starpilot_params(
|
||||
CAR.SUBARU_IMPREZA, gen_empty_fingerprint(), [], old_impreza_cp, toggles,
|
||||
)
|
||||
assert not old_impreza_fpcp.redneckCruiseAvailable
|
||||
assert old_impreza_fpcp.pcmCruiseSpeed
|
||||
assert not old_impreza_cp.openpilotLongitudinalControl
|
||||
|
||||
|
||||
class TestSubaruFingerprint:
|
||||
def test_eyesight_queries_do_not_change_diagnostic_state(self, monkeypatch):
|
||||
camera_requests = [request for request in FW_QUERY_CONFIG.requests if CarParams.Ecu.fwdCamera in request.whitelist_ecus]
|
||||
@@ -244,7 +194,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 CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
|
||||
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.LEGACY_2025_ANGLE_LIMITS)
|
||||
assert CanBus.main_for_cp(CP) == CanBus.alt
|
||||
assert CanBus.angle_for_cp(CP) == CanBus.main
|
||||
assert parsers[Bus.pt].bus == CanBus.alt
|
||||
@@ -256,32 +206,10 @@ def test_outback_2023_uses_d_platform_bus_layout():
|
||||
assert CP.lateralSmoothSeconds == pytest.approx(0.4)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", [CAR.SUBARU_OUTBACK_2023, CAR.SUBARU_LEGACY_2025])
|
||||
def test_stop_start_inputs_are_captured_for_supported_models(platform):
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
car_state = CarState(CP, None)
|
||||
parsers = car_state.get_can_parsers(CP)
|
||||
raw_dashlights = bytes.fromhex("13031407875a8100")
|
||||
parsers[Bus.alt].vl["Dashlights"]["COUNTER"] = 6
|
||||
parsers[Bus.alt].vl["Dashlights"]["STOP_START"] = 0
|
||||
parsers[Bus.alt].vl["Engine_Stop_Start"]["STOP_START_STATE"] = 3
|
||||
parsers[Bus.alt].vl_raw["Dashlights"] = raw_dashlights
|
||||
|
||||
car_state.update(parsers, SimpleNamespace(subaru_sng=False))
|
||||
|
||||
assert car_state.dashlights_msg["COUNTER"] == 6
|
||||
assert car_state.dashlights_dat == raw_dashlights
|
||||
assert car_state.stop_start_state == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform, expected_bus, start_frame", [
|
||||
(CAR.SUBARU_OUTBACK_2023, CanBus.alt, 101),
|
||||
(CAR.SUBARU_LEGACY_2025, CanBus.alt, 401),
|
||||
])
|
||||
def test_stop_start_request_is_bounded_and_uses_live_dashlights(platform, expected_bus, start_frame):
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
def test_stop_start_request_is_bounded_and_uses_live_dashlights():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023)
|
||||
controller = CarController({}, CP)
|
||||
controller.frame = start_frame
|
||||
controller.frame = 101
|
||||
|
||||
class TestActuators:
|
||||
steeringAngleDeg = 0.0
|
||||
@@ -300,7 +228,6 @@ def test_stop_start_request_is_bounded_and_uses_live_dashlights(platform, expect
|
||||
CS = SimpleNamespace(
|
||||
canValid=True,
|
||||
dashlights_msg={"COUNTER": 6, "STOP_START": 0},
|
||||
dashlights_dat=bytes.fromhex("13061407875a8100"),
|
||||
stop_start_state=0,
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
@@ -312,10 +239,9 @@ def test_stop_start_request_is_bounded_and_uses_live_dashlights(platform, expect
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
stop_start_msgs = [msg for msg in can_sends if msg[0] == 0x390]
|
||||
assert len(stop_start_msgs) == 1
|
||||
assert stop_start_msgs[0][2] == expected_bus
|
||||
assert stop_start_msgs[0][1] == bytes.fromhex("57071407875ac100")
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("Dashlights", 0)], expected_bus)
|
||||
parser.update([(expected_bus, [stop_start_msgs[0]])])
|
||||
assert stop_start_msgs[0][2] == CanBus.alt
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("Dashlights", 0)], CanBus.alt)
|
||||
parser.update([(1, [stop_start_msgs[0]])])
|
||||
assert parser.vl["Dashlights"]["STOP_START"] == 1
|
||||
assert parser.vl["Dashlights"]["COUNTER"] == 7
|
||||
|
||||
@@ -336,7 +262,6 @@ def test_legacy_2025_uses_gen2_angle_bus_layout():
|
||||
assert not (CP.flags & SubaruFlags.D_PLATFORM_CAMERA)
|
||||
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA)
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
|
||||
assert CanBus.main_for_cp(CP) == CanBus.main
|
||||
assert CanBus.angle_for_cp(CP) == CanBus.main
|
||||
assert parsers[Bus.pt].bus == CanBus.main
|
||||
@@ -414,7 +339,7 @@ def test_legacy_2025_engagement_continues_from_last_sent_angle():
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(0.47)
|
||||
|
||||
|
||||
def test_legacy_2025_reengages_immediately_after_manual_steering_stops():
|
||||
def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(
|
||||
@@ -426,7 +351,6 @@ def test_legacy_2025_reengages_immediately_after_manual_steering_stops():
|
||||
vEgoRaw=6.2,
|
||||
steeringAngleDeg=-121.55,
|
||||
steeringRateDeg=350.0,
|
||||
steeringTorque=250.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -435,29 +359,47 @@ def test_legacy_2025_reengages_immediately_after_manual_steering_stops():
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.steeringPressed = False
|
||||
CS.out.steeringAngleDeg = -113.78
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.steeringPressed = False
|
||||
CS.out.steeringTorque = 0.0
|
||||
CS.out.steeringAngleDeg = -113.78
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(3, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
for i in range(9):
|
||||
CS.out.steeringAngleDeg += 0.5
|
||||
CS.out.steeringRateDeg = 20.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(3 + i, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
for i in range(6):
|
||||
if i % 2:
|
||||
CS.out.steeringAngleDeg += 0.5
|
||||
CS.out.steeringRateDeg = 0.0 if i % 2 == 0 else 20.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(12 + i, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
for i in range(8):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(18 + i, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
|
||||
measured_angle = CS.out.steeringAngleDeg
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(4, [msg])])
|
||||
parser.update([(26, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
assert -113.78 < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < -100.0
|
||||
assert abs(parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] - measured_angle) < 0.1
|
||||
|
||||
|
||||
def test_legacy_2025_manual_handoff_reentry_uses_normal_angle_limits():
|
||||
def test_legacy_2025_manual_handoff_reclaim_is_gradual():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(
|
||||
@@ -469,7 +411,6 @@ def test_legacy_2025_manual_handoff_reentry_uses_normal_angle_limits():
|
||||
vEgoRaw=3.7,
|
||||
steeringAngleDeg=2.5,
|
||||
steeringRateDeg=-45.0,
|
||||
steeringTorque=250.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -478,32 +419,26 @@ def test_legacy_2025_manual_handoff_reentry_uses_normal_angle_limits():
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
|
||||
CS.out.steeringPressed = False
|
||||
CS.out.steeringTorque = 0.0
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(3, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
for i in range(19):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2 + i, [msg])])
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(4, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
first_reentry_angle = parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"]
|
||||
assert CC.actuators.steeringAngleDeg < first_reentry_angle < CS.out.steeringAngleDeg
|
||||
first_reclaim_angle = parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"]
|
||||
assert first_reclaim_angle == pytest.approx(CS.out.steeringAngleDeg, abs=0.1)
|
||||
|
||||
reentry_angles = []
|
||||
reclaim_angles = []
|
||||
for i in range(6):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(20 + i, [msg])])
|
||||
reentry_angles.append(parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"])
|
||||
reclaim_angles.append(parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"])
|
||||
|
||||
assert all(reentry_angles[i] >= reentry_angles[i + 1] for i in range(len(reentry_angles) - 1))
|
||||
assert reentry_angles[-1] > CC.actuators.steeringAngleDeg
|
||||
assert all(reclaim_angles[i] >= reclaim_angles[i + 1] for i in range(len(reclaim_angles) - 1))
|
||||
assert reclaim_angles[-1] > CC.actuators.steeringAngleDeg
|
||||
|
||||
|
||||
def test_ascent_2023_uses_gen2_angle_bus_layout():
|
||||
@@ -526,25 +461,6 @@ 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)
|
||||
@@ -564,10 +480,6 @@ def test_angle_controller_tracks_driver_override():
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
|
||||
assert not controller.driver_override
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
|
||||
assert controller.driver_override
|
||||
assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150
|
||||
assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100
|
||||
@@ -621,16 +533,15 @@ def test_angle_controller_blocks_low_speed_mads_engagement():
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
|
||||
|
||||
@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)
|
||||
def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-14.88))
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
vEgoRaw=21.66,
|
||||
steeringAngleDeg=-25.77,
|
||||
steeringRateDeg=0.0,
|
||||
steeringTorque=-250.0,
|
||||
steeringTorque=-149.0,
|
||||
steeringPressed=False,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -643,8 +554,8 @@ def test_angle_controller_uses_fixed_angle_rate_limits(platform):
|
||||
assert CS.out.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < -25.0
|
||||
|
||||
|
||||
def test_ascent_angle_controller_reengages_immediately_after_manual_steering_stops():
|
||||
platform = CAR.SUBARU_ASCENT_2023
|
||||
@pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
|
||||
def test_angle_controller_yields_until_manual_steering_settles(platform):
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-10.0))
|
||||
@@ -652,7 +563,7 @@ def test_ascent_angle_controller_reengages_immediately_after_manual_steering_sto
|
||||
vEgoRaw=21.66,
|
||||
steeringAngleDeg=-25.06,
|
||||
steeringRateDeg=35.0,
|
||||
steeringTorque=-250.0,
|
||||
steeringTorque=-149.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -661,28 +572,24 @@ def test_ascent_angle_controller_reengages_immediately_after_manual_steering_sto
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.steeringPressed = False
|
||||
CS.out.steeringTorque = 0.0
|
||||
CS.out.steeringAngleDeg = -17.91
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(3, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
for i in range(18):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2 + i, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(4, [msg])])
|
||||
parser.update([(20, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
assert CS.out.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < CC.actuators.steeringAngleDeg
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg, abs=0.1)
|
||||
|
||||
|
||||
def test_ascent_angle_controller_waits_for_parking_lot_safety_envelope():
|
||||
def test_ascent_angle_controller_blocks_parking_lot_aol_engagement():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=False, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-206.12))
|
||||
@@ -692,7 +599,6 @@ def test_ascent_angle_controller_waits_for_parking_lot_safety_envelope():
|
||||
steeringRateDeg=96.0,
|
||||
steeringTorque=7.0,
|
||||
steeringPressed=False,
|
||||
cruiseState=SimpleNamespace(available=True),
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
))
|
||||
@@ -705,77 +611,21 @@ def test_ascent_angle_controller_waits_for_parking_lot_safety_envelope():
|
||||
|
||||
CS.out.steeringAngleDeg = -100.0
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
for frame in range(2, _ASCENT_AOL_ARM_FRAMES + 1):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(frame, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(_ASCENT_AOL_ARM_FRAMES + 1, [msg])])
|
||||
parser.update([(2, [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([(_ASCENT_AOL_ARM_FRAMES + 2, [msg])])
|
||||
parser.update([(3, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
|
||||
def test_ascent_aol_does_not_arm_before_cruise_main_is_available():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=False, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=5.0))
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
vEgoRaw=10.0,
|
||||
steeringAngleDeg=0.0,
|
||||
steeringRateDeg=0.0,
|
||||
steeringTorque=0.0,
|
||||
steeringPressed=False,
|
||||
cruiseState=SimpleNamespace(available=False),
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
))
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
|
||||
|
||||
for frame in range(_ASCENT_AOL_ARM_FRAMES):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(frame + 1, [msg])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert controller.ascent_aol_arm_frames == 0
|
||||
|
||||
CS.out.cruiseState.available = True
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(_ASCENT_AOL_ARM_FRAMES + 1, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert controller.ascent_aol_arm_frames == 1
|
||||
|
||||
|
||||
def test_ascent_angle_controller_does_not_delay_normal_engagement():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=5.0))
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
vEgoRaw=10.0,
|
||||
steeringAngleDeg=0.0,
|
||||
steeringRateDeg=0.0,
|
||||
steeringTorque=0.0,
|
||||
steeringPressed=False,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
))
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
|
||||
|
||||
def test_lkas_hud_state_uses_angle_request_state():
|
||||
def test_lkas_hud_state_uses_lateral_active():
|
||||
update_source = inspect.getsource(CarController.update)
|
||||
|
||||
assert "CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert" in update_source
|
||||
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive" in update_source
|
||||
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled" not in update_source
|
||||
|
||||
|
||||
@@ -793,51 +643,3 @@ def test_lkas_hud_active_bit_follows_lateral_state(enabled, expected):
|
||||
|
||||
assert parser.can_valid
|
||||
assert parser.vl["ES_LKAS_State"]["LKAS_ACTIVE"] == expected
|
||||
|
||||
|
||||
def test_outback_manual_steering_keeps_cooperative_angle_request():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=True,
|
||||
actuators=SimpleNamespace(steeringAngleDeg=-225.0),
|
||||
)
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
vEgoRaw=0.9,
|
||||
steeringAngleDeg=-57.0,
|
||||
steeringRateDeg=-45.0,
|
||||
steeringTorque=0.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
))
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
|
||||
|
||||
for frame, steering_torque in enumerate((-79.0, -81.0, -170.0, -250.0, -250.0, 79.0, 81.0, 170.0, 250.0, 250.0), start=1):
|
||||
CS.out.steeringTorque = steering_torque
|
||||
CS.out.steeringPressed = abs(steering_torque) > 80.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(frame, [msg])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
assert CC.actuators.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < CS.out.steeringAngleDeg
|
||||
assert controller._lkas_status_active(CC)
|
||||
|
||||
|
||||
def test_ascent_hud_waits_for_angle_request():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(latActive=True)
|
||||
|
||||
assert not controller._lkas_status_active(CC)
|
||||
controller.angle_lkas_active = True
|
||||
assert controller._lkas_status_active(CC)
|
||||
|
||||
|
||||
def test_other_angle_cars_keep_lateral_status_behavior():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
|
||||
controller = CarController({}, CP)
|
||||
controller.angle_lkas_active = False
|
||||
|
||||
assert controller._lkas_status_active(SimpleNamespace(latActive=True))
|
||||
|
||||
@@ -89,7 +89,6 @@ class SubaruSafetyFlags(IntFlag):
|
||||
D_PLATFORM_CAMERA = 64
|
||||
FIXED_ANGLE_LIMITS = 128
|
||||
STOP_START_BUTTON = 256
|
||||
REDNECK_CRUISE = 512
|
||||
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
|
||||
|
||||
|
||||
@@ -271,15 +270,6 @@ class CAR(Platforms):
|
||||
)
|
||||
|
||||
|
||||
SUBARU_STOP_START_CARS = (
|
||||
CAR.SUBARU_OUTBACK_2023,
|
||||
CAR.SUBARU_LEGACY_2025,
|
||||
)
|
||||
|
||||
SUBARU_REDNECK_CRUISE_CARS = (
|
||||
CAR.SUBARU_IMPREZA_2020,
|
||||
)
|
||||
|
||||
SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
|
||||
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
|
||||
@@ -5,10 +5,9 @@ from opendbc.car.lateral import apply_steer_angle_limits_vm
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
|
||||
from opendbc.car.tesla.teslacan import TeslaCAN
|
||||
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
|
||||
from opendbc.car.tesla.preap.carcontroller import PreAPLongController, init_preap_can
|
||||
from opendbc.car.tesla.preap.stock_cc_spoofer import StockCCSpoofer
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags, LEGACY_CARS
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
def get_safety_CP():
|
||||
@@ -25,7 +24,6 @@ class CarController(CarControllerBase):
|
||||
self.coop_enabled = CP.carFingerprint == CAR.TESLA_MODEL_3 and any(
|
||||
config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in CP.safetyConfigs
|
||||
)
|
||||
self._clear_steering_limit_info()
|
||||
self.packer = CANPacker(dbc_names[Bus.party])
|
||||
self.tesla_can = TeslaCAN(self.packer)
|
||||
self.preap_long = None
|
||||
@@ -40,37 +38,9 @@ class CarController(CarControllerBase):
|
||||
self.stock_cc = StockCCSpoofer()
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP))
|
||||
elif CP.carFingerprint in LEGACY_CARS:
|
||||
self.packers = {
|
||||
CANBUS.party: CANPacker(dbc_names[Bus.party]),
|
||||
}
|
||||
self.tesla_can = TeslaCANRaven(self.packers)
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1))
|
||||
|
||||
def _clear_steering_limit_info(self):
|
||||
self.steering_limit_info_valid = False
|
||||
self.model_limit_error_deg = 0.0
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
self.steering_limit_mono_time = 0
|
||||
self.combined_limit_error_deg = 0.0
|
||||
|
||||
def get_steering_limit_info(self) -> dict[str, bool | float | int]:
|
||||
return {
|
||||
"valid": self.steering_limit_info_valid,
|
||||
"modelLimitErrorDeg": self.model_limit_error_deg,
|
||||
"resumeLimitErrorDeg": self.resume_limit_error_deg,
|
||||
"cooperativeLimitErrorDeg": self.cooperative_limit_error_deg,
|
||||
"cooperativeOffsetDeg": self.cooperative_offset_deg,
|
||||
"monoTime": self.steering_limit_mono_time,
|
||||
"combinedLimitErrorDeg": self.combined_limit_error_deg,
|
||||
}
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
self._clear_steering_limit_info()
|
||||
return self._update_preap(CC, CS)
|
||||
|
||||
actuators = CC.actuators
|
||||
@@ -78,12 +48,8 @@ class CarController(CarControllerBase):
|
||||
|
||||
# Preserve the stock controller path unless cooperative steering is explicitly enabled.
|
||||
lat_active = CC.latActive and (not CS.out.steeringDisengage if self.coop_enabled else CS.hands_on_level < 3)
|
||||
if not (self.coop_enabled and lat_active):
|
||||
self._clear_steering_limit_info()
|
||||
|
||||
if self.frame % 2 == 0:
|
||||
requested_angle = actuators.steeringAngleDeg
|
||||
|
||||
# Angular rate limit based on speed
|
||||
self.apply_angle_last = apply_steer_angle_limits_vm(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
|
||||
lat_active, CarControllerParams, self.VM)
|
||||
@@ -91,34 +57,9 @@ class CarController(CarControllerBase):
|
||||
self.apply_angle_command_last, lat_active = self.coop_steer.update(
|
||||
self.apply_angle_last, lat_active, self.coop_enabled, CS, self.VM,
|
||||
)
|
||||
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
|
||||
|
||||
if self.coop_enabled and lat_active:
|
||||
model_limit_error_deg = abs(requested_angle - self.apply_angle_last)
|
||||
resume_limit_error_deg = self.coop_steer.resume_limit_error_deg
|
||||
cooperative_limit_error_deg = self.coop_steer.cooperative_limit_error_deg
|
||||
cooperative_offset_deg = self.coop_steer.cooperative_offset_deg
|
||||
combined_limit_error_deg = abs(requested_angle + cooperative_offset_deg - self.apply_angle_command_last)
|
||||
limit_values = (model_limit_error_deg, resume_limit_error_deg, cooperative_limit_error_deg,
|
||||
cooperative_offset_deg, combined_limit_error_deg)
|
||||
|
||||
if all(np.isfinite(value) for value in limit_values):
|
||||
self.steering_limit_info_valid = True
|
||||
self.model_limit_error_deg = model_limit_error_deg
|
||||
self.resume_limit_error_deg = resume_limit_error_deg
|
||||
self.cooperative_limit_error_deg = cooperative_limit_error_deg
|
||||
self.cooperative_offset_deg = cooperative_offset_deg
|
||||
self.steering_limit_mono_time = now_nanos
|
||||
self.combined_limit_error_deg = combined_limit_error_deg
|
||||
else:
|
||||
self._clear_steering_limit_info()
|
||||
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
cntr = (self.frame // 2) % 16
|
||||
can_sends.append(self.tesla_can.create_steering_control(cntr, self.apply_angle_command_last, lat_active))
|
||||
else:
|
||||
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
|
||||
|
||||
if self.frame % 10 == 0 and self.CP.carFingerprint not in LEGACY_CARS:
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(self.tesla_can.create_steering_allowed())
|
||||
|
||||
# Longitudinal control
|
||||
@@ -127,21 +68,13 @@ class CarController(CarControllerBase):
|
||||
state = 13 if CC.cruiseControl.cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT
|
||||
accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
|
||||
cntr = (self.frame // 4) % 8
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
hw1_accel = accel if CC.longActive and not CC.cruiseControl.cancel else 0.
|
||||
hw1_active = CC.longActive and not CC.cruiseControl.cancel
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(state, hw1_accel, cntr, CS.out.vEgo, hw1_active, CS.out.gasPressed))
|
||||
else:
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, self.frame, CS.out.vEgo, CS.out.gasPressed))
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, CS.out.vEgo, CC.longActive))
|
||||
|
||||
else:
|
||||
# Increment counter so cancel is prioritized even without openpilot longitudinal
|
||||
if CC.cruiseControl.cancel:
|
||||
cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False, CS.out.gasPressed))
|
||||
else:
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, self.frame, CS.out.vEgo, False))
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False))
|
||||
|
||||
# TODO: HUD control
|
||||
new_actuators = actuators.as_builder()
|
||||
|
||||
@@ -4,10 +4,7 @@ from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.tesla.values import (
|
||||
DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags,
|
||||
CAR, LEGACY_CARS,
|
||||
)
|
||||
from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags, CAR
|
||||
from opendbc.car.tesla.preap.carstate import get_preap_can_parsers, update_preap
|
||||
from opendbc.car.tesla.preap.engagement import PreAPEngagement
|
||||
from opendbc.car.tesla.preap.nap_conf import nap_conf
|
||||
@@ -28,19 +25,8 @@ class CarState(CarStateBase):
|
||||
def __init__(self, CP, FPCP):
|
||||
super().__init__(CP, FPCP)
|
||||
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
|
||||
if CP.carFingerprint in LEGACY_CARS:
|
||||
self.can_define_party = CANDefine(DBC[CP.carFingerprint][Bus.party])
|
||||
self.can_define_pt = CANDefine(DBC[CP.carFingerprint][Bus.pt])
|
||||
self.can_define_chassis = CANDefine(DBC[CP.carFingerprint][Bus.chassis])
|
||||
self.can_defines = {
|
||||
**self.can_define_party.dv,
|
||||
**self.can_define_pt.dv,
|
||||
**self.can_define_chassis.dv,
|
||||
}
|
||||
self.shifter_values = self.can_defines["DI_torque2"]["DI_gear"]
|
||||
else:
|
||||
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
|
||||
self.can_define.dv["DI_torque2"]["DI_gear"]
|
||||
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
|
||||
self.can_define.dv["DI_torque2"]["DI_gear"]
|
||||
|
||||
self.autopark = False
|
||||
self.autopark_prev = False
|
||||
@@ -89,8 +75,6 @@ class CarState(CarStateBase):
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return update_preap(self, can_parsers)
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
return self.update_legacy(can_parsers)
|
||||
|
||||
cp_party = can_parsers[Bus.party]
|
||||
cp_ap_party = can_parsers[Bus.ap_party]
|
||||
@@ -189,94 +173,10 @@ class CarState(CarStateBase):
|
||||
|
||||
return ret, fp_ret
|
||||
|
||||
def update_legacy(self, can_parsers):
|
||||
cp_party = can_parsers[Bus.party]
|
||||
cp_ap_party = can_parsers[Bus.ap_party]
|
||||
cp_pt = can_parsers[Bus.pt]
|
||||
cp_ap_pt = can_parsers[Bus.ap_pt]
|
||||
cp_chassis = can_parsers[Bus.chassis]
|
||||
ret = structs.CarState()
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
|
||||
# Vehicle speed
|
||||
ret.vEgoRaw = cp_chassis.vl["ESP_B"]["ESP_vehicleSpeed"] * CV.KPH_TO_MS
|
||||
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
|
||||
|
||||
# Gas and brake
|
||||
ret.gasPressed = cp_pt.vl["DI_torque1"]["DI_pedalPos"] > 0
|
||||
ret.brake = 0
|
||||
ret.brakePressed = cp_chassis.vl["BrakeMessage"]["driverBrakeStatus"] != 1
|
||||
|
||||
# Steering wheel and EPAS status
|
||||
epas_status = cp_chassis.vl["EPAS_sysStatus"]
|
||||
self.hands_on_level = epas_status["EPAS_handsOnLevel"]
|
||||
ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"]
|
||||
ret.steeringRateDeg = -cp_chassis.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"]
|
||||
ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"]
|
||||
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
|
||||
|
||||
eac_status = self.can_defines["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None)
|
||||
ret.steerFaultPermanent = eac_status == "EAC_FAULT"
|
||||
ret.steerFaultTemporary = eac_status == "EAC_INHIBITED"
|
||||
eac_error_code = self.can_defines["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None)
|
||||
ret.steeringDisengage = self.hands_on_level >= 3 or (
|
||||
eac_status == "EAC_INHIBITED" and eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY"
|
||||
)
|
||||
|
||||
# Cruise
|
||||
cruise_state = self.can_defines["DI_state"]["DI_cruiseState"].get(int(cp_chassis.vl["DI_state"]["DI_cruiseState"]), None)
|
||||
speed_units = self.can_defines["DI_state"]["DI_speedUnits"].get(int(cp_chassis.vl["DI_state"]["DI_speedUnits"]), None)
|
||||
cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
|
||||
ret.cruiseState.enabled = cruise_enabled
|
||||
if speed_units == "KPH":
|
||||
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.KPH_TO_MS, 1e-3)
|
||||
elif speed_units == "MPH":
|
||||
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.MPH_TO_MS, 1e-3)
|
||||
ret.cruiseState.available = cruise_state == "STANDBY" or ret.cruiseState.enabled
|
||||
ret.cruiseState.standstill = False
|
||||
ret.standstill = ret.vEgoRaw < 0.1
|
||||
ret.accFaulted = cruise_state == "FAULT"
|
||||
|
||||
# Gear, body state, and safety state
|
||||
ret.gearShifter = GEAR_MAP[self.can_defines["DI_torque2"]["DI_gear"].get(
|
||||
int(cp_chassis.vl["DI_torque2"]["DI_gear"]), "DI_GEAR_INVALID")]
|
||||
|
||||
doors = ("DOOR_STATE_FL", "DOOR_STATE_FR", "DOOR_STATE_RL", "DOOR_STATE_RR", "DOOR_STATE_FrontTrunk", "BOOT_STATE")
|
||||
ret.doorOpen = any(
|
||||
self.can_defines["GTW_carState"][door].get(int(cp_chassis.vl["GTW_carState"][door]), "OPEN") == "OPEN"
|
||||
for door in doors
|
||||
)
|
||||
ret.leftBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorLStatus"] == 1
|
||||
ret.rightBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorRStatus"] == 1
|
||||
|
||||
_ = cp_chassis.vl["SDM1"]
|
||||
_ = cp_chassis.vl["RCM_status"]
|
||||
sd_time = cp_chassis.ts_nanos["SDM1"]["SDM_bcklDrivStatus"]
|
||||
rcm_time = cp_chassis.ts_nanos["RCM_status"]["RCM_buckleDriverStatus"]
|
||||
if sd_time and cp_chassis._last_update_nanos - sd_time <= 1_000_000_000:
|
||||
ret.seatbeltUnlatched = cp_chassis.vl["SDM1"]["SDM_bcklDrivStatus"] != 1
|
||||
elif rcm_time and cp_chassis._last_update_nanos - rcm_time <= 1_000_000_000:
|
||||
ret.seatbeltUnlatched = cp_chassis.vl["RCM_status"]["RCM_buckleDriverStatus"] != 1
|
||||
else:
|
||||
ret.seatbeltUnlatched = True
|
||||
|
||||
ret.stockAeb = cp_ap_pt.vl["DAS_control"]["DAS_aebEvent"] == 1
|
||||
ret.stockLkas = cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 2
|
||||
self.das_control = copy.copy(cp_ap_pt.vl["DAS_control"])
|
||||
return ret, fp_ret
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return get_preap_can_parsers(CP)
|
||||
if CP.carFingerprint in LEGACY_CARS:
|
||||
return {
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
|
||||
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.party),
|
||||
Bus.ap_pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.autopilot_party),
|
||||
Bus.chassis: CANParser(DBC[CP.carFingerprint][Bus.chassis], [("SDM1", 0), ("RCM_status", 0)], CANBUS.party),
|
||||
}
|
||||
return {
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
|
||||
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party)
|
||||
|
||||
@@ -62,9 +62,6 @@ class CooperativeSteeringController:
|
||||
self.angle_override = 0.0
|
||||
self.resume_rate_limiter_delta = SteerRateLimiter()
|
||||
self.resume_rate_limiter = SteerRateLimiter()
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
|
||||
def reset_override_state(self, apply_angle: float) -> None:
|
||||
self.apply_angle_last = apply_angle
|
||||
@@ -108,25 +105,19 @@ class CooperativeSteeringController:
|
||||
return self.resume_rate_limiter.update(apply_angle, angle_rate_delta)
|
||||
|
||||
def update(self, apply_angle: float, lat_active: bool, enabled: bool, CS, VM: VehicleModel) -> tuple[float, bool]:
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
if not enabled:
|
||||
self.reset_resume_state(apply_angle)
|
||||
self.reset_override_state(apply_angle)
|
||||
return apply_angle, lat_active
|
||||
|
||||
requested_angle = apply_angle
|
||||
apply_angle = self.apply_resume_rate_limit(lat_active, apply_angle)
|
||||
self.resume_limit_error_deg = abs(requested_angle - apply_angle)
|
||||
if not lat_active:
|
||||
self.reset_override_state(apply_angle)
|
||||
return apply_angle, False
|
||||
|
||||
apply_angle_delta = apply_angle - self.apply_angle_last
|
||||
self.apply_angle_last = apply_angle
|
||||
self.cooperative_offset_deg = self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
|
||||
apply_angle += self.cooperative_offset_deg
|
||||
apply_angle += self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
|
||||
|
||||
limited_angle = apply_steer_angle_limits_vm(
|
||||
apply_angle,
|
||||
@@ -138,6 +129,5 @@ class CooperativeSteeringController:
|
||||
VM,
|
||||
)
|
||||
self.coop_apply_angle_last = limited_angle
|
||||
self.cooperative_limit_error_deg = abs(apply_angle - limited_angle)
|
||||
self.unwind_override_angle(apply_angle - limited_angle)
|
||||
return limited_angle, True
|
||||
|
||||
@@ -5,12 +5,6 @@ from opendbc.car.tesla.values import CAR
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
FW_VERSIONS = {
|
||||
CAR.TESLA_MODEL_S_HW1: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'1016704-00-HAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\x10\x00A',
|
||||
],
|
||||
},
|
||||
CAR.TESLA_MODEL_3: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'TeM3_E014p10_0.0.0 (16),E014.17.00',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from opendbc.car import Bus, get_safety_config, structs
|
||||
from opendbc.car import get_safety_config, structs
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.carstate import CarState
|
||||
from opendbc.car.tesla.radar_interface import RadarInterface
|
||||
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR, DBC, LEGACY_CARS
|
||||
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR
|
||||
from opendbc.car.tesla.preap.interface import get_preap_accel_limits, get_preap_params
|
||||
|
||||
|
||||
@@ -32,21 +32,6 @@ class CarInterface(CarInterfaceBase):
|
||||
if candidate == CAR.TESLA_MODEL_S_PREAP:
|
||||
return get_preap_params(ret)
|
||||
|
||||
if candidate in LEGACY_CARS:
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla, TeslaSafetyFlags.FLAG_HW1.value)]
|
||||
ret.steerLimitTimer = 0.4
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.steerAtStandstill = True
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
ret.radarUnavailable = Bus.radar not in DBC[candidate]
|
||||
ret.radarTimeStepDEPRECATED = 0.125
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
|
||||
if alpha_long:
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.LONG_CONTROL.value
|
||||
return ret
|
||||
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
|
||||
@@ -88,7 +88,8 @@ class TeslaCANPreAP:
|
||||
else:
|
||||
values.update(_STW_DEFAULTS)
|
||||
|
||||
values["VSL_Enbl_Rq"] = 1
|
||||
# Preserve the live stalk layout, but force VSL enable on engage/resume spoofs.
|
||||
values["VSL_Enbl_Rq"] = 0 if button_to_press == 1 else 1
|
||||
|
||||
data = self.packer.make_can_msg("STW_ACTN_RQ", bus, values)[1]
|
||||
values["CRC_STW_ACTN_RQ"] = _crc8_stw(data[:7])
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"""Byte-level invariants for Pre-AP stalk spoof frames."""
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car.tesla.preap.teslacan import TeslaCANPreAP, _STW_DEFAULTS
|
||||
from opendbc.car.tesla.values import CANBUS, CruiseButtons
|
||||
|
||||
|
||||
def _spoof(button):
|
||||
tc = TeslaCANPreAP(CANPacker("tesla_can"))
|
||||
msg_stw = {"MC_STW_ACTN_RQ": 5, "CRC_STW_ACTN_RQ": 0, "DTR_Dist_Rq": 255}
|
||||
msg_stw.update(_STW_DEFAULTS)
|
||||
msg_stw["VSL_Enbl_Rq"] = 0
|
||||
_, dat, _ = tc.create_action_request(button, CANBUS.party, 6, msg_stw)
|
||||
return dat
|
||||
|
||||
|
||||
def test_vsl_enable_bit_is_set_on_cancel():
|
||||
dat = _spoof(CruiseButtons.CANCEL)
|
||||
assert (dat[0] >> 6) & 1 == 1
|
||||
|
||||
|
||||
def test_vsl_enable_bit_is_set_on_set_accel():
|
||||
dat = _spoof(CruiseButtons.SET_ACCEL)
|
||||
assert (dat[0] >> 6) & 1 == 1
|
||||
|
||||
|
||||
def test_stalk_button_and_vsl_bits_match_real_set_accel_frame():
|
||||
dat = _spoof(CruiseButtons.SET_ACCEL)
|
||||
assert dat[0] == 0x50
|
||||
@@ -16,7 +16,7 @@ class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
|
||||
self.radar_off_can = CP.radarUnavailable or Bus.radar not in DBC[CP.carFingerprint]
|
||||
self.radar_off_can = CP.radarUnavailable or CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP
|
||||
self.updated_messages: set[int] = set()
|
||||
self.track_id = 0
|
||||
self.radar_offset = float(nap_conf.radar_offset) if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP else 0.0
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import numpy as np
|
||||
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.tesla.values import CANBUS, CarControllerParams
|
||||
|
||||
@@ -7,7 +5,6 @@ from opendbc.car.tesla.values import CANBUS, CarControllerParams
|
||||
class TeslaCAN:
|
||||
def __init__(self, packer):
|
||||
self.packer = packer
|
||||
self.gas_release_frame = 0
|
||||
|
||||
def create_steering_control(self, angle, enabled):
|
||||
values = {
|
||||
@@ -18,20 +15,15 @@ class TeslaCAN:
|
||||
|
||||
return self.packer.make_can_msg("DAS_steeringControl", CANBUS.party, values)
|
||||
|
||||
def create_longitudinal_command(self, acc_state, accel, counter, frame, v_ego, gas_pressed):
|
||||
def create_longitudinal_command(self, acc_state, accel, counter, v_ego, active):
|
||||
set_speed = min(max(v_ego + accel, 0) * CV.MS_TO_KPH, 400)
|
||||
|
||||
if gas_pressed:
|
||||
self.gas_release_frame = frame
|
||||
|
||||
jerk = float(np.interp(frame - self.gas_release_frame, [0, 100], [0.0, CarControllerParams.JERK_LIMIT_MAX]))
|
||||
|
||||
values = {
|
||||
"DAS_setSpeed": set_speed,
|
||||
"DAS_accState": acc_state,
|
||||
"DAS_aebEvent": 0,
|
||||
"DAS_jerkMin": -jerk,
|
||||
"DAS_jerkMax": jerk,
|
||||
"DAS_jerkMin": CarControllerParams.JERK_LIMIT_MIN,
|
||||
"DAS_jerkMax": CarControllerParams.JERK_LIMIT_MAX,
|
||||
"DAS_accelMin": accel,
|
||||
"DAS_accelMax": max(accel, 0),
|
||||
"DAS_controlCounter": counter,
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import V_CRUISE_MAX
|
||||
from opendbc.car.tesla.values import CANBUS, CarControllerParams
|
||||
|
||||
|
||||
class TeslaCANRaven:
|
||||
"""CAN commands used by the legacy Model S/X powertrain and EPAS buses."""
|
||||
|
||||
def __init__(self, packers):
|
||||
self.packers = packers
|
||||
self.CCP = CarControllerParams
|
||||
self.jerk_upper = self.CCP.JERK_LIMIT_MAX
|
||||
self.jerk_lower = self.CCP.JERK_LIMIT_MIN
|
||||
|
||||
@staticmethod
|
||||
def checksum(msg_id, dat):
|
||||
return ((msg_id & 0xFF) + ((msg_id >> 8) & 0xFF) + sum(dat)) & 0xFF
|
||||
|
||||
def create_steering_control(self, counter, angle, enabled):
|
||||
values = {
|
||||
"DAS_steeringControlCounter": counter,
|
||||
"DAS_steeringAngleRequest": -angle,
|
||||
"DAS_steeringHapticRequest": 0,
|
||||
"DAS_steeringControlType": 1 if enabled else 0,
|
||||
}
|
||||
data = self.packers[CANBUS.party].make_can_msg("DAS_steeringControl", CANBUS.party, values)[1]
|
||||
values["DAS_steeringControlChecksum"] = self.checksum(0x488, data[:3])
|
||||
return self.packers[CANBUS.party].make_can_msg("DAS_steeringControl", CANBUS.party, values)
|
||||
|
||||
def create_longitudinal_command(self, acc_state, accel, counter, v_ego, active, gas_pressed):
|
||||
set_speed = max(v_ego * CV.MS_TO_KPH, 0)
|
||||
if active:
|
||||
set_speed = 0 if accel < 0 else V_CRUISE_MAX
|
||||
|
||||
if gas_pressed:
|
||||
self.jerk_upper = self.jerk_lower = 0.0
|
||||
else:
|
||||
self.jerk_lower = max(self.jerk_lower - self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MIN)
|
||||
self.jerk_upper = min(self.jerk_upper + self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MAX)
|
||||
|
||||
values = {
|
||||
"DAS_setSpeed": set_speed,
|
||||
"DAS_accState": acc_state,
|
||||
"DAS_aebEvent": 0,
|
||||
"DAS_jerkMin": self.jerk_lower,
|
||||
"DAS_jerkMax": self.jerk_upper,
|
||||
"DAS_accelMin": accel,
|
||||
"DAS_accelMax": max(accel, 0),
|
||||
"DAS_controlCounter": counter,
|
||||
}
|
||||
data = self.packers[CANBUS.party].make_can_msg("DAS_control", CANBUS.party, values)[1]
|
||||
values["DAS_controlChecksum"] = self.checksum(0x2b9, data[:7])
|
||||
return self.packers[CANBUS.party].make_can_msg("DAS_control", CANBUS.party, values)
|
||||
-1
File diff suppressed because one or more lines are too long
@@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline AP1/HW1 CAN, radar, controller, and panda-safety replay.
|
||||
|
||||
Usage: PYTHONPATH=. python -m opendbc.car.tesla.tests.replay_hw1_route PATH_TO_RLOGS
|
||||
|
||||
The supplied Pre-AP recording contains stock AP commands copied to bus 0 while
|
||||
ELM327/old firmware forwarded traffic. The counterfactual run skips those copies:
|
||||
the HW1 safety mode blocks stock 0x488/0x2b9 forwarding on bus 2. This is NOT a
|
||||
physical HW1 drive; it cannot validate engagement or steering actuation on-car.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import custom
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.carstate import CarState
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
from opendbc.car.tesla.radar_interface import RadarInterface
|
||||
from opendbc.car.tesla.values import CAR, DBC, TeslaSafetyFlags
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
def replay(paths: list[Path], simulate_active: bool = False):
|
||||
fp = {0: {0x201: 5}, 1: {}, 2: {}}
|
||||
cp = CarInterface.get_params(CAR.TESLA_MODEL_S_HW1, fp, [], True, False, False, None)
|
||||
assert cp.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.tesla
|
||||
assert cp.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value | TeslaSafetyFlags.LONG_CONTROL.value
|
||||
safety = libsafety_py.libsafety
|
||||
assert safety.set_safety_hooks(int(structs.CarParams.SafetyModel.tesla), cp.safetyConfigs[0].safetyParam) == 0
|
||||
safety.init_tests()
|
||||
|
||||
parsers = CarState.get_can_parsers(cp)
|
||||
cs = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp)
|
||||
active_controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp) if simulate_active else None
|
||||
radar = RadarInterface(cp)
|
||||
stats = Counter()
|
||||
first_rejected = []
|
||||
active_rejected = []
|
||||
last_ap_command: dict[tuple[int, bytes], int] = {}
|
||||
suppressed_examples = []
|
||||
|
||||
for path in paths:
|
||||
for event in LogReader(str(path)):
|
||||
if event.which() != "can":
|
||||
continue
|
||||
t = event.logMonoTime
|
||||
frames = [(x.address, bytes(x.dat), x.src) for x in event.can]
|
||||
stock_in_event = {(a, d) for a, d, b in frames if b == 2 and a in (0x488, 0x2b9)}
|
||||
for a, d, b in frames:
|
||||
if b == 2 and a in (0x488, 0x2b9):
|
||||
last_ap_command[(a, d)] = t
|
||||
if b == 0 and a in (0x488, 0x2b9):
|
||||
seen = last_ap_command.get((a, d), -1)
|
||||
if (a, d) in stock_in_event or (0 <= t - seen < 250_000_000):
|
||||
stats["suppressed_bus0_stock_copies"] += 1
|
||||
continue
|
||||
stats["unmatched_bus0_stock_commands"] += 1
|
||||
if len(suppressed_examples) < 5:
|
||||
suppressed_examples.append((path.name, t, hex(a), d.hex()))
|
||||
if b < 128:
|
||||
stats["physical_rx"] += 1
|
||||
if not safety.safety_rx_hook(libsafety_py.make_CANPacket(a, b, d)):
|
||||
stats["rx_rejected"] += 1
|
||||
if b == 2 and a in (0x488, 0x2b9):
|
||||
stats["stock_forward_blocked"] += safety.safety_fwd_hook(b, a) == -1
|
||||
|
||||
safety.set_timer((t // 1000) & 0xffffffff)
|
||||
safety.safety_tick_current_safety_config()
|
||||
stats["safety_invalid_ticks"] += not safety.safety_config_valid()
|
||||
stats["relay_malfunction_ticks"] += safety.get_relay_malfunction()
|
||||
stats["controls_allowed_ticks"] += safety.get_controls_allowed()
|
||||
|
||||
batch = [(t, frames)]
|
||||
for parser in parsers.values():
|
||||
parser.update(batch)
|
||||
stats["invalid_car_parser_ticks"] += not parser.can_valid
|
||||
out, _ = cs.update(parsers, None)
|
||||
cs.out = out
|
||||
stats["carstate_faulted_ticks"] += out.accFaulted
|
||||
stats["seatbelt_unlatched_ticks"] += out.seatbeltUnlatched
|
||||
stats["steering_inhibited_ticks"] += out.steerFaultTemporary
|
||||
stats["cruise_engaged_ticks"] += out.cruiseState.enabled
|
||||
|
||||
radar_data = radar.update(batch)
|
||||
if radar_data is not None:
|
||||
stats["radar_updates"] += 1
|
||||
stats["radar_points"] += len(radar_data.points)
|
||||
stats["radar_error_updates"] += radar_data.errors.canError or radar_data.errors.radarFault
|
||||
|
||||
cc = structs.CarControl.new_message()
|
||||
cc.actuators.steeringAngleDeg = out.steeringAngleDeg
|
||||
cc.actuators.accel = 0.
|
||||
# Do not fabricate engagement on the actual faulted/standby route.
|
||||
_, sends = controller.update(cc.as_reader(), cs, t, None)
|
||||
for a, d, b in sends:
|
||||
stats["generated_tx"] += 1
|
||||
stats[f"generated_{hex(a)}"] += 1
|
||||
if not safety.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d)):
|
||||
stats["tx_rejected"] += 1
|
||||
if len(first_rejected) < 5:
|
||||
first_rejected.append((path.name, t, hex(a), d.hex(), out.steeringAngleDeg, safety.get_relay_malfunction()))
|
||||
|
||||
if active_controller is not None:
|
||||
# A synthetic gate test only. This recording never engaged cruise, so
|
||||
# enabling controls here does NOT represent an actual car-state transition.
|
||||
eligible = (not (out.steerFaultTemporary or out.steerFaultPermanent or out.steeringDisengage or out.accFaulted or
|
||||
out.gasPressed or out.brakePressed or out.stockAeb or out.stockLkas) and out.vEgoRaw > 2.)
|
||||
simulated = structs.CarControl.new_message()
|
||||
simulated.latActive = eligible
|
||||
simulated.longActive = eligible
|
||||
simulated.actuators.steeringAngleDeg = out.steeringAngleDeg
|
||||
simulated.actuators.accel = 0.5 if eligible else 0.
|
||||
_, active_sends = active_controller.update(simulated.as_reader(), cs, t, None)
|
||||
if eligible:
|
||||
stats["simulated_eligible_ticks"] += 1
|
||||
safety.set_controls_allowed(True)
|
||||
for a, d, b in active_sends:
|
||||
stats["simulated_tx"] += 1
|
||||
stats[f"simulated_{hex(a)}"] += 1
|
||||
if not safety.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d)):
|
||||
stats["simulated_tx_rejected"] += 1
|
||||
if len(active_rejected) < 5:
|
||||
active_rejected.append((path.name, t, hex(a), d.hex(), out.steeringAngleDeg, out.vEgoRaw))
|
||||
safety.set_controls_allowed(False)
|
||||
stats["can_events"] += 1
|
||||
print(f"{path.name}: {dict(stats)}", flush=True)
|
||||
|
||||
print(f"unmatched bus-0 command examples: {suppressed_examples}")
|
||||
print(f"rejected TX examples: {first_rejected}")
|
||||
print(f"rejected synthetic-active TX examples: {active_rejected}")
|
||||
print(f"final: {dict(stats)}")
|
||||
return stats
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
argp = argparse.ArgumentParser(description=__doc__)
|
||||
argp.add_argument("rlogs", type=Path, help="directory containing segment rlog.zst files")
|
||||
argp.add_argument("--simulate-active", action="store_true", help="force safety engagement only on healthy standby samples")
|
||||
args = argp.parse_args()
|
||||
files = sorted(args.rlogs.glob("*.rlog.zst"))
|
||||
if not files:
|
||||
argp.error("no *.rlog.zst files found")
|
||||
replay(files, args.simulate_active)
|
||||
@@ -1,4 +1,3 @@
|
||||
import math
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
@@ -74,110 +73,3 @@ def test_safety_flag_is_model_3_only(candidate, enabled, expected):
|
||||
|
||||
if candidate != CAR.TESLA_MODEL_S_PREAP:
|
||||
assert CarController(DBC[candidate], params).coop_enabled is expected
|
||||
|
||||
|
||||
def assert_finite_nonnegative_limit_errors(controller):
|
||||
errors = (controller.resume_limit_error_deg, controller.cooperative_limit_error_deg)
|
||||
assert all(math.isfinite(error) and error >= 0.0 for error in errors)
|
||||
assert math.isfinite(controller.cooperative_offset_deg)
|
||||
|
||||
|
||||
def test_zero_torque_has_zero_cooperative_diagnostics(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
|
||||
angle, lat_active = controller.update(0.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert angle == 0.0
|
||||
assert lat_active
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg == 0.0
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_steady_light_torque_reports_offset_without_real_limiting(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
|
||||
|
||||
assert controller.cooperative_offset_deg > 2.5
|
||||
assert controller.resume_limit_error_deg < 2.5
|
||||
assert controller.cooperative_limit_error_deg < 2.5
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_torque_reversal_updates_signed_offset_without_negative_errors(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
|
||||
|
||||
for _ in range(200):
|
||||
controller.update(0.0, True, True, make_car_state(torque=-0.9), vehicle_model)
|
||||
|
||||
assert controller.cooperative_offset_deg < -2.5
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_release_reports_gradual_offset_unwind(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
|
||||
|
||||
offsets = []
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(), vehicle_model)
|
||||
offsets.append(controller.cooperative_offset_deg)
|
||||
|
||||
assert offsets[0] > offsets[-1] >= 0.0
|
||||
assert all(next_offset <= offset for offset, next_offset in zip(offsets, offsets[1:]))
|
||||
assert offsets[-1] == pytest.approx(0.0, abs=1e-6)
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_resume_ramp_reports_resume_limiting(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(0.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg > 2.5
|
||||
assert controller.cooperative_limit_error_deg < 2.5
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
|
||||
def test_final_limiter_reports_cooperative_target_clipping(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
|
||||
def test_final_limiter_remains_visible_with_light_torque(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(torque=-0.9), vehicle_model)
|
||||
|
||||
assert abs(controller.cooperative_offset_deg) > 0.0
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
|
||||
|
||||
def test_diagnostics_reset_on_disabled_update(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
|
||||
controller.update(4.0, True, False, make_car_state(torque=2.0), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg == 0.0
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import cereal.messaging as messaging
|
||||
|
||||
from cereal import car
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
from opendbc.car.tesla.values import CAR, DBC
|
||||
|
||||
|
||||
BASELINE_SHA = "a80064be4fdf4b8765a5e9dec44d8a5c266f48a8"
|
||||
BASELINE_SOURCE_SHA256 = {
|
||||
"opendbc_repo/opendbc/car/tesla/coop_steering.py": "9c9d60bbfae2aaa0d8c1fca203a9fa19a14a19feef5aa89e85ecaa6f6502a9f0",
|
||||
"opendbc_repo/opendbc/car/tesla/carcontroller.py": "1ef3cf646bc4b3c398bd12010e624b90e083426152d632fafb5b2e93fd3d8661",
|
||||
}
|
||||
BASELINE_FIXTURE = Path(__file__).parent / "fixtures" / "coop_steering_baseline_a80064be.json"
|
||||
|
||||
|
||||
def make_params(candidate=CAR.TESLA_MODEL_3, cooperative=True):
|
||||
toggles = SimpleNamespace(tesla_cooperative_steering=cooperative, trailer_load_kg=0.0)
|
||||
return CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, toggles)
|
||||
|
||||
|
||||
def make_car_state(torque=0.0, speed=15.0, angle=0.0, steering_disengage=False):
|
||||
return SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
steeringTorque=torque,
|
||||
steeringAngleDeg=angle,
|
||||
steeringDisengage=steering_disengage,
|
||||
vEgo=speed,
|
||||
vEgoRaw=speed,
|
||||
gasPressed=False,
|
||||
),
|
||||
hands_on_level=0,
|
||||
das_control={"DAS_controlCounter": 0},
|
||||
)
|
||||
|
||||
|
||||
def make_control(requested_angle=0.0, lat_active=True):
|
||||
control = car.CarControl.new_message()
|
||||
control.latActive = lat_active
|
||||
control.actuators.steeringAngleDeg = requested_angle
|
||||
return control.as_reader()
|
||||
|
||||
|
||||
def make_controller(candidate=CAR.TESLA_MODEL_3, cooperative=True):
|
||||
params = make_params(candidate, cooperative)
|
||||
return CarController(DBC[candidate], params)
|
||||
|
||||
|
||||
def run_frame(controller, requested_angle=0.0, torque=0.0, speed=15.0, measured_angle=0.0,
|
||||
lat_active=True, steering_disengage=False, now_nanos=1_000_000_000):
|
||||
return controller.update(
|
||||
make_control(requested_angle, lat_active),
|
||||
make_car_state(torque, speed, measured_angle, steering_disengage),
|
||||
now_nanos,
|
||||
SimpleNamespace(),
|
||||
)
|
||||
|
||||
|
||||
def get_limit_info(controller):
|
||||
return SimpleNamespace(**controller.get_steering_limit_info())
|
||||
|
||||
|
||||
def legacy_actuator_dict(actuators):
|
||||
return actuators.to_dict()
|
||||
|
||||
|
||||
def test_steering_limit_info_defaults_to_invalid():
|
||||
controller = make_controller()
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert not info.valid
|
||||
assert info.monoTime == 0
|
||||
|
||||
|
||||
def test_steering_limit_info_round_trips_through_custom_message():
|
||||
message = messaging.new_message("starpilotCarControl", valid=True)
|
||||
info = message.starpilotCarControl.steeringLimitInfo
|
||||
info.valid = True
|
||||
info.modelLimitErrorDeg = 1.25
|
||||
info.resumeLimitErrorDeg = 0.5
|
||||
info.cooperativeLimitErrorDeg = 2.0
|
||||
info.cooperativeOffsetDeg = -4.5
|
||||
info.monoTime = 1_234_567_890
|
||||
info.combinedLimitErrorDeg = 3.75
|
||||
|
||||
restored = messaging.log_from_bytes(message.to_bytes())
|
||||
restored_info = restored.starpilotCarControl.steeringLimitInfo
|
||||
assert restored_info.valid
|
||||
assert restored_info.modelLimitErrorDeg == 1.25
|
||||
assert restored_info.resumeLimitErrorDeg == 0.5
|
||||
assert restored_info.cooperativeLimitErrorDeg == 2.0
|
||||
assert restored_info.cooperativeOffsetDeg == -4.5
|
||||
assert restored_info.monoTime == 1_234_567_890
|
||||
assert restored_info.combinedLimitErrorDeg == 3.75
|
||||
|
||||
|
||||
def test_active_cooperative_controller_reports_diagnostics():
|
||||
controller = make_controller()
|
||||
requested_angle = 20.0
|
||||
now_nanos = 1_234_567_890
|
||||
|
||||
actuators, _ = run_frame(controller, requested_angle, torque=0.9, measured_angle=0.0, now_nanos=now_nanos)
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert info.valid
|
||||
assert info.monoTime == now_nanos
|
||||
assert info.modelLimitErrorDeg == pytest.approx(abs(requested_angle - controller.apply_angle_last), abs=1e-5)
|
||||
assert info.resumeLimitErrorDeg == pytest.approx(controller.coop_steer.resume_limit_error_deg, abs=1e-5)
|
||||
assert info.cooperativeLimitErrorDeg == pytest.approx(controller.coop_steer.cooperative_limit_error_deg, abs=1e-5)
|
||||
assert info.cooperativeOffsetDeg == pytest.approx(controller.coop_steer.cooperative_offset_deg, abs=1e-5)
|
||||
assert info.combinedLimitErrorDeg == pytest.approx(
|
||||
abs(requested_angle + info.cooperativeOffsetDeg - actuators.steeringAngleDeg), abs=1e-5,
|
||||
)
|
||||
assert info.modelLimitErrorDeg > 2.5
|
||||
assert info.cooperativeOffsetDeg > 0.0
|
||||
assert info.combinedLimitErrorDeg > 2.5
|
||||
errors = (info.modelLimitErrorDeg, info.resumeLimitErrorDeg,
|
||||
info.cooperativeLimitErrorDeg, info.combinedLimitErrorDeg)
|
||||
assert all(math.isfinite(error) and error >= 0.0 for error in errors)
|
||||
|
||||
|
||||
def test_cooperative_offset_alone_does_not_become_limiter_error():
|
||||
controller = make_controller()
|
||||
actuators = None
|
||||
|
||||
for frame in range(200):
|
||||
actuators, _ = run_frame(controller, torque=0.9, now_nanos=1_000_000_000 + frame * 10_000_000)
|
||||
|
||||
assert actuators is not None
|
||||
info = get_limit_info(controller)
|
||||
assert info.valid
|
||||
assert info.cooperativeOffsetDeg > 2.5
|
||||
assert info.modelLimitErrorDeg < 2.5
|
||||
assert info.resumeLimitErrorDeg < 2.5
|
||||
assert info.cooperativeLimitErrorDeg < 2.5
|
||||
assert info.combinedLimitErrorDeg < 2.5
|
||||
|
||||
|
||||
def test_combined_error_keeps_two_same_direction_small_limits_visible():
|
||||
controller = make_controller()
|
||||
# Prime the resume limiter to the first-stage output for this literal input.
|
||||
controller.coop_steer.reset_resume_state(-0.9954867959022522)
|
||||
|
||||
actuators, _ = run_frame(controller, -2.5, torque=-1.5, speed=12.5, measured_angle=0.0)
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert info.modelLimitErrorDeg == pytest.approx(1.5045133, abs=1e-5)
|
||||
assert info.resumeLimitErrorDeg == pytest.approx(0.0, abs=1e-5)
|
||||
assert info.cooperativeLimitErrorDeg == pytest.approx(1.5045133, abs=1e-5)
|
||||
assert info.modelLimitErrorDeg < 2.5
|
||||
assert info.cooperativeLimitErrorDeg < 2.5
|
||||
assert info.combinedLimitErrorDeg == pytest.approx(3.0090265, abs=1e-5)
|
||||
assert info.combinedLimitErrorDeg > 2.5
|
||||
|
||||
|
||||
def test_intervening_100hz_frame_retains_matching_50hz_sample():
|
||||
controller = make_controller()
|
||||
first, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_000_000_000)
|
||||
first_info = controller.get_steering_limit_info()
|
||||
|
||||
second, _ = run_frame(controller, -40.0, torque=-1.5, now_nanos=1_010_000_000)
|
||||
|
||||
assert controller.get_steering_limit_info() == first_info
|
||||
assert controller.get_steering_limit_info()["monoTime"] == 1_000_000_000
|
||||
|
||||
|
||||
def test_inactive_interval_clears_sample_until_next_steering_update():
|
||||
controller = make_controller()
|
||||
active, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_000_000_000)
|
||||
assert get_limit_info(controller).valid
|
||||
|
||||
inactive, _ = run_frame(controller, 8.0, torque=0.9, lat_active=False, now_nanos=1_010_000_000)
|
||||
assert not get_limit_info(controller).valid
|
||||
assert get_limit_info(controller).monoTime == 0
|
||||
|
||||
resumed, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_020_000_000)
|
||||
assert get_limit_info(controller).valid
|
||||
assert get_limit_info(controller).monoTime == 1_020_000_000
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "cooperative", "steering_disengage"), (
|
||||
(CAR.TESLA_MODEL_3, False, False),
|
||||
(CAR.TESLA_MODEL_Y, True, False),
|
||||
(CAR.TESLA_MODEL_3, True, True),
|
||||
))
|
||||
def test_diagnostics_invalid_when_not_in_supported_active_path(candidate, cooperative, steering_disengage):
|
||||
controller = make_controller(candidate, cooperative)
|
||||
|
||||
actuators, _ = run_frame(controller, torque=1.5, steering_disengage=steering_disengage)
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert not info.valid
|
||||
assert info.monoTime == 0
|
||||
|
||||
|
||||
def test_actual_actuators_and_steering_can_match_pinned_baseline_fixture():
|
||||
fixture = json.loads(BASELINE_FIXTURE.read_text())
|
||||
assert fixture["metadata"] == {
|
||||
"schemaVersion": 1,
|
||||
"baselineSha": BASELINE_SHA,
|
||||
"baselineSourceSha256": BASELINE_SOURCE_SHA256,
|
||||
"frameCount": 386,
|
||||
}
|
||||
|
||||
candidate = make_controller()
|
||||
for expected in fixture["frames"]:
|
||||
inputs = expected["input"]
|
||||
candidate_actuators, candidate_can = run_frame(
|
||||
candidate,
|
||||
inputs["requestedAngleDeg"],
|
||||
inputs["torqueNm"],
|
||||
inputs["speedMps"],
|
||||
inputs["measuredAngleDeg"],
|
||||
inputs["latActive"],
|
||||
inputs["steeringDisengage"],
|
||||
inputs["nowNanos"],
|
||||
)
|
||||
|
||||
assert legacy_actuator_dict(candidate_actuators) == expected["actuators"]
|
||||
assert [[address, data.hex(), bus] for address, data, bus in candidate_can] == expected["can"]
|
||||
@@ -1,134 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from cereal import custom
|
||||
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.fw_versions import match_fw_to_car
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.carstate import CarState
|
||||
from opendbc.car.tesla.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, DBC, TeslaSafetyFlags
|
||||
|
||||
|
||||
def test_hw1_is_distinct_from_preap_and_does_not_change_can_buses():
|
||||
preap = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP)
|
||||
hw1 = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
|
||||
assert preap.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.teslaPreAP
|
||||
assert hw1.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.tesla
|
||||
assert hw1.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value
|
||||
assert hw1.radarTimeStepDEPRECATED == pytest.approx(0.125)
|
||||
assert preap.radarTimeStepDEPRECATED == pytest.approx(0.05)
|
||||
assert CANBUS.party == 0 and CANBUS.autopilot_party == 2
|
||||
assert CarState.get_can_parsers(hw1)[Bus.ap_pt].bus == 2
|
||||
assert CarState.get_can_parsers(hw1)[Bus.chassis].message_states[0x211].ignore_alive
|
||||
assert CarState.get_can_parsers(preap)[Bus.party].bus == 0
|
||||
|
||||
|
||||
def test_hw1_requires_explicit_alpha_long_for_acceleration():
|
||||
ret = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
hw1 = CarInterface._get_params(ret, CAR.TESLA_MODEL_S_HW1, {0: {0x201: 5}}, [], True, False, False)
|
||||
assert hw1.openpilotLongitudinalControl
|
||||
assert hw1.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value | TeslaSafetyFlags.LONG_CONTROL.value
|
||||
|
||||
|
||||
def test_ap1_eps_fw_matches_hw1_without_matching_preap():
|
||||
version = FW_VERSIONS[CAR.TESLA_MODEL_S_HW1][(structs.CarParams.Ecu.eps, 0x730, None)][0]
|
||||
fw = structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.eps, address=0x730, brand="tesla", fwVersion=version)
|
||||
exact, candidates = match_fw_to_car([fw], "", log=False)
|
||||
assert exact and candidates == {CAR.TESLA_MODEL_S_HW1}
|
||||
|
||||
|
||||
def test_hw1_display_and_cruise_bytes_do_not_change_preap_signals():
|
||||
# Captured bus-0 DI_state (0x368) from the AP1 route: display 9 MPH, set speed 10 MPH.
|
||||
parser = CANParser("tesla_can", [(0x368, 0)], 0)
|
||||
parser.message_states[0x368].ignore_counter = True
|
||||
frames = [(0x368, bytes.fromhex("84185e3009980a2d"), 0)]
|
||||
parser.update([(1_000_000_000, frames)])
|
||||
parser.update([(2_000_000_000, frames)])
|
||||
state = parser.vl["DI_state"]
|
||||
assert state["DI_hw1DigitalSpeed"] == 9
|
||||
assert state["DI_hw1CruiseSet"] == 10
|
||||
assert state["DI_digitalSpeed"] == 10
|
||||
assert state["DI_cruiseSet"] != 10
|
||||
|
||||
|
||||
def test_hw1_packer_emits_bus_zero_with_matching_checksums():
|
||||
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.party])
|
||||
tesla_can = TeslaCANRaven({CANBUS.party: packer})
|
||||
for msg, expected_addr, checksum_index in (
|
||||
(tesla_can.create_steering_control(0, 0, False), 0x488, 3),
|
||||
(tesla_can.create_longitudinal_command(13, 0, 0, 10, False, False), 0x2b9, 7),
|
||||
):
|
||||
addr, data, bus = msg
|
||||
assert addr == expected_addr and bus == 0
|
||||
assert data[checksum_index] == TeslaCANRaven.checksum(addr, data[:checksum_index])
|
||||
|
||||
|
||||
def test_hw1_cancel_clears_acceleration_and_does_not_request_max_speed():
|
||||
cp = CarInterface._get_params(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1),
|
||||
CAR.TESLA_MODEL_S_HW1, {0: {}}, [], True, False, False)
|
||||
controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp)
|
||||
state = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
state.out.vEgo = 10.
|
||||
cc = structs.CarControl.new_message()
|
||||
cc.longActive = True
|
||||
cc.cruiseControl.cancel = True
|
||||
cc.actuators.accel = 2.
|
||||
_, sends = controller.update(cc.as_reader(), state, 0, None)
|
||||
_, data, bus = next(msg for msg in sends if msg[0] == 0x2b9)
|
||||
assert bus == 0
|
||||
parser = CANParser("tesla_can", [(0x2b9, 0)], 0)
|
||||
parser.update([(1_000_000_000, [(0x2b9, data, bus)])])
|
||||
decoded = parser.vl["DAS_control"]
|
||||
assert decoded["DAS_accState"] == 13
|
||||
assert decoded["DAS_accelMin"] == pytest.approx(0, abs=0.05)
|
||||
assert decoded["DAS_accelMax"] == pytest.approx(0, abs=0.05)
|
||||
assert decoded["DAS_setSpeed"] != 200
|
||||
|
||||
|
||||
def test_hw1_carstate_uses_ap1_powertrain_and_chassis():
|
||||
cp = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
parsers = CarState.get_can_parsers(cp)
|
||||
frames = [
|
||||
(0x155, bytes.fromhex("000000000005e308"), 0), # ESP speed 15.07 kph
|
||||
(0x368, bytes.fromhex("84185e3009980a2d"), 0),
|
||||
(0x201, bytes.fromhex("5444008df2"), 0),
|
||||
]
|
||||
for parser in parsers.values():
|
||||
for addr in (0x155, 0x368, 0x201):
|
||||
_ = parser.vl[addr]
|
||||
parser.message_states[addr].ignore_counter = True
|
||||
parser.message_states[addr].ignore_checksum = True
|
||||
parser.update([(1_000_000_000, frames)])
|
||||
parser.update([(2_000_000_000, frames)])
|
||||
state = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
ret, _ = state.update(parsers, None)
|
||||
assert not ret.cruiseState.enabled
|
||||
assert ret.cruiseState.speed == pytest.approx(10 * 0.44704)
|
||||
assert ret.vEgoRaw == pytest.approx(15.07 / 3.6)
|
||||
assert not ret.seatbeltUnlatched
|
||||
|
||||
# A stale belt frame cannot allow an engagement indefinitely.
|
||||
for parser in parsers.values():
|
||||
parser.update([(4_000_000_000, [])])
|
||||
ret, _ = state.update(parsers, None)
|
||||
assert ret.seatbeltUnlatched
|
||||
|
||||
|
||||
def test_hw1_can_use_rcm_buckle_when_sdm1_is_absent():
|
||||
cp = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
parsers = CarState.get_can_parsers(cp)
|
||||
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.chassis])
|
||||
addr, data, bus = packer.make_can_msg("RCM_status", 0, {"RCM_buckleDriverStatus": 1})
|
||||
assert addr == 0x211 and bus == 0
|
||||
frames = [(addr, data, bus)]
|
||||
for parser in parsers.values():
|
||||
_ = parser.vl["RCM_status"]
|
||||
parser.update([(1_000_000_000, frames)])
|
||||
state = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
out, _ = state.update(parsers, None)
|
||||
assert not out.seatbeltUnlatched
|
||||
@@ -3,7 +3,6 @@ import pytest
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.tesla.carstate import update_tesla_gas_pressed
|
||||
from opendbc.car.tesla.teslacan import TeslaCAN
|
||||
from opendbc.car.tesla.values import CarControllerParams
|
||||
|
||||
|
||||
class RecordingPacker:
|
||||
@@ -11,6 +10,7 @@ class RecordingPacker:
|
||||
return name, bus, values
|
||||
|
||||
|
||||
@pytest.mark.parametrize("active", [False, True])
|
||||
@pytest.mark.parametrize(
|
||||
("v_ego", "accel", "expected_set_speed"),
|
||||
[
|
||||
@@ -20,37 +20,12 @@ class RecordingPacker:
|
||||
(120.0, 2.0, 400.0),
|
||||
],
|
||||
)
|
||||
def test_longitudinal_set_speed_tracks_accel_continuously(v_ego, accel, expected_set_speed):
|
||||
_, _, values = TeslaCAN(RecordingPacker()).create_longitudinal_command(4, accel, 0, 200, v_ego, False)
|
||||
def test_longitudinal_set_speed_tracks_accel_continuously(active, v_ego, accel, expected_set_speed):
|
||||
_, _, values = TeslaCAN(RecordingPacker()).create_longitudinal_command(4, accel, 0, v_ego, active)
|
||||
|
||||
assert values["DAS_setSpeed"] == pytest.approx(expected_set_speed)
|
||||
|
||||
|
||||
def test_longitudinal_jerk_ramps_after_gas_release():
|
||||
can = TeslaCAN(RecordingPacker())
|
||||
|
||||
_, _, pressed = can.create_longitudinal_command(4, 0, 0, 20, 20, True)
|
||||
_, _, halfway = can.create_longitudinal_command(4, 0, 1, 70, 20, False)
|
||||
_, _, complete = can.create_longitudinal_command(4, 0, 2, 120, 20, False)
|
||||
|
||||
assert pressed["DAS_jerkMin"] == pytest.approx(0.0)
|
||||
assert pressed["DAS_jerkMax"] == pytest.approx(0.0)
|
||||
assert halfway["DAS_jerkMin"] == pytest.approx(-CarControllerParams.JERK_LIMIT_MAX / 2)
|
||||
assert halfway["DAS_jerkMax"] == pytest.approx(CarControllerParams.JERK_LIMIT_MAX / 2)
|
||||
assert complete["DAS_jerkMin"] == pytest.approx(-CarControllerParams.JERK_LIMIT_MAX)
|
||||
assert complete["DAS_jerkMax"] == pytest.approx(CarControllerParams.JERK_LIMIT_MAX)
|
||||
|
||||
|
||||
def test_longitudinal_jerk_release_timer_resets_while_gas_is_pressed():
|
||||
can = TeslaCAN(RecordingPacker())
|
||||
|
||||
can.create_longitudinal_command(4, 0, 0, 20, 20, True)
|
||||
_, _, values = can.create_longitudinal_command(4, 0, 1, 80, 20, True)
|
||||
|
||||
assert values["DAS_jerkMin"] == pytest.approx(0.0)
|
||||
assert values["DAS_jerkMax"] == pytest.approx(0.0)
|
||||
|
||||
|
||||
def test_tesla_gas_pressed_hysteresis_prevents_release_chatter():
|
||||
assert update_tesla_gas_pressed(False, 0.4) is False
|
||||
assert update_tesla_gas_pressed(False, 0.8) is False
|
||||
|
||||
@@ -70,16 +70,6 @@ class CAR(Platforms):
|
||||
Bus.radar: 'tesla_radar_bosch_generated',
|
||||
},
|
||||
)
|
||||
TESLA_MODEL_S_HW1 = TeslaPlatformConfig(
|
||||
[CarDocs("Tesla Model S (with HW1) 2014-16", "All", support_type=SupportType.COMMUNITY, support_link="#community")],
|
||||
CarSpecs(mass=2100., wheelbase=2.960, steerRatio=15.0),
|
||||
{
|
||||
Bus.chassis: 'tesla_can',
|
||||
Bus.party: 'tesla_can',
|
||||
Bus.pt: 'tesla_can',
|
||||
Bus.radar: 'tesla_radar_bosch_generated',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
@@ -136,13 +126,10 @@ class CarControllerParams:
|
||||
ACCEL_MIN = -3.48 # m/s^2
|
||||
JERK_LIMIT_MAX = 4.9 # m/s^3, ACC faults at 5.0
|
||||
JERK_LIMIT_MIN = -4.9 # m/s^3, ACC faults at 5.0
|
||||
JERK_RAMP_RATE = JERK_LIMIT_MAX * 0.002
|
||||
|
||||
|
||||
class TeslaSafetyFlags(IntFlag):
|
||||
LONG_CONTROL = 1
|
||||
FLAG_EXTERNAL_PANDA = 4
|
||||
FLAG_HW1 = 8
|
||||
COOP_STEERING = 256
|
||||
|
||||
|
||||
@@ -171,7 +158,5 @@ class CruiseButtons:
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
LEGACY_CARS = (CAR.TESLA_MODEL_S_HW1,)
|
||||
|
||||
STEER_THRESHOLD = 1
|
||||
STEER_DISENGAGE_THRESHOLD = 5.0
|
||||
|
||||
@@ -14,7 +14,6 @@ from opendbc.car.tesla.values import CAR as TESLA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from opendbc.car.values import Platform
|
||||
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN
|
||||
from opendbc.car.volvo.values import CAR as VOLVO
|
||||
from opendbc.car.body.values import CAR as COMMA
|
||||
from opendbc.car.psa.values import CAR as PSA
|
||||
|
||||
@@ -35,8 +34,6 @@ non_tested_cars = [
|
||||
GM.CHEVROLET_MALIBU_ASCM,
|
||||
GM.CHEVROLET_MALIBU_SDGM,
|
||||
GM.CHEVROLET_SUBURBAN,
|
||||
GM.CHEVROLET_SUBURBAN_ASCM,
|
||||
GM.CHEVROLET_SUBURBAN_CAMERA,
|
||||
GM.CHEVROLET_TRAX,
|
||||
GM.CHEVROLET_VOLT_ASCM,
|
||||
GM.CHEVROLET_VOLT_CAMERA,
|
||||
@@ -78,7 +75,6 @@ non_tested_cars = [
|
||||
HYUNDAI.HYUNDAI_ELANTRA_HEV_2024,
|
||||
HYUNDAI.HYUNDAI_KONA_EV_NON_SCC,
|
||||
HYUNDAI.HYUNDAI_KONA_NON_SCC,
|
||||
HYUNDAI.KIA_RAY_EV,
|
||||
HYUNDAI.HYUNDAI_PALISADE_2023,
|
||||
HYUNDAI.KIA_CEED_PHEV_2022_NON_SCC,
|
||||
HYUNDAI.KIA_FORTE_2019_NON_SCC,
|
||||
@@ -109,12 +105,6 @@ non_tested_cars = [
|
||||
TOYOTA.TOYOTA_COROLLA,
|
||||
TOYOTA.TOYOTA_RAV4H,
|
||||
|
||||
# No recorded routes yet
|
||||
VOLVO.VOLVO_V40,
|
||||
VOLVO.VOLVO_XC40_RECHARGE,
|
||||
VOLVO.VOLVO_S60_RECHARGE,
|
||||
VOLVO.POLESTAR_2,
|
||||
|
||||
]
|
||||
|
||||
non_tested_cars.extend(CC_ONLY_CAR)
|
||||
|
||||
@@ -2,7 +2,7 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from opendbc.car.can_definitions import CanData
|
||||
from opendbc.car.car_helpers import FRAME_FINGERPRINT, _apply_starpilot_access_policy, _get_gm_stored_candidate_fallback, _normalize_gm_suburban_camera_candidate, can_fingerprint
|
||||
from opendbc.car.car_helpers import FRAME_FINGERPRINT, _apply_starpilot_access_policy, _get_gm_stored_candidate_fallback, can_fingerprint
|
||||
from opendbc.car.fingerprints import _FINGERPRINTS as FINGERPRINTS
|
||||
from opendbc.car.gm.values import CAR as GM
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
@@ -116,14 +116,3 @@ class TestCanFingerprint:
|
||||
candidate = _apply_starpilot_access_policy("CHEVROLET_VOLT_CC", SimpleNamespace(block_user=True))
|
||||
|
||||
assert candidate == "CHEVROLET_VOLT_CC"
|
||||
|
||||
def test_gm_suburban_camera_variant_uses_vin_and_camera_bus_signature(self):
|
||||
fingerprints = {
|
||||
0: {190: 6, 201: 8, 209: 7, 211: 2, 241: 6, 304: 1, 320: 3},
|
||||
2: {0x24b: 8, 0x64b: 8},
|
||||
}
|
||||
|
||||
assert _normalize_gm_suburban_camera_candidate(None, fingerprints, "1GNSKJKJXKR148371") == "CHEVROLET_SUBURBAN_CAMERA"
|
||||
assert _normalize_gm_suburban_camera_candidate("GMC_YUKON", fingerprints, "1GNSKJKJXKR148371") == "CHEVROLET_SUBURBAN_CAMERA"
|
||||
assert _normalize_gm_suburban_camera_candidate(None, fingerprints, "1GNSKCKC5KR255194") is None
|
||||
assert _normalize_gm_suburban_camera_candidate(None, {0: fingerprints[0], 2: {0x320: 3}}, "1GNSKJKJXKR148371") is None
|
||||
|
||||
@@ -300,89 +300,6 @@ class TestCarInterfaces:
|
||||
)
|
||||
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
|
||||
def test_hyundai_elantra_hev_auto_aol_uses_main_engage_flag(self):
|
||||
toggles = get_test_starpilot_toggles()
|
||||
toggles.always_on_lateral_main = True
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
car_params = HyundaiCarInterface.get_params(
|
||||
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=toggles,
|
||||
)
|
||||
fp_car_params = HyundaiCarInterface.get_starpilot_params(
|
||||
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
|
||||
fingerprint,
|
||||
[],
|
||||
car_params,
|
||||
toggles,
|
||||
)
|
||||
|
||||
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
|
||||
assert not (fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value)
|
||||
|
||||
def test_hyundai_elantra_hev_lkas_aol_uses_lkas_engage_flag(self):
|
||||
toggles = get_test_starpilot_toggles()
|
||||
toggles.always_on_lateral_lkas = True
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
car_params = HyundaiCarInterface.get_params(
|
||||
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=toggles,
|
||||
)
|
||||
fp_car_params = HyundaiCarInterface.get_starpilot_params(
|
||||
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
|
||||
fingerprint,
|
||||
[],
|
||||
car_params,
|
||||
toggles,
|
||||
)
|
||||
|
||||
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
assert not (fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("candidate", "sets_main_aol_flag"),
|
||||
(
|
||||
(HYUNDAI_CAR.HYUNDAI_SONATA_HYBRID, True),
|
||||
(HYUNDAI_CAR.HYUNDAI_SONATA, False),
|
||||
),
|
||||
)
|
||||
def test_hyundai_main_aol_engage_flag_is_scoped_to_hybrid(self, candidate, sets_main_aol_flag):
|
||||
toggles = get_test_starpilot_toggles()
|
||||
toggles.always_on_lateral_main = True
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
car_params = HyundaiCarInterface.get_params(
|
||||
candidate,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=toggles,
|
||||
)
|
||||
fp_car_params = HyundaiCarInterface.get_starpilot_params(
|
||||
candidate,
|
||||
fingerprint,
|
||||
[],
|
||||
car_params,
|
||||
toggles,
|
||||
)
|
||||
|
||||
has_main_aol_flag = bool(fp_car_params.safetyConfigs[-1].safetyParam &
|
||||
HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value)
|
||||
assert has_main_aol_flag is sets_main_aol_flag
|
||||
|
||||
def test_toyota_disable_openpilot_long_sets_stock_long_safety_flag(self):
|
||||
CarInterface = interfaces[TOYOTA_CAR.TOYOTA_PRIUS_TSS2]
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
@@ -283,7 +283,6 @@ class TestFwFingerprintTiming:
|
||||
'tesla': 0.1,
|
||||
'toyota': 0.7,
|
||||
'volkswagen': 0.65,
|
||||
'volvo': 0.0,
|
||||
'rivian': 0.3,
|
||||
'psa': 0.1,
|
||||
},
|
||||
|
||||
@@ -26,7 +26,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"TESLA_MODEL_3" = [nan, 2.5, nan]
|
||||
"TESLA_MODEL_Y" = [nan, 2.5, nan]
|
||||
"TESLA_MODEL_X" = [nan, 2.5, nan]
|
||||
"TESLA_MODEL_S_HW1" = [nan, 2.5, nan]
|
||||
|
||||
# Guess
|
||||
"VOLKSWAGEN_ID3_MK1" = [nan, 2.5, nan]
|
||||
@@ -107,7 +106,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"KIA_CARNIVAL_4TH_GEN" = [1.75, 1.75, 0.15]
|
||||
"KIA_CARNIVAL_2025" = [1.75, 1.75, 0.15]
|
||||
"KIA_CARNIVAL_HEV_4TH_GEN" = [1.75, 1.75, 0.15]
|
||||
"KIA_RAY_EV" = [1.8, 2.0, 0.15]
|
||||
"GMC_ACADIA" = [1.6, 1.6, 0.2]
|
||||
"LEXUS_IS_TSS2" = [2.0, 2.0, 0.1]
|
||||
"HYUNDAI_KONA_EV_2ND_GEN" = [2.5, 2.5, 0.1]
|
||||
@@ -144,9 +142,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"HONDA_NBOX_2G" = [1.2, 1.2, 0.2]
|
||||
"ACURA_TLX_2G" = [1.2, 1.2, 0.15]
|
||||
"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]
|
||||
|
||||
@@ -90,8 +90,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"CHEVROLET_MALIBU" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_MALIBU_ASCM" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_MALIBU_SDGM" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_SUBURBAN_ASCM" = "CHEVROLET_SUBURBAN"
|
||||
"CHEVROLET_SUBURBAN_CAMERA" = "CHEVROLET_SUBURBAN"
|
||||
"HOLDEN_ASTRA" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_VOLT_CC" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_VOLT_CAMERA" = "CHEVROLET_VOLT"
|
||||
@@ -138,5 +136,3 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"CHEVROLET_SILVERADO_CC" = "CHEVROLET_SILVERADO"
|
||||
"CADILLAC_XT4_CC" = "CADILLAC_XT4"
|
||||
"CADILLAC_XT6" = "GMC_ACADIA"
|
||||
|
||||
"POLESTAR_2" = "VOLVO_XC40_RECHARGE"
|
||||
|
||||
@@ -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, TOYOTA_AUTO_HOLD_CARS
|
||||
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR
|
||||
from opendbc.can import CANPacker
|
||||
|
||||
Ecu = structs.CarParams.Ecu
|
||||
@@ -37,16 +37,12 @@ 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
|
||||
MAX_STEER_RATE = 100 # deg/s
|
||||
MAX_STEER_RATE_FRAMES = 17 # tx control frames needed before torque can be cut
|
||||
MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut
|
||||
TOYOTA_HIGHLANDER_TSS2_MAX_STEER_RATE_FRAMES = 8
|
||||
|
||||
# EPS allows user torque above threshold for 50 frames before permanently faulting
|
||||
MAX_USER_TORQUE = 500
|
||||
@@ -77,28 +73,9 @@ def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
|
||||
) or highlander_sdsu)
|
||||
|
||||
|
||||
def get_toyota_lat_active(car_fingerprint, requested_active: bool, steering_torque: float,
|
||||
steering_pressed: bool) -> bool:
|
||||
if not requested_active or abs(steering_torque) >= MAX_USER_TORQUE:
|
||||
return False
|
||||
|
||||
return not (car_fingerprint == CAR.TOYOTA_COROLLA_TSS2 and steering_pressed)
|
||||
|
||||
|
||||
def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
|
||||
return (
|
||||
auto_hold_enabled and
|
||||
CP.carFingerprint in TOYOTA_AUTO_HOLD_CARS and
|
||||
bool(CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
|
||||
)
|
||||
|
||||
|
||||
def get_rav4_interceptor_pedal_scale(v_ego: float) -> float:
|
||||
return float(np.interp(
|
||||
max(float(v_ego), 0.0),
|
||||
[0.0, TOYOTA_RAV4_LAUNCH_PEDAL_BLEND_SPEED, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION],
|
||||
[TOYOTA_RAV4_LAUNCH_PEDAL_SCALE, TOYOTA_RAV4_LOW_SPEED_PEDAL_SCALE, 0.3, 0.0],
|
||||
))
|
||||
def get_steer_rate_limit_frames(car_fingerprint) -> int:
|
||||
return (TOYOTA_HIGHLANDER_TSS2_MAX_STEER_RATE_FRAMES
|
||||
if car_fingerprint == CAR.TOYOTA_HIGHLANDER_TSS2 else MAX_STEER_RATE_FRAMES)
|
||||
|
||||
|
||||
def get_long_tune(CP, params):
|
||||
@@ -252,6 +229,7 @@ class CarController(CarControllerBase):
|
||||
self.standstill_req = False
|
||||
self.permit_braking = True
|
||||
self.steer_rate_counter = 0
|
||||
self.steer_rate_limit_frames = get_steer_rate_limit_frames(self.CP.carFingerprint)
|
||||
self.distance_button = 0
|
||||
|
||||
# *** start long control state ***
|
||||
@@ -272,8 +250,11 @@ class CarController(CarControllerBase):
|
||||
self.secoc_prev_reset_counter = 0
|
||||
|
||||
self.doors_locked = False
|
||||
self.auto_brake_hold = bool(self.CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
|
||||
self.brake_hold_active = False
|
||||
self._brake_hold_counter = 0
|
||||
self._brake_hold_reset = False
|
||||
self._prev_brake_pressed = False
|
||||
|
||||
def _compute_interceptor_gas_cmd(self, CC, CS):
|
||||
if not (self.CP.enableGasInterceptorDEPRECATED and self.CP.openpilotLongitudinalControl and CC.longActive):
|
||||
@@ -284,7 +265,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
max_interceptor_gas = 0.5
|
||||
if self.CP.carFingerprint == CAR.TOYOTA_RAV4:
|
||||
pedal_scale = get_rav4_interceptor_pedal_scale(CS.out.vEgo)
|
||||
pedal_scale = float(np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0]))
|
||||
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:
|
||||
@@ -319,32 +300,33 @@ class CarController(CarControllerBase):
|
||||
|
||||
self.last_standstill = CS.out.standstill
|
||||
|
||||
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
|
||||
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
|
||||
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:
|
||||
if brake_hold_allowed:
|
||||
self._brake_hold_counter += 1
|
||||
self.brake_hold_active = self._brake_hold_counter > activation_frames
|
||||
elif not brake_hold_allowed:
|
||||
self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer and not self._brake_hold_reset
|
||||
self._brake_hold_reset = not self._prev_brake_pressed and CS.out.brakePressed and not self._brake_hold_reset
|
||||
else:
|
||||
self._brake_hold_counter = 0
|
||||
self.brake_hold_active = False
|
||||
self._brake_hold_reset = False
|
||||
self._prev_brake_pressed = CS.out.brakePressed
|
||||
|
||||
return self.brake_hold_active
|
||||
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))
|
||||
|
||||
def reset_auto_hold_state(self):
|
||||
self._brake_hold_counter = 0
|
||||
self.brake_hold_active = False
|
||||
return can_sends
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
actuators = CC.actuators
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
hud_control = CC.hudControl
|
||||
pcm_cancel_cmd = CC.cruiseControl.cancel
|
||||
lat_active = get_toyota_lat_active(self.CP.carFingerprint, CC.latActive,
|
||||
CS.out.steeringTorque, CS.out.steeringPressed)
|
||||
lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE
|
||||
|
||||
if len(CC.orientationNED) == 3:
|
||||
self.pitch.update(CC.orientationNED[1])
|
||||
@@ -372,7 +354,7 @@ class CarController(CarControllerBase):
|
||||
# >100 degree/sec steering fault prevention
|
||||
self.steer_rate_counter, apply_steer_req = common_fault_avoidance(
|
||||
abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active,
|
||||
self.steer_rate_counter, MAX_STEER_RATE_FRAMES,
|
||||
self.steer_rate_counter, self.steer_rate_limit_frames,
|
||||
)
|
||||
|
||||
if not lat_active:
|
||||
@@ -434,10 +416,8 @@ class CarController(CarControllerBase):
|
||||
# *** gas and brake ***
|
||||
|
||||
self._update_standstill_request(CC, CS, actuators, starpilot_toggles)
|
||||
if supports_toyota_auto_hold(self.CP, getattr(starpilot_toggles, "toyota_auto_hold", False)):
|
||||
self.update_auto_hold_state(CS, pcm_cancel_cmd)
|
||||
else:
|
||||
self.reset_auto_hold_state()
|
||||
if self.auto_brake_hold:
|
||||
can_sends.extend(self.create_auto_brake_hold_messages(CS))
|
||||
|
||||
interceptor_gas_cmd = self._compute_interceptor_gas_cmd(CC, CS)
|
||||
|
||||
@@ -545,11 +525,6 @@ 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,
|
||||
|
||||
@@ -75,7 +75,6 @@ class CarState(CarStateBase):
|
||||
self.distance_button = 0
|
||||
|
||||
self.pcm_follow_distance = 0
|
||||
self.pcm_acc_status = 0
|
||||
|
||||
self.acc_type = 1
|
||||
self.lkas_hud = {}
|
||||
@@ -90,6 +89,8 @@ 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]
|
||||
@@ -207,7 +208,6 @@ class CarState(CarStateBase):
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
ret.accFaulted = ret.accFaulted or cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2
|
||||
|
||||
prev_pcm_acc_status = self.pcm_acc_status
|
||||
self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"]
|
||||
if self.CP.carFingerprint not in (NO_STOP_TIMER_CAR - TSS2_CAR):
|
||||
# ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request
|
||||
@@ -225,6 +225,9 @@ 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"]
|
||||
|
||||
@@ -242,11 +245,6 @@ class CarState(CarStateBase):
|
||||
|
||||
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
|
||||
|
||||
if self.CP.carFingerprint in LEGACY_PRIUS_CAR and not self.has_SDSU:
|
||||
prev_distance_button = self.distance_button
|
||||
self.distance_button = cp_acc.vl["ACC_CONTROL"]["DISTANCE"]
|
||||
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
|
||||
|
||||
if self.CP.carFingerprint in DISTANCE_BUTTON_CAR:
|
||||
prev_distance_button = self.distance_button
|
||||
self.distance_button = cp.vl["PCM_CRUISE_4"]["DISTANCE"]
|
||||
@@ -261,8 +259,8 @@ class CarState(CarStateBase):
|
||||
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
|
||||
|
||||
buttonEvents += [
|
||||
*create_button_events(self.pcm_acc_status == 9, prev_pcm_acc_status == 9, {1: ButtonType.accelCruise}),
|
||||
*create_button_events(self.pcm_acc_status == 10, prev_pcm_acc_status == 10, {1: ButtonType.decelCruise}),
|
||||
*create_button_events(self.pcm_acc_status == 9, False, {1: ButtonType.accelCruise}),
|
||||
*create_button_events(self.pcm_acc_status == 10, False, {1: ButtonType.decelCruise}),
|
||||
]
|
||||
|
||||
fp_ret.dashboardSpeedLimit = calculate_speed_limit(cp_cam)
|
||||
@@ -296,20 +294,14 @@ class CarState(CarStateBase):
|
||||
pt_messages = [
|
||||
("BLINKERS_STATE", float('nan')),
|
||||
]
|
||||
cam_messages = []
|
||||
|
||||
if CP.enableGasInterceptorDEPRECATED:
|
||||
pt_messages.append(("GAS_SENSOR", 50))
|
||||
|
||||
if CP.carFingerprint in LEGACY_PRIUS_CAR:
|
||||
pt_messages.append(("ACC_CONTROL", float('nan')))
|
||||
if CP.flags & ToyotaFlags.DSU_BYPASS.value:
|
||||
cam_messages.append(("ACC_CONTROL", float('nan')))
|
||||
|
||||
if CP.carFingerprint in DISTANCE_BUTTON_CAR:
|
||||
pt_messages.append(("PCM_CRUISE_4", 1))
|
||||
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, 0),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, 2),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2),
|
||||
}
|
||||
|
||||
@@ -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, NO_DSU_CAR, \
|
||||
from opendbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, SECOC_CAR, NO_DSU_CAR, \
|
||||
MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR, \
|
||||
ToyotaSafetyFlags, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS
|
||||
ToyotaSafetyFlags, LEGACY_PRIUS_CAR
|
||||
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 ret.openpilotLongitudinalControl and candidate in TOYOTA_AUTO_HOLD_CARS:
|
||||
ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
|
||||
if toyota_auto_hold and candidate in (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR):
|
||||
ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
|
||||
ret.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value
|
||||
|
||||
if not ret.openpilotLongitudinalControl:
|
||||
|
||||
@@ -10,20 +10,17 @@ 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, \
|
||||
get_toyota_lat_active, \
|
||||
get_steer_rate_limit_frames, \
|
||||
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, \
|
||||
update_permit_braking
|
||||
limit_prius_stopping_accel, should_bypass_toyota_long_pid, update_permit_braking
|
||||
from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_interceptor_gas_pressed, create_lkas_button_events
|
||||
from opendbc.car.toyota.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, MIN_ACC_SPEED, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, SECOC_CAR, \
|
||||
from opendbc.car.toyota.values import CAR, DBC, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, SECOC_CAR, \
|
||||
FW_QUERY_CONFIG, PLATFORM_CODE_ECUS, FUZZY_EXCLUDED_PLATFORMS, \
|
||||
ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, TOYOTA_AUTO_HOLD_CARS, \
|
||||
get_platform_codes
|
||||
ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, get_platform_codes
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.common.params import Params
|
||||
|
||||
@@ -190,15 +187,13 @@ class TestToyotaInterfaces:
|
||||
if car_model in TSS2_CAR and car_model not in SECOC_CAR:
|
||||
assert dbc[Bus.pt] == "toyota_nodsu_pt_generated"
|
||||
|
||||
@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):
|
||||
def test_auto_hold_sets_flag_on_supported_tss2(self):
|
||||
params = Params()
|
||||
try:
|
||||
params.put_bool("ToyotaAutoHold", True)
|
||||
car_params = CarInterface.get_params(
|
||||
candidate,
|
||||
{bus: ({0x2FF: 8} if candidate in (CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H) and bus == 0 else {})
|
||||
for bus in range(8)},
|
||||
CAR.TOYOTA_CAMRY_TSS2,
|
||||
{bus: {} for bus in range(8)},
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
@@ -209,30 +204,7 @@ class TestToyotaInterfaces:
|
||||
params.remove("ToyotaAutoHold")
|
||||
|
||||
assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
|
||||
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
|
||||
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
|
||||
|
||||
can_parsers = CarState.get_can_parsers(car_params)
|
||||
car_state = CarState(car_params, SimpleNamespace(flags=0))
|
||||
car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
|
||||
assert "PRE_COLLISION_2" not in can_parsers[Bus.cam].vl
|
||||
|
||||
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H])
|
||||
def test_auto_hold_is_disabled_by_default(self, candidate):
|
||||
params = Params()
|
||||
params.remove("ToyotaAutoHold")
|
||||
car_params = CarInterface.get_params(
|
||||
candidate,
|
||||
{bus: {} for bus in range(8)},
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=SimpleNamespace(),
|
||||
)
|
||||
|
||||
assert not car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
|
||||
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
|
||||
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
|
||||
|
||||
def test_prius_openpilot_long_uses_hybrid_long_defaults(self):
|
||||
car_params = CarInterface.get_params(
|
||||
@@ -735,14 +707,9 @@ class TestToyotaFingerprint:
|
||||
|
||||
|
||||
class TestToyotaCarController:
|
||||
def test_corolla_tss2_hands_off_immediately_when_driver_is_steering(self):
|
||||
assert not get_toyota_lat_active(CAR.TOYOTA_COROLLA_TSS2, True, 117, True)
|
||||
|
||||
def test_corolla_tss2_stays_active_without_driver_input(self):
|
||||
assert get_toyota_lat_active(CAR.TOYOTA_COROLLA_TSS2, True, 99, False)
|
||||
|
||||
def test_toyota_driver_handoff_behavior_is_corolla_only(self):
|
||||
assert get_toyota_lat_active(CAR.TOYOTA_RAV4_TSS2, True, 117, True)
|
||||
def test_highlander_tss2_uses_early_steer_rate_fault_guard(self):
|
||||
assert get_steer_rate_limit_frames(CAR.TOYOTA_HIGHLANDER_TSS2) == 8
|
||||
assert get_steer_rate_limit_frames(CAR.TOYOTA_RAV4_TSS2) == 18
|
||||
|
||||
@staticmethod
|
||||
def _make_controller(*, standstill_req=False, last_standstill=False):
|
||||
@@ -756,8 +723,6 @@ 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
|
||||
@@ -790,74 +755,6 @@ class TestToyotaCarController:
|
||||
|
||||
assert controller.standstill_req is True
|
||||
|
||||
def test_toyota_auto_hold_requires_toggle_supported_car_and_capability(self):
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
|
||||
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
|
||||
)
|
||||
|
||||
assert supports_toyota_auto_hold(CP, True)
|
||||
assert supports_toyota_auto_hold(SimpleNamespace(
|
||||
carFingerprint=CAR.TOYOTA_RAV4,
|
||||
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
|
||||
), True)
|
||||
assert supports_toyota_auto_hold(SimpleNamespace(
|
||||
carFingerprint=CAR.TOYOTA_RAV4H,
|
||||
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
|
||||
), True)
|
||||
assert not supports_toyota_auto_hold(CP, False)
|
||||
assert not supports_toyota_auto_hold(SimpleNamespace(
|
||||
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
|
||||
flags=0,
|
||||
), True)
|
||||
assert not supports_toyota_auto_hold(SimpleNamespace(
|
||||
carFingerprint=CAR.TOYOTA_CAMRY,
|
||||
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
|
||||
), True)
|
||||
|
||||
assert TOYOTA_AUTO_HOLD_CARS >= {CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H}
|
||||
|
||||
def test_toyota_auto_hold_latches_after_brake_press_until_gas(self):
|
||||
controller = self._make_controller()
|
||||
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
|
||||
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
cruiseState=SimpleNamespace(available=True, enabled=False),
|
||||
gasPressed=False,
|
||||
brakePressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
)
|
||||
|
||||
controller.update_auto_hold_state(cs, activation_frames=0)
|
||||
assert controller.brake_hold_active
|
||||
|
||||
cs.out.brakePressed = False
|
||||
controller.update_auto_hold_state(cs, activation_frames=0)
|
||||
assert controller.brake_hold_active
|
||||
|
||||
cs.out.gasPressed = True
|
||||
controller.update_auto_hold_state(cs, activation_frames=0)
|
||||
assert not controller.brake_hold_active
|
||||
|
||||
def test_toyota_auto_hold_does_not_trigger_without_brake_press(self):
|
||||
controller = self._make_controller()
|
||||
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
cruiseState=SimpleNamespace(available=True, enabled=False),
|
||||
gasPressed=False,
|
||||
brakePressed=False,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
)
|
||||
|
||||
controller.update_auto_hold_state(cs, activation_frames=0)
|
||||
assert not controller.brake_hold_active
|
||||
|
||||
def test_prius_resume_request_releases_standstill_latch(self):
|
||||
controller = self._make_controller(standstill_req=True, last_standstill=True)
|
||||
|
||||
@@ -995,30 +892,32 @@ class TestToyotaCarController:
|
||||
assert parser.can_valid
|
||||
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1
|
||||
|
||||
def test_auto_hold_uses_acc_control_brake_path(self):
|
||||
def test_auto_brake_hold_sends_modified_pre_collision_after_timer(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
|
||||
controller._brake_hold_reset = False
|
||||
controller._prev_brake_pressed = False
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
cruiseState=SimpleNamespace(available=True, enabled=False),
|
||||
gasPressed=False,
|
||||
brakePressed=True,
|
||||
brakePressed=False,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
pre_collision_2={},
|
||||
)
|
||||
|
||||
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,
|
||||
)]
|
||||
can_sends = controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
|
||||
parser = CANParser(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt], [("ACC_CONTROL", 0)], 0)
|
||||
parser = CANParser(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt], [("PRE_COLLISION_2", 0)], 0)
|
||||
parser.update([(1, can_sends)])
|
||||
assert controller.brake_hold_active
|
||||
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
|
||||
assert parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -1.0
|
||||
assert parser.vl["PRE_COLLISION_2"]["PBRTRGR"] == 1
|
||||
|
||||
def test_interceptor_stop_and_go_holds_small_launch_at_standstill(self):
|
||||
controller = self._make_controller()
|
||||
@@ -1046,27 +945,6 @@ 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
|
||||
@@ -1165,36 +1043,6 @@ class TestToyotaCarController:
|
||||
|
||||
|
||||
class TestToyotaCarState:
|
||||
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT])
|
||||
def test_legacy_prius_distance_button_generates_events(self, candidate):
|
||||
params = CarInterface.get_params(
|
||||
candidate,
|
||||
{bus: {} for bus in range(8)},
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False),
|
||||
)
|
||||
starpilot_params = CarInterface.get_starpilot_params(candidate, {bus: {} for bus in range(8)}, [], params, SimpleNamespace())
|
||||
car_state = CarState(params, starpilot_params)
|
||||
can_parsers = car_state.get_can_parsers(params)
|
||||
|
||||
assert "ACC_CONTROL" in can_parsers[Bus.pt].vl
|
||||
assert ("ACC_CONTROL" in can_parsers[Bus.cam].vl) == bool(params.flags & ToyotaFlags.DSU_BYPASS.value)
|
||||
|
||||
can_parsers[Bus.pt].vl["ACC_CONTROL"]["DISTANCE"] = 1
|
||||
ret, _ = car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
|
||||
assert [(event.type, event.pressed) for event in ret.buttonEvents] == [
|
||||
(structs.CarState.ButtonEvent.Type.gapAdjustCruise, True),
|
||||
]
|
||||
|
||||
can_parsers[Bus.pt].vl["ACC_CONTROL"]["DISTANCE"] = 0
|
||||
ret, _ = car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
|
||||
assert [(event.type, event.pressed) for event in ret.buttonEvents] == [
|
||||
(structs.CarState.ButtonEvent.Type.gapAdjustCruise, False),
|
||||
]
|
||||
|
||||
def test_lkas_button_platforms(self):
|
||||
assert CAR.TOYOTA_PRIUS in LKAS_BUTTON_CAR
|
||||
assert TSS2_CAR <= LKAS_BUTTON_CAR
|
||||
|
||||
@@ -89,6 +89,38 @@ 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,11 +624,6 @@ 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)
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ from opendbc.car.subaru.values import CAR as SUBARU
|
||||
from opendbc.car.tesla.values import CAR as TESLA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN
|
||||
from opendbc.car.volvo.values import CAR as VOLVO
|
||||
|
||||
Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | PSA | RIVIAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN | VOLVO
|
||||
Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | PSA | RIVIAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN
|
||||
BRANDS = get_args(Platform)
|
||||
|
||||
PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in brand}
|
||||
|
||||
@@ -185,29 +185,6 @@ def volkswagen_mqb_meb_checksum(address: int, sig, d: bytearray) -> int:
|
||||
return crc ^ 0xFF
|
||||
|
||||
|
||||
def volkswagen_mqb_meb_dyn_len_checksum(address: int, sig, d: bytearray, length: int, const: list[int]) -> int:
|
||||
d = d[:length]
|
||||
crc = 0xFF
|
||||
for i in range(1, len(d)):
|
||||
crc ^= d[i]
|
||||
crc = CRC8H2F[crc]
|
||||
counter = d[1] & 0x0F
|
||||
crc ^= const[counter]
|
||||
crc = CRC8H2F[crc]
|
||||
return crc ^ 0xFF
|
||||
|
||||
|
||||
def volkswagen_meb_alt_crc_checksum(address: int, sig, d: bytearray) -> int:
|
||||
entry = VOLKSWAGEN_MEB_ALT_CRC_CONSTANTS.get(address)
|
||||
if entry:
|
||||
length, const = entry
|
||||
checksum = volkswagen_mqb_meb_dyn_len_checksum(address, sig, d, length, const)
|
||||
if checksum == d[0]:
|
||||
return checksum
|
||||
|
||||
return volkswagen_mqb_meb_checksum(address, sig, d)
|
||||
|
||||
|
||||
def xor_checksum(address: int, sig, d: bytearray, initial_value: int = 0) -> int:
|
||||
checksum = initial_value
|
||||
checksum_byte = sig.start_bit // 8
|
||||
@@ -266,8 +243,6 @@ VOLKSWAGEN_MQB_MEB_CONSTANTS: dict[int, list[int]] = {
|
||||
0xC5, 0x91, 0x0F, 0x27, 0x34, 0x04, 0x7F, 0x02], # EA_02
|
||||
0x20A: [0x9D, 0xE8, 0x36, 0xA1, 0xCA, 0x3B, 0x1D, 0x33,
|
||||
0xE0, 0xD5, 0xBB, 0x5F, 0xAE, 0x3C, 0x31, 0x9F], # EML_06
|
||||
0x25D: [0xDA, 0x6B, 0x0E, 0xB2, 0x78, 0xBD, 0x5A, 0x81,
|
||||
0x7B, 0xD6, 0x41, 0x39, 0x76, 0xB6, 0xD7, 0x35], # KLR_01
|
||||
0x26B: [0xCE, 0xCC, 0xBD, 0x69, 0xA1, 0x3C, 0x18, 0x76,
|
||||
0x0F, 0x04, 0xF2, 0x3A, 0x93, 0x24, 0x19, 0x51], # TA_01
|
||||
0x30C: [0x0F] * 16, # ACC_02
|
||||
@@ -281,19 +256,3 @@ VOLKSWAGEN_MQB_MEB_CONSTANTS: dict[int, list[int]] = {
|
||||
0x65D: [0xAC, 0xB3, 0xAB, 0xEB, 0x7A, 0xE1, 0x3B, 0xF7,
|
||||
0x73, 0xBA, 0x7C, 0x9E, 0x06, 0x5F, 0x02, 0xD9], # ESP_20
|
||||
}
|
||||
|
||||
|
||||
VOLKSWAGEN_MEB_ALT_CRC_CONSTANTS: dict[int, tuple[int, list[int]]] = {
|
||||
0x0DB: (42, [0x09, 0xFA, 0xCA, 0x8E, 0x62, 0xD5, 0xD1, 0xF0,
|
||||
0x31, 0xA0, 0xAF, 0xDA, 0x4D, 0x1A, 0x0A, 0x97]), # AWV_03
|
||||
0xFC: (60, [0x69, 0xDC, 0xF9, 0x64, 0x6A, 0xCE, 0x55, 0x2C,
|
||||
0xC4, 0x38, 0x8F, 0xD1, 0xC6, 0x43, 0xB4, 0xB1]), # ESC_51
|
||||
0x102: (44, [0xD7, 0x12, 0x85, 0x7E, 0x0B, 0x34, 0xFA, 0x16,
|
||||
0x7A, 0x25, 0x2D, 0x8F, 0x04, 0x8E, 0x5D, 0x35]), # ESC_50
|
||||
0x10B: (44, [0x2C, 0xB1, 0x1A, 0x75, 0xBB, 0x65, 0x79, 0x47,
|
||||
0x81, 0x2B, 0xCC, 0x96, 0x17, 0xDB, 0xC0, 0x94]), # Motor_51
|
||||
0x139: (28, [0x96, 0x92, 0x95, 0xB5, 0x6E, 0xE3, 0xBD, 0xB4,
|
||||
0xFA, 0xAE, 0xBE, 0xCB, 0xCF, 0xA5, 0x77, 0xEF]), # VMM_02
|
||||
0x13D: (28, [0x18, 0x71, 0x10, 0x8D, 0xD7, 0xAA, 0xB0, 0x78,
|
||||
0xAC, 0x12, 0xAE, 0x0C, 0xDD, 0xF1, 0x85, 0x68]), # QFK_01
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user