mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-08 09:03:42 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6960cb9964 |
@@ -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.
|
||||
|
||||
+4
-51
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import importlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -129,41 +128,6 @@ elif arch == "aarch64" and AGNOS:
|
||||
arch = "larch64"
|
||||
assert arch in ["larch64", "aarch64", "x86_64", "Darwin"]
|
||||
|
||||
# AGNOS 19.6 ships native dependencies as versioned Python packages. Link
|
||||
# Cap'n Proto statically from that managed package so release binaries don't
|
||||
# depend on the removed libcapnp-1.0.2.so system library.
|
||||
try:
|
||||
capnproto = importlib.import_module("capnproto")
|
||||
except ModuleNotFoundError:
|
||||
capnproto = None
|
||||
try:
|
||||
ffmpeg = importlib.import_module("ffmpeg")
|
||||
except ModuleNotFoundError:
|
||||
ffmpeg = None
|
||||
|
||||
capnproto_include_dirs = [capnproto.INCLUDE_DIR] if capnproto is not None else []
|
||||
capnproto_lib_dirs = [capnproto.LIB_DIR] if capnproto is not None else []
|
||||
ffmpeg_include_dirs = [ffmpeg.INCLUDE_DIR] if ffmpeg is not None else []
|
||||
ffmpeg_lib_dirs = [ffmpeg.LIB_DIR] if ffmpeg is not None else []
|
||||
|
||||
# Cross-builds install managed dependencies in /work/.venv-linux-arm64, but
|
||||
# comma devices expose the same packages from /usr/local/venv. Never embed the
|
||||
# host/container mount path in release binaries.
|
||||
ffmpeg_runtime_lib_dirs = ffmpeg_lib_dirs
|
||||
if arch == "larch64" and ffmpeg is not None:
|
||||
ffmpeg_runtime_lib_dirs = [os.path.join("/usr/local/venv", os.path.relpath(ffmpeg.LIB_DIR, sys.prefix))]
|
||||
|
||||
# The managed native-dependency packages keep their tools inside the package
|
||||
# instead of installing them into /usr/local/venv/bin. cereal invokes capnpc
|
||||
# directly while SConscript files are evaluated, so make the packaged tools
|
||||
# discoverable to both SCons actions and configure-time subprocesses.
|
||||
dependency_bin_dirs = [
|
||||
package.BIN_DIR for package in (capnproto, ffmpeg)
|
||||
if package is not None and os.path.isdir(package.BIN_DIR)
|
||||
]
|
||||
if dependency_bin_dirs:
|
||||
os.environ["PATH"] = os.pathsep.join([*dependency_bin_dirs, os.environ["PATH"]])
|
||||
|
||||
# Homebrew llvm can shadow Apple clang and break macOS SDK header resolution.
|
||||
# Use the system toolchain explicitly on macOS for reliable local builds.
|
||||
cc = '/usr/bin/clang' if arch == "Darwin" else 'clang'
|
||||
@@ -305,10 +269,7 @@ env = Environment(
|
||||
"-Wno-vla-cxx-extension",
|
||||
] + cflags + ccflags,
|
||||
|
||||
# Managed dependencies must precede the compatibility sysroot. The sysroot
|
||||
# can intentionally retain legacy libraries for C3 support, but new release
|
||||
# binaries must link against the versions shipped in the managed venv.
|
||||
CPPPATH=capnproto_include_dirs + ffmpeg_include_dirs + cpppath + [
|
||||
CPPPATH=cpppath + [
|
||||
"#",
|
||||
"#third_party/acados/include",
|
||||
"#third_party/acados/include/blasfeo/include",
|
||||
@@ -327,11 +288,11 @@ env = Environment(
|
||||
RANLIB=ranlib,
|
||||
LINKFLAGS=ldflags,
|
||||
|
||||
RPATH=ffmpeg_runtime_lib_dirs + rpath,
|
||||
RPATH=rpath,
|
||||
|
||||
CFLAGS=["-std=gnu11"] + cflags,
|
||||
CXXFLAGS=["-std=c++1z"] + cxxflags,
|
||||
LIBPATH=capnproto_lib_dirs + ffmpeg_lib_dirs + libpath + [
|
||||
LIBPATH=libpath + [
|
||||
"#msgq_repo",
|
||||
"#third_party",
|
||||
"#selfdrive/pandad",
|
||||
@@ -410,15 +371,7 @@ SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog})
|
||||
SConscript(['cereal/SConscript'])
|
||||
|
||||
Import('socketmaster', 'msgq')
|
||||
if capnproto is not None:
|
||||
messaging = [
|
||||
socketmaster,
|
||||
msgq,
|
||||
File(os.path.join(capnproto.LIB_DIR, "libcapnp.a")),
|
||||
File(os.path.join(capnproto.LIB_DIR, "libkj.a")),
|
||||
]
|
||||
else:
|
||||
messaging = [socketmaster, msgq, 'capnp', 'kj']
|
||||
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
||||
Export('messaging')
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -220,12 +220,6 @@ struct StarPilotPlan @0xf98d843bfd7004a3 {
|
||||
disableThrottle @35 :Bool;
|
||||
trackingLead @36 :Bool;
|
||||
stopSignConfirmed @37 :Bool;
|
||||
pulseGlideCoasting @38 :Bool; # developer-only P&G phase for on-road status UI
|
||||
# Curve Speed Controller diagnostics, for tuning and rollout validation
|
||||
cscOverridden @39 :Bool; # driver cancelled this curve with RES+
|
||||
cscLearnedLatAccel @40 :Float32; # learned comfort at the current curvature, before margin
|
||||
cscBindingDistance @41 :Float32; # distance to the horizon point setting the target, m
|
||||
approachStopLength @42 :Float32; # pre-commit distance to a detected stop, m; 0 when off
|
||||
}
|
||||
|
||||
struct StarPilotRadarState @0xb86e6369214c01c8 {
|
||||
@@ -270,7 +264,6 @@ struct StarPilotSelfdriveState @0xf416ec09499d9d19 {
|
||||
alertSize @3 :AlertSize;
|
||||
alertType @4 :Text;
|
||||
alertSound @5 :Car.CarControl.HUDControl.AudibleAlert;
|
||||
vEgo @6 :Float32;
|
||||
|
||||
enum AlertStatus {
|
||||
normal @0;
|
||||
|
||||
Binary file not shown.
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 {
|
||||
@@ -2758,7 +2757,6 @@ struct Event {
|
||||
userBookmark @93 :UserBookmark;
|
||||
bookmarkButton @148 :UserBookmark;
|
||||
audioFeedback @149 :AudioFeedback;
|
||||
visionSpeedLimitBookmark @153 :UserBookmark;
|
||||
|
||||
lateralManeuverPlan @150 :LateralManeuverPlan;
|
||||
# *********** debug ***********
|
||||
|
||||
Binary file not shown.
@@ -69,7 +69,6 @@ static std::map<std::string, service> services = {
|
||||
{ "rawAudioData", {"rawAudioData", false, 20.000000, -1, 256000}},
|
||||
{ "bookmarkButton", {"bookmarkButton", true, 0.000000, 1, 256000}},
|
||||
{ "audioFeedback", {"audioFeedback", true, 0.000000, 1, 256000}},
|
||||
{ "visionSpeedLimitBookmark", {"visionSpeedLimitBookmark", false, 0.000000, 1, 256000}},
|
||||
{ "roadEncodeData", {"roadEncodeData", false, 20.000000, -1, 10485760}},
|
||||
{ "driverEncodeData", {"driverEncodeData", false, 20.000000, -1, 10485760}},
|
||||
{ "wideRoadEncodeData", {"wideRoadEncodeData", false, 20.000000, -1, 10485760}},
|
||||
|
||||
@@ -86,7 +86,6 @@ _services: dict[str, tuple] = {
|
||||
"rawAudioData": (False, 20.),
|
||||
"bookmarkButton": (True, 0., 1),
|
||||
"audioFeedback": (True, 0., 1),
|
||||
"visionSpeedLimitBookmark": (False, 0., 1),
|
||||
"roadEncodeData": (False, 20., None, QueueSize.BIG),
|
||||
"driverEncodeData": (False, 20., None, QueueSize.BIG),
|
||||
"wideRoadEncodeData": (False, 20., None, QueueSize.BIG),
|
||||
|
||||
+1
-15
@@ -1,22 +1,8 @@
|
||||
from cereal import car
|
||||
from openpilot.common.params import Params
|
||||
|
||||
|
||||
def _gm_car_params_present(params: Params) -> bool:
|
||||
try:
|
||||
raw_car_params = params.get("CarParams")
|
||||
if raw_car_params is None:
|
||||
return False
|
||||
with car.CarParams.from_bytes(raw_car_params) as CP:
|
||||
return CP.brand == "gm"
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def get_gps_location_service(params: Params) -> str:
|
||||
# GM card publishes the selected device/PPS/OnStar result through the
|
||||
# existing external service. Keep every other platform on its prior rule.
|
||||
if _gm_car_params_present(params) or params.get_bool("UbloxAvailable") or params.get_bool("CarGpsAvailable"):
|
||||
if params.get_bool("UbloxAvailable"):
|
||||
return "gpsLocationExternal"
|
||||
else:
|
||||
return "gpsLocation"
|
||||
|
||||
Binary file not shown.
+9
-70
@@ -16,14 +16,10 @@ 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}},
|
||||
{"BluetoothEnabled", {PERSISTENT, BOOL, "0"}},
|
||||
{"CalibrationParams", {PERSISTENT, BYTES}},
|
||||
{"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}},
|
||||
{"CameraDebugExpTime", {CLEAR_ON_MANAGER_START, STRING}},
|
||||
{"CarBatteryCapacity", {PERSISTENT, INT}},
|
||||
{"CarGpsAvailable", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"CarParams", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
|
||||
{"CarParamsCache", {CLEAR_ON_MANAGER_START, BYTES}},
|
||||
{"CarParamsPersistent", {PERSISTENT, BYTES}},
|
||||
@@ -66,8 +62,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"GsmRoaming", {PERSISTENT, BOOL}},
|
||||
{"HardwareSerial", {PERSISTENT, STRING}},
|
||||
{"HasAcceptedTerms", {PERSISTENT, STRING, "0"}},
|
||||
// Internal kill switch; the platform verification allowlist remains the hard safety gate.
|
||||
{"HondaBoschARadar", {PERSISTENT, BOOL, "1"}},
|
||||
{"HondaGasFactorParams", {PERSISTENT, FLOAT}},
|
||||
{"HondaLateralPidKiScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
{"HondaLateralPidKpScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
@@ -87,7 +81,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}},
|
||||
@@ -113,12 +106,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"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}},
|
||||
@@ -201,7 +188,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ApiCache_DriveStats", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"AutomaticallyDownloadModels", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"AutomaticUpdates", {PERSISTENT, BOOL, "1", "1", 0}},
|
||||
{"AllowGpuModelDownloadWithoutGpu", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"AvailableModelNames", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"AvailableModelSeries", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"AvailableModels", {PERSISTENT, STRING, "", "", 1}},
|
||||
@@ -265,32 +251,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}},
|
||||
@@ -316,7 +276,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
|
||||
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_ADVANCED}},
|
||||
{"TestModelLeadTrajectory", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 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}},
|
||||
@@ -337,12 +297,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}},
|
||||
@@ -383,15 +337,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ForceStops", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ForceStopDistanceOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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, "{}", "{}"}},
|
||||
@@ -404,12 +360,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}},
|
||||
@@ -519,9 +469,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}},
|
||||
@@ -555,11 +502,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}},
|
||||
@@ -575,7 +517,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"PIPPreviewMask", {PERSISTENT, JSON, "{\"width\":1928,\"height\":1208,\"center_left\":[315,548],\"center_right\":[1571,539],\"crop_size\":580}", "{\"width\":1928,\"height\":1208,\"center_left\":[315,548],\"center_right\":[1571,539],\"crop_size\":580}", 2}},
|
||||
{"PIPPreviewShowOnBlinker", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PIPPreviewShowOnBSM", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PIPPreviewInvert", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"GalaxyPaired", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"GalaxyUploadPending", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"PreferredSchedule", {PERSISTENT, INT, "2", "0", 0}},
|
||||
@@ -608,6 +549,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"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"}},
|
||||
@@ -718,8 +660,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SwitchbackModeEnabled", {CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"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, "", ""}},
|
||||
@@ -740,7 +680,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"TuningLevel", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"TuningLevelConfirmed", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"TurnDesires", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TurnSteeringLimitMuteSpeed", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"UnlockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"Updated", {PERSISTENT, STRING, "0", "0"}},
|
||||
{"UpdateSpeedLimits", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
@@ -754,7 +693,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"UserFavorites", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"UseVienna", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"VASMAnnotationConfig", {PERSISTENT, JSON, "{}", "{}", 2}},
|
||||
{"VASMConfidenceThreshold", {PERSISTENT, FLOAT, "0.94", "0.94", 2}},
|
||||
{"VASMConfidenceThreshold", {PERSISTENT, FLOAT, "0.85", "0.85", 2}},
|
||||
{"VASMEnabled", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"VASMLeftActive", {CLEAR_ON_MANAGER_START, STRING, "0", "0", 2}},
|
||||
{"VASMLeftConfidence", {CLEAR_ON_MANAGER_START, STRING, "0.0", "0.0", 2}},
|
||||
|
||||
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)
|
||||
Binary file not shown.
+175
-179
@@ -4,36 +4,36 @@
|
||||
|
||||
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
|
||||
# 451 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|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
|Acura|ADX 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ADX 2025-26">Buy Here</a></sub></details>|||
|
||||
|Acura|ADX 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ADX 2025-26">Buy Here</a></sub></details>|||
|
||||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|
||||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|
||||
|Acura|Integra 2023-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura Integra 2023-26">Buy Here</a></sub></details>|||
|
||||
|Acura|Integra 2023-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura Integra 2023-26">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX 2014-16|Advance Package|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2014-16">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX 2017-19|All|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2017-19">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX 2020|All|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2020">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2022-24">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX 2025|All except Type S|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2022-24">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX 2025|All except Type S|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX Hybrid 2017-19|All|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX Hybrid 2017-19">Buy Here</a></sub></details>|||
|
||||
|Acura|MDX Hybrid 2020|All|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX Hybrid 2020">Buy Here</a></sub></details>|||
|
||||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|
||||
|Acura|RDX 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
||||
|Acura|RDX 2022-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2022-26">Buy Here</a></sub></details>|||
|
||||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
||||
|Acura|RDX 2022-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2022-26">Buy Here</a></sub></details>|||
|
||||
|Acura|TLX 2015-17|Advance Package|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2015-17">Buy Here</a></sub></details>|||
|
||||
|Acura|TLX 2018-20|All|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2018-20">Buy Here</a></sub></details>|||
|
||||
|Acura|TLX 2021|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2021">Buy Here</a></sub></details>|||
|
||||
|Acura|TLX 2024-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2024-25">Buy Here</a></sub></details>|||
|
||||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
||||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|
||||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q2 2018">Buy Here</a></sub></details>|||
|
||||
|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|
||||
|Audi|Q4 e-tron 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2021-23">Buy Here</a></sub></details>|||
|
||||
|Audi|Q4 e-tron 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2024-25">Buy Here</a></sub></details>|||
|
||||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi RS3 2018">Buy Here</a></sub></details>|||
|
||||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|
||||
|Acura|TLX 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2021">Buy Here</a></sub></details>|||
|
||||
|Acura|TLX 2024-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2024-25">Buy Here</a></sub></details>|||
|
||||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
||||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|
||||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q2 2018">Buy Here</a></sub></details>|||
|
||||
|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|
||||
|Audi|Q4 e-tron 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2021-23">Buy Here</a></sub></details>|||
|
||||
|Audi|Q4 e-tron 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2024-25">Buy Here</a></sub></details>|||
|
||||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi RS3 2018">Buy Here</a></sub></details>|||
|
||||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|
||||
|Buick|Baby Enclave 2020-23|Driver Assist Package|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=Buick Baby Enclave 2020-23">Buy Here</a></sub></details>|||
|
||||
|Buick[<sup>3</sup>](#footnotes)|LaCrosse 2017-19|Driver Confidence Package 2|openpilot|18 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 OBD-II connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Buick LaCrosse 2017-19">Buy Here</a></sub></details>|||
|
||||
|Buick|LaCrosse ASCM Harness 2017-19|Adaptive Cruise Control (ACC)|Stock|3 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=Buick LaCrosse ASCM Harness 2017-19">Buy Here</a></sub></details>|||
|
||||
@@ -76,56 +76,55 @@ 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>|||
|
||||
|Chrysler|Pacifica Hybrid 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 Hybrid 2017-18">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica Hybrid 2019-25|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 Hybrid 2019-25">Buy Here</a></sub></details>|||
|
||||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None|<a href="https://youtu.be/VT-i3yRsX2s?t=2736" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|
||||
|CUPRA|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Born 2021-23">Buy Here</a></sub></details>|||
|
||||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|
||||
|CUPRA|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Born 2021-23">Buy Here</a></sub></details>|||
|
||||
|Dodge|Durango 2020-21|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=Dodge Durango 2020-21">Buy Here</a></sub></details>|||
|
||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Edge 2022|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Edge 2022">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Expedition 2022-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer 2020-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|F-150 Lightning 2022-25|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Lightning 2022-25">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Focus 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus 2018-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Focus Hybrid 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Maverick 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2022">Buy Here</a></sub></details>|||
|
||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2023-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2022">Buy Here</a></sub></details>|||
|
||||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2023-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Mondeo 2014-22|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mondeo 2014-22">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Mustang Mach-E 2021-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mustang Mach-E 2021-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Ranger 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Transit 2025|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Transit 2025">Buy Here</a></sub></details>|||
|
||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Edge 2022|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Edge 2022">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Expedition 2022-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer 2020-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|F-150 Lightning 2022-25|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Lightning 2022-25">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Focus 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus 2018-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Focus Hybrid 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga Hybrid 2024|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2022">Buy Here</a></sub></details>|||
|
||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2023-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2022">Buy Here</a></sub></details>|||
|
||||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2023-24">Buy Here</a></sub></details>|||
|
||||
|Ford|Mondeo 2014-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mondeo 2014-22">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Mustang Mach-E 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mustang Mach-E 2021-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Ranger 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Ford|Transit 2025|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Transit 2025">Buy Here</a></sub></details>|||
|
||||
|Genesis|G70 2018|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2018">Buy Here</a></sub></details>|||
|
||||
|Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2019-21">Buy Here</a></sub></details>|||
|
||||
|Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|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=Genesis G70 2022-23">Buy Here</a></sub></details>|||
|
||||
|Genesis|G80 2017|All|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J 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=Genesis G80 2017">Buy Here</a></sub></details>|||
|
||||
|Genesis|G80 2018-19|All|openpilot available[<sup>1</sup>](#footnotes)|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=Genesis G80 2018-19">Buy Here</a></sub></details>|||
|
||||
|Genesis|G80 2018-19|All|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=Genesis G80 2018-19">Buy Here</a></sub></details>|||
|
||||
|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P 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=Genesis G80 (2.5T Advanced Trim, with HDA II) 2024">Buy Here</a></sub></details>|||
|
||||
|Genesis|G90 2017-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G90 2017-20">Buy Here</a></sub></details>|||
|
||||
|Genesis|GV60 (Advanced Trim) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV60 (Advanced Trim) 2023">Buy Here</a></sub></details>|||
|
||||
|Genesis|GV60 (Performance Trim) 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K 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=Genesis GV60 (Performance Trim) 2022-23">Buy Here</a></sub></details>|||
|
||||
|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|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=Genesis GV70 (2.5T Trim, without HDA II) 2022-24">Buy Here</a></sub></details>|||
|
||||
|Genesis|GV70 (3.5T Sport Prestige Trim, with HDA II & LFA2) 2026|Highway Driving Assist II & Lane Follow Assist 2|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M 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=Genesis GV70 (3.5T Sport Prestige Trim, with HDA II & LFA2) 2026">Buy Here</a></sub></details>|||
|
||||
|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M 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=Genesis GV70 (3.5T Trim, without HDA II) 2022-23">Buy Here</a></sub></details>|||
|
||||
|Genesis|GV70 Electrified 2026|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M 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=Genesis GV70 Electrified 2026">Buy Here</a></sub></details>|||
|
||||
|Genesis|GV70 Electrified (Australia Only) 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q 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=Genesis GV70 Electrified (Australia Only) 2022">Buy Here</a></sub></details>|||
|
||||
@@ -141,46 +140,46 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|GMC[<sup>3</sup>](#footnotes)|Yukon No-ACC 2019-20|Adaptive Cruise Control (ACC)|openpilot|24 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 OBD-II connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Yukon No-ACC 2019-20">Buy Here</a></sub></details>|||
|
||||
|Holden[<sup>3</sup>](#footnotes)|Astra 2017|Adaptive Cruise Control (ACC)|openpilot|18 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 OBD-II connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Holden Astra 2017">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord 2016-17|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2016-17">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Accord 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Accord 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord Hybrid 2017|All|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2017">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord Hybrid 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||
|Honda|Accord Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Civic 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Civic 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
||||
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||
|Honda|Clarity 2018-21|All|openpilot|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Clarity 2018-21">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V 2023-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V Hybrid 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|e 2020|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V 2023-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
||||
|Honda|CR-V Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|
||||
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|
||||
|Honda|Fit (Taiwan) 2021|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2021">Buy Here</a></sub></details>|||
|
||||
|Honda|Fit (Taiwan) 2024-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2024-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Fit (Taiwan) 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2021">Buy Here</a></sub></details>|||
|
||||
|Honda|Fit (Taiwan) 2024-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2024-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
|
||||
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2019-22">Buy Here</a></sub></details>|||
|
||||
|Honda|HR-V 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Insight 2019-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|
||||
|Honda|Inspire 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|
||||
|Honda|N-Box 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|11 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
|
||||
|Honda|HR-V 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Insight 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|
||||
|Honda|Inspire 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|
||||
|Honda|N-Box 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|11 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
|
||||
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|
||||
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|
||||
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|
||||
|Honda|Odyssey (Singapore) 2021|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey (Singapore) 2021">Buy Here</a></sub></details>|||
|
||||
|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey (Taiwan) 2018-19">Buy Here</a></sub></details>|||
|
||||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Passport 2026|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2026">Buy Here</a></sub></details>|||
|
||||
|Honda|Passport 2026|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2026">Buy Here</a></sub></details>|||
|
||||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|
||||
|Honda|Pilot 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Prelude 2026|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Prelude 2026">Buy Here</a></sub></details>|||
|
||||
|Honda|Pilot 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|
||||
|Honda|Prelude 2026|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Prelude 2026">Buy Here</a></sub></details>|||
|
||||
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K 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=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
|
||||
@@ -326,54 +325,54 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Lexus|RX Hybrid 2017-19|All|Stock|0 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=Lexus RX Hybrid 2017-19">Buy Here</a></sub></details>|||
|
||||
|Lexus|RX Hybrid 2020-22|All|openpilot|0 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=Lexus RX Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||
|Lexus|UX Hybrid 2019-24|All|openpilot|0 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=Lexus UX Hybrid 2019-24">Buy Here</a></sub></details>|||
|
||||
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|
||||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||
|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|
||||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||
|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-5 2022-25">Buy Here</a></sub></details>|||
|
||||
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Nissan[<sup>6</sup>](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan B 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=Nissan Altima 2019-20, 2024">Buy Here</a></sub></details>|||
|
||||
|Nissan[<sup>6</sup>](#footnotes)|Leaf 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Nissan[<sup>6</sup>](#footnotes)|Leaf Instrument Cluster 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf Instrument Cluster 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Nissan[<sup>6</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
||||
|Nissan[<sup>6</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
||||
|Nissan[<sup>5</sup>](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan B 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=Nissan Altima 2019-20, 2024">Buy Here</a></sub></details>|||
|
||||
|Nissan[<sup>5</sup>](#footnotes)|Leaf 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Nissan[<sup>5</sup>](#footnotes)|Leaf Instrument Cluster 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf Instrument Cluster 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Nissan[<sup>5</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
||||
|Nissan[<sup>5</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
||||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|
||||
|Rivian|R1S 2022-24|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<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=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Rivian|R1S 2025|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<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=Rivian R1S 2025">Buy Here</a></sub></details>|||
|
||||
|Rivian|R1T 2022-24|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<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=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Rivian|R1T 2025|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<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=Rivian R1T 2025">Buy Here</a></sub></details>|||
|
||||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
||||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|
||||
|Subaru|Ascent 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Ascent 2023-25|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Ascent 2023-25">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2025|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Crosstrek 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2022-24|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru C 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=Subaru Forester 2022-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B 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=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2025|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Legacy 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B 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=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2023-24|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Outback 2023-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Škoda|Enyaq 2021-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2021-23">Buy Here</a></sub></details>|||
|
||||
|Škoda|Enyaq 2024-25[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2024-25">Buy Here</a></sub></details>|||
|
||||
|Škoda|Fabia 2022-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
||||
|Škoda|Kamiq 2021-23[<sup>12,14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
||||
|Škoda|Karoq 2019-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
|
||||
|Škoda|Kodiaq 2017-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kodiaq 2017-23">Buy Here</a></sub></details>|||
|
||||
|Škoda|Octavia 2015-19[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia 2015-19">Buy Here</a></sub></details>|||
|
||||
|Škoda|Octavia RS 2016[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia RS 2016">Buy Here</a></sub></details>|||
|
||||
|Škoda|Octavia Scout 2017-19[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|
||||
|Škoda|Scala 2020-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Scala 2020-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
||||
|Škoda|Superb 2015-22[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW4) 2024-26[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-26">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
||||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
||||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|
||||
|Subaru|Ascent 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Ascent 2023-25|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Ascent 2023-25">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2025|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Crosstrek 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2022-24|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru C 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=Subaru Forester 2022-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B 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=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2025|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Legacy 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B 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=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2023-24|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D 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=Subaru Outback 2023-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Škoda|Enyaq 2021-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2021-23">Buy Here</a></sub></details>|||
|
||||
|Škoda|Enyaq 2024-25[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2024-25">Buy Here</a></sub></details>|||
|
||||
|Škoda|Fabia 2022-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||
|Škoda|Kamiq 2021-23[<sup>11,13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||
|Škoda|Karoq 2019-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
|
||||
|Škoda|Kodiaq 2017-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kodiaq 2017-23">Buy Here</a></sub></details>|||
|
||||
|Škoda|Octavia 2015-19[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia 2015-19">Buy Here</a></sub></details>|||
|
||||
|Škoda|Octavia RS 2016[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia RS 2016">Buy Here</a></sub></details>|||
|
||||
|Škoda|Octavia Scout 2017-19[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|
||||
|Škoda|Scala 2020-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Scala 2020-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||
|Škoda|Superb 2015-22[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW4) 2024-26[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-26">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|
||||
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
||||
|Toyota|Alphard 2019-20|All|openpilot|0 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 Alphard 2019-20">Buy Here</a></sub></details>|||
|
||||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 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 Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|
||||
|Toyota|Avalon 2016|Toyota Safety Sense P|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 Avalon 2016">Buy Here</a></sub></details>|||
|
||||
@@ -386,8 +385,8 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Toyota|C-HR 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 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 C-HR 2021">Buy Here</a></sub></details>|||
|
||||
|Toyota|C-HR Hybrid 2017-20|All|Stock|0 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 C-HR Hybrid 2017-20">Buy Here</a></sub></details>|||
|
||||
|Toyota|C-HR Hybrid 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 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 C-HR Hybrid 2021-22">Buy Here</a></sub></details>|||
|
||||
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>11</sup>](#footnotes)|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 Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>11</sup>](#footnotes)|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 Camry 2021-24">Buy Here</a></sub></details>|||
|
||||
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>10</sup>](#footnotes)|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 Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>10</sup>](#footnotes)|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 Camry 2021-24">Buy Here</a></sub></details>|||
|
||||
|Toyota|Camry Hybrid 2018-20|All|Stock|0 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 Camry Hybrid 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Q2DYY0AWKgk" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Camry Hybrid 2021-24|All|openpilot|0 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 Camry Hybrid 2021-24">Buy Here</a></sub></details>|||
|
||||
|Toyota|Corolla 2017-19|All|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 Corolla 2017-19">Buy Here</a></sub></details>|||
|
||||
@@ -419,53 +418,53 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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 RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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 RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Sienna 2018-20|All|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 Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|ID.3 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2020-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.3 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2024-25">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2024-25">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.5 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.5 2022-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta 2018-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Passat 2015-22[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
||||
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
||||
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
||||
|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|ID.3 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2020-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.3 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2024-25">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2024-25">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|ID.5 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.5 2022-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta 2018-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Passat 2015-22[<sup>12</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||
|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
|
||||
|
||||
## StarPilot Community Cars
|
||||
|
||||
These additional vehicle ports are maintained by StarPilot rather than upstream openpilot.
|
||||
|
||||
# 17 Community Cars
|
||||
# 15 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,11 +477,9 @@ 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>|||
|
||||
|Toyota|Prius 2016-20 with TSS2 EPS retrofit|Custom retrofit|Stock|0 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 Prius 2016-20 with TSS2 EPS retrofit">Buy Here</a></sub></details>|||
|
||||
|Toyota|RAV4 Prime 2021-23|All|openpilot|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 RAV4 Prime 2021-23">Buy Here</a></sub></details>|||
|
||||
|Toyota|Sienna 2021-25|All|openpilot|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 Sienna 2021-25">Buy Here</a></sub></details>|||
|
||||
|Toyota|Yaris (Non-US only) 2020, 2023|All|openpilot available[<sup>1</sup>](#footnotes)|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 Yaris (Non-US only) 2020, 2023">Buy Here</a></sub></details>|||
|
||||
@@ -492,18 +489,17 @@ These additional vehicle ports are maintained by StarPilot rather than upstream
|
||||
<sup>2</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br />
|
||||
<sup>3</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/gm" target="_blank">GM</a>. <br />
|
||||
<sup>4</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
|
||||
<sup>5</sup>Enabling longitudinal control (alpha) will disable all CMBS functionality, including AEB and FCW. <br />
|
||||
<sup>6</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
|
||||
<sup>7</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
|
||||
<sup>8</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
|
||||
<sup>9</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
|
||||
<sup>10</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
|
||||
<sup>11</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
||||
<sup>12</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
|
||||
<sup>13</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
|
||||
<sup>14</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
|
||||
<sup>15</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
|
||||
<sup>16</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
|
||||
<sup>5</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
|
||||
<sup>6</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
|
||||
<sup>7</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
|
||||
<sup>8</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
|
||||
<sup>9</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
|
||||
<sup>10</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
||||
<sup>11</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
|
||||
<sup>12</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
|
||||
<sup>13</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
|
||||
<sup>14</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
|
||||
<sup>15</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
|
||||
|
||||
# Don't see your car here?
|
||||
|
||||
@@ -546,4 +542,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.
|
||||
|
||||
+94
-3
@@ -147,10 +147,101 @@ function launch {
|
||||
while true; do sleep 1; done
|
||||
fi
|
||||
|
||||
function prebuilt_runtime_compatible {
|
||||
python3 - <<'PY'
|
||||
import importlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
from openpilot.common.file_chunker import get_existing_chunks
|
||||
|
||||
start = time.monotonic()
|
||||
last = start
|
||||
log_path = os.environ.get("SP_BOOT_TIMING_LOG")
|
||||
|
||||
def emit(line):
|
||||
print(line, flush=True)
|
||||
if log_path:
|
||||
try:
|
||||
with open(log_path, "a") as f:
|
||||
f.write(line + "\n")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def log_step(label):
|
||||
global last
|
||||
now = time.monotonic()
|
||||
emit(f"SP_BOOT_TIMING prebuilt_compat {label} +{now - last:.3f}s total={now - start:.3f}s")
|
||||
last = now
|
||||
|
||||
mods = [
|
||||
"openpilot.common.params_pyx",
|
||||
"msgq.ipc_pyx",
|
||||
"msgq.visionipc.visionipc_pyx",
|
||||
"openpilot.common.transformations.transformations",
|
||||
"openpilot.selfdrive.pandad.pandad_api_impl",
|
||||
"openpilot.selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx",
|
||||
"openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx",
|
||||
]
|
||||
|
||||
for mod in mods:
|
||||
try:
|
||||
importlib.import_module(mod)
|
||||
except Exception as e:
|
||||
print(f"Prebuilt compatibility failure in {mod}: {e}", file=sys.stderr)
|
||||
raise
|
||||
log_step(f"import:{mod}")
|
||||
|
||||
repo_root = Path.cwd().parents[1]
|
||||
required_model_artifacts = [
|
||||
repo_root / "selfdrive/modeld/models/driving_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dmonitoring_model_metadata.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dm_warp_1928x1208_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dm_warp_1344x760_tinygrad.pkl",
|
||||
]
|
||||
required_files = [
|
||||
repo_root / "selfdrive/pandad/pandad_api_impl.so",
|
||||
repo_root / "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so",
|
||||
repo_root / "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so",
|
||||
repo_root / "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so",
|
||||
repo_root / "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so",
|
||||
repo_root / "opendbc_repo/opendbc/dbc/gm_global_a_powertrain_generated.dbc",
|
||||
]
|
||||
|
||||
for path in required_model_artifacts:
|
||||
try:
|
||||
artifact_paths = [Path(p) for p in get_existing_chunks(path)]
|
||||
except Exception as e:
|
||||
raise FileNotFoundError(f"Missing prebuilt runtime artifact: {path}") from e
|
||||
missing_chunks = [p for p in artifact_paths if not p.is_file()]
|
||||
if missing_chunks:
|
||||
missing = ", ".join(str(p) for p in missing_chunks)
|
||||
raise FileNotFoundError(f"Missing prebuilt runtime artifact chunks for {path}: {missing}")
|
||||
log_step("required_model_artifacts")
|
||||
|
||||
for path in required_files:
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"Missing prebuilt runtime artifact: {path}")
|
||||
log_step("required_files")
|
||||
PY
|
||||
}
|
||||
|
||||
USE_PREBUILT=1
|
||||
if [ -f /data/params/d/UsePrebuilt ]; then
|
||||
USE_PREBUILT=$(tr -d '\n' < /data/params/d/UsePrebuilt)
|
||||
fi
|
||||
|
||||
sp_launch_timing "prebuilt_decision_done"
|
||||
# Published trees carry this marker and must never compile on-device.
|
||||
# Developers can remove it explicitly when working from a source tree.
|
||||
if [ ! -f "$DIR/prebuilt" ]; then
|
||||
if [ "$USE_PREBUILT" = "1" ] && [ -f $DIR/prebuilt ] && ! prebuilt_runtime_compatible; then
|
||||
echo "Prebuilt runtime artifacts are incompatible on this device; rebuilding locally."
|
||||
USE_PREBUILT=0
|
||||
fi
|
||||
sp_launch_timing "prebuilt_compat_done"
|
||||
|
||||
if [ "$USE_PREBUILT" != "1" ] || [ ! -f $DIR/prebuilt ]; then
|
||||
sp_launch_timing "build_start"
|
||||
./build.py
|
||||
sp_launch_timing "build_done"
|
||||
|
||||
+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.1"
|
||||
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" "$@"
|
||||
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import copy
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car.honda.hondacan import honda_checksum
|
||||
|
||||
|
||||
class TestCanChecksums:
|
||||
@@ -91,13 +90,6 @@ class TestCanChecksums:
|
||||
assert parser.vl['LKAS_HUD']['CHECKSUM'] == std
|
||||
assert parser.vl['LKAS_HUD_A']['CHECKSUM'] == ext
|
||||
|
||||
def test_honda_canfd_checksum_offset_is_scoped_to_mvl_messages(self):
|
||||
# Existing high-ID Honda messages retain the legacy extended-ID offset;
|
||||
# only the Accord MVL replacement-radar IDs use the CAN-FD offset.
|
||||
payload = bytearray(8)
|
||||
assert honda_checksum(0xF31AA54, None, payload) == 11 # existing radarless LKAS_HUD_2
|
||||
assert honda_checksum(0xF31AA52, None, payload) == 4 # Accord MVL RADAR_LEAD2
|
||||
|
||||
def verify_volkswagen_mqb_crc(self, subtests, msg_name: str, msg_addr: int, test_messages: list[bytes], counter_field: str = 'COUNTER'):
|
||||
"""Test AUTOSAR E2E Profile 2 CRCs"""
|
||||
assert len(test_messages) == 16 # All counter values must be tested
|
||||
|
||||
@@ -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
|
||||
@@ -67,7 +66,7 @@ def _normalize_forced_candidate(candidate: str | None) -> str | None:
|
||||
return LEGACY_FORCED_CANDIDATE_MAP.get(candidate, candidate)
|
||||
|
||||
|
||||
def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int, dict], vin: str | None = None) -> str | None:
|
||||
def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int, dict]) -> str | None:
|
||||
"""
|
||||
Normalize ambiguous/mismatched Bolt candidates using robust PT message signatures.
|
||||
This guards against occasional wrong variant selection causing persistent canError.
|
||||
@@ -80,22 +79,7 @@ def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int,
|
||||
msg_304_len = pt.get(304) # 8 on 2017 Gen1, 1 on 2018-2021 Gen1
|
||||
has_pedal = 513 in pt
|
||||
|
||||
# The 2022-23 Bolt ACC, pedal, and CC variants share the same FPv1
|
||||
# fingerprint. Recover an ambiguous match when the observed traffic
|
||||
# matches the Bolt family and the VIN confirms model year 2022/2023.
|
||||
bolt_2022_2023_signature = (
|
||||
pt.get(190) == 7 and
|
||||
msg_211_len == 3 and
|
||||
pt.get(566) == 8 and
|
||||
pt.get(458) == 5
|
||||
)
|
||||
vin_year = vin[9] if isinstance(vin, str) and len(vin) > 9 else None
|
||||
|
||||
# VIN model-year codes: N=2022, P=2023.
|
||||
if candidate is None and bolt_2022_2023_signature and vin_year in ("N", "P"):
|
||||
return "CHEVROLET_BOLT_ACC_2022_2023_PEDAL" if has_pedal else "CHEVROLET_BOLT_ACC_2022_2023"
|
||||
# If detection failed entirely but the signature is clearly Bolt, recover to a sane default.
|
||||
|
||||
if candidate is None and 170 in pt and 188 in pt and msg_211_len in (2, 3):
|
||||
if msg_211_len == 3:
|
||||
return "CHEVROLET_BOLT_ACC_2022_2023_PEDAL" if has_pedal else "CHEVROLET_BOLT_ACC_2022_2023"
|
||||
@@ -247,13 +231,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)
|
||||
@@ -300,7 +279,7 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
|
||||
def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, alpha_long_allowed: bool,
|
||||
is_release: bool, params: Params, num_pandas: int = 1, cached_params: CarParamsT | None = None, starpilot_toggles: SimpleNamespace = None):
|
||||
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params)
|
||||
candidate = _normalize_gm_bolt_candidate(candidate, fingerprints, vin)
|
||||
candidate = _normalize_gm_bolt_candidate(candidate, fingerprints)
|
||||
candidate = _normalize_gm_volt_candidate(candidate, fingerprints)
|
||||
candidate = _normalize_forced_candidate(candidate)
|
||||
fingerprinted_candidate = candidate
|
||||
|
||||
@@ -264,7 +264,6 @@ MIGRATION = {
|
||||
"GENESIS G70 2020": HYUNDAI.GENESIS_G70_2020,
|
||||
"GENESIS GV70 1ST GEN": HYUNDAI.GENESIS_GV70_1ST_GEN,
|
||||
"GENESIS GV70 ELECTRIFIED 2026": HYUNDAI.GENESIS_GV70_ELECTRIFIED_2ND_GEN,
|
||||
"GENESIS GV70 2026": HYUNDAI.GENESIS_GV70_2026,
|
||||
"GENESIS G80 2017": HYUNDAI.GENESIS_G80,
|
||||
"GENESIS G90 2017": HYUNDAI.GENESIS_G90,
|
||||
"GENESIS GV80 2023": HYUNDAI.GENESIS_GV80,
|
||||
|
||||
@@ -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
|
||||
@@ -230,14 +255,9 @@ class CarController(CarControllerBase):
|
||||
|
||||
if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui:
|
||||
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.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,
|
||||
hud_control, CS.acc_tja_status_stock_values,
|
||||
hands_free_cluster))
|
||||
hud_control, CS.acc_tja_status_stock_values))
|
||||
|
||||
self.main_on_last = main_on
|
||||
self.lkas_enabled_last = CC.latActive
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
# 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
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.ford.fordcan import CanBus
|
||||
from opendbc.car.ford.values import DBC, CarControllerParams, FordFlags
|
||||
from opendbc.car.gps import get_car_gps_config
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
@@ -31,40 +27,12 @@ class CarState(CarStateBase):
|
||||
self.lc_button = 0
|
||||
self.lkas_available = False
|
||||
self.lateral_motion_control = None
|
||||
self.lateral_control_status = None
|
||||
self.steering_angle_offset_deg = 0.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
|
||||
|
||||
def _update_car_gps(self, cp) -> 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
|
||||
self.car_gps = gps
|
||||
self._car_gps_timestamp_nanos = timestamp_nanos
|
||||
|
||||
def get_car_gps(self):
|
||||
return self.car_gps
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
self._update_car_gps(cp)
|
||||
|
||||
ret = structs.CarState()
|
||||
|
||||
if self.CP.flags & FordFlags.ALT_STEER_ANGLE:
|
||||
@@ -113,8 +81,7 @@ class CarState(CarStateBase):
|
||||
|
||||
if self.CP.flags & FordFlags.CANFD:
|
||||
# this signal is always 0 on non-CAN FD cars
|
||||
self.lateral_control_status = int(cp.vl["Lane_Assist_Data3_FD1"]["LatCtlSte_D_Stat"])
|
||||
ret.steerFaultTemporary |= self.lateral_control_status not in (1, 2, 3)
|
||||
ret.steerFaultTemporary |= cp.vl["Lane_Assist_Data3_FD1"]["LatCtlSte_D_Stat"] not in (1, 2, 3)
|
||||
|
||||
# cruise state
|
||||
is_metric = cp.vl["INSTRUMENT_PANEL"]["METRIC_UNITS"] == 1 if not self.CP.flags & FordFlags.CANFD else \
|
||||
@@ -210,81 +177,7 @@ 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 []
|
||||
|
||||
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], [], 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
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ def create_acc_msg(packer, CAN: CanBus, long_active: bool, gas: float, accel: fl
|
||||
|
||||
|
||||
def create_acc_ui_msg(packer, CAN: CanBus, CP, main_on: bool, enabled: bool, fcw_alert: bool, standstill: bool,
|
||||
show_distance_bars: bool, hud_control, stock_values: dict, hands_free_cluster: bool = False):
|
||||
show_distance_bars: bool, hud_control, stock_values: dict):
|
||||
"""
|
||||
Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam
|
||||
assist status.
|
||||
@@ -197,8 +197,6 @@ def create_acc_ui_msg(packer, CAN: CanBus, CP, main_on: bool, enabled: bool, fcw
|
||||
status = 3 # ActiveInterventionLeft
|
||||
elif hud_control.rightLaneDepart:
|
||||
status = 4 # ActiveInterventionRight
|
||||
elif hands_free_cluster:
|
||||
status = 7 # Hands-free assistance display
|
||||
else:
|
||||
status = 2 # Active
|
||||
elif main_on:
|
||||
|
||||
@@ -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
|
||||
@@ -56,10 +54,10 @@ class CarInterface(CarInterfaceBase):
|
||||
cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
|
||||
ret.safetyConfigs = cfgs
|
||||
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
ret.openpilotLongitudinalControl = bool(alpha_long)
|
||||
if ret.openpilotLongitudinalControl:
|
||||
ret.alphaLongitudinalAvailable = ret.radarUnavailable
|
||||
if alpha_long or not ret.radarUnavailable:
|
||||
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.LONG_CONTROL.value
|
||||
ret.openpilotLongitudinalControl = True
|
||||
|
||||
if ret.flags & FordFlags.CANFD:
|
||||
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.CANFD.value
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,43 +1,17 @@
|
||||
import random
|
||||
from collections.abc import Iterable
|
||||
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
|
||||
from opendbc.car.ford.interface import CarInterface
|
||||
from opendbc.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, FordSafetyFlags, get_platform_codes, match_vin_to_car
|
||||
from opendbc.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, get_platform_codes, match_vin_to_car
|
||||
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)
|
||||
@@ -180,158 +154,3 @@ class TestFordFW:
|
||||
live_fw[(0x760, None)] = {b"M1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
|
||||
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
|
||||
assert len(candidates) == 0, "Should not match new model year hint"
|
||||
|
||||
|
||||
def test_mach_e_longitudinal_toggle_controls_stock_acc_selection():
|
||||
stock = CarInterface.get_params(
|
||||
CAR.FORD_MUSTANG_MACH_E_MK1, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
enhanced = CarInterface.get_params(
|
||||
CAR.FORD_MUSTANG_MACH_E_MK1, gen_empty_fingerprint(), [], True, False, False, None)
|
||||
|
||||
assert stock.alphaLongitudinalAvailable
|
||||
assert not stock.openpilotLongitudinalControl
|
||||
assert stock.pcmCruise
|
||||
assert not (stock.safetyConfigs[-1].safetyParam & FordSafetyFlags.LONG_CONTROL)
|
||||
|
||||
assert enhanced.alphaLongitudinalAvailable
|
||||
assert enhanced.openpilotLongitudinalControl
|
||||
assert enhanced.safetyConfigs[-1].safetyParam & FordSafetyFlags.LONG_CONTROL
|
||||
|
||||
|
||||
def test_mach_e_can_gps_decode():
|
||||
nav1 = {
|
||||
"GpsHsphLattSth_D_Actl": 2,
|
||||
"GpsHsphLongEast_D_Actl": 2,
|
||||
"GPS_Latitude_Degrees": 37,
|
||||
"GPS_Latitude_Minutes": 57,
|
||||
"GPS_Latitude_Min_dec": 0.8864,
|
||||
"GPS_Longitude_Degrees": -121,
|
||||
"GPS_Longitude_Minutes": 44,
|
||||
"GPS_Longitude_Min_dec": 0.22,
|
||||
}
|
||||
nav2 = {
|
||||
"GpsUtcYr_No_Actl": 2026,
|
||||
"GpsUtcMnth_No_Actl": 8,
|
||||
"GpsUtcDay_No_Actl": 26,
|
||||
"GPS_UTC_hours": 0,
|
||||
"GPS_UTC_minutes": 24,
|
||||
"GPS_UTC_seconds": 38,
|
||||
"Gps_B_Falt": 0,
|
||||
}
|
||||
nav3 = {
|
||||
"GPS_dimension": 2,
|
||||
"GPS_Hdop": 0.6,
|
||||
"GPS_Vdop": 0.8,
|
||||
"GPS_Sat_num_in_view": 31,
|
||||
"GPS_MSL_altitude": 90,
|
||||
"GPS_Speed": 10,
|
||||
"GPS_Heading": 180,
|
||||
}
|
||||
|
||||
gps = parse_ford_can_gps(nav1, nav2, nav3)
|
||||
|
||||
assert gps is not None
|
||||
assert gps["latitude"] == 37.96477333333333
|
||||
assert gps["longitude"] == -121.737
|
||||
assert abs(gps["altitude"] - 27.432) < 1e-9
|
||||
assert abs(gps["speed"] - 10 * 0.44704) < 1e-9
|
||||
assert gps["hasFix"]
|
||||
assert gps["satelliteCount"] == 0 # 31 is Ford's invalid sentinel.
|
||||
|
||||
|
||||
def test_mach_e_can_gps_fault_invalidates_fix():
|
||||
nav1 = {
|
||||
"GpsHsphLattSth_D_Actl": 2,
|
||||
"GpsHsphLongEast_D_Actl": 2,
|
||||
"GPS_Latitude_Degrees": 37,
|
||||
"GPS_Latitude_Minutes": 57,
|
||||
"GPS_Latitude_Min_dec": 0.8864,
|
||||
"GPS_Longitude_Degrees": -121,
|
||||
"GPS_Longitude_Minutes": 44,
|
||||
"GPS_Longitude_Min_dec": 0.22,
|
||||
}
|
||||
nav2 = {
|
||||
"GpsUtcYr_No_Actl": 2026,
|
||||
"GpsUtcMnth_No_Actl": 8,
|
||||
"GpsUtcDay_No_Actl": 26,
|
||||
"GPS_UTC_hours": 0,
|
||||
"GPS_UTC_minutes": 24,
|
||||
"GPS_UTC_seconds": 38,
|
||||
"Gps_B_Falt": 1,
|
||||
}
|
||||
nav3 = {
|
||||
"GPS_dimension": 2,
|
||||
"GPS_Hdop": 0.6,
|
||||
"GPS_Vdop": 0.8,
|
||||
"GPS_Sat_num_in_view": 31,
|
||||
"GPS_MSL_altitude": 90,
|
||||
"GPS_Speed": 0,
|
||||
"GPS_Heading": 180,
|
||||
}
|
||||
|
||||
gps = parse_ford_can_gps(nav1, nav2, nav3)
|
||||
|
||||
assert gps is not None
|
||||
assert not gps["hasFix"]
|
||||
assert gps["latitude"] == 37.96477333333333
|
||||
assert gps["altitude"] == 0.0
|
||||
|
||||
|
||||
def test_mach_e_can_gps_messages_are_optional_main_bus_inputs():
|
||||
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
parser = CarInterface.CarState.get_can_parsers(cp)[Bus.pt]
|
||||
gps_config = get_car_gps_config(cp)
|
||||
|
||||
assert gps_config is not None
|
||||
assert gps_config.messages == FORD_MACH_E_GPS_MESSAGES
|
||||
assert gps_config.decoder is parse_ford_can_gps
|
||||
assert set(parser.addresses) >= {0x462, 0x463, 0x464}
|
||||
assert set(FORD_MACH_E_GPS_MESSAGES) == set(gps_config.messages) == {
|
||||
parser.dbc.addr_to_msg[0x462].name,
|
||||
parser.dbc.addr_to_msg[0x463].name,
|
||||
parser.dbc.addr_to_msg[0x464].name,
|
||||
}
|
||||
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)
|
||||
CP = SimpleNamespace(openpilotLongitudinalControl=False)
|
||||
hud = SimpleNamespace(leftLaneDepart=False, rightLaneDepart=False)
|
||||
stock_values = dict.fromkeys([
|
||||
"HaDsply_No_Cs", "HaDsply_No_Cnt", "AccStopStat_D_Dsply", "AccTrgDist2_D_Dsply",
|
||||
"AccStopRes_B_Dsply", "TjaWarn_D_Rq", "TjaMsgTxt_D_Dsply", "IaccLamp_D_Rq",
|
||||
"AccMsgTxt_D2_Rq", "FcwDeny_B_Dsply", "FcwMemStat_B_Actl", "AccTGap_B_Dsply",
|
||||
"CadsAlignIncplt_B_Actl", "AccFllwMde_B_Dsply", "CadsRadrBlck_B_Actl",
|
||||
"CmbbPostEvnt_B_Dsply", "AccStopMde_B_Dsply", "FcwMemSens_D_Actl",
|
||||
"FcwMsgTxt_D_Rq", "AccWarn_D_Dsply", "FcwVisblWarn_B_Rq", "FcwAudioWarn_B_Rq",
|
||||
"AccTGap_D_Dsply", "AccMemEnbl_B_RqDrv", "FdaMem_B_Stat",
|
||||
], 0)
|
||||
|
||||
regular = fordcan.create_acc_ui_msg(
|
||||
packer, CAN, CP, True, True, False, False, False, hud, stock_values)
|
||||
hands_free = fordcan.create_acc_ui_msg(
|
||||
packer, CAN, CP, True, True, False, False, False, hud, stock_values, True)
|
||||
expected_regular = packer.make_can_msg("ACCDATA_3", 0, {"Tja_D_Stat": 2})
|
||||
expected_hands_free = packer.make_can_msg("ACCDATA_3", 0, {"Tja_D_Stat": 7})
|
||||
|
||||
assert regular == expected_regular
|
||||
assert hands_free == expected_hands_free
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -852,7 +841,6 @@ class CarController(CarControllerBase):
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
CAR.CHEVROLET_MALIBU_CC,
|
||||
CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
CAR.BUICK_LACROSSE,
|
||||
}
|
||||
|
||||
if (self.CP.enableGasInterceptorDEPRECATED and self.CP.carFingerprint in CC_REGEN_PADDLE_CAR and
|
||||
@@ -1015,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))
|
||||
@@ -1042,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
|
||||
@@ -1059,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(
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import copy
|
||||
import math
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from collections.abc import Mapping
|
||||
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,
|
||||
@@ -40,140 +36,6 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D
|
||||
HARD_BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise}
|
||||
NORMAL_CRUISE_BUTTONS = (CruiseButtons.RES_ACCEL, CruiseButtons.DECEL_SET)
|
||||
|
||||
# The CT6 DBC describes the PPS receiver output. These messages have been
|
||||
# observed on the GM powertrain bus at approximately 10 Hz; they remain an
|
||||
# optional parser so a vehicle which does not provide them is unaffected.
|
||||
PPS_GPS_MESSAGES = (
|
||||
"PPS_ElevHdSpd_FO",
|
||||
"PPS_PosLat_FO",
|
||||
"PPS_PosLong_FO",
|
||||
"PPS_Time_FO",
|
||||
"PPS_QualMetrics_FO",
|
||||
)
|
||||
# PPS_SigAcqTime_FO is intentionally not a health gate: on the observed GM
|
||||
# route its validity bit remains 1 even while the position bundle is valid.
|
||||
PPS_GPS_VALIDITY_SIGNALS = (
|
||||
"PPSLatV", "PPSLongV", "PPS2DAbsPosErrEstmtV", "PPSMdV", "PPSPstnDilPrcsV",
|
||||
"PPSTmdayV", "PPSCldrDayV", "PPSCldrYrV",
|
||||
)
|
||||
PPS_GPS_VALIDITY_MESSAGES = {
|
||||
"PPSLatV": "PPS_PosLat_FO",
|
||||
"PPSLongV": "PPS_PosLong_FO",
|
||||
"PPS2DAbsPosErrEstmtV": "PPS_QualMetrics_FO",
|
||||
"PPSMdV": "PPS_QualMetrics_FO",
|
||||
"PPSPstnDilPrcsV": "PPS_QualMetrics_FO",
|
||||
"PPSTmdayV": "PPS_Time_FO",
|
||||
"PPSCldrDayV": "PPS_Time_FO",
|
||||
"PPSCldrYrV": "PPS_Time_FO",
|
||||
}
|
||||
|
||||
|
||||
def pps_checksum_ok(data: bytes) -> bool:
|
||||
"""Validate the 11-bit checksum used by the observed PPS frames."""
|
||||
if len(data) < 2:
|
||||
return False
|
||||
received = ((data[-2] & 0x07) << 8) | data[-1]
|
||||
expected = sum(data[:-2]) + (data[-2] >> 3) + 0x4C
|
||||
return (expected & 0x7FF) == received
|
||||
|
||||
|
||||
def _invalid_pps_sample(timestamp_nanos: int) -> dict:
|
||||
return {
|
||||
"timestamp_nanos": timestamp_nanos,
|
||||
"latitude": 0.0,
|
||||
"longitude": 0.0,
|
||||
"altitude": 0.0,
|
||||
"speed": 0.0,
|
||||
"bearingDeg": 0.0,
|
||||
"horizontalAccuracy": 0.0,
|
||||
"unixTimestampMillis": 0,
|
||||
"verticalAccuracy": 0.0,
|
||||
"bearingAccuracyDeg": 180.0,
|
||||
"speedAccuracy": 0.0,
|
||||
"hasFix": False,
|
||||
"satelliteCount": 0,
|
||||
"vNED": [0.0, 0.0, 0.0],
|
||||
}
|
||||
|
||||
|
||||
def decode_gm_pps_gps(values: Mapping[str, Mapping[str, float]], raw: Mapping[str, bytes],
|
||||
timestamp_nanos: int) -> dict | None:
|
||||
"""Decode one coherent PPS bundle into the existing car-GPS sample shape."""
|
||||
if any(not pps_checksum_ok(raw.get(name, b"")) for name in PPS_GPS_MESSAGES):
|
||||
return None
|
||||
|
||||
try:
|
||||
if any(int(values[PPS_GPS_VALIDITY_MESSAGES[signal]].get(signal, 1)) != 0 for signal in PPS_GPS_VALIDITY_SIGNALS):
|
||||
return None
|
||||
# Mode 6 is explicitly "DR ONLY" in the DBC and is not an independent GPS
|
||||
# position. All other advertised modes include GNSS.
|
||||
if int(values["PPS_QualMetrics_FO"]["PPSMd"]) == 6:
|
||||
return None
|
||||
|
||||
latitude = float(values["PPS_PosLat_FO"]["PPSLat"])
|
||||
longitude = float(values["PPS_PosLong_FO"]["PPSLong"])
|
||||
if not (math.isfinite(latitude) and math.isfinite(longitude) and
|
||||
-90.0 <= latitude / 3_600_000.0 <= 90.0 and
|
||||
-180.0 <= longitude / 3_600_000.0 <= 180.0):
|
||||
return None
|
||||
latitude /= 3_600_000.0
|
||||
longitude /= 3_600_000.0
|
||||
if latitude == 0.0 and longitude == 0.0:
|
||||
return None
|
||||
|
||||
year = int(values["PPS_Time_FO"]["PPSCldrYr"])
|
||||
day_of_year = int(values["PPS_Time_FO"]["PPSCldrDay"])
|
||||
millis_of_day = int(values["PPS_Time_FO"]["PPSTmday"])
|
||||
if not 2014 <= year <= 2141 or not 0 <= day_of_year <= 365 or not 0 <= millis_of_day < 86_400_000:
|
||||
return None
|
||||
timestamp = datetime(year, 1, 1, tzinfo=UTC) + timedelta(days=day_of_year, milliseconds=millis_of_day)
|
||||
|
||||
speed = float(values["PPS_ElevHdSpd_FO"]["PPSVel"]) * CV.KPH_TO_MS
|
||||
heading = float(values["PPS_ElevHdSpd_FO"]["PPSHedng"])
|
||||
if int(values["PPS_ElevHdSpd_FO"].get("PPSVelV", 1)) != 0 or not math.isfinite(speed) or not 0.0 <= speed <= 200.0:
|
||||
speed = 0.0
|
||||
if (int(values["PPS_ElevHdSpd_FO"].get("PPSHedngV", 1)) != 0 or
|
||||
not math.isfinite(heading) or not 0.0 <= heading < 360.0):
|
||||
heading = 0.0
|
||||
|
||||
altitude = float(values["PPS_ElevHdSpd_FO"]["PPSElvtn"])
|
||||
if int(values["PPS_ElevHdSpd_FO"].get("PPSElvtnV", 1)) != 0 or not math.isfinite(altitude):
|
||||
altitude = 0.0
|
||||
else:
|
||||
altitude /= 100.0
|
||||
|
||||
horizontal_accuracy = float(values["PPS_QualMetrics_FO"]["PPS2DAbsPosErrEstmt"])
|
||||
vertical_accuracy = float(values["PPS_QualMetrics_FO"]["PPS3DAbsPosErrEstmt"])
|
||||
bearing_accuracy = float(values["PPS_QualMetrics_FO"]["PPSAbsHdngErrEstmt"])
|
||||
if not math.isfinite(horizontal_accuracy) or horizontal_accuracy < 0.0:
|
||||
horizontal_accuracy = 0.0
|
||||
if not math.isfinite(vertical_accuracy) or vertical_accuracy < 0.0:
|
||||
vertical_accuracy = 0.0
|
||||
if not math.isfinite(bearing_accuracy) or bearing_accuracy < 0.0:
|
||||
bearing_accuracy = 180.0
|
||||
except (KeyError, TypeError, ValueError, OverflowError):
|
||||
return None
|
||||
|
||||
heading_rad = math.radians(heading)
|
||||
return {
|
||||
"timestamp_nanos": timestamp_nanos,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"altitude": altitude,
|
||||
"speed": speed,
|
||||
"bearingDeg": heading,
|
||||
"horizontalAccuracy": horizontal_accuracy,
|
||||
"unixTimestampMillis": int(timestamp.timestamp() * 1000),
|
||||
"verticalAccuracy": vertical_accuracy,
|
||||
"bearingAccuracyDeg": bearing_accuracy,
|
||||
# The CT6 DBC does not document the velocity-error units, so do not expose
|
||||
# a made-up conversion as a speed accuracy value.
|
||||
"speedAccuracy": 0.0,
|
||||
"hasFix": True,
|
||||
"satelliteCount": 0,
|
||||
"vNED": [speed * math.cos(heading_rad), speed * math.sin(heading_rad), 0.0],
|
||||
}
|
||||
|
||||
|
||||
def get_hard_cruise_buttons(steering_button_msg: dict) -> int:
|
||||
return steering_button_msg.get("ACCButtonsHard", CruiseButtons.INIT)
|
||||
@@ -242,92 +104,6 @@ class CarState(CarStateBase):
|
||||
self.lkas_enabled = 0
|
||||
self.pcm_acc_status = AccState.OFF
|
||||
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.onstar_gps = None
|
||||
self._car_gps_timestamp_nanos = 0
|
||||
self._prev_gps_lat = None
|
||||
self._prev_gps_lon = None
|
||||
self._last_gps_bearing = None
|
||||
|
||||
self.pps_gps = None
|
||||
self._pps_gps_timestamp_nanos = 0
|
||||
|
||||
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.onstar_gps = gps
|
||||
self.car_gps = gps
|
||||
self._car_gps_timestamp_nanos = timestamp_nanos
|
||||
|
||||
def _update_pps_gps(self, cp) -> None:
|
||||
"""Decode a complete, checksum-valid PPS burst when one is available."""
|
||||
timestamps = [max(cp.ts_nanos[name].values(), default=0) for name in PPS_GPS_MESSAGES]
|
||||
if not all(timestamps):
|
||||
return
|
||||
|
||||
timestamp_nanos = max(timestamps)
|
||||
if timestamp_nanos <= self._pps_gps_timestamp_nanos:
|
||||
return
|
||||
if timestamp_nanos - min(timestamps) > 100_000_000:
|
||||
return
|
||||
|
||||
burst_ids = (
|
||||
int(cp.vl["PPS_ElevHdSpd_FO"]["PPSElvHedngSpdBrstID"]),
|
||||
int(cp.vl["PPS_PosLat_FO"]["PPSLatBrstID"]),
|
||||
int(cp.vl["PPS_PosLong_FO"]["PPSLongBrstID"]),
|
||||
int(cp.vl["PPS_Time_FO"]["PPSTmBrstID"]),
|
||||
int(cp.vl["PPS_QualMetrics_FO"]["PPSPosQltyMtcBrstID"]),
|
||||
)
|
||||
if len(set(burst_ids)) != 1:
|
||||
return
|
||||
|
||||
values = {name: cp.vl[name] for name in PPS_GPS_MESSAGES}
|
||||
raw = {name: cp.vl_raw[name] for name in PPS_GPS_MESSAGES}
|
||||
gps = decode_gm_pps_gps(values, raw, timestamp_nanos)
|
||||
self.pps_gps = gps if gps is not None else _invalid_pps_sample(timestamp_nanos)
|
||||
self._pps_gps_timestamp_nanos = timestamp_nanos
|
||||
|
||||
def get_car_gps(self):
|
||||
return self.car_gps
|
||||
|
||||
def get_car_gps_sources(self):
|
||||
return {
|
||||
"pps": self.pps_gps,
|
||||
"onstar": self.onstar_gps,
|
||||
}
|
||||
|
||||
def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]):
|
||||
if not self.CP.pcmCruise:
|
||||
@@ -413,11 +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)
|
||||
pps_cp = can_parsers.get(Bus.adas)
|
||||
if pps_cp is not None:
|
||||
self._update_pps_gps(pps_cp)
|
||||
|
||||
if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1:
|
||||
ret.gearShifter = self.parse_gear_shifter("T")
|
||||
else:
|
||||
@@ -657,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,
|
||||
@@ -683,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
|
||||
@@ -766,18 +533,8 @@ class CarState(CarStateBase):
|
||||
("ASCMLKASteeringCmd", 0),
|
||||
]
|
||||
|
||||
parsers = {
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus.POWERTRAIN),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CanBus.CAMERA),
|
||||
Bus.loopback: CANParser(DBC[CP.carFingerprint][Bus.pt], loopback_messages, CanBus.LOOPBACK),
|
||||
}
|
||||
if getattr(CP, "brand", None) == "gm":
|
||||
# Keep the CT6 object DBC isolated from the active vehicle DBC. Every
|
||||
# signal is optional, so a GM vehicle without PPS frames stays can-valid
|
||||
# and follows the existing device/OnStar behavior.
|
||||
parsers[Bus.adas] = CANParser(
|
||||
"cadillac_ct6_object",
|
||||
[(name, 0) for name in PPS_GPS_MESSAGES],
|
||||
CanBus.POWERTRAIN,
|
||||
)
|
||||
return parsers
|
||||
|
||||
@@ -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,6 +207,7 @@ 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],
|
||||
|
||||
@@ -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,9 +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,
|
||||
}
|
||||
|
||||
|
||||
def malibu_phase_map_for_button(button):
|
||||
@@ -339,28 +336,6 @@ def stabilize_bolt_cc_button(controller, CP, requested_button):
|
||||
return requested_button
|
||||
|
||||
|
||||
def _create_volt_cc_spam_command(CS, actuators, ms_convert):
|
||||
accel = float(actuators.accel)
|
||||
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
|
||||
ego_speed = CS.out.vEgo * ms_convert
|
||||
|
||||
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):
|
||||
accel = actuators.accel
|
||||
v_ego = CS.out.vEgo
|
||||
@@ -375,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)
|
||||
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:
|
||||
@@ -448,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:
|
||||
|
||||
@@ -273,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 = {
|
||||
@@ -500,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):
|
||||
@@ -664,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
|
||||
|
||||
@@ -683,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
|
||||
@@ -698,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
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -896,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,14 +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,
|
||||
PPS_GPS_MESSAGES,
|
||||
decode_gm_pps_gps,
|
||||
get_hard_cruise_buttons,
|
||||
pps_checksum_ok,
|
||||
update_auto_hold_drive_timers,
|
||||
)
|
||||
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,
|
||||
@@ -27,7 +20,6 @@ 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 ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
@@ -73,182 +65,6 @@ 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
|
||||
|
||||
|
||||
class TestPpsGps:
|
||||
_frames = [
|
||||
(0x260, bytes.fromhex("10ddac000d831277"), 0),
|
||||
(0x261, bytes.fromhex("08386fce09ca"), 0),
|
||||
(0x262, bytes.fromhex("6d98820341de"), 0),
|
||||
(0x264, bytes.fromhex("0018f90578b5eaac"), 0),
|
||||
(0x265, bytes.fromhex("1a0000800a0258fd"), 0),
|
||||
]
|
||||
|
||||
def test_observed_bundle_checksum_and_conversion(self):
|
||||
parser = CANParser("cadillac_ct6_object", [(name, 0) for name in PPS_GPS_MESSAGES], 0)
|
||||
parser.update([(1_000_000_000, self._frames)])
|
||||
|
||||
assert all(pps_checksum_ok(parser.vl_raw[name]) for name in PPS_GPS_MESSAGES)
|
||||
gps = decode_gm_pps_gps(
|
||||
{name: parser.vl[name] for name in PPS_GPS_MESSAGES},
|
||||
{name: parser.vl_raw[name] for name in PPS_GPS_MESSAGES},
|
||||
1_000_000_000,
|
||||
)
|
||||
assert gps is not None
|
||||
assert gps["hasFix"]
|
||||
assert gps["latitude"] == pytest.approx(38.3101, abs=1e-4)
|
||||
assert gps["longitude"] == pytest.approx(-85.7701, abs=1e-4)
|
||||
assert gps["unixTimestampMillis"] == 1788742068655
|
||||
|
||||
def test_invalid_position_validity_is_rejected(self):
|
||||
parser = CANParser("cadillac_ct6_object", [(name, 0) for name in PPS_GPS_MESSAGES], 0)
|
||||
parser.update([(1_000_000_000, self._frames)])
|
||||
values = {name: dict(parser.vl[name]) for name in PPS_GPS_MESSAGES}
|
||||
values["PPS_PosLat_FO"]["PPSLatV"] = 1
|
||||
gps = decode_gm_pps_gps(
|
||||
values,
|
||||
{name: parser.vl_raw[name] for name in PPS_GPS_MESSAGES},
|
||||
1_000_000_000,
|
||||
)
|
||||
assert gps is None
|
||||
|
||||
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 TestGMInterface:
|
||||
@parameterized.expand([
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
@@ -385,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 = {
|
||||
@@ -449,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()
|
||||
@@ -774,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)
|
||||
@@ -788,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,
|
||||
@@ -803,60 +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.5 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=60.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=60.0 * CV.KPH_TO_MS),
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
|
||||
controller.frame = int(0.7 / DT_CTRL)
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
|
||||
def test_volt_cc_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)
|
||||
@@ -864,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,
|
||||
)
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
import math
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from collections.abc import Callable, Mapping
|
||||
from functools import cache
|
||||
from typing import Any
|
||||
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.can.dbc import DBC as DBC_FILE
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car.ford.values import CAR as FORD_CAR
|
||||
from opendbc.car.gm.values import CAR as GM_CAR, DBC as GM_DBC
|
||||
|
||||
|
||||
CarGpsSample = dict[str, Any]
|
||||
CanGpsDecoder = Callable[..., CarGpsSample | None]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CarGpsConfig:
|
||||
"""Vehicle-specific CAN GPS inputs and decoder."""
|
||||
|
||||
brand: str
|
||||
messages: tuple[str, ...]
|
||||
decoder: CanGpsDecoder
|
||||
|
||||
|
||||
def _dop_accuracy(dop: float, default: float = 500.0) -> float:
|
||||
"""Convert Ford's dimensionless DOP into a conservative meter estimate."""
|
||||
return max(1.0, dop * 5.0) if 0.0 <= dop <= 5.8 else default
|
||||
|
||||
|
||||
def parse_ford_can_gps(nav1: Mapping[str, float], nav2: Mapping[str, float], nav3: Mapping[str, float]) -> CarGpsSample | None:
|
||||
"""Decode the Ford APIM GPS messages into the fields used by gpsLocationExternal."""
|
||||
year = int(nav2["GpsUtcYr_No_Actl"])
|
||||
month = int(nav2["GpsUtcMnth_No_Actl"])
|
||||
day = int(nav2["GpsUtcDay_No_Actl"])
|
||||
hour = int(nav2["GPS_UTC_hours"])
|
||||
minute = int(nav2["GPS_UTC_minutes"])
|
||||
second = int(nav2["GPS_UTC_seconds"])
|
||||
try:
|
||||
timestamp_ms = int(datetime(year, month, day, hour, minute, second, tzinfo=UTC).timestamp() * 1000)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
latitude_direction = int(nav1["GpsHsphLattSth_D_Actl"])
|
||||
longitude_direction = int(nav1["GpsHsphLongEast_D_Actl"])
|
||||
latitude_degrees = abs(float(nav1["GPS_Latitude_Degrees"]))
|
||||
longitude_degrees = abs(float(nav1["GPS_Longitude_Degrees"]))
|
||||
latitude_minutes = float(nav1["GPS_Latitude_Minutes"]) + float(nav1["GPS_Latitude_Min_dec"])
|
||||
longitude_minutes = float(nav1["GPS_Longitude_Minutes"]) + float(nav1["GPS_Longitude_Min_dec"])
|
||||
|
||||
coordinates_valid = (
|
||||
latitude_direction in (1, 2) and longitude_direction in (1, 2) and
|
||||
0.0 <= latitude_degrees <= 90.0 and 0.0 <= longitude_degrees <= 180.0 and
|
||||
0.0 <= latitude_minutes < 60.0 and 0.0 <= longitude_minutes < 60.0
|
||||
)
|
||||
latitude = (1.0 if latitude_direction == 2 else -1.0) * (latitude_degrees + latitude_minutes / 60.0) if coordinates_valid else 0.0
|
||||
longitude = (1.0 if longitude_direction == 1 else -1.0) * (longitude_degrees + longitude_minutes / 60.0) if coordinates_valid else 0.0
|
||||
|
||||
dimension = int(nav3["GPS_dimension"])
|
||||
has_fix = coordinates_valid and dimension in (1, 2) and int(nav2["Gps_B_Falt"]) == 0
|
||||
|
||||
speed_mph = float(nav3["GPS_Speed"])
|
||||
speed_mps = speed_mph * CV.MPH_TO_MS
|
||||
heading = float(nav3["GPS_Heading"])
|
||||
if speed_mph in (254.0, 255.0) or not math.isfinite(speed_mps) or not 0.0 <= speed_mps <= 200.0:
|
||||
speed_mps = 0.0
|
||||
if not math.isfinite(heading) or not 0.0 <= heading < 360.0:
|
||||
heading = 0.0
|
||||
|
||||
vertical_accuracy = _dop_accuracy(float(nav3["GPS_Vdop"]))
|
||||
horizontal_accuracy = _dop_accuracy(float(nav3["GPS_Hdop"]))
|
||||
satellite_count = int(nav3["GPS_Sat_num_in_view"])
|
||||
if not 0 <= satellite_count < 30: # 30 and 31 are Ford's unknown/invalid values.
|
||||
satellite_count = 0
|
||||
|
||||
heading_rad = math.radians(heading)
|
||||
return {
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"altitude": (float(nav3["GPS_MSL_altitude"]) * 0.3048) if has_fix else 0.0,
|
||||
"speed": speed_mps,
|
||||
"bearingDeg": heading,
|
||||
"horizontalAccuracy": horizontal_accuracy,
|
||||
"unixTimestampMillis": timestamp_ms,
|
||||
"verticalAccuracy": vertical_accuracy,
|
||||
"bearingAccuracyDeg": max(5.0, horizontal_accuracy * 2.0) if speed_mps > 1.0 else 180.0,
|
||||
"speedAccuracy": max(0.5, horizontal_accuracy),
|
||||
"hasFix": has_fix,
|
||||
"satelliteCount": satellite_count,
|
||||
"vNED": [speed_mps * math.cos(heading_rad), speed_mps * math.sin(heading_rad), 0.0],
|
||||
}
|
||||
|
||||
|
||||
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] = {
|
||||
FORD_CAR.FORD_MUSTANG_MACH_E_MK1: CarGpsConfig(
|
||||
brand="ford",
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@cache
|
||||
def _gm_dbc_has_onstar_gps(dbc_name: str) -> bool:
|
||||
return "TCICOnStarGPSPosition" in DBC_FILE(dbc_name).name_to_msg
|
||||
|
||||
|
||||
def get_car_gps_config(CP) -> CarGpsConfig | None:
|
||||
cp_brand = getattr(CP, "brand", None)
|
||||
config = CAR_GPS_CONFIGS.get(CP.carFingerprint)
|
||||
if config is not None and config.brand == cp_brand:
|
||||
return config
|
||||
|
||||
# The OnStar position message is shared by the GM powertrain DBCs. Keep the
|
||||
# existing explicit map for known vehicles, but enable the same decoder for
|
||||
# other GM fingerprints only when their active DBC actually defines it.
|
||||
if cp_brand == "gm":
|
||||
try:
|
||||
dbc_name = GM_DBC[CP.carFingerprint][Bus.pt]
|
||||
if _gm_dbc_has_onstar_gps(dbc_name):
|
||||
return CarGpsConfig(
|
||||
brand="gm",
|
||||
messages=CHEVROLET_BOLT_GPS_MESSAGES,
|
||||
decoder=parse_chevrolet_bolt_can_gps,
|
||||
)
|
||||
except (KeyError, OSError, TypeError, RuntimeError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def car_gps_available(CP) -> bool:
|
||||
return get_car_gps_config(CP) is not None
|
||||
@@ -7,7 +7,6 @@ from opendbc.car.honda import hondacan
|
||||
from opendbc.car.honda.values import (
|
||||
CAR,
|
||||
CruiseButtons,
|
||||
CruiseSettings,
|
||||
HONDA_BOSCH,
|
||||
HONDA_BOSCH_CANFD,
|
||||
HONDA_BOSCH_RADARLESS,
|
||||
@@ -17,7 +16,6 @@ from opendbc.car.honda.values import (
|
||||
HondaFlags,
|
||||
)
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.common.pid import PIDController
|
||||
from openpilot.common.params import Params
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
@@ -213,14 +211,6 @@ class CarController(CarControllerBase):
|
||||
self.CAN = hondacan.CanBus(CP)
|
||||
self.tja_control = CP.carFingerprint in HONDA_BOSCH_TJA_CONTROL
|
||||
|
||||
# MVL CAN-FD ownership state. These do not affect non-CANFD vehicles.
|
||||
self.radar_disable_counter = 0
|
||||
self.radar_mux = 0
|
||||
self.radar_hud_pulse = 0
|
||||
self.last_acc_enabled = False
|
||||
self.lkas_button_send_remaining = 0
|
||||
self.last_lkas_button_frame = 0
|
||||
|
||||
self.braking = False
|
||||
self.brake_steady = 0.0
|
||||
self.brake_last = 0.0
|
||||
@@ -244,10 +234,6 @@ class CarController(CarControllerBase):
|
||||
self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor
|
||||
self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor
|
||||
self.pitch = 0.0
|
||||
self.mvl_accord_mode = CP.carFingerprint == CAR.HONDA_ACCORD_11G
|
||||
# MVL Bosch low-speed extra-brake integrator. Active only for Accord 11G MVL mode.
|
||||
self.mvl_brake_pid = PIDController(k_p=0.0, k_i=1.0, pos_limit=0.0, neg_limit=-2.0, rate=50)
|
||||
self.mvl_brake_pid.reset()
|
||||
|
||||
def _modified_civic_standard_active(self) -> bool:
|
||||
return self.CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH and bool(self.CP.flags & HondaFlags.EPS_MODIFIED)
|
||||
@@ -269,7 +255,6 @@ class CarController(CarControllerBase):
|
||||
hud_v_cruise = hud_control.setSpeed / CS.v_cruise_factor if hud_control.speedVisible else 255
|
||||
pcm_cancel_cmd = CC.cruiseControl.cancel
|
||||
gas_interceptor_command = 0.0
|
||||
min_gas = self.params.BOSCH_GAS_LOOKUP_BP[0]
|
||||
if len(CC.orientationNED) == 3:
|
||||
self.pitch = CC.orientationNED[1]
|
||||
hill_brake = math.sin(self.pitch) * ACCELERATION_DUE_TO_GRAVITY
|
||||
@@ -323,56 +308,11 @@ class CarController(CarControllerBase):
|
||||
# Send CAN commands
|
||||
can_sends = []
|
||||
|
||||
# Bosch radar ownership. On CAN-FD, leave the stock radar active until the comma relay is open,
|
||||
# then enter extended diagnostics and disable radar RX/TX. This prevents a gap between stock
|
||||
# ACC_CONTROL disappearing and panda permitting openpilot's replacement stream.
|
||||
# tester present - w/ no response (keeps radar disabled)
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and self.CP.openpilotLongitudinalControl:
|
||||
if self.mvl_accord_mode and CS.stock_acc_alive:
|
||||
if CS.canfd_relay_open:
|
||||
if self.radar_disable_counter % 50 == 0:
|
||||
can_sends.append((0x18DAB0F1, b'\x02\x10\x03\x00\x00\x00\x00\x00', self.CAN.pt))
|
||||
elif self.radar_disable_counter % 50 == 5:
|
||||
can_sends.append((0x18DAB0F1, b'\x03\x28\x83\x03\x00\x00\x00\x00', self.CAN.pt))
|
||||
self.radar_disable_counter += 1
|
||||
elif self.frame % 10 == 0:
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(make_tester_present_msg(0x18DAB0F1, self.CAN.pt, suppress_response=True))
|
||||
|
||||
# After the stock CAN-FD radar is silent, reproduce its low-rate/tick-aligned messages on both
|
||||
# the PT and camera sides of the open relay. Pack once and mirror identical bytes so counters and
|
||||
# checksums stay synchronized. v3 deliberately uses an idle lane/object payload first; model-based
|
||||
# cluster rendering is deferred until the ownership/DTC handover is proven.
|
||||
mvl_radar_owned = self.mvl_accord_mode and CS.canfd_relay_open and not CS.stock_acc_alive
|
||||
if mvl_radar_owned and self.CP.openpilotLongitudinalControl:
|
||||
if CC.enabled and not self.last_acc_enabled:
|
||||
self.radar_hud_pulse = 30
|
||||
self.last_acc_enabled = CC.enabled
|
||||
|
||||
radar_msgs = []
|
||||
if CS.hud_tick:
|
||||
radar_msgs.append(hondacan.create_radar_hud_canfd(self.packer, self.CAN.pt, CC.enabled, self.radar_hud_pulse > 0))
|
||||
if self.radar_hud_pulse > 0:
|
||||
self.radar_hud_pulse -= 1
|
||||
if CS.supp_tick:
|
||||
radar_msgs.append(hondacan.create_canfd_supplemental(self.packer, self.CAN.pt))
|
||||
if CS.radar_50hz_tick:
|
||||
if self.radar_mux >= 58:
|
||||
self.radar_mux = 1
|
||||
elif self.radar_mux == 10:
|
||||
self.radar_mux = 17
|
||||
elif self.radar_mux == 26:
|
||||
self.radar_mux = 33
|
||||
elif self.radar_mux == 42:
|
||||
self.radar_mux = 49
|
||||
else:
|
||||
self.radar_mux += 1
|
||||
radar_msgs.extend(hondacan.create_canfd_50hz_radar_messages(self.packer, self.CAN.pt, self.radar_mux))
|
||||
if CS.radar_5hz_tick:
|
||||
radar_msgs.extend(hondacan.create_canfd_5hz_radar_messages(self.packer, self.CAN.pt, CS.radar_ref_counter))
|
||||
|
||||
for addr, dat, _ in radar_msgs:
|
||||
can_sends.append((addr, dat, self.CAN.pt))
|
||||
can_sends.append((addr, dat, self.CAN.camera))
|
||||
|
||||
# Send steering command.
|
||||
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_torque, CC.latActive, self.tja_control))
|
||||
|
||||
@@ -412,37 +352,23 @@ class CarController(CarControllerBase):
|
||||
ts = self.frame * DT_CTRL
|
||||
|
||||
if self.CP.carFingerprint in HONDA_BOSCH:
|
||||
if self.mvl_accord_mode and (accel < min_gas) and (1e-3 < CS.out.vEgo < 3.0):
|
||||
brake_addon = self.mvl_brake_pid.update(error=accel - CS.out.aEgo, speed=CS.out.vEgo)
|
||||
target_accel = min(accel, accel + brake_addon)
|
||||
else:
|
||||
if self.mvl_accord_mode:
|
||||
self.mvl_brake_pid.reset()
|
||||
target_accel = accel
|
||||
|
||||
self.accel = float(np.clip(target_accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX))
|
||||
# MVL uses requested accel (not extra-brake-adjusted accel) to decide propulsion crossover.
|
||||
gas_pedal_force = (accel if self.mvl_accord_mode else self.accel) + hill_brake
|
||||
self.accel = float(np.clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX))
|
||||
gas_pedal_force = self.accel + hill_brake
|
||||
|
||||
if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
|
||||
gas_pedal_force += wind_brake_mps2 * self.bosch_wind_factor
|
||||
|
||||
if actuators.longControlState == LongCtrlState.pid and not CS.out.gasPressed:
|
||||
gas_error = (accel if self.mvl_accord_mode else self.accel) - CS.out.aEgo
|
||||
gas_error = self.accel - CS.out.aEgo
|
||||
|
||||
if gas_error != 0.0 and gas_pedal_force > min_gas:
|
||||
if gas_error != 0.0 and gas_pedal_force > 0.0:
|
||||
if self.CP.carFingerprint == CAR.HONDA_INSIGHT:
|
||||
gas_learn_speed = 150.0
|
||||
elif self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR):
|
||||
gas_learn_speed = 300.0
|
||||
else:
|
||||
gas_learn_speed = 50.0
|
||||
gas_factor_floor = 0.01 if self.mvl_accord_mode else 0.1
|
||||
gas_learn_force = (gas_pedal_force - min_gas) if self.mvl_accord_mode else gas_pedal_force
|
||||
self.bosch_gas_factor = float(np.clip(
|
||||
self.bosch_gas_factor + gas_error / gas_learn_speed * gas_learn_force,
|
||||
gas_factor_floor, 3.0,
|
||||
))
|
||||
self.bosch_gas_factor = float(np.clip(self.bosch_gas_factor + gas_error / gas_learn_speed * gas_pedal_force, 0.1, 3.0))
|
||||
|
||||
if gas_error != 0.0 and not CS.out.brakePressed and CS.out.vEgo > 0.0:
|
||||
wind_learn_speed = 100.0 if self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR) else 1000.0
|
||||
@@ -452,8 +378,7 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor / wind_adjust, 0.1, 3.0))
|
||||
|
||||
wind_brake_threshold = min_gas if self.mvl_accord_mode else 0.0
|
||||
if gas_pedal_force <= wind_brake_threshold:
|
||||
if gas_pedal_force <= 0.0:
|
||||
self.bosch_wind_factor = max(self.bosch_wind_factor, self.bosch_wind_factor_before_brake)
|
||||
else:
|
||||
self.bosch_wind_factor_before_brake = self.bosch_wind_factor
|
||||
@@ -465,21 +390,15 @@ class CarController(CarControllerBase):
|
||||
self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor
|
||||
self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor
|
||||
|
||||
gas_lookup_input = ((gas_pedal_force - min_gas) * self.bosch_gas_factor + min_gas) if self.mvl_accord_mode else \
|
||||
gas_pedal_force * self.bosch_gas_factor
|
||||
self.gas = float(np.interp(gas_lookup_input, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V))
|
||||
self.gas = float(np.interp(gas_pedal_force * self.bosch_gas_factor, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V))
|
||||
self.gas = min(self.gas, max(60.0, self.bosch_last_gas + 60.0))
|
||||
self.bosch_last_gas = self.gas
|
||||
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
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 if self.mvl_accord_mode else None,
|
||||
)
|
||||
)
|
||||
can_sends.extend(
|
||||
hondacan.create_acc_commands(self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas, self.stopping_counter, self.CP.carFingerprint)
|
||||
)
|
||||
else:
|
||||
apply_brake = np.clip(self.brake_last - wind_brake, 0.0, 1.0)
|
||||
apply_brake = int(np.clip(apply_brake * self.params.NIDEC_BRAKE_MAX, 0, self.params.NIDEC_BRAKE_MAX - 1))
|
||||
@@ -521,16 +440,9 @@ class CarController(CarControllerBase):
|
||||
idx = (self.frame // 2) % 0x10
|
||||
can_sends.append(create_gas_interceptor_command(self.packer, gas_interceptor_command, idx))
|
||||
|
||||
# Send dashboard UI commands. On CAN-FD, ACC_HUD is owned by the radar and must only start
|
||||
# after the handover, aligned to the radar's 10 Hz HUD tick.
|
||||
if (mvl_radar_owned and CS.hud_tick and self.CP.openpilotLongitudinalControl):
|
||||
can_sends.append(
|
||||
hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, actuators.accel,
|
||||
hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud)
|
||||
)
|
||||
|
||||
# Send dashboard UI commands.
|
||||
if self.frame % 10 == 0:
|
||||
if self.CP.openpilotLongitudinalControl and not self.mvl_accord_mode:
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# On Nidec, this also controls longitudinal positive acceleration
|
||||
can_sends.append(
|
||||
hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, pcm_accel, hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud)
|
||||
@@ -546,7 +458,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# TODO: combining with create_acc_hud block above will change message order and will need replay logs regenerated
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS - {CAR.HONDA_ACCORD_11G}):
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS):
|
||||
can_sends.append(hondacan.create_radar_hud(self.packer, self.CAN.pt))
|
||||
if self.CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH:
|
||||
can_sends.append(hondacan.create_legacy_brake_command(self.packer, self.CAN.pt))
|
||||
@@ -557,29 +469,6 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
|
||||
|
||||
# CAN-FD: while engaged, the camera still needs a valid SCM_BUTTONS stream behind the open relay.
|
||||
# Take over that stream, echoing the live ambient-light byte. Also send a short LKAS-setting pulse
|
||||
# when stock LKAS is ready, matching MVL's workaround for the Honda touch-steering-wheel timer.
|
||||
if (self.mvl_accord_mode and CC.enabled and self.frame % 4 == 0
|
||||
and not pcm_cancel_cmd and not CC.cruiseControl.resume):
|
||||
if (self.lkas_button_send_remaining == 0 and CS.lkas_hud["LKAS_READY"]
|
||||
and self.frame >= self.last_lkas_button_frame + 500):
|
||||
self.lkas_button_send_remaining = 3
|
||||
|
||||
if self.lkas_button_send_remaining > 0:
|
||||
self.last_lkas_button_frame = self.frame
|
||||
self.lkas_button_send_remaining -= 1
|
||||
cruise_setting = CruiseSettings.LKAS
|
||||
elif CS.cruise_setting == CruiseSettings.LKAS:
|
||||
cruise_setting = 0
|
||||
else:
|
||||
cruise_setting = CS.cruise_setting
|
||||
|
||||
can_sends.append(hondacan.spam_buttons_command(
|
||||
self.packer, self.CAN, CS.cruise_buttons, self.CP.carFingerprint,
|
||||
cruise_setting=cruise_setting, ambient_light=CS.scm_ambient_light, bus=self.CAN.camera,
|
||||
))
|
||||
|
||||
if self.frame > 0 and self.frame % 6000 == 0:
|
||||
self.param_store.put_float("HondaGasFactorParams", self.bosch_gas_factor)
|
||||
self.param_store.put_float("HondaWindFactorParams", self.bosch_wind_factor)
|
||||
|
||||
@@ -68,33 +68,11 @@ class CarState(CarStateBase):
|
||||
self.initial_accFault_cleared = False
|
||||
self.initial_accFault_cleared_timer = int(10 / DT_CTRL)
|
||||
|
||||
# CAN-FD deferred radar handover state. The stock radar remains responsible for ACC_CONTROL
|
||||
# until the relay is open and the controller has silenced it; radar reference/tick messages are
|
||||
# used to keep the replacement radar look-alikes on the stock cadence after the handover.
|
||||
self.radar_ref_counter = 0
|
||||
self.radar_5hz_tick_counter = 0
|
||||
self.radar_5hz_tick = False
|
||||
self.supp_tick_counter = 0
|
||||
self.supp_tick = False
|
||||
self.hud_tick_counter = 0
|
||||
self.hud_tick = False
|
||||
self.radar_50hz_tick_counter = 0
|
||||
self.radar_50hz_tick = False
|
||||
self.scm_ambient_light = 0
|
||||
self.stock_acc_counter = 0
|
||||
self.stock_acc_alive = False
|
||||
self.camera_steer_counter = 0
|
||||
self.camera_steer_seen = False
|
||||
self.canfd_frames = 0
|
||||
self.canfd_relay_open = False
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
if self.CP.enableBsm:
|
||||
cp_body = can_parsers[Bus.body]
|
||||
if self.CP.carFingerprint == CAR.HONDA_ACCORD_11G:
|
||||
cp_radar = can_parsers[Bus.radar]
|
||||
|
||||
ret = structs.CarState()
|
||||
|
||||
@@ -107,10 +85,6 @@ class CarState(CarStateBase):
|
||||
prev_cruise_setting = self.cruise_setting
|
||||
self.cruise_setting = cp.vl["SCM_BUTTONS"]["CRUISE_SETTING"]
|
||||
self.cruise_buttons = cp.vl["SCM_BUTTONS"]["CRUISE_BUTTONS"]
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | {CAR.HONDA_ACCORD_11G}):
|
||||
# The camera consumes this byte in SCM_BUTTONS too (adaptive high beam); preserve it when
|
||||
# openpilot temporarily takes over SCM_BUTTONS toward the camera.
|
||||
self.scm_ambient_light = cp.vl["SCM_BUTTONS"]["AMBIENT_LIGHT_MAYBE"]
|
||||
|
||||
# used for car hud message
|
||||
self.is_metric = self.CP.carFingerprint in (CAR.HONDA_ODYSSEY_TWN,) or not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"]
|
||||
@@ -142,8 +116,8 @@ class CarState(CarStateBase):
|
||||
steer_status = self.steer_status_values[cp.vl["STEER_STATUS"]["STEER_STATUS"]]
|
||||
ret.steerFaultPermanent = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TJA_LOW_SPEED_LOCKOUT",
|
||||
"TMP_FAULT")
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH_ALT_RADAR | {CAR.HONDA_ACCORD_11G}):
|
||||
# MVL treats CAN-FD low-speed lockout as an expected EPS state too.
|
||||
if self.CP.carFingerprint in HONDA_BOSCH_ALT_RADAR:
|
||||
# TODO: See if this logic works for all other Honda
|
||||
min_steer_speed = max(CarControllerParams.STEER_GLOBAL_MIN_SPEED, self.CP.minSteerSpeed)
|
||||
expected_low_speed_lockout = steer_status == "LOW_SPEED_LOCKOUT" and ret.vEgo < min_steer_speed
|
||||
ret.steerFaultTemporary = steer_status != "NORMAL" and not expected_low_speed_lockout
|
||||
@@ -275,63 +249,6 @@ class CarState(CarStateBase):
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD):
|
||||
self.lkas_hud = cp_cam.vl["LKAS_HUD"]
|
||||
|
||||
if self.CP.carFingerprint == CAR.HONDA_ACCORD_11G:
|
||||
# Phase replacement radar messages from the stock radar's tick references. CarState reads the
|
||||
# tick in one 100 Hz cycle and CarController transmits on the next, so pulse one frame before
|
||||
# the expected next stock tick.
|
||||
self.radar_ref_counter = cp.vl["RADAR_REFERENCE"]["COUNTER"]
|
||||
|
||||
ref_tick_vals = cp.vl_all.get("RADAR_REFERENCE", {}).get("COUNTER", [])
|
||||
if len(ref_tick_vals) > 0:
|
||||
self.radar_5hz_tick_counter = 0
|
||||
else:
|
||||
self.radar_5hz_tick_counter += 1
|
||||
self.radar_5hz_tick = self.radar_5hz_tick_counter == 11
|
||||
|
||||
supp_tick_vals = cp_radar.vl_all.get("RADAR_SUPP_TICK_REFERENCE", {}).get("IGNORE", [])
|
||||
if len(supp_tick_vals) > 0:
|
||||
self.supp_tick_counter = 0
|
||||
else:
|
||||
self.supp_tick_counter += 1
|
||||
self.supp_tick = self.supp_tick_counter == 99
|
||||
|
||||
hud_tick_vals = cp_radar.vl_all.get("RADAR_HUD_TICK_REFERENCE", {}).get("IGNORE", [])
|
||||
if len(hud_tick_vals) > 0:
|
||||
self.hud_tick_counter = 0
|
||||
else:
|
||||
self.hud_tick_counter += 1
|
||||
self.hud_tick = self.hud_tick_counter == 9
|
||||
|
||||
tick_50hz_vals = cp_radar.vl_all.get("RADAR_50HZ_TICK_REFERENCE", {}).get("IGNORE", [])
|
||||
if len(tick_50hz_vals) > 0:
|
||||
self.radar_50hz_tick_counter = 0
|
||||
else:
|
||||
self.radar_50hz_tick_counter += 1
|
||||
self.radar_50hz_tick = self.radar_50hz_tick_counter == 1
|
||||
|
||||
# ACC_CONTROL is normally present every 2 frames. Keep treating the stock radar as alive until
|
||||
# four consecutive frames have passed without it, preventing overlap between stock and OP ACC.
|
||||
self.canfd_frames += 1
|
||||
if len(cp.vl_all.get("ACC_CONTROL", {}).get("COUNTER", [])) > 0:
|
||||
self.stock_acc_counter = 0
|
||||
else:
|
||||
self.stock_acc_counter += 1
|
||||
self.stock_acc_alive = self.stock_acc_counter < 4
|
||||
|
||||
# Before the relay opens, the camera's STEERING_CONTROL is physically visible on PT. Once the
|
||||
# relay opens it disappears. Fallback to 5 seconds in case the camera frame was never observed.
|
||||
if len(cp.vl_all.get("STEERING_CONTROL", {}).get("COUNTER", [])) > 0:
|
||||
self.camera_steer_counter = 0
|
||||
self.camera_steer_seen = True
|
||||
else:
|
||||
self.camera_steer_counter += 1
|
||||
self.canfd_relay_open = (self.camera_steer_seen and self.camera_steer_counter >= 5) or self.canfd_frames >= 500
|
||||
else:
|
||||
self.supp_tick = False
|
||||
self.hud_tick = False
|
||||
self.radar_5hz_tick = False
|
||||
self.radar_50hz_tick = False
|
||||
|
||||
if self.CP.enableBsm:
|
||||
# BSM messages are on B-CAN, requires a panda forwarding B-CAN messages to CAN 0
|
||||
# more info here: https://github.com/commaai/openpilot/pull/1867
|
||||
@@ -354,10 +271,6 @@ class CarState(CarStateBase):
|
||||
|
||||
def get_can_parsers(self, CP):
|
||||
pt_messages = [("GAS_SENSOR", 0)] if CP.enableGasInterceptorDEPRECATED else []
|
||||
if CP.carFingerprint == CAR.HONDA_ACCORD_11G:
|
||||
# Both deliberately go silent during the handover, so skip alive/timeout checks.
|
||||
pt_messages += [("ACC_CONTROL", float("nan")), ("STEERING_CONTROL", float("nan"))]
|
||||
|
||||
pt_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).pt)
|
||||
if CP.enableGasInterceptorDEPRECATED:
|
||||
pt_parser.message_states[0x201].ignore_checksum = True
|
||||
@@ -369,11 +282,5 @@ class CarState(CarStateBase):
|
||||
}
|
||||
if CP.enableBsm:
|
||||
parsers[Bus.body] = CANParser(DBC[CP.carFingerprint][Bus.body], [], CanBus(CP).radar)
|
||||
if CP.carFingerprint == CAR.HONDA_ACCORD_11G:
|
||||
parsers[Bus.radar] = CANParser(DBC[CP.carFingerprint][Bus.radar], [
|
||||
("RADAR_SUPP_TICK_REFERENCE", 0),
|
||||
("RADAR_HUD_TICK_REFERENCE", 0),
|
||||
("RADAR_50HZ_TICK_REFERENCE", 0),
|
||||
], CanBus(CP).radar)
|
||||
|
||||
return parsers
|
||||
|
||||
@@ -1039,8 +1039,6 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdCamera, 0x18dab5f1, None): [
|
||||
b'8S102-30A-A050\x00\x00',
|
||||
b'8S102-30A-A060\x00\x00',
|
||||
b'8S102-30A-A070\x00\x00',
|
||||
b'8S102-30A-A080\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HONDA_CRV_6G: {
|
||||
|
||||
@@ -2,7 +2,6 @@ from opendbc.car import CanBusBase
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.honda.values import (HondaFlags, HONDA_BOSCH, HONDA_BOSCH_ALT_RADAR, HONDA_BOSCH_RADARLESS,
|
||||
HONDA_BOSCH_CANFD, CarControllerParams)
|
||||
from opendbc.car.honda.values import CAR
|
||||
|
||||
# CAN bus layout with relay
|
||||
# 0 = ACC-CAN - radar side
|
||||
@@ -71,16 +70,14 @@ 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):
|
||||
def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_counter, car_fingerprint):
|
||||
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
|
||||
gas_command = gas if active and gas_force > min_gas_accel else -30000
|
||||
gas_command = gas if active and accel > min_gas_accel else -30000
|
||||
accel_command = accel if active else 0
|
||||
braking = 1 if active and gas_force < min_gas_accel else 0
|
||||
braking = 1 if active and accel < 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
|
||||
|
||||
@@ -90,7 +87,7 @@ def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_count
|
||||
'STANDSTILL': standstill,
|
||||
}
|
||||
|
||||
if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
if car_fingerprint in HONDA_BOSCH_RADARLESS:
|
||||
acc_control_values.update({
|
||||
"CONTROL_ON": enabled,
|
||||
"IDLESTOP_ALLOW": stopping_counter > 200, # allow idle stop after 4 seconds (50 Hz)
|
||||
@@ -150,20 +147,10 @@ def create_acc_hud(packer, bus, CP, enabled, pcm_speed, pcm_accel, hud_control,
|
||||
'SET_ME_X01_2': 1,
|
||||
}
|
||||
|
||||
if CP.carFingerprint == CAR.HONDA_ACCORD_11G:
|
||||
# Stock CAN-FD radar toggles these together with ACC/lead/braking state. Keeping them low when
|
||||
# neither lead nor braking is present matches MVL's observed CAN-FD payload shape.
|
||||
set_me = int(enabled and (bool(acc_hud_values['HUD_LEAD']) or (pcm_accel < 0.2)))
|
||||
acc_hud_values['SET_ME_X01'] = set_me
|
||||
acc_hud_values['SET_ME_X01_2'] = set_me
|
||||
|
||||
if CP.carFingerprint in HONDA_BOSCH:
|
||||
acc_hud_values['ACC_ON'] = int(enabled)
|
||||
# Preserve StarPilot's existing Bosch HUD behavior outside CAN-FD. MVL's CAN-FD
|
||||
# replacement radar expects FCW/CMBS enabled, while legacy Bosch keeps them off.
|
||||
canfd = CP.carFingerprint == CAR.HONDA_ACCORD_11G
|
||||
acc_hud_values['FCM_OFF'] = 0 if canfd else 1
|
||||
acc_hud_values['FCM_OFF_2'] = 0 if canfd else 1
|
||||
acc_hud_values['FCM_OFF'] = 1
|
||||
acc_hud_values['FCM_OFF_2'] = 1
|
||||
else:
|
||||
# Shows the distance bars, TODO: stock camera shows updates temporarily while disabled
|
||||
acc_hud_values['ACC_ON'] = int(enabled)
|
||||
@@ -232,104 +219,16 @@ def create_legacy_brake_command(packer, bus):
|
||||
return packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus, {})
|
||||
|
||||
|
||||
def spam_buttons_command(packer, CAN, button_val, car_fingerprint, cruise_setting=0, ambient_light=None, bus=None):
|
||||
def spam_buttons_command(packer, CAN, button_val, car_fingerprint):
|
||||
values = {
|
||||
'CRUISE_BUTTONS': button_val,
|
||||
'CRUISE_SETTING': cruise_setting,
|
||||
'CRUISE_SETTING': 0,
|
||||
}
|
||||
# Existing StarPilot callers should retain their previous payload; only the CAN-FD camera
|
||||
# takeover path explicitly echoes the live ambient-light byte.
|
||||
if ambient_light is not None:
|
||||
values['AMBIENT_LIGHT_MAYBE'] = ambient_light
|
||||
if bus is None:
|
||||
# send buttons to camera on radarless (camera does ACC) cars
|
||||
bus = CAN.camera if car_fingerprint in HONDA_BOSCH_RADARLESS else CAN.pt
|
||||
# send buttons to camera on radarless (camera does ACC) cars
|
||||
bus = CAN.camera if car_fingerprint in HONDA_BOSCH_RADARLESS else CAN.pt
|
||||
return packer.make_can_msg("SCM_BUTTONS", bus, values)
|
||||
|
||||
|
||||
def create_radar_hud_canfd(packer, bus, acc, acc_pulse=False):
|
||||
values = {
|
||||
'CMBS_ENABLED_MAYBE': 1 if (acc and acc_pulse) else 0,
|
||||
'ACC_ON': acc,
|
||||
'SET_ME_X01': 0x01,
|
||||
'SET_ME_X01_2': 0x01,
|
||||
}
|
||||
return packer.make_can_msg("RADAR_HUD_CANFD", bus, values)
|
||||
|
||||
|
||||
def create_canfd_supplemental(packer, bus):
|
||||
values = {
|
||||
'SET_ME_X01': 0x01,
|
||||
'SET_ME_X41': 0x41,
|
||||
}
|
||||
return packer.make_can_msg("BOSCH_SUPPLEMENTAL_CANFD", bus, values)
|
||||
|
||||
|
||||
RADAR_MUX_BANK_STARTS = (1, 17, 33, 49)
|
||||
PATH_OFFSET_INVALID = 2047
|
||||
# These extended IDs use the CAN-FD checksum offset used by the Accord 11G
|
||||
# replacement-radar stream. Keep the legacy Honda offset for existing high-ID
|
||||
# messages on other Honda platforms.
|
||||
HONDA_CANFD_MVL_CHECKSUM_IDS = frozenset((
|
||||
0x6CD5558, 0x6CD5559, 0xF31AA52, 0xF31AA5C, 0x1A45AA4E,
|
||||
))
|
||||
|
||||
|
||||
def _lane_path_offsets(radar_mux):
|
||||
pos = next((radar_mux - start for start in RADAR_MUX_BANK_STARTS if start <= radar_mux <= start + 9), 0)
|
||||
if pos == 0:
|
||||
return (0, 0, 0, 0)
|
||||
if pos == 1:
|
||||
return (0, 0, PATH_OFFSET_INVALID, PATH_OFFSET_INVALID)
|
||||
return (PATH_OFFSET_INVALID,) * 4
|
||||
|
||||
|
||||
def create_canfd_50hz_radar_messages(packer, bus, radar_mux):
|
||||
offsets = _lane_path_offsets(radar_mux)
|
||||
lane_path_values = {
|
||||
'MUX': radar_mux,
|
||||
'PATH_OFFSET_1': offsets[0],
|
||||
'PATH_OFFSET_2': offsets[1],
|
||||
'PATH_OFFSET_3': offsets[2],
|
||||
'PATH_OFFSET_4': offsets[3],
|
||||
}
|
||||
hud_objects_values = {
|
||||
'MUX': radar_mux,
|
||||
'OBJECT_ID': 0,
|
||||
'IS_LEAD_CAR': 0,
|
||||
'CAR_TYPE': -1,
|
||||
'ROTATION': -128,
|
||||
'LONG_DIST': 196.9,
|
||||
'LAT_DIST': 204.7,
|
||||
}
|
||||
return [
|
||||
packer.make_can_msg('LANE_PATH', bus, lane_path_values),
|
||||
packer.make_can_msg('HUD_OBJECTS', bus, hud_objects_values),
|
||||
]
|
||||
|
||||
|
||||
def create_canfd_5hz_radar_messages(packer, bus, radar_ref_cntr):
|
||||
radar_lead_values = {
|
||||
'CNTR_REF': radar_ref_cntr,
|
||||
'SET_ME_X01': 0x01,
|
||||
'TARGET_SPEED_MAYBE': 140,
|
||||
# v3 initially sends MVL's stock-like idle lane state. Model-derived dash rendering can be added
|
||||
# separately after the DTC/ownership handover is proven on-car.
|
||||
'LEFT_LANE': 0,
|
||||
'RIGHT_LANE': 0,
|
||||
'LANE_PATH_LENGTH': 6,
|
||||
}
|
||||
radar_lead2_values = {
|
||||
'SET_ME_X88': 136,
|
||||
'SET_ME_X78': 120,
|
||||
'LEAD_DISTANCE_MAYBE': 0,
|
||||
}
|
||||
return [
|
||||
packer.make_can_msg('RADAR_LEAD', bus, radar_lead_values),
|
||||
packer.make_can_msg('RADAR_LEAD2', bus, radar_lead2_values),
|
||||
]
|
||||
|
||||
|
||||
def honda_checksum(address: int, sig, d: bytearray) -> int:
|
||||
s = 0
|
||||
extended = address > 0x7FF
|
||||
@@ -344,5 +243,5 @@ def honda_checksum(address: int, sig, d: bytearray) -> int:
|
||||
s += (x & 0xF) + (x >> 4)
|
||||
s = 8 - s
|
||||
if extended:
|
||||
s += 10 if address in HONDA_CANFD_MVL_CHECKSUM_IDS else 3
|
||||
s += 3
|
||||
return s & 0xF
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
import numpy as np
|
||||
from openpilot.common.params import Params, UnknownKeyName
|
||||
from opendbc.car import get_safety_config, structs, uds
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.disable_ecu import disable_ecu
|
||||
from opendbc.car.honda.hondacan import CanBus
|
||||
from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_A, HONDA_BOSCH_A_RADAR_VERIFIED, HONDA_BOSCH_CANFD, \
|
||||
from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_CANFD, \
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, HondaSafetyFlags
|
||||
from opendbc.car.honda.carcontroller import CarController
|
||||
from opendbc.car.honda.carstate import CarState
|
||||
@@ -45,17 +44,7 @@ class CarInterface(CarInterfaceBase):
|
||||
cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
|
||||
ret.safetyConfigs = cfgs
|
||||
|
||||
# HONDA_BOSCH_A describes the physical harness family. Radar remains unavailable until the
|
||||
# exact platform is added to HONDA_BOSCH_A_RADAR_VERIFIED after real-capture validation.
|
||||
try:
|
||||
# The explicit default keeps the verified platform usable on installs that have not yet
|
||||
# persisted the internal kill-switch parameter; the verified-platform set remains mandatory.
|
||||
bosch_a_radar_tryout = not docs and Params().get_bool("HondaBoschARadar", default=True)
|
||||
except UnknownKeyName:
|
||||
bosch_a_radar_tryout = False
|
||||
ret.radarUnavailable = not (candidate in HONDA_BOSCH_A and
|
||||
candidate in HONDA_BOSCH_A_RADAR_VERIFIED and
|
||||
bosch_a_radar_tryout)
|
||||
ret.radarUnavailable = True
|
||||
# Disable the radar and let openpilot control longitudinal
|
||||
# WARNING: THIS DISABLES AEB!
|
||||
# If Bosch radarless, this blocks ACC messages from the camera
|
||||
@@ -154,13 +143,9 @@ class CarInterface(CarInterfaceBase):
|
||||
CarControllerParams.BOSCH_GAS_LOOKUP_BP = [-0.2, 2.0]
|
||||
|
||||
elif candidate == CAR.HONDA_ACCORD_11G:
|
||||
# MVL Boston sp-honda-dev-202608 tuning (48211d6a63).
|
||||
ret.longitudinalActuatorDelay = 0.05
|
||||
ret.steerActuatorDelay = 0.3
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 12789], [0, 12789]]
|
||||
ret.lateralTuning.pid.kf = 0.000035
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.115], [0.052]]
|
||||
CarControllerParams.BOSCH_GAS_LOOKUP_BP = [0.0, 2.0]
|
||||
ret.steerActuatorDelay = 0.22
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 5200], [0, 2560, 12747]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.ACURA_ILX:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] # TODO: determine if there is a dead zone at the top end
|
||||
@@ -329,8 +314,6 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.RADARLESS.value
|
||||
if candidate in HONDA_BOSCH_CANFD:
|
||||
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_CANFD.value
|
||||
if candidate == CAR.HONDA_ACCORD_11G:
|
||||
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_CANFD_MVL.value
|
||||
|
||||
# min speed to enable ACC. if car can do stop and go, then set enabling speed
|
||||
# to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not
|
||||
@@ -362,13 +345,6 @@ class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def init(CP, can_recv, can_send, communication_control=None):
|
||||
if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl:
|
||||
# CAN-FD radar ownership is handed over only after the comma relay is confirmed open.
|
||||
# Disabling the radar here can create a gap before panda enters the Honda safety mode and
|
||||
# replacement ACC_CONTROL is permitted, which can latch CRUISE_FAULT/DTCs in the brake module.
|
||||
# deinit() still passes an explicit enable request and therefore falls through to disable_ecu.
|
||||
if CP.carFingerprint == CAR.HONDA_ACCORD_11G and communication_control is None:
|
||||
return
|
||||
|
||||
# 0x80 silences response
|
||||
if communication_control is None:
|
||||
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX,
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import math
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.honda.hondacan import CanBus
|
||||
from opendbc.car.honda.values import DBC
|
||||
from opendbc.car.interfaces import RadarInterfaceBase
|
||||
from opendbc.car.honda.values import DBC
|
||||
|
||||
|
||||
def _create_nidec_can_parser(car_fingerprint):
|
||||
@@ -16,615 +11,39 @@ def _create_nidec_can_parser(car_fingerprint):
|
||||
return CANParser(DBC[car_fingerprint][Bus.radar], messages, 1)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Honda Bosch-A 16-slot object bank
|
||||
# ============================================================================
|
||||
# 16 CAN-visible object slots. Each slot has 4 main frames (f0..f3, one CAN ID each -- NOT sub-frames
|
||||
# muxed onto a shared ID) plus one synchronized auxiliary 5th frame. This supersedes any prior model of
|
||||
# 0x280/0x284/0x288/0x28C as pieces of one object, or of 0x2C8/0x2C9 as a separate "coarse" list: it is
|
||||
# ONE 16-slot bank with one auxiliary frame per slot. See honda_bosch_a_radar.dbc for the bit geometry.
|
||||
BOSCH_A_DBC_NAME = 'honda_bosch_a_radar'
|
||||
BOSCH_A_NUM_SLOTS = 16
|
||||
|
||||
|
||||
def _bosch_a_main_base(slot: int) -> int:
|
||||
return 0x280 + 4 * slot if slot < 4 else 0x2D0 + 4 * (slot - 4)
|
||||
|
||||
|
||||
def _bosch_a_aux_id(slot: int) -> int:
|
||||
return 0x2C8 + slot if slot < 8 else 0x290 + (slot - 8)
|
||||
|
||||
|
||||
BOSCH_A_MAIN_IDS = [[_bosch_a_main_base(s) + i for i in range(4)] for s in range(BOSCH_A_NUM_SLOTS)]
|
||||
BOSCH_A_AUX_IDS = [_bosch_a_aux_id(s) for s in range(BOSCH_A_NUM_SLOTS)]
|
||||
BOSCH_A_ALL_IDS = [addr for ids in BOSCH_A_MAIN_IDS for addr in ids] + BOSCH_A_AUX_IDS
|
||||
|
||||
# Publish RadarPoints when the last MAIN object frame arrives. Passive captures prove that slot 15's
|
||||
# companion frame, 0x297, follows 0x2FF and is the final observed object-family frame in a full sweep:
|
||||
#
|
||||
# ... 0x2FC, 0x2FD, 0x2FE, 0x2FF, 0x297
|
||||
#
|
||||
# Keep 0x2FF as the RadarPoint trigger while the companion data remains optional/debug-only. Making
|
||||
# 0x297 the sole trigger would allow one dropped auxiliary frame to suppress an otherwise-valid point
|
||||
# update, contrary to the parser's "aux never gates validity" contract.
|
||||
BOSCH_A_TRIGGER_MSG = BOSCH_A_MAIN_IDS[BOSCH_A_NUM_SLOTS - 1][3]
|
||||
BOSCH_A_SWEEP_END_MSG = BOSCH_A_AUX_IDS[BOSCH_A_NUM_SLOTS - 1] # 0x297
|
||||
|
||||
# Observed coherent Bosch-A sweep cadence is ~14.5-16 Hz in the available captures.
|
||||
BOSCH_A_FREQ_HZ = 15
|
||||
|
||||
# Range: f0 raw_range (12-bit, B2:B3 high nibble) -> meters. Firmware q16 = 8*raw_range; physical
|
||||
# calibration keeps slope/offset as named, replay-refinable constants (do not add a second radar/camera
|
||||
# longitudinal offset on top of this).
|
||||
BOSCH_A_RANGE_SCALE_M = 0.05712
|
||||
BOSCH_A_RANGE_OFFSET_M = -3.0
|
||||
|
||||
# Azimuth: f0 raw_angle (11-bit, B4:B5 high 3 bits), offset-binary about 1024.
|
||||
#
|
||||
# The f3 angular-edge pair independently closes the exact center-angle scale:
|
||||
# azimuth_rad = (raw_angle - 1024) / 2048
|
||||
#
|
||||
# This supersedes the older empirical 0.032 deg/count fit.
|
||||
BOSCH_A_AZIMUTH_SCALE_RAD = 1.0 / 2048.0
|
||||
BOSCH_A_AZIMUTH_CENTER = 1024
|
||||
|
||||
# Invalid sentinels (section 3/4/5/6 of the spec).
|
||||
BOSCH_A_STATUS_INVALID = 0xF
|
||||
BOSCH_A_RANGE_RAW_INVALID = 0xFFF
|
||||
BOSCH_A_ANGLE_RAW_INVALID = 0x7FF
|
||||
BOSCH_A_LIFE_INVALID = 0xFFF
|
||||
BOSCH_A_TRACK_ID_MIN = 1
|
||||
BOSCH_A_TRACK_ID_MAX = 0x3F
|
||||
# AUX logical 0x00CA has an explicit 0x3FF invalid sentinel. Firmware proves the normalization below;
|
||||
# captures strongly support interpreting the active value as previous/current range ratio.
|
||||
BOSCH_A_RANGE_RATIO_INVALID = 0x3FF
|
||||
BOSCH_A_LOGICAL_00CA_INVALID = BOSCH_A_RANGE_RATIO_INVALID # compatibility/debug alias
|
||||
BOSCH_A_RANGE_RATIO_SCALE = 0.001
|
||||
BOSCH_A_RANGE_RATIO_OFFSET = 0.5
|
||||
|
||||
# The synchronized AUX frame contains an 11-bit offset-binary velocity-like field and a 10-bit
|
||||
# companion quality/uncertainty field. The byte locations are now decoded in the DBC. The exact
|
||||
# Bosch descriptor name is still being verified, so keep the conversion constants isolated here.
|
||||
# Capture validation shows active values in [0, 1728], with 0x7FE used as the inactive sentinel.
|
||||
BOSCH_A_DIRECT_VREL_INVALID = 0x7FE
|
||||
BOSCH_A_DIRECT_VREL_MIN_RAW = 0
|
||||
BOSCH_A_DIRECT_VREL_MAX_RAW = 1728
|
||||
BOSCH_A_DIRECT_VREL_CENTER_RAW = 864
|
||||
BOSCH_A_DIRECT_VREL_SCALE_MPS = 1.0 / 64.0
|
||||
# Empirical raw-quality policy. U11 error against an independent range-derivative reference rises
|
||||
# with u10 in a graded way, not a cliff: replay evidence bins it roughly as 0-255 clean, 256-511 only
|
||||
# mildly degraded, 512-767 clearly degraded, 768+ worst. 511 is set at that mild/clear boundary so
|
||||
# U11 is still trusted directly through the mildly-degraded band; only u10 above this triggers the
|
||||
# high-u10 coast path below. This is deliberately not presented as a Bosch physical unit or
|
||||
# descriptor constant; it keeps saturated/high-uncertainty AUX values from becoming authoritative
|
||||
# velocity measurements.
|
||||
BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW = 511
|
||||
|
||||
# Measurement-authority policy. These are replay-derived safety/tuning gates, not recovered Bosch
|
||||
# constants. Range innovation is measured from the previous accepted observation so a reset cannot
|
||||
# become the baseline for following sweeps.
|
||||
BOSCH_A_RANGE_SIGMA_DEGRADED_RAW = 4
|
||||
BOSCH_A_RANGE_INNOVATION_MAX_M = 2.0
|
||||
BOSCH_A_RANGE_INNOVATION_HARD_MAX_M = 5.0
|
||||
BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS = 50.0
|
||||
BOSCH_A_USE_TAN_LATERAL_PROJECTION = True
|
||||
|
||||
# Accepted-range history is retained for continuity and the adjacent two-point fallback. Rejected
|
||||
# range resets never enter it, and it is not used for a multi-sample OLS velocity fit.
|
||||
BOSCH_A_VREL_MAX_SAMPLES = 8
|
||||
|
||||
# Staleness gate -- TUNING constant, reused plumbing pattern (not a firmware fact). At the observed
|
||||
# ~15 Hz cadence, 0.20 s is approximately three missed sweeps.
|
||||
BOSCH_A_STALE_S = 0.20
|
||||
|
||||
|
||||
@dataclass
|
||||
class _BoschASlotState:
|
||||
last_seen_nanos: int | None = None
|
||||
|
||||
# Firmware logical-ID companion data -- telemetry/debug only for now, never a RadarPoint validity
|
||||
# gate. 0x00C9 has a firmware transform but no proven physical meaning; 0x00CA has an explicit
|
||||
# invalid sentinel and its physical meaning remains unresolved.
|
||||
logical_00c9_raw: float = float('nan')
|
||||
logical_00ca_raw: float = float('nan')
|
||||
direct_vrel_raw: int | None = None
|
||||
direct_vrel_uncertainty_raw: int | None = None
|
||||
|
||||
def reset(self):
|
||||
self.last_seen_nanos = None
|
||||
self.logical_00c9_raw = float('nan')
|
||||
self.logical_00ca_raw = float('nan')
|
||||
self.direct_vrel_raw = None
|
||||
self.direct_vrel_uncertainty_raw = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class _BoschATrackState:
|
||||
"""Persistent state for one Bosch CAN object identity, independent of wire slot."""
|
||||
track_id: int
|
||||
prev_frame_idx: int | None = None
|
||||
prev_life: int | None = None
|
||||
last_seen_nanos: int | None = None
|
||||
wire_slot: int | None = None
|
||||
samples: deque = field(default_factory=lambda: deque(maxlen=BOSCH_A_VREL_MAX_SAMPLES))
|
||||
last_trusted_vrel: float | None = None
|
||||
last_trusted_vrel_nanos: int | None = None
|
||||
|
||||
|
||||
def _bosch_a_direct_vrel(raw_value: int | float | None,
|
||||
uncertainty_raw: int | float | None = None) -> float | None:
|
||||
"""Decode the capture-validated AUX relative-velocity candidate.
|
||||
|
||||
None means that AUX was absent/invalid and the caller should use the existing fallback policy. The [0, 1728]
|
||||
active domain is deliberately enforced here because values above the observed +13.5 m/s rail have
|
||||
not appeared on active objects; 0x7FE is the observed inactive sentinel.
|
||||
"""
|
||||
if raw_value is None:
|
||||
return None
|
||||
raw = int(raw_value)
|
||||
if raw == BOSCH_A_DIRECT_VREL_INVALID:
|
||||
return None
|
||||
if not BOSCH_A_DIRECT_VREL_MIN_RAW <= raw <= BOSCH_A_DIRECT_VREL_MAX_RAW:
|
||||
return None
|
||||
# u10 is retained as a raw quality indicator because its physical units remain unresolved. The
|
||||
# conservative threshold is evidence-backed tuning, not a recovered firmware validity rule.
|
||||
if uncertainty_raw is not None and int(uncertainty_raw) > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW:
|
||||
return None
|
||||
return (raw - BOSCH_A_DIRECT_VREL_CENTER_RAW) * BOSCH_A_DIRECT_VREL_SCALE_MPS
|
||||
|
||||
|
||||
def _bosch_a_range_ratio(raw_value: int | float | None) -> float | None:
|
||||
if raw_value is None:
|
||||
return None
|
||||
raw = int(raw_value)
|
||||
if raw == BOSCH_A_RANGE_RATIO_INVALID or not 0 <= raw < BOSCH_A_RANGE_RATIO_INVALID:
|
||||
return None
|
||||
return BOSCH_A_RANGE_RATIO_OFFSET + BOSCH_A_RANGE_RATIO_SCALE * raw
|
||||
|
||||
|
||||
def _bosch_a_range_ratio_vrel(raw_value: int | float | None, d_rel: float, dt: float) -> float | None:
|
||||
"""Return the range rate implied by the empirical previous/current range ratio."""
|
||||
ratio = _bosch_a_range_ratio(raw_value)
|
||||
if ratio is None or dt <= 0.0 or not math.isfinite(d_rel):
|
||||
return None
|
||||
return d_rel * (1.0 - ratio) / dt
|
||||
|
||||
|
||||
def _bosch_a_measurement_degraded(range_sigma_raw: int, existence_raw: int,
|
||||
direct_vrel_uncertainty_raw: int | None) -> bool:
|
||||
range_quality_bad = range_sigma_raw >= BOSCH_A_RANGE_SIGMA_DEGRADED_RAW or existence_raw in (0, 0x7F)
|
||||
velocity_quality_bad = (direct_vrel_uncertainty_raw is not None and
|
||||
direct_vrel_uncertainty_raw > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW)
|
||||
return range_quality_bad or velocity_quality_bad
|
||||
|
||||
|
||||
def _create_bosch_a_can_parser(CP):
|
||||
messages = [(addr, BOSCH_A_FREQ_HZ) for addr in BOSCH_A_ALL_IDS]
|
||||
# Bus.radar selects the Bosch-A DBC; the object/fusion feed itself is
|
||||
# physically on the camera-side ACC-CAN.
|
||||
return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, CanBus(CP).camera)
|
||||
|
||||
|
||||
class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
self.track_id = 0
|
||||
self.radar_fault = False
|
||||
self.radar_wrong_config = False
|
||||
self.radar_off_can = CP.radarUnavailable
|
||||
self.bosch_a_radar = (not self.radar_off_can and Bus.radar in DBC[CP.carFingerprint] and
|
||||
DBC[CP.carFingerprint][Bus.radar] == BOSCH_A_DBC_NAME)
|
||||
|
||||
# Nidec
|
||||
if self.radar_off_can:
|
||||
self.rcp = None
|
||||
self.trigger_msg = 0x445
|
||||
elif self.bosch_a_radar:
|
||||
self.rcp = _create_bosch_a_can_parser(CP)
|
||||
self.trigger_msg = BOSCH_A_TRIGGER_MSG
|
||||
self._slots = [_BoschASlotState() for _ in range(BOSCH_A_NUM_SLOTS)]
|
||||
self._tracks: dict[int, _BoschATrackState] = {}
|
||||
self._slot_track_ids: list[int | None] = [None] * BOSCH_A_NUM_SLOTS
|
||||
self._last_trigger_nanos = -1
|
||||
else:
|
||||
# Nidec
|
||||
self.rcp = _create_nidec_can_parser(CP.carFingerprint)
|
||||
self.trigger_msg = 0x445
|
||||
self.track_id = 0
|
||||
self.radar_fault = False
|
||||
self.radar_wrong_config = False
|
||||
|
||||
self.trigger_msg = 0x445
|
||||
self.updated_messages = set()
|
||||
|
||||
def update(self, can_strings):
|
||||
if self.radar_off_can or self.rcp is None:
|
||||
# in Bosch radar and we are only steering for now, so sleep 0.05s to keep
|
||||
# radard at 20Hz and return no points
|
||||
if self.radar_off_can:
|
||||
return super().update(None)
|
||||
|
||||
vls = self.rcp.update(can_strings)
|
||||
self.updated_messages.update(vls)
|
||||
|
||||
if self.trigger_msg not in self.updated_messages:
|
||||
if self.bosch_a_radar and self._last_trigger_nanos >= 0:
|
||||
now = self.rcp._last_update_nanos
|
||||
if (now - self._last_trigger_nanos) * 1e-9 > BOSCH_A_STALE_S:
|
||||
return self._bosch_a_stale_radardata()
|
||||
return None
|
||||
|
||||
rr = self._update(self.updated_messages)
|
||||
self.updated_messages.clear()
|
||||
return rr
|
||||
|
||||
def _bosch_a_stale_radardata(self):
|
||||
# Whole-bus silence: clear every live point/history and emit an EMPTY RadarData (not None) so
|
||||
# radard drops any lead within a cycle instead of freezing a phantom. The next observation starts
|
||||
# a fresh incarnation for its CAN identity.
|
||||
self.pts.clear()
|
||||
self._tracks.clear()
|
||||
self._slot_track_ids = [None] * BOSCH_A_NUM_SLOTS
|
||||
for slot_state in self._slots:
|
||||
slot_state.reset()
|
||||
self._last_trigger_nanos = -1
|
||||
stale = structs.RadarData()
|
||||
if not self.rcp.can_valid:
|
||||
stale.errors.canError = True
|
||||
stale.errors.radarUnavailableTemporary = True
|
||||
return stale
|
||||
|
||||
def _bosch_a_retire_track(self, track_id: int):
|
||||
self._tracks.pop(track_id, None)
|
||||
self.pts.pop(track_id, None)
|
||||
for slot, slot_track_id in enumerate(self._slot_track_ids):
|
||||
if slot_track_id == track_id:
|
||||
self._slot_track_ids[slot] = None
|
||||
|
||||
def _bosch_a_retire_stale_tracks(self, now: int):
|
||||
for track_id, track in list(self._tracks.items()):
|
||||
if track.last_seen_nanos is not None and (now - track.last_seen_nanos) * 1e-9 > BOSCH_A_STALE_S:
|
||||
self._bosch_a_retire_track(track_id)
|
||||
|
||||
def _update(self, updated_messages):
|
||||
if self.bosch_a_radar:
|
||||
return self._update_bosch_a(updated_messages)
|
||||
return self._update_nidec(updated_messages)
|
||||
|
||||
def _update_bosch_a(self, updated_messages):
|
||||
ret = structs.RadarData()
|
||||
if not self.rcp.can_valid:
|
||||
ret.errors.canError = True
|
||||
|
||||
now = self.rcp._last_update_nanos
|
||||
self._last_trigger_nanos = now
|
||||
self._bosch_a_retire_stale_tracks(now)
|
||||
|
||||
observations = []
|
||||
|
||||
for slot in range(BOSCH_A_NUM_SLOTS):
|
||||
f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[slot]
|
||||
aux = BOSCH_A_AUX_IDS[slot]
|
||||
st = self._slots[slot]
|
||||
|
||||
if not (f0 in updated_messages and f1 in updated_messages and
|
||||
f2 in updated_messages and f3 in updated_messages):
|
||||
# Incomplete main-frame set: a missing CAN frame must not be treated as a lifecycle mismatch or
|
||||
# death/replacement. Logical tracks are retired independently by their global staleness gate.
|
||||
continue
|
||||
|
||||
v0 = self.rcp.vl[f0]
|
||||
v1 = self.rcp.vl[f1]
|
||||
v2 = self.rcp.vl[f2]
|
||||
v3 = self.rcp.vl[f3]
|
||||
|
||||
idx0 = int(v0['FRAME_IDX'])
|
||||
if not (idx0 == int(v1['FRAME_IDX']) == int(v2['FRAME_IDX']) == int(v3['FRAME_IDX'])):
|
||||
# The four main frames don't share a common cycle index -- not a coherent observation this
|
||||
# window. Only combine main-frame data from a coherent common frame index (section 2).
|
||||
continue
|
||||
|
||||
status = int(v0['STATUS'])
|
||||
range_raw = int(v0['RANGE_RAW'])
|
||||
angle_raw = int(v0['AZIMUTH_RAW'])
|
||||
range_sigma_raw = int(v0['RANGE_SIGMA_RAW'])
|
||||
existence_raw = int(v1['OBJECT_EXISTENCE_PROBABILITY_RAW'])
|
||||
life = int(v2['LIFECYCLE_RAW'])
|
||||
track_id = int(v3['TRACK_ID'])
|
||||
track_id_valid = BOSCH_A_TRACK_ID_MIN <= track_id <= BOSCH_A_TRACK_ID_MAX
|
||||
st.last_seen_nanos = now
|
||||
direct_vrel_raw = None
|
||||
direct_vrel_uncertainty_raw = None
|
||||
range_ratio_raw = None
|
||||
|
||||
# Attach synchronized companion data only when its cycle matches. Missing AUX never invalidates
|
||||
# an otherwise coherent F0-F3 object; it only removes independent motion/quality evidence.
|
||||
if aux in updated_messages:
|
||||
av = self.rcp.vl[aux]
|
||||
if int(av['FRAME_IDX']) == idx0:
|
||||
direct_vrel_raw = int(av['REL_VELOCITY_RAW'])
|
||||
direct_vrel_uncertainty_raw = int(av['REL_VELOCITY_UNCERTAINTY_RAW'])
|
||||
range_ratio_raw = int(av['RANGE_RATIO_RAW'])
|
||||
logical_00c9_raw = av['FW_LID_00C9_RAW']
|
||||
logical_00ca_raw = av['FW_LID_00CA_RAW']
|
||||
st.logical_00c9_raw = logical_00c9_raw
|
||||
st.logical_00ca_raw = (
|
||||
logical_00ca_raw if logical_00ca_raw != BOSCH_A_LOGICAL_00CA_INVALID else float('nan')
|
||||
)
|
||||
st.direct_vrel_raw = direct_vrel_raw
|
||||
st.direct_vrel_uncertainty_raw = direct_vrel_uncertainty_raw
|
||||
|
||||
object_valid = (status != BOSCH_A_STATUS_INVALID and range_raw != BOSCH_A_RANGE_RAW_INVALID and
|
||||
angle_raw != BOSCH_A_ANGLE_RAW_INVALID and life != BOSCH_A_LIFE_INVALID)
|
||||
|
||||
observations.append({
|
||||
'slot': slot,
|
||||
'frame_idx': idx0,
|
||||
'life': life,
|
||||
'track_id': track_id,
|
||||
'track_id_valid': track_id_valid,
|
||||
'object_valid': object_valid,
|
||||
'range_sigma_raw': range_sigma_raw,
|
||||
'existence_raw': existence_raw,
|
||||
'direct_vrel_raw': direct_vrel_raw,
|
||||
'direct_vrel_uncertainty_raw': direct_vrel_uncertainty_raw,
|
||||
'range_ratio_raw': range_ratio_raw,
|
||||
})
|
||||
|
||||
# First collapse duplicate wire observations of one CAN identity. The dictionary is also the
|
||||
# output uniqueness boundary: one valid Bosch identity can never create two RadarPoints.
|
||||
valid_by_id = {}
|
||||
for observation in observations:
|
||||
if not (observation['object_valid'] and observation['track_id_valid']):
|
||||
# An invalid observation ends publication for the object currently occupying this wire slot,
|
||||
# but it does not immediately destroy the persistent state. The object may be multiplexed to
|
||||
# another slot or may return before the per-identity stale deadline; a later lifecycle break
|
||||
# or staleness expiry will clear its derivative history.
|
||||
ids_to_hide = {self._slot_track_ids[observation['slot']]}
|
||||
if observation['track_id_valid']:
|
||||
ids_to_hide.add(observation['track_id'])
|
||||
for invalid_id in ids_to_hide - {None}:
|
||||
self.pts.pop(invalid_id, None)
|
||||
continue
|
||||
track_id = observation['track_id']
|
||||
current = valid_by_id.get(track_id)
|
||||
if current is None:
|
||||
valid_by_id[track_id] = observation
|
||||
continue
|
||||
|
||||
# Prefer the wire slot currently associated with the persistent state. If neither candidate is
|
||||
# preferred, retain the lower slot for deterministic handling of a malformed duplicate frame.
|
||||
track = self._tracks.get(track_id)
|
||||
current_score = (0 if track is not None and track.wire_slot == current['slot'] else 1, current['slot'])
|
||||
candidate_score = (0 if track is not None and track.wire_slot == observation['slot'] else 1,
|
||||
observation['slot'])
|
||||
if candidate_score < current_score:
|
||||
valid_by_id[track_id] = observation
|
||||
|
||||
valid_ids = set(valid_by_id)
|
||||
for track_id, observation in sorted(valid_by_id.items(), key=lambda item: item[1]['slot']):
|
||||
slot = observation['slot']
|
||||
idx0 = observation['frame_idx']
|
||||
life = observation['life']
|
||||
|
||||
# A wire-slot replacement ends publication for the old occupant, but not its persistent
|
||||
# identity/history. If the old ID is still valid in another slot this sweep, keep its point;
|
||||
# otherwise hide it until that ID returns or reaches its own stale deadline.
|
||||
old_id = self._slot_track_ids[slot]
|
||||
if old_id is not None and old_id != track_id and old_id not in valid_ids:
|
||||
self.pts.pop(old_id, None)
|
||||
|
||||
track = self._tracks.get(track_id)
|
||||
if track is None:
|
||||
track = _BoschATrackState(track_id=track_id)
|
||||
self._tracks[track_id] = track
|
||||
|
||||
same_incarnation = False
|
||||
if track.prev_frame_idx is not None and track.prev_life is not None:
|
||||
frame_delta = (idx0 - track.prev_frame_idx) & 0xF
|
||||
life_delta = (life - track.prev_life) & 0xFFF
|
||||
same_incarnation = life_delta == 2 * frame_delta
|
||||
|
||||
if not same_incarnation:
|
||||
# The CAN identity remains the externally-visible key, but a lifecycle discontinuity starts a
|
||||
# new incarnation and must not inherit the previous object's range-rate history.
|
||||
track.samples.clear()
|
||||
track.last_trusted_vrel = None
|
||||
track.last_trusted_vrel_nanos = None
|
||||
self.pts.pop(track_id, None)
|
||||
|
||||
v0 = self.rcp.vl[BOSCH_A_MAIN_IDS[slot][0]]
|
||||
range_raw = int(v0['RANGE_RAW'])
|
||||
angle_raw = int(v0['AZIMUTH_RAW'])
|
||||
dRel = BOSCH_A_RANGE_SCALE_M * range_raw + BOSCH_A_RANGE_OFFSET_M
|
||||
azimuth_rad = BOSCH_A_AZIMUTH_SCALE_RAD * (angle_raw - BOSCH_A_AZIMUTH_CENTER)
|
||||
# The firmware's internal geometry path uses tan(angle) for a forward-axis distance. The
|
||||
# Bosch object range is consumed as that forward-axis quantity here, so use the same projection
|
||||
# rather than treating it as radial/slant range. Keep the switch explicit while the final
|
||||
# output bridge remains under static review.
|
||||
#
|
||||
# Sign: AZIMUTH_RAW > center (positive azimuth_rad) is a LEFT-of-center detection in car frame's
|
||||
# y axis (left is positive), matching the Nidec RadarPoint.yRel contract. Confirmed against real
|
||||
# captures 2026-08-22: a stationary cluster of queued vehicles visible on the left in the road
|
||||
# camera was rendering on the right in both the Qt and raylib radar-track overlays with the
|
||||
# previously-flipped sign.
|
||||
lateral_projection = math.tan if BOSCH_A_USE_TAN_LATERAL_PROJECTION else math.sin
|
||||
yRel = dRel * lateral_projection(azimuth_rad)
|
||||
|
||||
now_s = now * 1e-9
|
||||
direct_vrel_raw = observation['direct_vrel_raw']
|
||||
direct_vrel_uncertainty_raw = observation['direct_vrel_uncertainty_raw']
|
||||
direct_vrel = _bosch_a_direct_vrel(direct_vrel_raw, direct_vrel_uncertainty_raw)
|
||||
live_direct_vrel = _bosch_a_direct_vrel(direct_vrel_raw)
|
||||
range_ratio_raw = observation['range_ratio_raw']
|
||||
|
||||
# A live U11 rejected solely by the conservative U10 qualification threshold is neither an
|
||||
# unavailable velocity nor permission to synthesize a one-sweep range derivative. It is also
|
||||
# NOT permission to skip range-innovation checking below: u10 correlates with range_sigma in
|
||||
# replay data, so a high-u10 sweep is exactly the condition where a bad/discontinuous range
|
||||
# (slot migration, reset) is most likely, not less likely. Range acceptance is therefore
|
||||
# decided on the same terms as every other sweep first; only once the range clears that gate
|
||||
# does a high-u10 U11 fall back to coasting instead of publishing a synthesized derivative.
|
||||
high_u10_live_vrel = (direct_vrel is None and live_direct_vrel is not None and
|
||||
direct_vrel_uncertainty_raw is not None and
|
||||
direct_vrel_uncertainty_raw > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW)
|
||||
|
||||
# Validate against the previous accepted range. Qualified U11 and the range-ratio field are
|
||||
# independent corroboration paths; high-U10 U11 is deliberately excluded from this decision.
|
||||
previous_sample = track.samples[-1] if track.samples else None
|
||||
fallback_vrel = 0.0
|
||||
ratio_vrel = None
|
||||
range_rejected = False
|
||||
degraded = _bosch_a_measurement_degraded(
|
||||
observation['range_sigma_raw'], observation['existence_raw'], direct_vrel_uncertainty_raw,
|
||||
)
|
||||
if previous_sample is not None:
|
||||
previous_time, previous_range = previous_sample
|
||||
dt = now_s - previous_time
|
||||
if dt <= 0.0:
|
||||
range_rejected = True
|
||||
else:
|
||||
fallback_vrel = (dRel - previous_range) / dt
|
||||
ratio = _bosch_a_range_ratio(range_ratio_raw)
|
||||
ratio_vrel = _bosch_a_range_ratio_vrel(range_ratio_raw, dRel, dt)
|
||||
|
||||
residuals_m = []
|
||||
if direct_vrel is not None:
|
||||
residuals_m.append(abs(dRel - (previous_range + direct_vrel * dt)))
|
||||
if ratio is not None:
|
||||
residuals_m.append(abs(previous_range - dRel * ratio))
|
||||
|
||||
if residuals_m:
|
||||
innovation_m = min(residuals_m)
|
||||
range_rejected = (
|
||||
innovation_m > BOSCH_A_RANGE_INNOVATION_HARD_MAX_M or
|
||||
(degraded and innovation_m > BOSCH_A_RANGE_INNOVATION_MAX_M)
|
||||
)
|
||||
else:
|
||||
range_rejected = abs(fallback_vrel) > BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS
|
||||
|
||||
if range_rejected:
|
||||
# Keep the last accepted point briefly as an unmeasured coast. The rejected geometry is not
|
||||
# published and never becomes the baseline for a later derivative.
|
||||
accepted_fresh = previous_sample is not None and now_s - previous_sample[0] <= BOSCH_A_STALE_S
|
||||
point = self.pts.get(track_id)
|
||||
if accepted_fresh and point is not None:
|
||||
point.measured = False
|
||||
else:
|
||||
self.pts.pop(track_id, None)
|
||||
track.prev_frame_idx = idx0
|
||||
track.prev_life = life
|
||||
track.last_seen_nanos = now
|
||||
track.wire_slot = slot
|
||||
for old_slot, old_id in enumerate(self._slot_track_ids):
|
||||
if old_slot != slot and old_id == track_id:
|
||||
self._slot_track_ids[old_slot] = None
|
||||
self._slot_track_ids[slot] = track_id
|
||||
continue
|
||||
|
||||
if high_u10_live_vrel:
|
||||
# The range cleared innovation checking above, so geometry here is trustworthy; only vRel is
|
||||
# in question. Preserve current geometry but coast only a recent authoritative motion
|
||||
# estimate without a KF update, rather than publishing a one-sweep-derivative synthesis.
|
||||
trusted_fresh = (track.last_trusted_vrel is not None and track.last_trusted_vrel_nanos is not None and
|
||||
(now - track.last_trusted_vrel_nanos) * 1e-9 <= BOSCH_A_STALE_S)
|
||||
if trusted_fresh:
|
||||
point = self.pts.get(track_id)
|
||||
if point is not None:
|
||||
point.dRel = dRel
|
||||
point.yRel = yRel
|
||||
point.vRel = track.last_trusted_vrel
|
||||
point.measured = False
|
||||
else:
|
||||
track.last_trusted_vrel = None
|
||||
track.last_trusted_vrel_nanos = None
|
||||
self.pts.pop(track_id, None)
|
||||
|
||||
# Do not let a coast-only range observation become a future derivative baseline.
|
||||
track.prev_frame_idx = idx0
|
||||
track.prev_life = life
|
||||
track.last_seen_nanos = now
|
||||
track.wire_slot = slot
|
||||
for old_slot, old_id in enumerate(self._slot_track_ids):
|
||||
if old_slot != slot and old_id == track_id:
|
||||
self._slot_track_ids[old_slot] = None
|
||||
self._slot_track_ids[slot] = track_id
|
||||
continue
|
||||
|
||||
# Native U11 is unavailable here (sentinel/out-of-range -- the high-u10-but-live case above
|
||||
# already returned before this point) and the range-ratio field is unusable or degraded too.
|
||||
# The remaining option is the raw one-sweep (dRel-previous_range)/dt derivative, which is
|
||||
# structurally the same hazard the high-u10 case guards against: a synthesized rate becoming an
|
||||
# authoritative measurement. Coast a recent trusted velocity instead, on the same terms as above,
|
||||
# rather than publish it.
|
||||
# A true birth observation (no previous accepted range yet) can never mature into a published
|
||||
# point this cycle regardless of vRel source -- `matured` below requires a second sample -- so
|
||||
# only intercept once a fallback derivative would actually have something to poison.
|
||||
u11_and_ratio_unavailable = (direct_vrel is None and (ratio_vrel is None or degraded) and
|
||||
previous_sample is not None)
|
||||
if u11_and_ratio_unavailable:
|
||||
trusted_fresh = (track.last_trusted_vrel is not None and track.last_trusted_vrel_nanos is not None and
|
||||
(now - track.last_trusted_vrel_nanos) * 1e-9 <= BOSCH_A_STALE_S)
|
||||
if trusted_fresh:
|
||||
point = self.pts.get(track_id)
|
||||
if point is not None:
|
||||
point.dRel = dRel
|
||||
point.yRel = yRel
|
||||
point.vRel = track.last_trusted_vrel
|
||||
point.measured = False
|
||||
else:
|
||||
track.last_trusted_vrel = None
|
||||
track.last_trusted_vrel_nanos = None
|
||||
self.pts.pop(track_id, None)
|
||||
|
||||
# Do not let a coast-only range observation become a future derivative baseline.
|
||||
track.prev_frame_idx = idx0
|
||||
track.prev_life = life
|
||||
track.last_seen_nanos = now
|
||||
track.wire_slot = slot
|
||||
for old_slot, old_id in enumerate(self._slot_track_ids):
|
||||
if old_slot != slot and old_id == track_id:
|
||||
self._slot_track_ids[old_slot] = None
|
||||
self._slot_track_ids[slot] = track_id
|
||||
continue
|
||||
|
||||
track.samples.append((now_s, dRel))
|
||||
sample_count = len(track.samples)
|
||||
|
||||
# Prefer qualified native U11; otherwise the range-ratio field. The raw one-sweep derivative is
|
||||
# never published as a measurement -- see the coast/drop branch above, which intercepts before
|
||||
# this point whenever neither native U11 nor the ratio field is usable.
|
||||
if direct_vrel is not None:
|
||||
vRel = direct_vrel
|
||||
else:
|
||||
vRel = ratio_vrel
|
||||
trustworthy_vrel = True
|
||||
|
||||
# A birth observation has no range-rate yet. Keep it as history, but do not publish a RadarPoint
|
||||
# until a second coherent observation of the same CAN identity supplies a finite derivative.
|
||||
matured = sample_count >= 2 and math.isfinite(vRel)
|
||||
if trustworthy_vrel:
|
||||
track.last_trusted_vrel = vRel
|
||||
track.last_trusted_vrel_nanos = now
|
||||
if matured and track_id not in self.pts:
|
||||
self.pts[track_id] = structs.RadarData.RadarPoint()
|
||||
self.pts[track_id].trackId = track_id
|
||||
self.pts[track_id].aRel = float('nan')
|
||||
self.pts[track_id].yvRel = float('nan')
|
||||
|
||||
if matured:
|
||||
self.pts[track_id].dRel = dRel
|
||||
self.pts[track_id].yRel = yRel
|
||||
self.pts[track_id].vRel = vRel
|
||||
self.pts[track_id].measured = True
|
||||
else:
|
||||
self.pts.pop(track_id, None)
|
||||
|
||||
track.prev_frame_idx = idx0
|
||||
track.prev_life = life
|
||||
track.last_seen_nanos = now
|
||||
track.wire_slot = slot
|
||||
for old_slot, old_id in enumerate(self._slot_track_ids):
|
||||
if old_slot != slot and old_id == track_id:
|
||||
self._slot_track_ids[old_slot] = None
|
||||
self._slot_track_ids[slot] = track_id
|
||||
|
||||
ret.points = [self.pts[track_id] for track_id in sorted(self.pts)]
|
||||
return ret
|
||||
|
||||
def _update_nidec(self, updated_messages):
|
||||
ret = structs.RadarData()
|
||||
|
||||
for ii in sorted(updated_messages):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ import re
|
||||
from types import SimpleNamespace
|
||||
import pytest
|
||||
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.honda.interface import CarInterface
|
||||
@@ -196,16 +196,6 @@ class TestHondaFingerprint:
|
||||
assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD
|
||||
assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_LONG
|
||||
|
||||
def test_mvl_handover_is_scoped_to_accord_11g(self):
|
||||
toggles = get_test_toggles()
|
||||
accord_cp = CarInterface.get_params(CAR.HONDA_ACCORD_11G, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
crv_cp = CarInterface.get_params(CAR.HONDA_CRV_6G, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
|
||||
assert Bus.radar in DBC[accord_cp.carFingerprint]
|
||||
assert Bus.radar not in DBC[crv_cp.carFingerprint]
|
||||
assert accord_cp.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD_MVL
|
||||
assert not crv_cp.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD_MVL
|
||||
|
||||
def test_nidec_pedal_detection_enables_interceptor_path(self):
|
||||
toggles = get_test_toggles()
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
|
||||
@@ -57,9 +57,6 @@ class HondaSafetyFlags(IntFlag):
|
||||
RADARLESS = 8
|
||||
BOSCH_CANFD = 16
|
||||
GAS_INTERCEPTOR = 32
|
||||
# Accord 11G MVL radar/camera handover messages. Keep the generic CAN-FD
|
||||
# safety profile unchanged for other Honda CAN-FD platforms (for example CR-V 6G).
|
||||
BOSCH_CANFD_MVL = 64
|
||||
|
||||
|
||||
class HondaFlags(IntFlag):
|
||||
@@ -120,17 +117,11 @@ class HondaCarDocs(CarDocs):
|
||||
|
||||
self.car_parts = CarParts.common([harness])
|
||||
|
||||
if CP.alphaLongitudinalAvailable:
|
||||
self.footnotes.append(Footnote.EXP_LONG)
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
CIVIC_DIESEL = CarFootnote(
|
||||
"2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.",
|
||||
Column.FSR_STEERING)
|
||||
EXP_LONG = CarFootnote(
|
||||
"Enabling longitudinal control (alpha) will disable all CMBS functionality, including AEB and FCW.",
|
||||
Column.LONGITUDINAL)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -171,12 +162,7 @@ class CAR(Platforms):
|
||||
HondaCarDocs("Honda N-Box 2018", "All", min_steer_speed=5.),
|
||||
],
|
||||
CarSpecs(mass=890., wheelbase=2.520, steerRatio=18.64),
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated'},
|
||||
)
|
||||
HONDA_ACCORD = HondaBoschPlatformConfig(
|
||||
[
|
||||
@@ -186,21 +172,15 @@ class CAR(Platforms):
|
||||
],
|
||||
# steerRatio: 11.82 is spec end-to-end
|
||||
CarSpecs(mass=3279 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.33, centerToFrontRatio=0.39, tireStiffnessFactor=0.8467),
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
|
||||
flags=HondaFlags.ALLOW_MANUAL_TRANS,
|
||||
)
|
||||
HONDA_ACCORD_11G = HondaBoschCANFDPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Honda Accord 2023-25", "All"),
|
||||
HondaCarDocs("Honda Accord Hybrid 2023-25", "All"),
|
||||
],
|
||||
CarSpecs(mass=3477 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.7, centerToFrontRatio=0.39),
|
||||
{Bus.pt: 'honda_common_canfd_generated', Bus.radar: 'honda_common_canfd_generated'},
|
||||
],
|
||||
CarSpecs(mass=3477 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.0, centerToFrontRatio=0.39),
|
||||
)
|
||||
HONDA_CIVIC_BOSCH = HondaBoschPlatformConfig(
|
||||
[
|
||||
@@ -210,22 +190,12 @@ class CAR(Platforms):
|
||||
HondaCarDocs("Honda Civic Hatchback 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
],
|
||||
CarSpecs(mass=1326, wheelbase=2.7, steerRatio=15.38, centerToFrontRatio=0.4), # steerRatio: 10.93 is end-to-end spec
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
|
||||
)
|
||||
HONDA_CIVIC_BOSCH_DIESEL = HondaBoschPlatformConfig(
|
||||
[], # don't show in docs
|
||||
HONDA_CIVIC_BOSCH.specs,
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
|
||||
)
|
||||
HONDA_CIVIC_2022 = HondaBoschPlatformConfig(
|
||||
[
|
||||
@@ -244,12 +214,7 @@ class CAR(Platforms):
|
||||
[HondaCarDocs("Honda CR-V 2017-22", min_steer_speed=15. * CV.MPH_TO_MS)],
|
||||
# steerRatio: 12.3 is spec end-to-end
|
||||
CarSpecs(mass=3410 * CV.LB_TO_KG, wheelbase=2.66, steerRatio=16.0, centerToFrontRatio=0.41, tireStiffnessFactor=0.677),
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
HONDA_CRV_6G = HondaBoschCANFDPlatformConfig(
|
||||
@@ -263,12 +228,7 @@ class CAR(Platforms):
|
||||
[HondaCarDocs("Honda CR-V Hybrid 2017-22", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
# mass: mean of 4 models in kg, steerRatio: 12.3 is spec end-to-end
|
||||
CarSpecs(mass=1667, wheelbase=2.66, steerRatio=16, centerToFrontRatio=0.41, tireStiffnessFactor=0.677),
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
|
||||
)
|
||||
HONDA_HRV_3G = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Honda HR-V 2023-25", "All")],
|
||||
@@ -285,12 +245,7 @@ class CAR(Platforms):
|
||||
ACURA_RDX_3G = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Acura RDX 2019-21", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=4068 * CV.LB_TO_KG, wheelbase=2.75, steerRatio=11.95, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), # as spec
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
ACURA_RDX_3G_MMR = HondaBoschPlatformConfig(
|
||||
@@ -302,32 +257,17 @@ class CAR(Platforms):
|
||||
HONDA_INSIGHT = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.0, centerToFrontRatio=0.39, tireStiffnessFactor=0.82), # as spec
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'honda_insight_ex_2019_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'honda_insight_ex_2019_can_generated'},
|
||||
)
|
||||
HONDA_E = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated'},
|
||||
)
|
||||
HONDA_E_ADVANCE = HondaBoschPlatformConfig(
|
||||
[], # don't show in docs, base trim already in docs
|
||||
CarSpecs(mass=1527, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
|
||||
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
|
||||
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
|
||||
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
|
||||
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
|
||||
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
|
||||
{Bus.pt: 'honda_e_advance_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
|
||||
{Bus.pt: 'honda_e_advance_2020_can_generated'},
|
||||
)
|
||||
HONDA_PILOT_4G = HondaBoschCANFDPlatformConfig(
|
||||
[HondaCarDocs("Honda Pilot 2023-25", "All")],
|
||||
@@ -528,12 +468,6 @@ HONDA_BOSCH = CAR.with_flags(HondaFlags.BOSCH)
|
||||
HONDA_BOSCH_RADARLESS = CAR.with_flags(HondaFlags.BOSCH_RADARLESS)
|
||||
HONDA_BOSCH_CANFD = CAR.with_flags(HondaFlags.BOSCH_CANFD)
|
||||
HONDA_BOSCH_ALT_RADAR = CAR.with_flags(HondaFlags.BOSCH_ALT_RADAR)
|
||||
# Plain bosch_a harness: not CANFD, not radarless, not alt-radar. These platforms have a firmware-correct
|
||||
# RadarInterface (16-slot Bosch-A object bank, RX-only, see radar_interface.py) available behind
|
||||
# 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
|
||||
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 = {
|
||||
CAR.HONDA_ACCORD,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Provenance: portions of HKG angle control are adapted from sunnypilot/opendbc's
|
||||
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
|
||||
import numpy as np
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs
|
||||
@@ -10,13 +8,11 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_an
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.hyundai import hyundaicanfd, hyundaican
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
@@ -26,9 +22,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
|
||||
@@ -86,10 +79,7 @@ BLINDSPOT_WARNING_SOUND_SAMPLES = 36
|
||||
|
||||
def egmp_dynamic_longitudinal_tuning(CP) -> bool:
|
||||
return CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5_PE) or \
|
||||
kia_ev6_gt_line_longitudinal_tuning(
|
||||
CP.carFingerprint, getattr(CP, "carVin", ""),
|
||||
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
|
||||
)
|
||||
kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", ""))
|
||||
|
||||
|
||||
def get_canfd_scc_decel_step(CP) -> float:
|
||||
@@ -97,10 +87,7 @@ def get_canfd_scc_decel_step(CP) -> float:
|
||||
|
||||
|
||||
def should_reset_ev6_gt_line_longitudinal_tuning(CP, long_control_state: LongCtrlState) -> bool:
|
||||
return kia_ev6_gt_line_longitudinal_tuning(
|
||||
CP.carFingerprint, getattr(CP, "carVin", ""),
|
||||
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
|
||||
) and \
|
||||
return kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", "")) and \
|
||||
long_control_state == LongCtrlState.off
|
||||
|
||||
|
||||
@@ -436,12 +423,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)
|
||||
@@ -459,7 +440,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
|
||||
@@ -476,12 +456,6 @@ class CarController(CarControllerBase):
|
||||
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
|
||||
|
||||
def _update_dash_icon_state(self, CC):
|
||||
if CC.latActive:
|
||||
@@ -631,8 +605,6 @@ 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)
|
||||
|
||||
# Hold torque with induced temporary fault when cutting the actuation bit
|
||||
# FIXME: we don't use this with CAN FD?
|
||||
torque_fault = CC.latActive and not apply_steer_req
|
||||
@@ -659,10 +631,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
|
||||
actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
|
||||
is_ev6_gt_line = kia_ev6_gt_line_longitudinal_tuning(
|
||||
self.CP.carFingerprint, getattr(self.CP, "carVin", ""),
|
||||
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
|
||||
)
|
||||
is_ev6_gt_line = kia_ev6_gt_line_longitudinal_tuning(self.CP.carFingerprint, getattr(self.CP, "carVin", ""))
|
||||
is_ccnc_angle_long = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR
|
||||
if is_ccnc_angle_long and (self._ev9_long_tuning.stop_request or not CC.enabled or CC.cruiseControl.override):
|
||||
self._ioniq_6_long_tuning = reset_egmp_longitudinal_tuning(self._ioniq_6_long_tuning)
|
||||
@@ -728,8 +697,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,
|
||||
@@ -755,7 +722,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))
|
||||
|
||||
# HUD messages
|
||||
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
|
||||
@@ -764,7 +730,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,
|
||||
longitudinal_active=longitudinal_active,
|
||||
))
|
||||
if self.long_active_ecu:
|
||||
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(
|
||||
@@ -785,19 +750,14 @@ 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 CC.cruiseControl.resume:
|
||||
# send resume at a max freq of 10Hz
|
||||
@@ -818,7 +778,6 @@ class CarController(CarControllerBase):
|
||||
# TODO: unclear if this is needed
|
||||
jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
|
||||
use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value
|
||||
main_cruise_enabled = getattr(CS, "main_cruise_on", False) if getattr(CS, "main_cruise_tracking", False) else True
|
||||
if blended_hda2:
|
||||
stopping = stopping and CS.out.vEgoRaw < 0.1
|
||||
can_sends.extend(hyundaican.create_acc_commands_can_canfd_blended_hda2(
|
||||
@@ -834,15 +793,11 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
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))
|
||||
CC.cruiseControl.override, use_fca, self.CP))
|
||||
|
||||
# 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:
|
||||
@@ -859,9 +814,7 @@ class CarController(CarControllerBase):
|
||||
can_sends = []
|
||||
|
||||
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
|
||||
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
|
||||
lfa_longitudinal_active = longitudinal_active if self.CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN else self.CP.openpilotLongitudinalControl
|
||||
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)
|
||||
@@ -872,6 +825,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)
|
||||
@@ -890,7 +846,7 @@ class CarController(CarControllerBase):
|
||||
if angle_lkas_alt:
|
||||
steering_msg_active = bool(steering_msg_active and drive_gear)
|
||||
angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive)
|
||||
forward_stock_lkas = 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)
|
||||
@@ -900,7 +856,13 @@ class CarController(CarControllerBase):
|
||||
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))
|
||||
send_lfa_status=self.ecu_disable_failed and
|
||||
self.CP.carFingerprint == CAR.KIA_EV9))
|
||||
elif self.ecu_disable_failed and self.CP.carFingerprint == CAR.KIA_EV9:
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(
|
||||
self.packer, self.CP, self.CAN, CC.enabled, False, 0.0, 0.0,
|
||||
CS.stock_lfa_msg, lka_icon=lka_icon, send_lfa_status=True, lfa_only=True,
|
||||
))
|
||||
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,
|
||||
@@ -1067,20 +1029,16 @@ 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
|
||||
|
||||
# cruise standstill resume
|
||||
elif CC.cruiseControl.resume:
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
|
||||
for _ in range(20):
|
||||
can_sends.append(hyundaicanfd.create_buttons(
|
||||
self.packer, self.CP, self.CAN, (CS.buttons_counter + 1) % 0x100,
|
||||
Buttons.RES_ACCEL, base_values=CS.cruise_buttons_msg,
|
||||
))
|
||||
self.last_button_frame = self.frame
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
|
||||
# TODO: resume for alt button cars
|
||||
pass
|
||||
else:
|
||||
for _ in range(20):
|
||||
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.RES_ACCEL))
|
||||
|
||||
@@ -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
|
||||
@@ -11,7 +9,6 @@ from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, CAR, DBC, Buttons, CarControllerParams, \
|
||||
CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_CORNER_RADAR_BSM_CAR, \
|
||||
CANFD_ALT_BUTTONS_RESUME_CAR, \
|
||||
hyundai_cancel_button_enables_cruise, ALT_BUS_LDA_BUTTON_CARS, ALT_BUS_LDA_BUTTON_SWL_STAT_CARS
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
|
||||
@@ -38,8 +35,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:
|
||||
@@ -137,7 +132,6 @@ class CarState(CarStateBase):
|
||||
self.is_metric = False
|
||||
self.buttons_counter = 0
|
||||
self.main_cruise_on = False
|
||||
self.main_cruise_tracking = bool(getattr(FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
|
||||
|
||||
self.cruise_info = {}
|
||||
self.msg_161 = {}
|
||||
@@ -146,7 +140,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
|
||||
@@ -178,10 +171,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)
|
||||
@@ -247,20 +238,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,
|
||||
@@ -344,15 +325,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:
|
||||
@@ -376,9 +348,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"])
|
||||
@@ -446,22 +415,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()
|
||||
@@ -590,9 +558,7 @@ class CarState(CarStateBase):
|
||||
self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"])
|
||||
self.lda_button = cp.vl[self.cruise_btns_msg_canfd]["LDA_BTN"]
|
||||
self.left_paddle = 0
|
||||
if self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
|
||||
self.cruise_buttons_msg = copy.copy(cp.vl[self.cruise_btns_msg_canfd])
|
||||
elif self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
self.cruise_buttons_msg = copy.copy(cp.vl["CRUISE_BUTTONS"])
|
||||
self.left_paddle = cp.vl["CRUISE_BUTTONS"]["LEFT_PADDLE"]
|
||||
self.buttons_counter = cp.vl[self.cruise_btns_msg_canfd]["COUNTER"]
|
||||
@@ -625,7 +591,7 @@ class CarState(CarStateBase):
|
||||
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
|
||||
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas}),
|
||||
*create_button_events(self.left_paddle, prev_left_paddle, {1: ButtonType.altButton2})]
|
||||
if self.CP.openpilotLongitudinalControl and (self.CP.carFingerprint == CAR.KIA_EV9 or self.main_cruise_tracking):
|
||||
if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint == CAR.KIA_EV9:
|
||||
ret.cruiseState.available = self.update_main_cruise(ret)
|
||||
|
||||
ret.blockPcmEnable = not self.recent_button_interaction()
|
||||
@@ -652,9 +618,7 @@ class CarState(CarStateBase):
|
||||
def get_can_parsers_canfd(self, CP):
|
||||
msgs = []
|
||||
cam_msgs = []
|
||||
if CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
|
||||
msgs.append(("CRUISE_BUTTONS_ALT", 50))
|
||||
elif not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
|
||||
if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
|
||||
# The EV9 can stop publishing this during the non-ECU-disabled startup
|
||||
# state. Keep decoding it when present without making CAN invalid.
|
||||
msgs += [
|
||||
@@ -730,12 +694,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))
|
||||
@@ -744,7 +702,7 @@ 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1404,14 +1402,6 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00JK__ RDR ----- 1.00 1.01 99110-DS500 ',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_GV70_2026: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00JK MFC AT USA LHD 1.00 1.11 99211-IY600 241125',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00JK__ RDR ----- 1.00 1.01 99110-AR600 ',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_GV80_2025: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00JX__ RDR ----- 1.00 1.03 99110-T6500 ',
|
||||
@@ -1698,9 +1688,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',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -40,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
|
||||
@@ -60,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
|
||||
|
||||
@@ -68,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
|
||||
@@ -80,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:
|
||||
@@ -106,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])
|
||||
@@ -203,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 = []
|
||||
@@ -316,12 +284,11 @@ def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_las
|
||||
return commands
|
||||
|
||||
|
||||
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP,
|
||||
main_cruise_enabled=True):
|
||||
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP):
|
||||
commands = []
|
||||
|
||||
scc11_values = {
|
||||
"MainMode_ACC": int(bool(main_cruise_enabled)),
|
||||
"MainMode_ACC": 1,
|
||||
"TauGapSet": hud_control.leadDistanceBars,
|
||||
"VSetDis": set_speed if enabled else 0,
|
||||
"AliveCounterACC": idx % 0x10,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
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
|
||||
from opendbc.car.crc import CRC16_XMODEM
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CANFD_ALT_BUTTONS_RESUME_CAR
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR
|
||||
|
||||
|
||||
def _set_value(msg: bytearray, sig, ival: int) -> None:
|
||||
@@ -65,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)
|
||||
@@ -100,11 +153,18 @@ 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):
|
||||
send_lfa_status=False, lfa_only=False):
|
||||
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 or send_lfa_status:
|
||||
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LFA", CAN.ECAN, enabled, lat_active, apply_torque))
|
||||
if lfa_only:
|
||||
return ret
|
||||
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
|
||||
|
||||
@@ -198,8 +258,10 @@ 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) or send_lfa_status:
|
||||
ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values))
|
||||
if lfa_only:
|
||||
return ret
|
||||
ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values))
|
||||
else:
|
||||
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
@@ -249,27 +311,16 @@ def create_suppress_lfa(packer, CAN, lfa_block_msg, lka_steering_alt):
|
||||
|
||||
|
||||
def create_buttons(packer, CP, CAN, cnt, btn=0, base_values=None, left_paddle=False, right_paddle=False):
|
||||
values = {k: v for k, v in base_values.items() if k not in ("CHECKSUM", "_CHECKSUM", "COUNTER")} if base_values else {}
|
||||
values = {k: v for k, v in base_values.items() if k not in ("_CHECKSUM", "COUNTER")} if base_values else {}
|
||||
values.update({
|
||||
"COUNTER": cnt,
|
||||
"SET_ME_1": 1,
|
||||
"CRUISE_BUTTONS": btn,
|
||||
"LEFT_PADDLE": int(left_paddle),
|
||||
"RIGHT_PADDLE": int(right_paddle),
|
||||
})
|
||||
if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR):
|
||||
values.update({
|
||||
"LEFT_PADDLE": int(left_paddle),
|
||||
"RIGHT_PADDLE": int(right_paddle),
|
||||
})
|
||||
|
||||
bus = CAN.ECAN if CP.flags & HyundaiFlags.CANFD_LKA_STEERING else CAN.CAM
|
||||
if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
|
||||
address, dat, bus = packer.make_can_msg("CRUISE_BUTTONS_ALT", bus, values)
|
||||
dat = bytearray(dat)
|
||||
checksum = hkg_can_fd_checksum(address, None, dat)
|
||||
dat[0] = checksum & 0xFF
|
||||
dat[1] = (checksum >> 8) & 0xFF
|
||||
return address, bytes(dat), bus
|
||||
|
||||
return packer.make_can_msg("CRUISE_BUTTONS", bus, values)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import time
|
||||
# Provenance: portions of HKG angle integration are adapted from sunnypilot/opendbc's
|
||||
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
|
||||
from opendbc.car import get_safety_config, structs, uds
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
|
||||
@@ -14,15 +12,13 @@ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
|
||||
CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR, \
|
||||
HyundaiStarPilotSafetyFlags, \
|
||||
hyundai_cancel_button_enables_cruise, \
|
||||
kia_ev6_gt_line_longitudinal_tuning, \
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
|
||||
kia_ev6_gt_line_longitudinal_tuning
|
||||
from opendbc.car.hyundai.radar_interface import get_radar_track_config, radar_tracks_available
|
||||
from opendbc.car.interfaces import CarInterfaceBase, ACCEL_MIN
|
||||
from opendbc.car.disable_ecu import disable_ecu, ecu_log
|
||||
from opendbc.car.hyundai.carcontroller import CarController
|
||||
from opendbc.car.hyundai.carstate import CarState
|
||||
from opendbc.car.hyundai.radar_interface import RadarInterface
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
Ecu = structs.CarParams.Ecu
|
||||
@@ -33,7 +29,7 @@ ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.can
|
||||
# Track when ECU disable happened - used to permanently suppress CAN errors from disabled ECU
|
||||
ECU_DISABLE_TIMESTAMP = 0.0
|
||||
KONA_NON_SCC_FCA_RADAR_ADDR = 0x602
|
||||
KIA_EV9_ACCEL_MAX = 2.2
|
||||
KIA_EV9_ACCEL_MAX = 2.5
|
||||
|
||||
|
||||
def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
@@ -50,7 +46,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
|
||||
|
||||
|
||||
@@ -110,8 +106,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
@staticmethod
|
||||
def apply_post_fingerprint_params(CP: structs.CarParams, candidate, fingerprint, car_fw) -> None:
|
||||
gt_line_testing_ground = testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID)
|
||||
if kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, gt_line_testing_ground):
|
||||
if kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin):
|
||||
apply_kia_ev6_gt_line_longitudinal_params(CP)
|
||||
|
||||
@staticmethod
|
||||
@@ -226,9 +221,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):
|
||||
|
||||
@@ -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,13 +20,12 @@ 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
|
||||
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
|
||||
from opendbc.car.hyundai import hyundaican, hyundaicanfd
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
|
||||
RADAR_START_ADDR, get_radar_track_config
|
||||
from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \
|
||||
@@ -79,7 +78,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,
|
||||
@@ -119,7 +117,6 @@ ANGLE_STEERING_CARS = (
|
||||
CAR.KIA_EV6_2025,
|
||||
CAR.KIA_EV9,
|
||||
CAR.GENESIS_GV70_ELECTRIFIED_2ND_GEN,
|
||||
CAR.GENESIS_GV70_2026,
|
||||
CAR.GENESIS_GV80_2025,
|
||||
)
|
||||
|
||||
@@ -129,20 +126,6 @@ def get_test_toggles() -> SimpleNamespace:
|
||||
|
||||
|
||||
class TestHyundaiFingerprint:
|
||||
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)
|
||||
@@ -539,27 +522,6 @@ class TestHyundaiFingerprint:
|
||||
k4_cp = CarInterface.get_params(CAR.KIA_K4_2025, fingerprint, [], False, False, False, None)
|
||||
assert not (k4_cp.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
|
||||
|
||||
@pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN))
|
||||
def test_carnival_hda1_resume_uses_alternate_button_frame(self, candidate):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0] = {0x1AA: 16}
|
||||
CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False, None)
|
||||
assert CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS
|
||||
assert not CP.openpilotLongitudinalControl
|
||||
assert "CRUISE_BUTTONS_ALT" in {
|
||||
state.name for state in CarState(CP, None).get_can_parsers(CP)[Bus.pt].message_states.values()
|
||||
}
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
address, dat, bus = hyundaicanfd.create_buttons(
|
||||
packer, CP, CanBus(CP), 0x41, Buttons.RES_ACCEL,
|
||||
base_values={"SET_ME_1": 1, "DISTANCE_UNIT": 0},
|
||||
)
|
||||
assert (address, bus) == (0x1AA, CanBus(CP).CAM)
|
||||
assert dat[2] == 0x41
|
||||
assert (dat[4] >> 4) & 0x7 == Buttons.RES_ACCEL
|
||||
assert int.from_bytes(dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(dat))
|
||||
|
||||
def test_ioniq_6_hda1_layout_stays_non_lka(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[1] = {0x100: 8, 0x110: 8}
|
||||
@@ -570,14 +532,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"
|
||||
@@ -693,126 +647,6 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
|
||||
|
||||
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)
|
||||
@@ -857,74 +691,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
|
||||
@@ -940,18 +706,38 @@ class TestHyundaiFingerprint:
|
||||
assert combined_safety_param & HyundaiSafetyFlags.LONG
|
||||
assert combined_safety_param & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE
|
||||
|
||||
@pytest.mark.parametrize("candidate, tracks_main_cruise", (
|
||||
(CAR.HYUNDAI_ELANTRA_2021, False),
|
||||
(CAR.HYUNDAI_ELANTRA_HEV_2024, True),
|
||||
(CAR.HYUNDAI_SONATA_HYBRID, False),
|
||||
))
|
||||
def test_legacy_hyundai_long_main_cruise_tracking_is_vehicle_specific(self, candidate, tracks_main_cruise):
|
||||
def test_sonata_hybrid_aol_main_lkas_sync_is_scoped(self):
|
||||
toggles = SimpleNamespace(always_on_lateral_lkas=True, main_cruise_aol_toggle=True)
|
||||
|
||||
sonata_hybrid_cp = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
sonata_hybrid_fpcp = CarInterface.get_starpilot_params(
|
||||
CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], sonata_hybrid_cp, toggles,
|
||||
)
|
||||
assert sonata_hybrid_fpcp.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_SYNC
|
||||
|
||||
sonata_cp = CarInterface.get_params(CAR.HYUNDAI_SONATA, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
sonata_fpcp = CarInterface.get_starpilot_params(CAR.HYUNDAI_SONATA, gen_empty_fingerprint(), [], sonata_cp, toggles)
|
||||
assert not (sonata_fpcp.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_SYNC)
|
||||
|
||||
disabled_toggles = SimpleNamespace(always_on_lateral_lkas=True, main_cruise_aol_toggle=False)
|
||||
disabled_fpcp = CarInterface.get_starpilot_params(
|
||||
CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], sonata_hybrid_cp, disabled_toggles,
|
||||
)
|
||||
assert not (disabled_fpcp.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_SYNC)
|
||||
|
||||
minimal_fpcp = CarInterface.get_starpilot_params(
|
||||
CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], sonata_hybrid_cp, SimpleNamespace(),
|
||||
)
|
||||
assert not (minimal_fpcp.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_SYNC)
|
||||
|
||||
@pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2021, CAR.HYUNDAI_SONATA_HYBRID))
|
||||
def test_legacy_hyundai_long_does_not_gate_availability_on_main_cruise(self, candidate):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
FPCP = CarInterface.get_starpilot_params(
|
||||
candidate, gen_empty_fingerprint(), [], CP, toggles,
|
||||
)
|
||||
assert bool(FPCP.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING) is tracks_main_cruise
|
||||
assert not (FPCP.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
|
||||
|
||||
ioniq_cp = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
ioniq_fpcp = CarInterface.get_starpilot_params(
|
||||
@@ -993,7 +779,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()
|
||||
@@ -1012,69 +797,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):
|
||||
@@ -1290,45 +1012,6 @@ class TestHyundaiFingerprint:
|
||||
assert long_xceed.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.HYBRID_GAS
|
||||
assert long_xceed.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG
|
||||
|
||||
def test_g80_alpha_long_preserves_legacy_safety(self):
|
||||
toggles = get_test_toggles()
|
||||
|
||||
stock_g80 = CarInterface.get_params(CAR.GENESIS_G80, gen_empty_fingerprint(), [], False, False, False, toggles)
|
||||
assert CAR.GENESIS_G80 in LEGACY_LONGITUDINAL_CAR
|
||||
assert stock_g80.alphaLongitudinalAvailable
|
||||
assert not stock_g80.openpilotLongitudinalControl
|
||||
assert stock_g80.pcmCruise
|
||||
assert stock_g80.safetyConfigs[-1].safetyModel == CarParams.SafetyModel.hyundaiLegacy
|
||||
assert not (stock_g80.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG)
|
||||
|
||||
long_g80 = CarInterface.get_params(CAR.GENESIS_G80, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
assert long_g80.alphaLongitudinalAvailable
|
||||
assert long_g80.openpilotLongitudinalControl
|
||||
assert not long_g80.pcmCruise
|
||||
assert long_g80.safetyConfigs[-1].safetyModel == CarParams.SafetyModel.hyundaiLegacy
|
||||
assert long_g80.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG
|
||||
|
||||
@pytest.mark.parametrize("ecu_disabled", (True, False))
|
||||
def test_g80_alpha_long_disables_stock_scc(self, monkeypatch, ecu_disabled):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.GENESIS_G80, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
|
||||
called = {}
|
||||
|
||||
def fake_disable_ecu(*args, **kwargs):
|
||||
called.update(kwargs)
|
||||
return ecu_disabled
|
||||
|
||||
monkeypatch.setattr("opendbc.car.hyundai.interface.disable_ecu", fake_disable_ecu)
|
||||
CarInterface.init(CP, None, None)
|
||||
|
||||
assert called["addr"] == 0x7d0
|
||||
assert called["bus"] == 0
|
||||
assert called["reset"] is False
|
||||
assert CP.openpilotLongitudinalControl == ecu_disabled
|
||||
assert CP.pcmCruise != ecu_disabled
|
||||
assert bool(CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG) == ecu_disabled
|
||||
|
||||
def test_xceed_phev_disable_failure_falls_back_to_stock_acc(self, monkeypatch):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_XCEED_PHEV, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
@@ -1368,7 +1051,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)
|
||||
@@ -1384,24 +1067,6 @@ class TestHyundaiFingerprint:
|
||||
assert reset_state.accel_last == pytest.approx(0.0)
|
||||
assert reset_state.long_control_state_last == LongCtrlState.off
|
||||
|
||||
def test_kia_ev6_gt_line_testing_ground_longitudinal_params(self, monkeypatch):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
CP.carVin = "00000000000000000"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"opendbc.car.hyundai.interface.testing_ground",
|
||||
SimpleNamespace(use=lambda slot_id: slot_id == "5"),
|
||||
)
|
||||
CarInterface.apply_post_fingerprint_params(CP, CAR.KIA_EV6, gen_empty_fingerprint(), [])
|
||||
|
||||
assert CP.startAccel == pytest.approx(1.4)
|
||||
assert CP.vEgoStarting == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
|
||||
|
||||
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)
|
||||
|
||||
def test_kia_ev6_non_gt_line_keeps_family_longitudinal_params(self):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
@@ -1516,28 +1181,6 @@ class TestHyundaiFingerprint:
|
||||
assert exact
|
||||
assert matches == {CAR.KIA_CARNIVAL_2025}
|
||||
|
||||
def test_genesis_gv70_2026_route_fw_exact_matches(self):
|
||||
route_fw = {
|
||||
(Ecu.fwdCamera, 0x7c4): b'\xf1\x00JK MFC AT USA LHD 1.00 1.11 99211-IY600 241125',
|
||||
(Ecu.fwdRadar, 0x7d0): b'\xf1\x00JK__ RDR ----- 1.00 1.01 99110-AR600 ',
|
||||
}
|
||||
car_fw = [
|
||||
CarParams.CarFw(ecu=ecu, fwVersion=version, address=address, subAddress=0, brand="hyundai")
|
||||
for (ecu, address), version in route_fw.items()
|
||||
]
|
||||
|
||||
exact, matches = match_fw_to_car(car_fw, "", allow_exact=True, allow_fuzzy=False, log=False)
|
||||
assert exact
|
||||
assert matches == {CAR.GENESIS_GV70_2026}
|
||||
|
||||
def test_genesis_gv70_2026_uses_gas_angle_configuration(self):
|
||||
CP = CarInterface.get_params(CAR.GENESIS_GV70_2026, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
|
||||
assert CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING
|
||||
assert not (CP.flags & HyundaiFlags.EV)
|
||||
assert CP.steerControlType == CarParams.SteerControlType.angle
|
||||
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_ANGLE_STEERING
|
||||
|
||||
def test_kona_non_scc_fca_radar_fw_is_optional(self):
|
||||
fw_versions = FW_VERSIONS[CAR.HYUNDAI_KONA_NON_SCC]
|
||||
car_fw = [
|
||||
@@ -1745,20 +1388,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()
|
||||
@@ -1966,7 +1595,6 @@ class TestHyundaiFingerprint:
|
||||
assert CP.startAccel == pytest.approx(0.2)
|
||||
assert CP.vEgoStarting == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.3)
|
||||
assert KIA_EV9_ACCEL_MAX == pytest.approx(2.2)
|
||||
assert CarInterface.get_pid_accel_limits(CP, 0.0, 0.0)[1] == pytest.approx(KIA_EV9_ACCEL_MAX)
|
||||
|
||||
ioniq_6_cp = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
@@ -2480,7 +2108,7 @@ 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_generic_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)
|
||||
@@ -2523,11 +2151,9 @@ class TestHyundaiFingerprint:
|
||||
parser.update([(1, lkas_msgs)])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0
|
||||
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 0
|
||||
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"] == 0
|
||||
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 0
|
||||
|
||||
CP.openpilotLongitudinalControl = True
|
||||
lfa_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LFA", 0)], can_bus.ECAN)
|
||||
@@ -2537,45 +2163,6 @@ class TestHyundaiFingerprint:
|
||||
assert lfa_parser.can_valid
|
||||
assert lfa_parser.vl["LFA"]["DAMP_FACTOR"] == 100
|
||||
|
||||
controller.long_active_ecu = True
|
||||
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 == [("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_ioniq_6_keeps_lfa_status_when_longitudinal_is_inactive(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.HYUNDAI_IONIQ_6
|
||||
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,
|
||||
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
|
||||
@@ -2707,70 +2294,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_inactive_status_in_drive(self, standstill):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING |
|
||||
HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
|
||||
CP.openpilotLongitudinalControl = False
|
||||
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
can_bus = CanBus(CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN)
|
||||
stock_lkas = {
|
||||
"CHECKSUM": 1234,
|
||||
"COUNTER": 42,
|
||||
"LKA_OptUsmSta": 2,
|
||||
"LKA_MODE": 2,
|
||||
"LKA_RcgSta": 3,
|
||||
"LKA_AVAILABLE": 3,
|
||||
"LKA_LHLnWrnSta": 3,
|
||||
"LKA_RHLnWrnSta": 3,
|
||||
"LKA_WARNING": 1,
|
||||
"LKA_HndsoffSnd": 1,
|
||||
"LKA_StrSnd": 1,
|
||||
"LKA_SysIndReq": 4,
|
||||
"LKA_ICON": 2,
|
||||
"FCA_SYSWARN": 1,
|
||||
"StrTqReqVal": 17,
|
||||
"TORQUE_REQUEST": 17,
|
||||
"ActToiSta": 3,
|
||||
"STEER_REQ": 1,
|
||||
"ToiFltSta": 3,
|
||||
"LFA_BUTTON": 1,
|
||||
"LKA_SysWrn": 15,
|
||||
"LKA_ASSIST": 1,
|
||||
"Damping_Gain": 0,
|
||||
"STEER_MODE": 5,
|
||||
"NEW_SIGNAL_2": 0,
|
||||
"LKAS_ANGLE_ACTIVE": 2,
|
||||
"LKA_UsmMod": 3,
|
||||
"HAS_LANE_SAFETY": 1,
|
||||
"ADAS_StrAnglReqVal": 12.3,
|
||||
"ADAS_ACIAnglTqRedcGainVal": 0.42,
|
||||
"DAMP_FACTOR": 0,
|
||||
}
|
||||
cc = SimpleNamespace(enabled=False, latActive=False,
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
|
||||
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg=stock_lkas,
|
||||
out=SimpleNamespace(standstill=standstill, steeringAngleDeg=0.0,
|
||||
gearShifter=structs.CarState.GearShifter.drive))
|
||||
|
||||
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
|
||||
get_test_toggles(), lka_icon=1, lfa_icon=1)
|
||||
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
|
||||
assert len(lkas_msgs) == 1
|
||||
|
||||
parser.update([(1, lkas_msgs)])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["LKAS_ALT"]["LKA_StrSnd"] == 2
|
||||
assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 1
|
||||
assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 0
|
||||
assert parser.vl["LKAS_ALT"]["LKA_AVAILABLE"] == 0
|
||||
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1
|
||||
|
||||
def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV9
|
||||
@@ -2896,22 +2419,6 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["SCC14"]["ComfortBandLower"] == pytest.approx(0.0)
|
||||
assert parser.vl["SCC14"]["JerkLowerLimit"] == pytest.approx(5.0)
|
||||
|
||||
def test_can_acc_commands_follow_sonata_main_cruise_state(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.HYUNDAI_SONATA_HYBRID
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("SCC11", 0)], 0)
|
||||
|
||||
msgs = hyundaican.create_acc_commands(packer, enabled=False, accel=0.0, upper_jerk=1.0, idx=3,
|
||||
hud_control=SimpleNamespace(leadDistanceBars=3, leadVisible=False), set_speed=42,
|
||||
stopping=False, long_override=False, use_fca=False, CP=CP,
|
||||
main_cruise_enabled=False)
|
||||
parser.update([(1, msgs)])
|
||||
|
||||
assert parser.can_valid
|
||||
assert parser.vl["SCC11"]["MainMode_ACC"] == 0
|
||||
|
||||
def test_can_acc_commands_use_enabled_fca_status(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.GENESIS_G90
|
||||
@@ -3130,47 +2637,26 @@ class TestHyundaiFingerprint:
|
||||
("LKAS", can_bus.ACAN),
|
||||
]
|
||||
|
||||
def test_ev9_fallback_active_lateral_uses_lkas_without_injecting_lfa(self):
|
||||
def test_ev9_fallback_keeps_lfa_status_without_longitudinal_control(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV9
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CCNC |
|
||||
HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CANFD_LKA_STEERING |
|
||||
HyundaiFlags.CANFD_LKA_STEERING_ALT)
|
||||
CP.openpilotLongitudinalControl = True
|
||||
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.frame = 1
|
||||
controller.ecu_disable_failed = True
|
||||
controller.long_active_ecu = False
|
||||
CP.openpilotLongitudinalControl = False
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
can_bus = CanBus(CP)
|
||||
cc = SimpleNamespace(
|
||||
enabled=True,
|
||||
latActive=True,
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False),
|
||||
leftBlinker=False,
|
||||
rightBlinker=False,
|
||||
hudControl=SimpleNamespace(),
|
||||
)
|
||||
cs = SimpleNamespace(
|
||||
stock_lfa_msg={},
|
||||
stock_lkas_msg={},
|
||||
out=SimpleNamespace(
|
||||
standstill=False,
|
||||
steeringAngleDeg=-31.5,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
msgs = hyundaicanfd.create_steering_messages(
|
||||
packer, CP, can_bus, True, True, 0.44, -31.5, send_lfa_status=True,
|
||||
)
|
||||
|
||||
msgs = controller.create_canfd_msgs(0, True, 0.44, -31.5, 0.0, 0.0, False,
|
||||
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2)
|
||||
assert [(packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [
|
||||
("LFA", can_bus.ECAN),
|
||||
("LKAS_ALT", can_bus.ACAN),
|
||||
]
|
||||
|
||||
def test_ev9_fallback_does_not_inject_lfa_while_parked(self):
|
||||
def test_ev9_fallback_lfa_only_does_not_send_lkas_at_standstill(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV9
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CCNC |
|
||||
@@ -3178,31 +2664,15 @@ class TestHyundaiFingerprint:
|
||||
HyundaiFlags.CANFD_LKA_STEERING_ALT)
|
||||
CP.openpilotLongitudinalControl = False
|
||||
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.ecu_disable_failed = True
|
||||
cc = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=False,
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False),
|
||||
leftBlinker=False,
|
||||
rightBlinker=False,
|
||||
hudControl=SimpleNamespace(),
|
||||
)
|
||||
cs = SimpleNamespace(
|
||||
stock_lfa_msg={},
|
||||
stock_lkas_msg={},
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
steeringAngleDeg=0.0,
|
||||
gearShifter=structs.CarState.GearShifter.park,
|
||||
),
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
can_bus = CanBus(CP)
|
||||
msgs = hyundaicanfd.create_steering_messages(
|
||||
packer, CP, can_bus, True, False, 0.0, 0.0, send_lfa_status=True, lfa_only=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 not [msg for msg in msgs if msg[0] in (0x110, 0x12A)]
|
||||
assert [(packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [
|
||||
("LFA", can_bus.ECAN),
|
||||
]
|
||||
|
||||
def test_kia_ev6_lkas_helper_preserves_stock_camera_fields_with_stock_long(self):
|
||||
CP = CarParams.new_message()
|
||||
@@ -3866,9 +3336,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():
|
||||
@@ -3876,8 +3344,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]
|
||||
|
||||
@@ -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):
|
||||
@@ -880,16 +872,6 @@ class CAR(Platforms):
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
GENESIS_GV70_2026 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Genesis GV70 (3.5T Sport Prestige Trim, with HDA II & LFA2) 2026",
|
||||
"Highway Driving Assist II & Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_m])),
|
||||
],
|
||||
GENESIS_GV70_1ST_GEN.specs,
|
||||
flags=HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
GENESIS_G80 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Genesis G80 2018-19", "All", car_parts=CarParts.common([CarHarness.hyundai_h]))],
|
||||
CarSpecs(mass=2060, wheelbase=3.01, steerRatio=16.5),
|
||||
@@ -948,11 +930,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),
|
||||
@@ -998,11 +975,6 @@ CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR = frozenset({
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
|
||||
"C4DLC",
|
||||
})
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID = "5"
|
||||
|
||||
KIA_RAY_EV_VIN_VDS_PREFIXES = frozenset({
|
||||
"CG81A",
|
||||
})
|
||||
|
||||
|
||||
ALT_BUS_LDA_BUTTON_CARS = frozenset()
|
||||
@@ -1013,13 +985,9 @@ def hyundai_cancel_button_enables_cruise(car_fingerprint) -> bool:
|
||||
return car_fingerprint in CANCEL_BUTTON_ENABLE_CARS
|
||||
|
||||
|
||||
def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str, testing_ground_active: bool = False) -> bool:
|
||||
vin_match = isinstance(vin, str) and len(vin) == 17 and vin[3:8] in KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES
|
||||
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 kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str) -> bool:
|
||||
return car_fingerprint == CAR.KIA_EV6 and isinstance(vin, str) and \
|
||||
len(vin) == 17 and vin[3:8] in KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES
|
||||
|
||||
|
||||
def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]:
|
||||
@@ -1111,7 +1079,7 @@ PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z]
|
||||
# We've seen both ICE and hybrid for these platforms, and they have hybrid descriptors (e.g. MQ4 vs MQ4H)
|
||||
CANFD_FUZZY_WHITELIST = {CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_K8_HEV_1ST_GEN,
|
||||
CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN,
|
||||
CAR.KIA_SORENTO_HEV_4TH_GEN_LFA2, CAR.GENESIS_GV70_2026}
|
||||
CAR.KIA_SORENTO_HEV_4TH_GEN_LFA2}
|
||||
|
||||
# List of ECUs expected to have platform codes, camera and radar should exist on all cars
|
||||
# TODO: use abs, it has the platform code and part number on many platforms
|
||||
@@ -1214,7 +1182,6 @@ CANFD_SECURITYACCESS_CAR = {
|
||||
}
|
||||
CANFD_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) - CANFD_SECURITYACCESS_CAR # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR
|
||||
CANFD_ANGLE_LONGITUDINAL_CAR = {CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5_PE}
|
||||
CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN}
|
||||
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,
|
||||
@@ -1245,9 +1212,6 @@ NON_SCC_CAR = CAR.with_flags(HyundaiFlags.NON_SCC)
|
||||
# HyundaiFlags.CANFD_RADAR_SCC | HyundaiFlags.CANFD_NO_RADAR_DISABLE | )
|
||||
UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.LEGACY) | CAR.with_flags(HyundaiFlags.UNSUPPORTED_LONGITUDINAL)
|
||||
|
||||
LEGACY_LONGITUDINAL_CAR = {
|
||||
CAR.GENESIS_G80,
|
||||
CAR.KIA_XCEED_PHEV,
|
||||
}
|
||||
LEGACY_LONGITUDINAL_CAR = {CAR.KIA_XCEED_PHEV}
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -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
|
||||
@@ -240,17 +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)
|
||||
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
|
||||
@@ -263,22 +257,13 @@ 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 \
|
||||
getattr(starpilot_toggles, "main_cruise_aol_toggle", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_SYNC.value
|
||||
|
||||
elif platform in TOYOTA:
|
||||
fp_ret.canUsePedal = not CP.autoResumeSng
|
||||
fp_ret.canUseSDSU = candidate not in UNSUPPORTED_DSU_CAR and candidate not in TSS2_CAR
|
||||
@@ -289,7 +274,7 @@ class CarInterfaceBase(ABC):
|
||||
if 0x2FF in fingerprint[0] or (0x2AA in fingerprint[0] and candidate in NO_DSU_CAR):
|
||||
fp_ret.flags |= ToyotaStarPilotFlags.SMART_DSU.value
|
||||
|
||||
if candidate in (TOYOTA.TOYOTA_PRIUS, TOYOTA.TOYOTA_PRIUS_RETROFIT):
|
||||
if candidate == TOYOTA.TOYOTA_PRIUS:
|
||||
if 0x23 in fingerprint[0]:
|
||||
fp_ret.flags |= ToyotaStarPilotFlags.ZSS.value
|
||||
|
||||
@@ -300,14 +285,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
|
||||
|
||||
@@ -15,19 +15,19 @@ LEAF_ADAS_ECU_BUS = 0
|
||||
LEAF_ADAS_COMMAND_BUS = 1
|
||||
LEAF_ADAS_COMMAND_ADDRS = frozenset((0x1C3, 0x2B0))
|
||||
LEAF_2025_SV_PLUS_CAMERA_FW = b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80'
|
||||
LEAF_2025_SV_PLUS_ALPHA_LONG_ENABLED = False
|
||||
|
||||
LEAF_KWP_EXTENDED_REQUEST = b"\x10\xC0"
|
||||
LEAF_KWP_EXTENDED_RESPONSE = b"\x50\xC0"
|
||||
LEAF_KWP_DISABLE_NORMAL_TX_NO_RESPONSE = b"\x28\x02"
|
||||
# This Leaf camera uses KWP2000 rather than UDS for session management.
|
||||
LEAF_KWP_DATA_MONITOR_REQUEST = b"\x10\xF0"
|
||||
LEAF_KWP_DATA_MONITOR_RESPONSE = b"\x50\xF0"
|
||||
LEAF_KWP_DISABLE_NORMAL_TX = b"\x28\x01"
|
||||
|
||||
LEAF_KWP_TAKEOVER_SESSIONS = (
|
||||
(LEAF_KWP_EXTENDED_REQUEST, LEAF_KWP_EXTENDED_RESPONSE),
|
||||
(LEAF_KWP_DATA_MONITOR_REQUEST, LEAF_KWP_DATA_MONITOR_RESPONSE),
|
||||
)
|
||||
|
||||
|
||||
def is_leaf_2025_sv_plus_longitudinal(candidate, car_fw):
|
||||
return LEAF_2025_SV_PLUS_ALPHA_LONG_ENABLED and candidate == CAR.NISSAN_LEAF and any(
|
||||
return candidate == CAR.NISSAN_LEAF and any(
|
||||
fw.address == LEAF_ADAS_ECU_ADDR and bytes(fw.fwVersion) == LEAF_2025_SV_PLUS_CAMERA_FW
|
||||
for fw in car_fw
|
||||
)
|
||||
@@ -161,7 +161,7 @@ class CarInterface(CarInterfaceBase):
|
||||
for diag_request, diag_response in LEAF_KWP_TAKEOVER_SESSIONS:
|
||||
ecu_log(f"Nissan Leaf ADAS takeover using KWP session {diag_request.hex()}")
|
||||
ecu_disabled = disable_ecu(can_recv, can_send, bus=LEAF_ADAS_ECU_BUS, addr=LEAF_ADAS_ECU_ADDR,
|
||||
com_cont_req=LEAF_KWP_DISABLE_NORMAL_TX_NO_RESPONSE, require_response=False, retry=1,
|
||||
com_cont_req=LEAF_KWP_DISABLE_NORMAL_TX, require_response=True, retry=1,
|
||||
diag_request=diag_request, diag_response=diag_response, response_offset=NISSAN_RX_OFFSET)
|
||||
if ecu_disabled:
|
||||
break
|
||||
|
||||
@@ -4,7 +4,6 @@ import pytest
|
||||
|
||||
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
|
||||
from opendbc.car.can_definitions import CanData
|
||||
from opendbc.car.nissan import interface as nissan_interface
|
||||
from opendbc.car.nissan.carstate import CarState
|
||||
from opendbc.car.nissan.interface import CarInterface, LEAF_2025_SV_PLUS_CAMERA_FW, leaf_adas_commands_present, \
|
||||
leaf_adas_commands_silent, restore_leaf_adas_tx
|
||||
@@ -19,12 +18,6 @@ SUPPORTED_LEAF_FW = [structs.CarParams.CarFw(
|
||||
)]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def experimental_leaf_long(monkeypatch):
|
||||
"""Exercise the dormant implementation without making it available in production."""
|
||||
monkeypatch.setattr(nissan_interface, "LEAF_2025_SV_PLUS_ALPHA_LONG_ENABLED", True)
|
||||
|
||||
|
||||
def run_controller(alpha_long, accel=0.0, long_active=True, long_state=structs.CarControl.Actuators.LongControlState.pid):
|
||||
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, alpha_long, False, False, TEST_TOGGLES)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, CP, TEST_TOGGLES)
|
||||
@@ -40,28 +33,7 @@ def run_controller(alpha_long, accel=0.0, long_active=True, long_state=structs.C
|
||||
return {msg[0]: msg for msg in can_sends}
|
||||
|
||||
|
||||
def test_leaf_2025_sv_plus_alpha_long_is_disabled(monkeypatch):
|
||||
stock = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, False, False, False, None)
|
||||
alpha_long = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
|
||||
|
||||
assert not stock.alphaLongitudinalAvailable
|
||||
assert not stock.openpilotLongitudinalControl
|
||||
assert stock.pcmCruise
|
||||
assert not (stock.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL)
|
||||
|
||||
assert not alpha_long.alphaLongitudinalAvailable
|
||||
assert not alpha_long.openpilotLongitudinalControl
|
||||
assert alpha_long.pcmCruise
|
||||
assert not alpha_long.autoResumeSng
|
||||
assert not (alpha_long.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL)
|
||||
|
||||
disable_calls = []
|
||||
monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: disable_calls.append((args, kwargs)))
|
||||
CarInterface.init(alpha_long, None, None)
|
||||
assert not disable_calls
|
||||
|
||||
|
||||
def test_dormant_leaf_2025_sv_plus_alpha_long_params(experimental_leaf_long):
|
||||
def test_leaf_2025_sv_plus_alpha_long_params():
|
||||
stock = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, False, False, False, None)
|
||||
alpha_long = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
|
||||
|
||||
@@ -107,13 +79,7 @@ def test_stock_controller_does_not_send_longitudinal_messages():
|
||||
assert not ({0x2B0, 0x1C3, 0x707} & can_sends.keys())
|
||||
|
||||
|
||||
def test_disabled_alpha_long_controller_does_not_send_longitudinal_messages():
|
||||
can_sends = run_controller(True)
|
||||
|
||||
assert not ({0x2B0, 0x1C3, 0x707} & can_sends.keys())
|
||||
|
||||
|
||||
def test_alpha_long_controller_sends_stock_shaped_commands_and_keepalive(experimental_leaf_long):
|
||||
def test_alpha_long_controller_sends_stock_shaped_commands_and_keepalive():
|
||||
can_sends = run_controller(True)
|
||||
|
||||
assert can_sends[0x2B0][1].hex() == "ff6090ac5b000e03"
|
||||
@@ -123,13 +89,13 @@ def test_alpha_long_controller_sends_stock_shaped_commands_and_keepalive(experim
|
||||
assert can_sends[0x707][2] == 0
|
||||
|
||||
|
||||
def test_alpha_long_controller_clamps_to_panda_accel_limit(experimental_leaf_long):
|
||||
def test_alpha_long_controller_clamps_to_panda_accel_limit():
|
||||
can_sends = run_controller(True, accel=5.0)
|
||||
|
||||
assert can_sends[0x2B0][1].hex() == "007f8fac5b000e0c"
|
||||
|
||||
|
||||
def test_alpha_long_controller_blends_friction_brake_below_regen_limit(experimental_leaf_long):
|
||||
def test_alpha_long_controller_blends_friction_brake_below_regen_limit():
|
||||
can_sends = run_controller(True, accel=-2.0)
|
||||
|
||||
assert can_sends[0x2B0][1].hex() == "a827d5ac5b000e09"
|
||||
@@ -138,7 +104,7 @@ def test_alpha_long_controller_blends_friction_brake_below_regen_limit(experimen
|
||||
assert brake[5] & 0x84 == 0x84
|
||||
|
||||
|
||||
def test_alpha_long_controller_sends_inactive_commands_when_disengaged(experimental_leaf_long):
|
||||
def test_alpha_long_controller_sends_inactive_commands_when_disengaged():
|
||||
can_sends = run_controller(True, accel=1.0, long_active=False)
|
||||
|
||||
assert can_sends[0x2B0][1].hex() == "dc53a2ac1b000e03"
|
||||
@@ -147,7 +113,7 @@ def test_alpha_long_controller_sends_inactive_commands_when_disengaged(experimen
|
||||
|
||||
@pytest.mark.parametrize(("signal", "button_type"), [("SET_BUTTON", ButtonType.decelCruise),
|
||||
("RES_BUTTON", ButtonType.accelCruise)])
|
||||
def test_leaf_set_resume_release_enables_alpha_long(signal, button_type, experimental_leaf_long):
|
||||
def test_leaf_set_resume_release_enables_alpha_long(signal, button_type):
|
||||
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, TEST_TOGGLES)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, CP, TEST_TOGGLES)
|
||||
CS = CarState(CP, FPCP)
|
||||
@@ -164,7 +130,7 @@ def test_leaf_set_resume_release_enables_alpha_long(signal, button_type, experim
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ecu_disabled", [False, True])
|
||||
def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled, experimental_leaf_long):
|
||||
def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled):
|
||||
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
|
||||
calls = []
|
||||
|
||||
@@ -182,17 +148,17 @@ def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled, ex
|
||||
assert calls[0]["addr"] == 0x707
|
||||
assert calls[0]["bus"] == 0
|
||||
assert calls[0]["response_offset"] == 0x20
|
||||
assert calls[0]["require_response"] is False
|
||||
assert calls[0]["diag_request"] == b"\x10\xc0"
|
||||
assert calls[0]["diag_response"] == b"\x50\xc0"
|
||||
assert calls[0]["com_cont_req"] == b"\x28\x02"
|
||||
assert calls[0]["require_response"] is True
|
||||
assert calls[0]["diag_request"] == b"\x10\xf0"
|
||||
assert calls[0]["diag_response"] == b"\x50\xf0"
|
||||
assert calls[0]["com_cont_req"] == b"\x28\x01"
|
||||
assert calls[0]["retry"] == 1
|
||||
assert CP.openpilotLongitudinalControl is ecu_disabled
|
||||
assert CP.pcmCruise is not ecu_disabled
|
||||
assert bool(CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL) is ecu_disabled
|
||||
|
||||
|
||||
def test_leaf_kwp_no_response_disable_can_confirm_ecu_silence(monkeypatch, experimental_leaf_long):
|
||||
def test_leaf_kwp_data_monitor_session_can_confirm_ecu_disable(monkeypatch):
|
||||
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
|
||||
|
||||
monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: True)
|
||||
@@ -205,7 +171,7 @@ def test_leaf_kwp_no_response_disable_can_confirm_ecu_silence(monkeypatch, exper
|
||||
assert CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL
|
||||
|
||||
|
||||
def test_leaf_positive_disable_response_without_command_silence_falls_back(monkeypatch, experimental_leaf_long):
|
||||
def test_leaf_positive_disable_response_without_command_silence_falls_back(monkeypatch):
|
||||
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
|
||||
restore_calls = []
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -22,23 +22,14 @@ _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_CONFIRM_FRAMES = 2
|
||||
_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
|
||||
_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_PULSE_FRAMES = 30
|
||||
_STOP_START_PULSE_PERIOD_FRAMES = 5
|
||||
_REDNECK_BUTTON_INTERVAL_FRAMES = 10
|
||||
_REDNECK_BUTTON_COPIES = 2
|
||||
_ASCENT_OVERRIDE_HOLD_FRAMES = 10
|
||||
_ASCENT_REENGAGE_SETTLE_FRAMES = 8
|
||||
_ASCENT_REENGAGE_MAX_STEER_RATE = 2.0
|
||||
_ASCENT_REENGAGE_MAX_ANGLE_DELTA = 1.0
|
||||
_ASCENT_RECLAIM_FRAMES = 36
|
||||
_ASCENT_RECLAIM_EXPONENT = 2.5
|
||||
_ASCENT_MADS_MIN_SPEED = 0.44704
|
||||
_ASCENT_MADS_MAX_STEER_ANGLE = 120.0
|
||||
|
||||
|
||||
def get_safety_CP():
|
||||
@@ -52,7 +43,6 @@ 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
|
||||
@@ -60,13 +50,13 @@ class CarController(CarControllerBase):
|
||||
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.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.ascent_lkas_active = False
|
||||
self.ascent_handoff_active = False
|
||||
self.ascent_override_hold_frames = 0
|
||||
self.ascent_reengage_settle_frames = 0
|
||||
self.ascent_reengage_reference_angle = 0.0
|
||||
self.ascent_reclaim_frames = 0
|
||||
self.ascent_reclaim_start_angle = 0.0
|
||||
|
||||
self.cruise_button_prev = 0
|
||||
self.steer_rate_counter = 0
|
||||
@@ -83,72 +73,8 @@ class CarController(CarControllerBase):
|
||||
self.prev_close_distance = 0
|
||||
self.epb_resume_frames_remaining = -1
|
||||
self.last_standstill_frame = 0
|
||||
self.stop_start_attempted = False
|
||||
self.stop_start_request_started = False
|
||||
self.stop_start_request_frame = 0
|
||||
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.
|
||||
|
||||
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 \
|
||||
not getattr(starpilot_toggles, "subaru_stop_start_off", False) or self.stop_start_attempted:
|
||||
return None
|
||||
|
||||
if self.frame > _STOP_START_STARTUP_DEADLINE_FRAMES or getattr(CC, "enabled", False):
|
||||
self.stop_start_attempted = True
|
||||
return None
|
||||
|
||||
if self.frame < _STOP_START_STARTUP_DELAY_FRAMES or not getattr(getattr(CS, "out", None), "canValid", True):
|
||||
return None
|
||||
|
||||
out = CS.out
|
||||
if not getattr(out, "standstill", False) or out.gearShifter not in (
|
||||
structs.CarState.GearShifter.park,
|
||||
structs.CarState.GearShifter.neutral,
|
||||
):
|
||||
return None
|
||||
|
||||
dashlights_msg = getattr(CS, "dashlights_msg", None)
|
||||
if not dashlights_msg:
|
||||
return None
|
||||
|
||||
if not self.stop_start_request_started:
|
||||
self.stop_start_request_started = True
|
||||
self.stop_start_request_frame = self.frame
|
||||
self.stop_start_initial_state = getattr(CS, "stop_start_state", None)
|
||||
self.stop_start_counter = (int(dashlights_msg.get("COUNTER", 0)) + 1) % 0x10
|
||||
|
||||
current_state = getattr(CS, "stop_start_state", None)
|
||||
if self.stop_start_initial_state is not None and self.stop_start_initial_state != 3 and current_state == 3:
|
||||
self.stop_start_attempted = True
|
||||
self.stop_start_acknowledged = True
|
||||
return None
|
||||
|
||||
elapsed = self.frame - self.stop_start_request_frame
|
||||
if elapsed >= _STOP_START_PULSE_FRAMES:
|
||||
self.stop_start_attempted = True
|
||||
return None
|
||||
|
||||
if elapsed % _STOP_START_PULSE_PERIOD_FRAMES != 0:
|
||||
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.stop_start_counter = (self.stop_start_counter + 1) % 0x10
|
||||
return msg
|
||||
|
||||
def _reset_legacy_2025_handoff(self):
|
||||
self.driver_override = False
|
||||
self.angle_override_confirm_frames = 0
|
||||
self.legacy_2025_handoff_active = False
|
||||
self.legacy_2025_override_hold_frames = 0
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
@@ -161,8 +87,7 @@ class CarController(CarControllerBase):
|
||||
self._reset_legacy_2025_handoff()
|
||||
return False
|
||||
|
||||
driver_override = self._update_angle_driver_override(CS)
|
||||
if driver_override:
|
||||
if CS.out.steeringPressed:
|
||||
self.legacy_2025_handoff_active = True
|
||||
self.legacy_2025_override_hold_frames = _LEGACY_2025_OVERRIDE_HOLD_FRAMES
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
@@ -171,7 +96,7 @@ class CarController(CarControllerBase):
|
||||
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:
|
||||
abs(CS.out.steeringRateDeg) > _LEGACY_2025_REENGAGE_MAX_STEER_RATE:
|
||||
self.legacy_2025_handoff_active = True
|
||||
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
@@ -184,7 +109,7 @@ class CarController(CarControllerBase):
|
||||
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 \
|
||||
wheel_stable = abs(CS.out.steeringRateDeg) <= _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
|
||||
@@ -212,86 +137,67 @@ class CarController(CarControllerBase):
|
||||
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 _reset_ascent_handoff(self):
|
||||
self.ascent_handoff_active = False
|
||||
self.ascent_override_hold_frames = 0
|
||||
self.ascent_reengage_settle_frames = 0
|
||||
self.ascent_reengage_reference_angle = 0.0
|
||||
self.ascent_reclaim_frames = 0
|
||||
self.ascent_reclaim_start_angle = 0.0
|
||||
|
||||
def _angle_manual_handoff(self, CS, lat_active):
|
||||
def _ascent_manual_handoff(self, CS, lat_active):
|
||||
if not lat_active:
|
||||
self._reset_angle_handoff()
|
||||
self._reset_ascent_handoff()
|
||||
return False
|
||||
|
||||
driver_override = self._update_angle_driver_override(CS)
|
||||
if driver_override:
|
||||
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
|
||||
if CS.out.steeringPressed:
|
||||
self.ascent_handoff_active = True
|
||||
self.ascent_override_hold_frames = _ASCENT_OVERRIDE_HOLD_FRAMES
|
||||
self.ascent_reengage_settle_frames = 0
|
||||
self.ascent_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
self.ascent_reclaim_frames = 0
|
||||
return True
|
||||
|
||||
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
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
if not self.ascent_handoff_active and not self.ascent_lkas_active and \
|
||||
abs(CS.out.steeringRateDeg) > _ASCENT_REENGAGE_MAX_STEER_RATE:
|
||||
self.ascent_handoff_active = True
|
||||
self.ascent_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
if not self.angle_handoff_active:
|
||||
if not self.ascent_handoff_active:
|
||||
return False
|
||||
|
||||
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
|
||||
if self.ascent_override_hold_frames > 0:
|
||||
self.ascent_override_hold_frames -= 1
|
||||
if self.ascent_override_hold_frames == 0:
|
||||
self.ascent_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
|
||||
wheel_stable = abs(CS.out.steeringRateDeg) <= _ASCENT_REENGAGE_MAX_STEER_RATE and \
|
||||
abs(CS.out.steeringAngleDeg - self.ascent_reengage_reference_angle) <= _ASCENT_REENGAGE_MAX_ANGLE_DELTA
|
||||
if wheel_stable:
|
||||
self.angle_reengage_settle_frames += 1
|
||||
self.ascent_reengage_settle_frames += 1
|
||||
else:
|
||||
self.angle_reengage_settle_frames = 0
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
self.ascent_reengage_settle_frames = 0
|
||||
self.ascent_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
if self.angle_reengage_settle_frames < _ANGLE_REENGAGE_SETTLE_FRAMES:
|
||||
if self.ascent_reengage_settle_frames < _ASCENT_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
|
||||
self.ascent_handoff_active = False
|
||||
self.ascent_reengage_settle_frames = 0
|
||||
self.ascent_reclaim_frames = _ASCENT_RECLAIM_FRAMES
|
||||
self.ascent_reclaim_start_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
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 _angle_reclaim_target(self, target_angle):
|
||||
if self.angle_reclaim_frames <= 0:
|
||||
def _ascent_reclaim_target(self, target_angle):
|
||||
if self.ascent_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
|
||||
progress = (_ASCENT_RECLAIM_FRAMES - self.ascent_reclaim_frames + 1) / _ASCENT_RECLAIM_FRAMES
|
||||
eased_progress = progress ** _ASCENT_RECLAIM_EXPONENT
|
||||
target_angle = self.ascent_reclaim_start_angle + eased_progress * \
|
||||
(target_angle - self.ascent_reclaim_start_angle)
|
||||
self.ascent_reclaim_frames -= 1
|
||||
return target_angle
|
||||
|
||||
def lateral_angle(self, CC, CS):
|
||||
@@ -318,56 +224,41 @@ class CarController(CarControllerBase):
|
||||
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):
|
||||
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023:
|
||||
mads_only = CC.latActive and not CC.enabled
|
||||
mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \
|
||||
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
|
||||
mads_only_ok = CS.out.vEgoRaw > _ASCENT_MADS_MIN_SPEED and \
|
||||
abs(CS.out.steeringAngleDeg) < _ASCENT_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
|
||||
|
||||
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
|
||||
manual_handoff = self._ascent_manual_handoff(CS, lkas_available)
|
||||
lkas_active = lkas_available and not manual_handoff
|
||||
|
||||
if lkas_active and not self.angle_lkas_active:
|
||||
if lkas_active and not self.ascent_lkas_active:
|
||||
self.apply_steer_last = CS.out.steeringAngleDeg
|
||||
|
||||
steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
|
||||
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023:
|
||||
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,
|
||||
)
|
||||
steer_target = self._ascent_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
|
||||
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,
|
||||
)
|
||||
self.apply_steer_last = apply_steer
|
||||
self.angle_lkas_active = lkas_active
|
||||
self.ascent_lkas_active = lkas_active
|
||||
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
|
||||
|
||||
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
|
||||
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
|
||||
getattr(CS.out, "gearShifter", structs.CarState.GearShifter.drive) == structs.CarState.GearShifter.drive and \
|
||||
not getattr(CS.out, "standstill", False)
|
||||
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
|
||||
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
|
||||
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
|
||||
|
||||
lat_active = CC.latActive and not self.driver_override
|
||||
apply_steer = apply_steer_angle_limits_vm(
|
||||
steer_target,
|
||||
CC.actuators.steeringAngleDeg,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
@@ -380,7 +271,6 @@ class CarController(CarControllerBase):
|
||||
apply_steer = CS.out.steeringAngleDeg
|
||||
|
||||
self.apply_steer_last = apply_steer
|
||||
self.angle_lkas_active = lat_active
|
||||
return subarucan.create_steering_control_angle(self.packer, apply_steer, lat_active, self.angle_bus)
|
||||
|
||||
def lateral_torque(self, CC, CS):
|
||||
@@ -412,17 +302,9 @@ class CarController(CarControllerBase):
|
||||
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 = []
|
||||
|
||||
stop_start_msg = self._stop_start_off_request(CC, CS, starpilot_toggles)
|
||||
if stop_start_msg is not None:
|
||||
can_sends.append(stop_start_msg)
|
||||
|
||||
# *** steering ***
|
||||
if (self.frame % self.p.STEER_STEP) == 0:
|
||||
if self.CP.flags & SubaruFlags.LKAS_ANGLE:
|
||||
@@ -480,8 +362,7 @@ 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, CC.latActive, hud_control.visualAlert,
|
||||
@@ -499,7 +380,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))
|
||||
@@ -515,20 +396,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 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):
|
||||
@@ -23,11 +15,6 @@ class CarState(CarStateBase):
|
||||
self.shifter_values = can_define.dv["Transmission"]["Gear"]
|
||||
|
||||
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,12 +24,6 @@ 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"]
|
||||
|
||||
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
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
@@ -146,17 +127,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,8 +40,6 @@ 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:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
|
||||
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value
|
||||
|
||||
@@ -53,8 +51,6 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if ret.flags & SubaruFlags.LKAS_ANGLE:
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
if candidate == CAR.SUBARU_OUTBACK_2023:
|
||||
ret.lateralSmoothSeconds = 0.4
|
||||
|
||||
elif candidate in (CAR.SUBARU_ASCENT, CAR.SUBARU_ASCENT_2023):
|
||||
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
|
||||
|
||||
@@ -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,32 +182,6 @@ 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.
|
||||
|
||||
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.
|
||||
"""
|
||||
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
|
||||
values["COUNTER"] = counter % 0x10
|
||||
values["STOP_START"] = 1
|
||||
return packer.make_can_msg("Dashlights", bus, values)
|
||||
|
||||
|
||||
def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value, bus=CanBus.main):
|
||||
values = {s: es_brake_msg[s] for s in [
|
||||
"CHECKSUM",
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
@@ -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]
|
||||
@@ -243,7 +193,6 @@ def test_outback_2023_uses_d_platform_bus_layout():
|
||||
|
||||
assert CP.flags & SubaruFlags.D_PLATFORM
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
|
||||
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.LEGACY_2025_ANGLE_LIMITS)
|
||||
assert CanBus.main_for_cp(CP) == CanBus.alt
|
||||
assert CanBus.angle_for_cp(CP) == CanBus.main
|
||||
@@ -253,77 +202,6 @@ def test_outback_2023_uses_d_platform_bus_layout():
|
||||
assert parsers[Bus.main].bus == CanBus.main
|
||||
assert controller.angle_bus == CanBus.main
|
||||
assert controller.status_bus == CanBus.main
|
||||
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)
|
||||
controller = CarController({}, CP)
|
||||
controller.frame = start_frame
|
||||
|
||||
class TestActuators:
|
||||
steeringAngleDeg = 0.0
|
||||
|
||||
def as_builder(self):
|
||||
return SimpleNamespace(steeringAngleDeg=self.steeringAngleDeg)
|
||||
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=False,
|
||||
longActive=False,
|
||||
actuators=TestActuators(),
|
||||
hudControl=SimpleNamespace(leadVisible=False),
|
||||
cruiseControl=SimpleNamespace(cancel=False),
|
||||
)
|
||||
CS = SimpleNamespace(
|
||||
canValid=True,
|
||||
dashlights_msg={"COUNTER": 6, "STOP_START": 0},
|
||||
dashlights_dat=bytes.fromhex("13061407875a8100"),
|
||||
stop_start_state=0,
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
gearShifter=structs.CarState.GearShifter.park,
|
||||
),
|
||||
)
|
||||
toggles = SimpleNamespace(subaru_stop_start_off=True, subaru_sng=False)
|
||||
|
||||
_, 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 parser.vl["Dashlights"]["STOP_START"] == 1
|
||||
assert parser.vl["Dashlights"]["COUNTER"] == 7
|
||||
|
||||
controller.frame = 103
|
||||
CS.stop_start_state = 3
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
assert not any(msg[0] == 0x390 for msg in can_sends)
|
||||
assert controller.stop_start_acknowledged
|
||||
|
||||
|
||||
def test_legacy_2025_uses_gen2_angle_bus_layout():
|
||||
@@ -336,7 +214,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
|
||||
@@ -426,7 +303,6 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
|
||||
vEgoRaw=6.2,
|
||||
steeringAngleDeg=-121.55,
|
||||
steeringRateDeg=350.0,
|
||||
steeringTorque=250.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -435,14 +311,10 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
|
||||
|
||||
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 = -113.78
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
@@ -491,7 +363,6 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
|
||||
vEgoRaw=3.7,
|
||||
steeringAngleDeg=2.5,
|
||||
steeringRateDeg=-45.0,
|
||||
steeringTorque=250.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -500,13 +371,9 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
|
||||
|
||||
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
|
||||
for i in range(19):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
@@ -561,66 +428,14 @@ def test_angle_controller_tracks_driver_override():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(latActive=True, actuators=SimpleNamespace(steeringAngleDeg=15.0))
|
||||
CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=175.0))
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
|
||||
assert not controller.driver_override
|
||||
CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=250.0))
|
||||
|
||||
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
|
||||
assert controller.apply_steer_last == CS.out.steeringAngleDeg
|
||||
assert msg[0] == 0x124
|
||||
|
||||
CS.out.steeringTorque = 125.0
|
||||
controller.lateral_angle(CC, CS)
|
||||
assert controller.driver_override
|
||||
|
||||
CS.out.steeringTorque = 75.0
|
||||
controller.lateral_angle(CC, CS)
|
||||
assert not controller.driver_override
|
||||
|
||||
|
||||
def test_angle_controller_blocks_low_speed_mads_engagement():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=True,
|
||||
actuators=SimpleNamespace(steeringAngleDeg=15.0),
|
||||
)
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
vEgoRaw=0.3,
|
||||
steeringAngleDeg=80.0,
|
||||
steeringTorque=0.0,
|
||||
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"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.vEgoRaw = 1.0
|
||||
CS.out.steeringAngleDeg = 130.0
|
||||
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.steeringAngleDeg = 0.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(3, [msg])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
|
||||
|
||||
def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
@@ -630,7 +445,7 @@ def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
|
||||
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,16 +458,15 @@ def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
|
||||
assert CS.out.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < -25.0
|
||||
|
||||
|
||||
@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)
|
||||
def test_ascent_angle_controller_yields_until_manual_steering_settles():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-10.0))
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
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,14 +475,10 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
|
||||
|
||||
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
|
||||
for i in range(18):
|
||||
|
||||
@@ -37,11 +37,6 @@ class CarControllerParams:
|
||||
self.STEER_OVERRIDE_TORQUE_HIGH = 200
|
||||
self.STEER_OVERRIDE_TORQUE_LOW = 150
|
||||
|
||||
# Crosstrek 2025 reports manual parking-lot inputs below the generic handoff threshold.
|
||||
if CP.carFingerprint == CAR.SUBARU_CROSSTREK_2025:
|
||||
self.STEER_OVERRIDE_TORQUE_HIGH = 150
|
||||
self.STEER_OVERRIDE_TORQUE_LOW = 100
|
||||
|
||||
if CP.flags & SubaruFlags.GLOBAL_GEN2:
|
||||
# TODO: lower rate limits, this reaches min/max in 0.5s which negatively affects tuning
|
||||
self.STEER_MAX = 1500
|
||||
@@ -88,8 +83,6 @@ class SubaruSafetyFlags(IntFlag):
|
||||
D_PLATFORM = 32
|
||||
D_PLATFORM_CAMERA = 64
|
||||
FIXED_ANGLE_LIMITS = 128
|
||||
STOP_START_BUTTON = 256
|
||||
REDNECK_CRUISE = 512
|
||||
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
|
||||
|
||||
|
||||
@@ -271,15 +264,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]) + \
|
||||
|
||||
@@ -68,13 +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
|
||||
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
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -125,6 +125,7 @@ class CarControllerParams:
|
||||
ACCEL_MAX = 2.0 # m/s^2
|
||||
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
|
||||
|
||||
|
||||
class TeslaSafetyFlags(IntFlag):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -76,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,
|
||||
@@ -107,11 +105,6 @@ non_tested_cars = [
|
||||
TOYOTA.TOYOTA_COROLLA,
|
||||
TOYOTA.TOYOTA_RAV4H,
|
||||
|
||||
# No recorded routes yet
|
||||
VOLVO.VOLVO_XC40_RECHARGE,
|
||||
VOLVO.VOLVO_S60_RECHARGE,
|
||||
VOLVO.POLESTAR_2,
|
||||
|
||||
]
|
||||
|
||||
non_tested_cars.extend(CC_ONLY_CAR)
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestCanFingerprint:
|
||||
fingerprint_iter = iter([can])
|
||||
car_fingerprint, finger = can_fingerprint(lambda **kwargs: [next(fingerprint_iter, [])]) # noqa: B023
|
||||
|
||||
if car_model in (TOYOTA.TOYOTA_MATRIX_RETROFIT, TOYOTA.TOYOTA_PRIUS_RETROFIT):
|
||||
if car_model == TOYOTA.TOYOTA_MATRIX_RETROFIT:
|
||||
assert fingerprint == {}
|
||||
assert car_fingerprint is None
|
||||
elif car_fingerprint is None and str(car_model).startswith(("BUICK_", "CADILLAC_", "CHEVROLET_", "GMC_", "HOLDEN_")):
|
||||
|
||||
@@ -74,6 +74,7 @@ def get_test_starpilot_toggles() -> SimpleNamespace:
|
||||
disable_openpilot_long=False,
|
||||
force_fingerprint=False,
|
||||
lock_doors=False,
|
||||
reverse_cruise_increase=False,
|
||||
sng_hack=False,
|
||||
subaru_sng=False,
|
||||
subaru_sng_manual_parking_brake=False,
|
||||
@@ -164,18 +165,13 @@ class TestCarInterfaces:
|
||||
"Center_Stack_2": {"LKAS_Button": 1},
|
||||
}, is_ram=True)
|
||||
|
||||
@pytest.mark.parametrize("candidate", (
|
||||
CHRYSLER_CAR.CHRYSLER_PACIFICA_2020,
|
||||
CHRYSLER_CAR.JEEP_GRAND_CHEROKEE,
|
||||
CHRYSLER_CAR.JEEP_GRAND_CHEROKEE_2019,
|
||||
))
|
||||
def test_chrysler_steer_to_zero_module(self, candidate):
|
||||
def test_chrysler_wd_mod_enables_steer_to_zero(self):
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
fingerprint[0][0x4FF] = 8
|
||||
toggles = get_test_starpilot_toggles()
|
||||
|
||||
car_params = ChryslerCarInterface.get_params(
|
||||
candidate,
|
||||
CHRYSLER_CAR.CHRYSLER_PACIFICA_2020,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=False,
|
||||
@@ -186,7 +182,7 @@ class TestCarInterfaces:
|
||||
assert car_params.minSteerSpeed > 0.
|
||||
|
||||
fp_car_params = ChryslerCarInterface.get_starpilot_params(
|
||||
candidate,
|
||||
CHRYSLER_CAR.CHRYSLER_PACIFICA_2020,
|
||||
fingerprint,
|
||||
[],
|
||||
car_params,
|
||||
@@ -210,14 +206,11 @@ class TestCarInterfaces:
|
||||
button_message="CRUISE_BUTTONS",
|
||||
auto_high_beam=0,
|
||||
)
|
||||
controller.update(CC, CS, 0, toggles)
|
||||
controller.update(CC, CS, 0, toggles)
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
|
||||
lkas_parser = CANParser(CHRYSLER_DBC[car_params.carFingerprint][Bus.pt], [("LKAS_COMMAND", 50)], 0)
|
||||
lkas_parser.update([0, can_sends])
|
||||
assert lkas_parser.vl["LKAS_COMMAND"]["LKAS_CONTROL_BIT"] == 1
|
||||
assert lkas_parser.vl["LKAS_COMMAND"]["STEERING_TORQUE"] != 0
|
||||
|
||||
@pytest.mark.parametrize("candidate", (CHRYSLER_CAR.JEEP_GRAND_CHEROKEE, CHRYSLER_CAR.JEEP_GRAND_CHEROKEE_2019))
|
||||
def test_jeep_brake_hold_safety_capability_is_provisioned(self, candidate):
|
||||
@@ -300,89 +293,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,
|
||||
},
|
||||
|
||||
@@ -106,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]
|
||||
@@ -127,13 +126,12 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"HYUNDAI_STARIA_4TH_GEN" = [1.8, 2.0, 0.15]
|
||||
"GENESIS_GV70_ELECTRIFIED_1ST_GEN" = [1.9, 1.9, 0.09]
|
||||
"GENESIS_GV70_ELECTRIFIED_2ND_GEN" = [nan, 2.5, nan]
|
||||
"GENESIS_GV70_2026" = [nan, 2.5, nan]
|
||||
"GENESIS_G80_2ND_GEN_FL" = [2.5819356441497803, 2.5, 0.11244568973779678]
|
||||
"GENESIS_GV80_2025" = [nan, 2.5, nan]
|
||||
# Note that some Rivians achieve significantly less lateral acceleration than this
|
||||
"RIVIAN_R1_GEN1" = [2.8, 2.5, 0.07]
|
||||
"HYUNDAI_NEXO_1ST_GEN" = [2.5, 2.5, 0.1]
|
||||
"HONDA_ACCORD_11G" = [3.0, 1.35, 0.16]
|
||||
"HONDA_ACCORD_11G" = [1.35, 1.35, 0.17]
|
||||
"HONDA_PILOT_4G" = [1.25, 1.25, 0.21]
|
||||
"HONDA_PASSPORT_4G" = [1.2, 1.2, 0.16]
|
||||
"ACURA_MDX_4G_MMR" = [1.25, 1.25, 0.15]
|
||||
@@ -143,8 +141,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]
|
||||
|
||||
# Dashcam or fallback configured as ideal car
|
||||
"MOCK" = [10.0, 10, 0.0]
|
||||
|
||||
@@ -9,7 +9,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
|
||||
"TOYOTA_ALPHARD_TSS2" = "TOYOTA_SIENNA"
|
||||
"TOYOTA_PRIUS_V" = "TOYOTA_PRIUS"
|
||||
"TOYOTA_PRIUS_RETROFIT" = "TOYOTA_PRIUS"
|
||||
"TOYOTA_SIENNA_4TH_GEN" = "TOYOTA_RAV4_PRIME"
|
||||
"LEXUS_IS" = "LEXUS_NX"
|
||||
"LEXUS_CTH" = "LEXUS_NX"
|
||||
@@ -136,5 +135,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
|
||||
from opendbc.can import CANPacker
|
||||
|
||||
Ecu = structs.CarParams.Ecu
|
||||
@@ -37,9 +37,6 @@ 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
|
||||
|
||||
# LKA limits
|
||||
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
|
||||
@@ -62,32 +59,12 @@ def is_camry_hybrid(CP) -> bool:
|
||||
|
||||
|
||||
def is_ths_hybrid(CP) -> bool:
|
||||
return CP.carFingerprint in LEGACY_PRIUS_CAR or is_camry_hybrid(CP)
|
||||
return CP.carFingerprint == CAR.TOYOTA_PRIUS or is_camry_hybrid(CP)
|
||||
|
||||
|
||||
def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
|
||||
highlander_sdsu = (
|
||||
CP.carFingerprint == CAR.TOYOTA_HIGHLANDER and
|
||||
bool(getattr(starpilot_toggles, "has_sdsu", False))
|
||||
)
|
||||
def should_bypass_toyota_long_pid(CP) -> bool:
|
||||
return bool(CP.enableGasInterceptorDEPRECATED or (
|
||||
CP.carFingerprint == CAR.TOYOTA_CAMRY and not is_camry_hybrid(CP)
|
||||
) or highlander_sdsu)
|
||||
|
||||
|
||||
def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
|
||||
return (
|
||||
auto_hold_enabled and
|
||||
CP.carFingerprint in TOYOTA_AUTO_HOLD_CARS and
|
||||
bool(CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
|
||||
)
|
||||
|
||||
|
||||
def get_rav4_interceptor_pedal_scale(v_ego: float) -> float:
|
||||
return float(np.interp(
|
||||
max(float(v_ego), 0.0),
|
||||
[0.0, TOYOTA_RAV4_LAUNCH_PEDAL_BLEND_SPEED, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION],
|
||||
[TOYOTA_RAV4_LAUNCH_PEDAL_SCALE, TOYOTA_RAV4_LOW_SPEED_PEDAL_SCALE, 0.3, 0.0],
|
||||
))
|
||||
|
||||
|
||||
@@ -97,7 +74,7 @@ def get_long_tune(CP, params):
|
||||
k_f = 1.0
|
||||
|
||||
if is_ths_hybrid(CP):
|
||||
k_f = 0.8 if CP.carFingerprint in LEGACY_PRIUS_CAR else 1.0
|
||||
k_f = 0.8 if CP.carFingerprint == CAR.TOYOTA_PRIUS else 1.0
|
||||
elif CP.carFingerprint not in TSS2_CAR:
|
||||
kiBP = [0., 5., 35.]
|
||||
kiV = [3.6, 2.4, 1.5]
|
||||
@@ -262,8 +239,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):
|
||||
@@ -274,7 +254,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:
|
||||
@@ -315,12 +295,15 @@ class CarController(CarControllerBase):
|
||||
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 > brake_hold_allowed_timer
|
||||
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
|
||||
|
||||
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))
|
||||
@@ -358,10 +341,8 @@ class CarController(CarControllerBase):
|
||||
apply_torque = apply_meas_steer_torque_limits(new_torque, self.last_torque, CS.out.steeringTorqueEps, self.params)
|
||||
|
||||
# >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, apply_steer_req = common_fault_avoidance(abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active,
|
||||
self.steer_rate_counter, MAX_STEER_RATE_FRAMES)
|
||||
|
||||
if not lat_active:
|
||||
apply_torque = 0
|
||||
@@ -422,11 +403,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)):
|
||||
if self.auto_brake_hold:
|
||||
can_sends.extend(self.create_auto_brake_hold_messages(CS))
|
||||
elif self.brake_hold_active:
|
||||
self._brake_hold_counter = 0
|
||||
self.brake_hold_active = False
|
||||
|
||||
interceptor_gas_cmd = self._compute_interceptor_gas_cmd(CC, CS)
|
||||
|
||||
@@ -476,7 +454,7 @@ class CarController(CarControllerBase):
|
||||
a_ego_future = a_ego_blended + j_ego * future_t
|
||||
|
||||
if CC.longActive:
|
||||
if should_bypass_toyota_long_pid(self.CP, starpilot_toggles):
|
||||
if should_bypass_toyota_long_pid(self.CP):
|
||||
# Pedal/SDSU Toyotas have shown better behavior when we trust the planner
|
||||
# target directly instead of letting the Toyota longitudinal PID swing it
|
||||
# around. Keep the shared rate limits above, but bypass the extra
|
||||
@@ -499,7 +477,7 @@ class CarController(CarControllerBase):
|
||||
pcm_accel_cmd += pitch_compensation
|
||||
|
||||
feedforward = pcm_accel_cmd
|
||||
if self.CP.carFingerprint in LEGACY_PRIUS_CAR:
|
||||
if self.CP.carFingerprint == CAR.TOYOTA_PRIUS:
|
||||
feedforward = get_prius_feedforward(feedforward, CS.out.vEgo)
|
||||
elif is_camry_hybrid(self.CP) and feedforward > 0.0:
|
||||
# Preserve the established Camry Hybrid acceleration response while
|
||||
@@ -529,15 +507,14 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
pcm_accel_cmd = limit_no_lead_cruise_sign_flip(pcm_accel_cmd, actuators.accel, stopping, CS.out.vEgo,
|
||||
CS.out.cruiseState.speed, bool(hud_control.leadVisible))
|
||||
if self.CP.carFingerprint in LEGACY_PRIUS_CAR:
|
||||
if self.CP.carFingerprint == CAR.TOYOTA_PRIUS:
|
||||
pcm_accel_cmd = limit_prius_stopping_accel(pcm_accel_cmd, actuators.accel, stopping, CS.out.vEgo, lead)
|
||||
|
||||
pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
|
||||
|
||||
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,
|
||||
getattr(starpilot_toggles, "reverse_cruise_increase", False)))
|
||||
CS.acc_type, fcw_alert, self.distance_button, starpilot_toggles.reverse_cruise_increase))
|
||||
if self.CP.flags & ToyotaFlags.SECOC.value:
|
||||
acc_cmd_2 = toyotacan.create_accel_command_2(self.packer, pcm_accel_cmd)
|
||||
acc_cmd_2 = add_mac(self.secoc_key,
|
||||
@@ -557,8 +534,7 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(toyotacan.create_acc_cancel_command(self.packer))
|
||||
else:
|
||||
can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, True, False, lead, CS.acc_type, False,
|
||||
self.distance_button,
|
||||
getattr(starpilot_toggles, "reverse_cruise_increase", False)))
|
||||
self.distance_button, starpilot_toggles.reverse_cruise_increase))
|
||||
|
||||
# *** hud ui ***
|
||||
if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V:
|
||||
|
||||
@@ -8,7 +8,7 @@ from opendbc.car.common.filter_simple import FirstOrderFilter
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.toyota.values import ToyotaFlags, ToyotaStarPilotFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, \
|
||||
TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR, \
|
||||
SECOC_CAR, LEGACY_PRIUS_CAR
|
||||
SECOC_CAR
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
SteerControlType = structs.CarParams.SteerControlType
|
||||
@@ -23,7 +23,7 @@ TEMP_STEER_FAULTS = (0, 9, 11, 21, 25)
|
||||
# - lka/lta msg drop out: 3 (recoverable)
|
||||
# - prolonged high driver torque: 17 (permanent)
|
||||
PERM_STEER_FAULTS = (3, 17)
|
||||
LKAS_BUTTON_CAR = TSS2_CAR | LEGACY_PRIUS_CAR
|
||||
LKAS_BUTTON_CAR = TSS2_CAR | {CAR.TOYOTA_PRIUS}
|
||||
DISTANCE_BUTTON_CAR = {CAR.TOYOTA_SIENNA_4TH_GEN}
|
||||
|
||||
|
||||
@@ -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 = {}
|
||||
@@ -209,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
|
||||
@@ -247,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"]
|
||||
@@ -266,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)
|
||||
@@ -301,23 +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))
|
||||
|
||||
if CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value:
|
||||
cam_messages.append(("PRE_COLLISION_2", 50))
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ Ecu = CarParams.Ecu
|
||||
|
||||
FINGERPRINTS = {
|
||||
CAR.TOYOTA_MATRIX_RETROFIT: [{}],
|
||||
CAR.TOYOTA_PRIUS_RETROFIT: [{}],
|
||||
}
|
||||
|
||||
FW_VERSIONS = {
|
||||
@@ -1360,7 +1359,6 @@ FW_VERSIONS = {
|
||||
b'\x018966333T5100\x00\x00\x00\x00',
|
||||
b'\x018966333X6000\x00\x00\x00\x00',
|
||||
b'\x01896633T07000\x00\x00\x00\x00',
|
||||
b'\x01896633T24000\x00\x00\x00\x00',
|
||||
b'\x01896633T38000\x00\x00\x00\x00',
|
||||
b'\x01896633T58000\x00\x00\x00\x00',
|
||||
b'\x01896633T63000\x00\x00\x00\x00',
|
||||
@@ -1378,7 +1376,6 @@ FW_VERSIONS = {
|
||||
b'\x01F152606461\x00\x00\x00\x00\x00\x00',
|
||||
b'\x01F15260646200\x00\x00\x00\x00',
|
||||
b'\x01F152633A71\x00\x00\x00\x00\x00\x00',
|
||||
b'\x01F152633E2100\x00\x00\x00\x00',
|
||||
b'F152633423\x00\x00\x00\x00\x00\x00',
|
||||
b'F152633680\x00\x00\x00\x00\x00\x00',
|
||||
b'F152633681\x00\x00\x00\x00\x00\x00',
|
||||
@@ -1413,7 +1410,6 @@ FW_VERSIONS = {
|
||||
b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
|
||||
b'\x028646F3309100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
|
||||
b'\x028646F3309400\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
|
||||
b'\x028646F3313000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.LEXUS_ES: {
|
||||
@@ -1451,26 +1447,21 @@ FW_VERSIONS = {
|
||||
CAR.LEXUS_GS_F: {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\x0233075200\x00\x00\x00\x00\x00\x00\x00\x00530B9000\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\x02330Y8000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.abs, 0x7b0, None): [
|
||||
b'F152630700\x00\x00\x00\x00\x00\x00',
|
||||
b'F152630400\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.dsu, 0x791, None): [
|
||||
b'881513016200\x00\x00\x00\x00',
|
||||
b'881513011300\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x7a1, None): [
|
||||
b'8965B30551\x00\x00\x00\x00\x00\x00',
|
||||
b'8965B30431\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x750, 0xf): [
|
||||
b'8821F4702000\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x750, 0x6d): [
|
||||
b'8646F3002100\x00\x00\x00\x00',
|
||||
b'8646F3001200\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.LEXUS_NX: {
|
||||
|
||||
@@ -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
|
||||
from opendbc.car.disable_ecu import disable_ecu
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
@@ -67,7 +67,7 @@ class CarInterface(CarInterfaceBase):
|
||||
# These messages are normally absent there on pre-TSS2 platforms.
|
||||
camera_fingerprint = fingerprint.get(2, {})
|
||||
has_dsu_bypass = 0x343 in camera_fingerprint or 0x4CB in camera_fingerprint
|
||||
late_prius_camera = candidate in LEGACY_PRIUS_CAR and any(
|
||||
late_prius_camera = candidate == CAR.TOYOTA_PRIUS and any(
|
||||
fw.ecu == Ecu.fwdCamera and bytes(fw.fwVersion).startswith(b'8646F4705') for fw in car_fw
|
||||
)
|
||||
if candidate in (CAR.LEXUS_IS, CAR.TOYOTA_CAMRY) or late_prius_camera:
|
||||
@@ -83,7 +83,7 @@ class CarInterface(CarInterfaceBase):
|
||||
if Ecu.hybrid in found_ecus:
|
||||
ret.flags |= ToyotaFlags.HYBRID.value
|
||||
|
||||
if candidate in LEGACY_PRIUS_CAR:
|
||||
if candidate == CAR.TOYOTA_PRIUS:
|
||||
stop_and_go = True
|
||||
ret.flags |= ToyotaFlags.HYBRID.value
|
||||
# Only give steer angle deadzone to for bad angle sensor prius
|
||||
@@ -164,7 +164,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.GAS_INTERCEPTOR.value
|
||||
|
||||
toyota_auto_hold = Params(return_defaults=True).get_bool("ToyotaAutoHold")
|
||||
if toyota_auto_hold and candidate in TOYOTA_AUTO_HOLD_CARS:
|
||||
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
|
||||
|
||||
@@ -175,7 +175,7 @@ class CarInterface(CarInterfaceBase):
|
||||
# to a negative value, so it won't matter.
|
||||
ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptorDEPRECATED) else MIN_ACC_SPEED
|
||||
|
||||
prius_long_defaults = candidate in LEGACY_PRIUS_CAR and ret.openpilotLongitudinalControl
|
||||
prius_long_defaults = candidate == CAR.TOYOTA_PRIUS and ret.openpilotLongitudinalControl
|
||||
camry_hybrid_long_defaults = (candidate == CAR.TOYOTA_CAMRY and ret.openpilotLongitudinalControl and
|
||||
bool(ret.flags & ToyotaFlags.HYBRID.value))
|
||||
|
||||
|
||||
@@ -10,19 +10,16 @@ from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
|
||||
from opendbc.car.toyota import toyotacan
|
||||
from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \
|
||||
get_prius_positive_feedforward_scale, \
|
||||
get_rav4_interceptor_pedal_scale, \
|
||||
limit_interceptor_pcm_accel, \
|
||||
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
|
||||
limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
|
||||
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, get_platform_codes
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.common.params import Params
|
||||
|
||||
@@ -43,32 +40,6 @@ class TestToyotaInterfaces:
|
||||
# At this time, only RAV4 2023 is expected to use LTA/angle control
|
||||
assert ANGLE_CONTROL_CAR == {CAR.TOYOTA_RAV4_TSS2_2023}
|
||||
|
||||
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_RAV4_TSS2, CAR.TOYOTA_RAV4_TSS2_2023])
|
||||
def test_rav4_can_filter_is_optional(self, candidate):
|
||||
def get_params(has_can_filter):
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
if has_can_filter:
|
||||
fingerprint[0][0x2AA] = 8
|
||||
|
||||
car_params = CarInterface.get_params(
|
||||
candidate,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False),
|
||||
)
|
||||
return CarInterface.get_starpilot_params(candidate, fingerprint, [], car_params, SimpleNamespace())
|
||||
|
||||
without_filter = get_params(False)
|
||||
with_filter = get_params(True)
|
||||
|
||||
assert not without_filter.flags & ToyotaStarPilotFlags.RADAR_CAN_FILTER.value
|
||||
assert not without_filter.flags & ToyotaStarPilotFlags.SMART_DSU.value
|
||||
assert with_filter.flags & ToyotaStarPilotFlags.RADAR_CAN_FILTER.value
|
||||
assert with_filter.flags & ToyotaStarPilotFlags.SMART_DSU.value
|
||||
|
||||
def test_rav4_prime_force_torque_controller(self):
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
@@ -104,22 +75,6 @@ class TestToyotaInterfaces:
|
||||
assert default_params.lateralTuning.torque.steeringAngleDeadzoneDeg == pytest.approx(0.3)
|
||||
assert forced_params.lateralTuning.torque.steeringAngleDeadzoneDeg == pytest.approx(0.3)
|
||||
|
||||
def test_prius_tss2_eps_retrofit_uses_legacy_body_and_eps_scale(self):
|
||||
params = CarInterface.get_params(
|
||||
CAR.TOYOTA_PRIUS_RETROFIT,
|
||||
{bus: {} for bus in range(8)},
|
||||
[],
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False),
|
||||
)
|
||||
|
||||
assert params.lateralTuning.which() == "torque"
|
||||
assert params.safetyConfigs[0].safetyParam & 0xFF == 73
|
||||
assert params.flags & ToyotaFlags.TSS2.value == 0
|
||||
assert params.steerRatio == pytest.approx(15.74)
|
||||
|
||||
def test_sienna_4th_gen_uses_torque_controller(self):
|
||||
params = CarInterface.get_params(
|
||||
CAR.TOYOTA_SIENNA_4TH_GEN,
|
||||
@@ -189,13 +144,12 @@ 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,
|
||||
CAR.TOYOTA_CAMRY_TSS2,
|
||||
{bus: {} for bus in range(8)},
|
||||
[],
|
||||
alpha_long=False,
|
||||
@@ -209,28 +163,6 @@ class TestToyotaInterfaces:
|
||||
assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
|
||||
assert 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" 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.ALLOW_AEB
|
||||
|
||||
def test_prius_openpilot_long_uses_hybrid_long_defaults(self):
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.TOYOTA_PRIUS,
|
||||
@@ -537,15 +469,6 @@ class TestToyotaInterfaces:
|
||||
|
||||
assert not should_bypass_toyota_long_pid(car_params)
|
||||
|
||||
def test_highlander_sdsu_bypasses_toyota_longitudinal_pid(self):
|
||||
car_params = SimpleNamespace(
|
||||
carFingerprint=CAR.TOYOTA_HIGHLANDER,
|
||||
enableGasInterceptorDEPRECATED=False,
|
||||
)
|
||||
|
||||
assert should_bypass_toyota_long_pid(car_params, SimpleNamespace(has_sdsu=True))
|
||||
assert not should_bypass_toyota_long_pid(car_params, SimpleNamespace(has_sdsu=False))
|
||||
|
||||
def test_camry_continental_radar_converts_absolute_target_speed(self):
|
||||
radar_interface = RadarInterface.__new__(RadarInterface)
|
||||
radar_interface.CP = SimpleNamespace(wheelSpeedFactor=1.0)
|
||||
@@ -776,84 +699,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])
|
||||
controller.frame = 0
|
||||
controller.brake_hold_active = False
|
||||
controller._brake_hold_counter = 0
|
||||
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
cruiseState=SimpleNamespace(available=True, enabled=False),
|
||||
gasPressed=False,
|
||||
brakePressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
pre_collision_2={},
|
||||
)
|
||||
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
assert controller.brake_hold_active
|
||||
|
||||
cs.out.brakePressed = False
|
||||
controller.frame = 2
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
assert controller.brake_hold_active
|
||||
|
||||
cs.out.gasPressed = True
|
||||
controller.frame = 4
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
|
||||
assert not controller.brake_hold_active
|
||||
|
||||
def test_toyota_auto_hold_does_not_trigger_without_brake_press(self):
|
||||
controller = self._make_controller()
|
||||
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
|
||||
controller.frame = 0
|
||||
controller.brake_hold_active = False
|
||||
controller._brake_hold_counter = 0
|
||||
cs = SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
cruiseState=SimpleNamespace(available=True, enabled=False),
|
||||
gasPressed=False,
|
||||
brakePressed=False,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
pre_collision_2={},
|
||||
)
|
||||
|
||||
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=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)
|
||||
|
||||
@@ -975,34 +820,20 @@ class TestToyotaCarController:
|
||||
parser.update([(1, [reverse_msg])])
|
||||
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 2
|
||||
|
||||
def test_acc_control_accepts_toggle_namespace_without_reverse_cruise_option(self):
|
||||
# Older or partially refreshed toggle broadcasts do not include this optional field.
|
||||
toggles = SimpleNamespace()
|
||||
assert getattr(toggles, "reverse_cruise_increase", False) is False
|
||||
|
||||
packer = CANPacker(DBC[CAR.TOYOTA_HIGHLANDER_TSS2][Bus.pt])
|
||||
msg = toyotacan.create_accel_command(
|
||||
packer, 0.0, False, True, False, False, 1, False, 0,
|
||||
getattr(toggles, "reverse_cruise_increase", False),
|
||||
)
|
||||
parser = CANParser(DBC[CAR.TOYOTA_HIGHLANDER_TSS2][Bus.pt], [("ACC_CONTROL", 0)], 0)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
assert parser.can_valid
|
||||
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1
|
||||
|
||||
def test_auto_brake_hold_sends_modified_pre_collision_after_timer(self):
|
||||
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={},
|
||||
@@ -1042,27 +873,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
|
||||
@@ -1161,36 +971,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
|
||||
|
||||
@@ -250,11 +250,6 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=3045. * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.74, tireStiffnessFactor=0.6371),
|
||||
dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
|
||||
)
|
||||
TOYOTA_PRIUS_RETROFIT = PlatformConfig(
|
||||
[ToyotaCommunityCarDocs("Toyota Prius 2016-20 with TSS2 EPS retrofit", package="Custom retrofit")],
|
||||
TOYOTA_PRIUS.specs,
|
||||
dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
|
||||
)
|
||||
TOYOTA_PRIUS_V = PlatformConfig(
|
||||
[ToyotaCarDocs("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED)],
|
||||
CarSpecs(mass=3340. * CV.LB_TO_KG, wheelbase=2.78, steerRatio=17.4, tireStiffnessFactor=0.5533),
|
||||
@@ -604,11 +599,9 @@ STEER_THRESHOLD = 100
|
||||
|
||||
# These cars have non-standard EPS torque scale factors. All others are 73
|
||||
EPS_SCALE = defaultdict(lambda: 73,
|
||||
{CAR.TOYOTA_PRIUS: 66, CAR.TOYOTA_PRIUS_RETROFIT: 73, CAR.TOYOTA_COROLLA: 88, CAR.TOYOTA_MATRIX_RETROFIT: 88,
|
||||
{CAR.TOYOTA_PRIUS: 66, CAR.TOYOTA_COROLLA: 88, CAR.TOYOTA_MATRIX_RETROFIT: 88,
|
||||
CAR.LEXUS_IS: 77, CAR.LEXUS_RC: 77, CAR.LEXUS_CTH: 100, CAR.TOYOTA_PRIUS_V: 100})
|
||||
|
||||
LEGACY_PRIUS_CAR = frozenset((CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT))
|
||||
|
||||
# Toyota/Lexus Safety Sense 2.0 and 2.5
|
||||
TSS2_CAR = CAR.with_flags(ToyotaFlags.TSS2)
|
||||
|
||||
@@ -624,11 +617,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}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Volvo CMA platform support for openpilot
|
||||
@@ -1,287 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
from opendbc.can.packer import CANPacker
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.lateral import apply_std_steer_angle_limits
|
||||
from opendbc.car.volvo.helpers import LCA3CounterSync
|
||||
from opendbc.car.volvo.volvocan import (create_lca_message, create_pscm_message, create_lca_3_message, create_lca_2_message, create_lca_4_message,
|
||||
create_lca_5_message, create_lca_6_message, create_lca_7_message, create_pscm_related_message)
|
||||
from opendbc.car.volvo.values import CarControllerParams
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_names, CP):
|
||||
super().__init__(dbc_names, CP)
|
||||
self.packer = CANPacker(dbc_names[Bus.party])
|
||||
self.apply_angle_last = 0.0 # Track last applied steering angle
|
||||
|
||||
self.gear_acc = 60
|
||||
self.lca_4_acc = 0 # Bresenham accumulator for 29 Hz
|
||||
|
||||
# Counter management for LCA_2
|
||||
self.lca_2_counter_1 = None # Will grab initial value from CarState
|
||||
self.lca_2_counter_2 = None
|
||||
|
||||
# Counter management for PSCM_RELATED
|
||||
self.pscm_related_counter = None # Will grab initial value from CarState
|
||||
|
||||
# Counter management for LCA_3 (pattern-based)
|
||||
self.lca_3_counter_sync = LCA3CounterSync()
|
||||
|
||||
# Counter management for LCA_5 (formerly SPEED_1)
|
||||
self.lca_5_counter = None # Will grab initial value from CarState
|
||||
|
||||
self.last_lat_active = False # Track state
|
||||
|
||||
self.lca_7_acc = 0 # Bresenham accumulator for 29 Hz
|
||||
self.lca_7_last_steer = 0 # used to calculate change in steer from last update
|
||||
|
||||
# LCA torque-authority envelope state. Both arms are persistent across frames.
|
||||
# See CarControllerParams.LCA_AUTH_* and route_analysis/lca_override_mechanism.md.
|
||||
# When lat_active goes True they ramp up from 0 to ±MAX at REBUILD_RATE; on
|
||||
# driver override they collapse at COLLAPSE_RATE (symmetric until SPLIT, then
|
||||
# asymmetric: yielding arm → 0, counter arm holds at ±PLATEAU).
|
||||
self.lca_auth_pos = 0.0
|
||||
self.lca_auth_neg = 0.0
|
||||
# "Light contact" rising-edge detector for haptic-ack on resting hands.
|
||||
# Per-frame |drv| derivative; LIGHT_HOLD_FRAMES counter ticks down while
|
||||
# the brief-yield window is active and does not re-arm during that window.
|
||||
self.lca_auth_drv_prev = 0.0
|
||||
self.lca_auth_light_frames = 0
|
||||
# Frames since real_override was last active — used to gate light_collapse
|
||||
# re-arming during active co-steering (must be > LIGHT_COOLDOWN_FRAMES).
|
||||
self.lca_auth_real_off_frames = 1000 # large initial → light can fire immediately
|
||||
# Override-mode latch with hysteresis (enter at ENTER, exit at EXIT).
|
||||
# Prevents threshold flapping when driver torque hovers near the boundary,
|
||||
# which caused ~10 Hz EPS-torque ripple felt during lane-change overrides.
|
||||
self.lca_auth_override_active = False
|
||||
# LP-filtered |drv| for yield-arm magnitude calculation. Suppresses 1-2
|
||||
# unit driver-torque jitter that would otherwise propagate (~10x amplified
|
||||
# via YIELD_SLOPE) into envelope ripple felt at the wheel.
|
||||
self.lca_auth_drv_mag_filt = 0.0
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
can_sends = []
|
||||
actuators = CC.actuators
|
||||
|
||||
# Detect disengagement
|
||||
if not CC.latActive and self.last_lat_active:
|
||||
#self.lca_commands.reset() # Clear state ← IMPORTANT!
|
||||
pass
|
||||
|
||||
lat_active = CC.latActive
|
||||
|
||||
# lateral control - angle-based steering
|
||||
# NOTE: LCA message is sent every frame (even when inactive) to replace stock LCA
|
||||
# Stock LCA is permanently blocked by panda safety, so we must always send
|
||||
if self.frame % CarControllerParams.STEER_STEP == 0: # 100 Hz
|
||||
# Get desired steering angle from controlsd (LatControlAngle)
|
||||
apply_angle = actuators.steeringAngleDeg # degrees
|
||||
|
||||
# Clamp commanded angle to actual ± ANGLE_ERROR. Without this, a driver override
|
||||
# lets the model's plan drift far from the wheel's actual position; on release,
|
||||
# the EPS slams back toward that stale command and overshoots. Stock Volvo Pilot
|
||||
# Assist keeps this gap inside ~2° even under sustained override.
|
||||
apply_angle = float(np.clip(
|
||||
apply_angle,
|
||||
CS.out.steeringAngleDeg - CarControllerParams.ANGLE_ERROR,
|
||||
CS.out.steeringAngleDeg + CarControllerParams.ANGLE_ERROR,
|
||||
))
|
||||
|
||||
# Rate limit + inactive passthrough (apply_angle = steering angle when not lat_active)
|
||||
apply_angle = apply_std_steer_angle_limits(apply_angle, self.apply_angle_last, CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg, lat_active, CarControllerParams.ANGLE_LIMITS)
|
||||
|
||||
# Update LCA torque-authority envelope (replicates stock Pilot Assist's
|
||||
# easy-override and bounce-free release). Stock holds both arms at ±614
|
||||
# in steady state; on override the arms collapse to a shifted plateau
|
||||
# (counter arm deeper than yielding arm); rebuilds at +230 c/s.
|
||||
P = CarControllerParams
|
||||
DT = 0.01 # 100 Hz
|
||||
# Override trigger uses an explicit |steeringTorque| threshold rather than
|
||||
# CS.steeringPressed, which is a very-sensitive DM-fallback floor (raw>2)
|
||||
# — fires from resting hands alone and is not an override-intent signal.
|
||||
drv_mag = abs(CS.out.steeringTorque)
|
||||
drv_rate = drv_mag - self.lca_auth_drv_prev
|
||||
self.lca_auth_drv_prev = drv_mag
|
||||
# LP filter on |drv| used for yield-arm magnitude — absorbs 1-2 unit
|
||||
# driver-torque jitter that would otherwise propagate into ~10 unit
|
||||
# envelope ripple via the YIELD_SLOPE multiplier.
|
||||
self.lca_auth_drv_mag_filt = ((1.0 - P.LCA_AUTH_YIELD_LP_ALPHA) * self.lca_auth_drv_mag_filt
|
||||
+ P.LCA_AUTH_YIELD_LP_ALPHA * drv_mag)
|
||||
# Hysteretic override latch — enter at ENTER, hold until drv drops below
|
||||
# EXIT. Eliminates ~10 Hz envelope flapping when |drv| hovers near a
|
||||
# single threshold during sustained co-steering.
|
||||
if not self.lca_auth_override_active and drv_mag > P.LCA_AUTH_OVERRIDE_ENTER:
|
||||
self.lca_auth_override_active = True
|
||||
elif self.lca_auth_override_active and drv_mag < P.LCA_AUTH_OVERRIDE_EXIT:
|
||||
self.lca_auth_override_active = False
|
||||
real_override = self.lca_auth_override_active
|
||||
# Track frames since real_override was last active. Used to gate light
|
||||
# contact re-firing — light_collapse must NOT trigger while the driver
|
||||
# is actively co-steering (real_override repeatedly entering/exiting).
|
||||
if real_override:
|
||||
self.lca_auth_real_off_frames = 0
|
||||
else:
|
||||
self.lca_auth_real_off_frames += 1
|
||||
# Per-frame rising edge into the "light contact" zone arms a brief-yield
|
||||
# window for haptic acknowledgment of hand-on-wheel. Suppressed while
|
||||
# the window is already active OR real_override has been off less than
|
||||
# LIGHT_COOLDOWN_FRAMES (i.e., user is actively co-steering).
|
||||
if (drv_mag > P.LCA_AUTH_LIGHT_THRESH and
|
||||
drv_rate > P.LCA_AUTH_LIGHT_RISE_DELTA and
|
||||
self.lca_auth_light_frames == 0 and
|
||||
self.lca_auth_real_off_frames > P.LCA_AUTH_LIGHT_COOLDOWN_FRAMES):
|
||||
self.lca_auth_light_frames = P.LCA_AUTH_LIGHT_HOLD_FRAMES
|
||||
else:
|
||||
self.lca_auth_light_frames = max(0, self.lca_auth_light_frames - 1)
|
||||
light_collapse = self.lca_auth_light_frames > 0
|
||||
overriding = real_override or light_collapse
|
||||
# Collapse rate scales with driver torque so a sharp pothole jolt drops the
|
||||
# envelope faster than a soft sustained press. Floor at base rate so light
|
||||
# contact still produces a perceptible (but small) dip.
|
||||
collapse_rate = P.LCA_AUTH_COLLAPSE_RATE * max(1.0, drv_mag / float(P.LCA_AUTH_OVERRIDE_ENTER))
|
||||
step = collapse_rate * DT
|
||||
if not lat_active:
|
||||
self.lca_auth_pos = 0.0
|
||||
self.lca_auth_neg = 0.0
|
||||
self.lca_auth_drv_prev = 0.0
|
||||
self.lca_auth_light_frames = 0
|
||||
self.lca_auth_override_active = False
|
||||
self.lca_auth_drv_mag_filt = 0.0
|
||||
self.lca_auth_real_off_frames = 1000
|
||||
elif overriding:
|
||||
if self.lca_auth_pos > P.LCA_AUTH_SPLIT or -self.lca_auth_neg > P.LCA_AUTH_SPLIT:
|
||||
# Symmetric collapse phase: both arms shrink toward ±SPLIT
|
||||
self.lca_auth_pos = max(float(P.LCA_AUTH_SPLIT), self.lca_auth_pos - step)
|
||||
self.lca_auth_neg = min(-float(P.LCA_AUTH_SPLIT), self.lca_auth_neg + step)
|
||||
else:
|
||||
# Asymmetric plateau phase. CS.out.steeringTorque > 0 in openpilot
|
||||
# convention = driver pushing right → yields right authority
|
||||
# (LOOSELY/+ arm), retains left (INV/- arm).
|
||||
# Yield arm scales with |drv| above OVERRIDE_THRESH — strong presses
|
||||
# (potholes, hard corrections) cross past zero so EPS hands the wheel
|
||||
# to the driver in their direction.
|
||||
excess = max(0.0, self.lca_auth_drv_mag_filt - float(P.LCA_AUTH_OVERRIDE_ENTER))
|
||||
yield_signed = float(P.LCA_AUTH_YIELD_BASE) - P.LCA_AUTH_YIELD_SLOPE * excess
|
||||
yield_signed = max(float(P.LCA_AUTH_YIELD_MIN), min(yield_signed, float(P.LCA_AUTH_YIELD_BASE)))
|
||||
if CS.out.steeringTorque > 0: # driver pushing right
|
||||
target_pos = yield_signed # yield arm (+ side)
|
||||
target_neg = -float(P.LCA_AUTH_PLATEAU_COUNTER) # counter arm
|
||||
else: # driver pushing left (or zero — default to symmetric collapse direction)
|
||||
target_pos = float(P.LCA_AUTH_PLATEAU_COUNTER) # counter arm
|
||||
target_neg = -yield_signed # yield arm (− side)
|
||||
# Drive each arm toward its plateau target at COLLAPSE_RATE
|
||||
self.lca_auth_pos = max(target_pos, self.lca_auth_pos - step) \
|
||||
if self.lca_auth_pos > target_pos \
|
||||
else min(target_pos, self.lca_auth_pos + step)
|
||||
self.lca_auth_neg = min(target_neg, self.lca_auth_neg + step) \
|
||||
if self.lca_auth_neg < target_neg \
|
||||
else max(target_neg, self.lca_auth_neg - step)
|
||||
else:
|
||||
# No override → rebuild both arms toward saturation
|
||||
rebuild_step = P.LCA_AUTH_REBUILD_RATE * DT
|
||||
self.lca_auth_pos = min(float(P.LCA_AUTH_MAX), self.lca_auth_pos + rebuild_step)
|
||||
self.lca_auth_neg = max(-float(P.LCA_AUTH_MAX), self.lca_auth_neg - rebuild_step)
|
||||
|
||||
# LCA - 0x58 - 100 Hz (angle-based)
|
||||
can_sends.append(create_lca_message(self.packer, lat_active, apply_angle, CS.msg_lca,
|
||||
authority_pos=int(round(self.lca_auth_pos)),
|
||||
authority_neg=int(round(self.lca_auth_neg))))
|
||||
self.apply_angle_last = apply_angle
|
||||
|
||||
# PSCM (bus 2 -> 0) - 0x16 - 100 Hz
|
||||
can_sends.append(create_pscm_message(self.packer, lat_active, CS.msg_pscm, self.frame))
|
||||
# EGSM - 0x45 - 100 Hz
|
||||
#can_sends.append(create_egsm_message(self.packer, CS.msg_egsm))
|
||||
|
||||
# PSCM_RELATED (bus 2 -> 0) - 0x17 - 100 Hz
|
||||
# Initialize counter from CarState on first run
|
||||
if self.pscm_related_counter is None:
|
||||
self.pscm_related_counter = CS.msg_pscm_related['SIG1_BYTE_1_HI_NIBBLE']
|
||||
|
||||
# Increment counter by +1, wrap from 14 → 0 (modulo 15)
|
||||
self.pscm_related_counter = (self.pscm_related_counter + 1) % 15
|
||||
|
||||
can_sends.append(create_pscm_related_message(self.packer, lat_active, CS.pilot_assist_engaged,
|
||||
CS.msg_pscm_related, self.pscm_related_counter))
|
||||
|
||||
# LCA_3 - 0x57 - avg 66.66 Hz
|
||||
#if (self.frame * 67) % 100 < 67: # if (self.frame % 3) < 2:
|
||||
# 0x57 at ~66.67 Hz: send on 2 out of every 3 frames
|
||||
# Pattern: send on frame % 3 == 0 or 2, skip when frame % 3 == 1
|
||||
if self.frame % 3 != 1: # → 2/3 * 100 Hz = 66.67 Hz
|
||||
# Update counter with observed value, get counter to send
|
||||
counter, is_synced = self.lca_3_counter_sync.update(CS.msg_lca_3['COUNTER_1'])
|
||||
can_sends.append(create_lca_3_message(self.packer, lat_active, apply_angle, CS.msg_lca_3, counter))
|
||||
#can_sends.append(create_0x1a_message(self.packer, CS.msg_0x1a))
|
||||
|
||||
# SPEED messages - 0x60, 0x68 - 50 Hz
|
||||
if self.frame % 2 == 0: # 50 Hz
|
||||
#can_sends.append(create_speed_message(self.packer, CS.msg_speed))
|
||||
#can_sends.append(create_speed_2_message(self.packer, CS.msg_speed_2))
|
||||
pass
|
||||
|
||||
# LCA_2 - 0x69 - 50 Hz
|
||||
# Spoof PILOT_ASSIST_ENGAGED to keep PSCM accepting LCA commands
|
||||
if self.frame % 2 == 0: # 50 Hz
|
||||
# Initialize counters from CarState on first run
|
||||
if self.lca_2_counter_1 is None:
|
||||
self.lca_2_counter_1 = CS.msg_lca_2['COUNTER_1']
|
||||
self.lca_2_counter_2 = CS.msg_lca_2['COUNTER_2']
|
||||
|
||||
# Increment counters (COUNTER_1 by +2, COUNTER_2 by +4, both modulo 16)
|
||||
self.lca_2_counter_1 = (self.lca_2_counter_1 + 2) % 16
|
||||
self.lca_2_counter_2 = (self.lca_2_counter_2 + 4) % 16
|
||||
|
||||
can_sends.append(create_lca_2_message(self.packer, lat_active, CS.msg_lca_2,
|
||||
self.lca_2_counter_1, self.lca_2_counter_2))
|
||||
|
||||
# LCA_5 (formerly SPEED_1) - 0x67 - 50 Hz
|
||||
# Contains wheel speeds + LCA signals (LCA_TURN_BITS, LCA_5_STEER)
|
||||
if self.frame % 2 == 0: # 50 Hz
|
||||
# Initialize counter from CarState on first run
|
||||
if self.lca_5_counter is None:
|
||||
self.lca_5_counter = CS.msg_lca_5['COUNTER']
|
||||
|
||||
# Increment counter by +4, wrap at 15 (0xF never used)
|
||||
self.lca_5_counter = (self.lca_5_counter + 4) % 15
|
||||
|
||||
can_sends.append(create_lca_5_message(self.packer, lat_active, apply_angle,
|
||||
CS.msg_lca_5, self.lca_5_counter))
|
||||
|
||||
# LCA_4 - 0x90 - 29 Hz
|
||||
# Spoof LCA_ENABLE bits to maintain PA ON state when openpilot is active
|
||||
# Using Bresenham-style accumulator for precise 29 Hz
|
||||
self.lca_4_acc += 29
|
||||
if self.lca_4_acc >= 100:
|
||||
self.lca_4_acc -= 100
|
||||
can_sends.append(create_lca_4_message(self.packer, lat_active, CS.msg_lca_4, apply_angle))
|
||||
|
||||
# LCA_6 - 0X97 - 25 Hz
|
||||
if self.frame % 4 == 0: # 25 Hz
|
||||
can_sends.append(create_lca_6_message(self.packer, lat_active, CS.msg_lca_6, apply_angle))
|
||||
|
||||
# LCA_7 - 0x92 - 29 Hz
|
||||
# Using Bresenham-style accumulator for precise 29 Hz
|
||||
self.lca_7_acc += 29
|
||||
if self.lca_7_acc >= 100:
|
||||
self.lca_7_acc -= 100
|
||||
delta_steer = apply_angle - self.lca_7_last_steer
|
||||
can_sends.append(create_lca_7_message(self.packer, lat_active, CS.msg_lca_7, apply_angle, delta_steer))
|
||||
self.lca_7_last_steer = apply_angle
|
||||
|
||||
# GEAR_POSITION - 0x80 - 40 Hz
|
||||
#self.gear_acc += 40 # Bresenham-style approach
|
||||
#if self.gear_acc >= 100:
|
||||
# self.gear_acc -= 100
|
||||
if self.frame % 5 == 0 or self.frame % 5 == 2: # 2/5 * 100 Hz = 40 Hz # openpilot forward delay causes DTC in EGSM, but fixes DTC in PSCM
|
||||
#can_sends.append(create_gear_position_message(self.packer, CS.msg_gear_position))
|
||||
pass
|
||||
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steeringAngleDeg = self.apply_angle_last
|
||||
self.frame += 1
|
||||
self.last_lat_active = CC.latActive
|
||||
return new_actuators, can_sends
|
||||
@@ -1,146 +0,0 @@
|
||||
from cereal import custom
|
||||
from opendbc.car import structs, Bus
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.car.volvo.values import DBC, VolvoSPAPlatformConfig, CAR
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
TransmissionType = structs.CarParams.TransmissionType
|
||||
|
||||
# main-bus SPEED (0x60) is raw counts in the DBC; measured against GPS ground speed.
|
||||
# Must match VOLVO_SPEED_TO_MS in opendbc/safety/modes/volvo.h.
|
||||
SPEED_TO_MS = 0.003977
|
||||
STEERING_PRESSED_THRESHOLD = 2
|
||||
|
||||
|
||||
class CarState(CarStateBase):
|
||||
def __init__(self, CP, FPCP):
|
||||
super().__init__(CP, FPCP)
|
||||
self.is_spa = isinstance(CAR(CP.carFingerprint).config, VolvoSPAPlatformConfig)
|
||||
self.gas_pressed_prev = False
|
||||
self.dispatch_lca_2_msg = False
|
||||
self.msg_pscm = {}
|
||||
self.msg_lca = {}
|
||||
self.msg_lca_2 = {}
|
||||
self.msg_lca_3 = {}
|
||||
self.msg_gear_position = {}
|
||||
self.pilot_assist_engaged = False
|
||||
self.msg_lca_5 = {} # Formerly msg_speed_1
|
||||
self.msg_speed = {}
|
||||
self.msg_speed_2 = {}
|
||||
self.msg_0x1a = {}
|
||||
self.msg_egsm = {}
|
||||
self.msg_pscm_related = {}
|
||||
self.msg_lca_4 = {}
|
||||
self.msg_lca_6 = {}
|
||||
self.msg_lca_7 = {}
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp_main = can_parsers[Bus.main]
|
||||
cp_pt = can_parsers[Bus.pt]
|
||||
cp_party = can_parsers[Bus.party]
|
||||
ret = structs.CarState()
|
||||
|
||||
# car speed
|
||||
# SPEED on the main bus, not BUS1_SPEED on the PT bus: the main bus is identical
|
||||
# across harnesses, while which car bus lands on PT (bus 1) is not, and the PT DBC
|
||||
# in use depends on the fingerprint. Regressed against GPS ground speed over two
|
||||
# routes on different harnesses: r=0.99989 both, residual sd 0.35-0.40 km/h.
|
||||
ret.vEgoRaw = cp_main.vl["SPEED"]["SPEED"] * SPEED_TO_MS
|
||||
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
|
||||
ret.standstill = ret.vEgoRaw <= 0.1 # 0.1 m/s
|
||||
|
||||
# gas
|
||||
# CMA ECM_1.ACCELERATOR_PEDAL_POS is raw 0-255 (DBC factor 1, idle ~20).
|
||||
# SPA ECM_1.ACCELERATOR_PEDAL_POS is DBC-scaled to percent (factor 0.00390625, idle ~0).
|
||||
# Thresholds must match volvo.h (see opendbc/safety/modes/volvo.h GAS_PRESSED_THRESHOLD_*)
|
||||
# and opendbc/safety/tests/test_volvo.py::test_gas_threshold_self_consistent.
|
||||
if self.is_spa:
|
||||
ret.gasPressed = cp_pt.vl["ECM_1"]["ACCELERATOR_PEDAL_POS"] > 1.0 # percent
|
||||
else:
|
||||
ret.gasPressed = cp_pt.vl["ECM_1"]["ACCELERATOR_PEDAL_POS"] > 20+1 # raw counts, 20 baseline + 1 tolerance
|
||||
|
||||
# brake
|
||||
#ret.brakePressed = bool(cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_A"] or cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_B"])
|
||||
# BRAKE_PEDAL_PRESSED_A goes active when user starts pressing brake pedal, but no brake light is on yet due to tolerance
|
||||
# BRAKE_PEDAL_PRESSED_B goes active when when the brake pedal is pressed above minimum threshold, brake light is on
|
||||
ret.brakePressed = cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_B"] == 1
|
||||
ret.parkingBrake = False # TODO: add parking brake
|
||||
|
||||
# stability control - becomes true when ESC intervenes (e.g., aquaplaning)
|
||||
ret.espActive = cp_main.vl["LCA_2"]["ESC_ACTUATING"] == 1 and cp_main.vl["LCA_2"]["ESC_ELIGIBLE"] == 1
|
||||
|
||||
# steering wheel
|
||||
ret.steeringAngleDeg = cp_party.vl['PSCM']['PSCM_ANGLE_SENSOR'] # openpilot expects a negative value for a right turn
|
||||
#ret.steeringAngleDeg = cp_party.vl['SAS']['SAS_ANGLE_SENSOR']
|
||||
|
||||
ret.steeringTorque = -cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'] # Car right turn is negative, openpilot right turn is positive
|
||||
driver_input = abs(cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'])
|
||||
ret.steeringPressed = driver_input > STEERING_PRESSED_THRESHOLD
|
||||
|
||||
# EPS status - placeholder until actual signal is found
|
||||
self.eps_active = True # Assume EPS is active for now
|
||||
|
||||
if self.is_spa:
|
||||
# SPA: byte 0 bit 1, inverted (0 = cruise on, 1 = cruise off)
|
||||
cruise_raw = cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_SPA_ENABLED"] == 1
|
||||
else:
|
||||
# CMA: two separate boolean signals
|
||||
cruise_raw = cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_ENABLED"] == 1 or cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC"] == 1
|
||||
|
||||
ret.cruiseState.enabled = cruise_raw
|
||||
|
||||
self.gas_pressed_prev = ret.gasPressed
|
||||
ret.cruiseState.available = True # TODO: Determine actual availability
|
||||
ret.cruiseState.speed = 0 # TODO: Find cruise set speed (not required for lateral control)
|
||||
ret.cruiseState.nonAdaptive = False
|
||||
ret.cruiseState.standstill = ret.standstill # False # Todo: Find cruise control standstill signal
|
||||
|
||||
# gear
|
||||
gearPosition = cp_main.vl['GEAR_POSITION']['GEAR_POSITION'] # 0: P; 1: R; 2: N; 3: D; 4: B;
|
||||
if gearPosition == 0:
|
||||
ret.gearShifter = GearShifter.park
|
||||
elif gearPosition == 1:
|
||||
ret.gearShifter = GearShifter.reverse
|
||||
elif gearPosition == 2:
|
||||
ret.gearShifter = GearShifter.neutral
|
||||
elif gearPosition == 3:
|
||||
ret.gearShifter = GearShifter.drive
|
||||
elif gearPosition == 4:
|
||||
ret.gearShifter = GearShifter.drive
|
||||
|
||||
# blinkers TODO FlexRay
|
||||
ret.leftBlinker = False
|
||||
ret.rightBlinker = False
|
||||
|
||||
# lock info TODO FlexRay
|
||||
ret.doorOpen = False # TODO: add door open
|
||||
ret.seatbeltUnlatched = False # TODO: add seatbelt unlatched
|
||||
|
||||
# Store entire message dictionaries
|
||||
self.msg_pscm = cp_party.vl['PSCM']
|
||||
self.msg_lca = cp_main.vl['LCA']
|
||||
self.msg_lca_2 = cp_main.vl['LCA_2']
|
||||
self.msg_lca_3 = cp_main.vl['LCA_3']
|
||||
self.msg_lca_4 = cp_main.vl['LCA_4']
|
||||
self.msg_lca_5 = cp_main.vl['LCA_5']
|
||||
self.msg_lca_6 = cp_main.vl['LCA_6']
|
||||
self.msg_lca_7 = cp_main.vl['LCA_7']
|
||||
self.msg_speed = cp_main.vl['SPEED']
|
||||
self.msg_speed_2 = cp_main.vl['SPEED_2']
|
||||
self.msg_gear_position = cp_main.vl['GEAR_POSITION']
|
||||
self.msg_egsm = cp_party.vl['EGSM']
|
||||
self.msg_pscm_related = cp_party.vl['PSCM_RELATED']
|
||||
|
||||
self.pilot_assist_engaged = cp_main.vl['LCA_2']['PILOT_ASSIST_ENGAGED'] == 1
|
||||
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
return ret, fp_ret
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
return {
|
||||
Bus.main: CANParser(DBC[CP.carFingerprint][Bus.main], [], 0),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 1),
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], 2),
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
# ruff: noqa: E501
|
||||
""" AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
|
||||
from opendbc.car.volvo.values import CAR
|
||||
|
||||
FINGERPRINTS = {
|
||||
CAR.VOLVO_XC40_RECHARGE: [{
|
||||
21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 304: 8, 336: 8, 339: 8, 341: 8, 394: 8, 395: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1302: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8
|
||||
}],
|
||||
CAR.VOLVO_S60_RECHARGE: [{
|
||||
21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 336: 8, 339: 8, 341: 8, 395: 8, 587: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1298: 8, 1302: 8, 1319: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8, 1554: 8, 1587: 8, 1718: 8, 1792: 8, 1793: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1843: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1871: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1920: 8, 1927: 8, 1937: 8, 1943: 8, 1953: 8, 1954: 8, 1955: 8, 1968: 8, 1969: 8, 1971: 8, 1975: 8, 1988: 8, 1990: 8, 2000: 8, 2001: 8, 2002: 8, 2004: 8, 2017: 8, 2018: 8, 2020: 8
|
||||
}],
|
||||
CAR.POLESTAR_2: [{
|
||||
7: 4, 21: 8, 22: 8, 23: 8, 26: 8, 35: 8, 37: 8, 53: 8, 58: 8, 67: 8, 69: 8, 70: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 112: 8, 117: 8, 128: 8, 133: 8, 138: 8, 144: 8, 146: 8, 147: 8, 151: 8, 256: 8, 277: 8, 278: 8, 284: 8, 293: 8, 309: 8, 320: 8, 325: 8, 336: 8, 339: 8, 341: 8, 348: 8, 349: 8, 352: 8, 368: 8, 370: 8, 373: 8, 375: 8, 376: 8, 389: 8, 395: 8, 400: 8, 408: 8, 411: 8, 417: 8, 420: 8, 426: 8, 429: 8, 435: 8, 440: 8, 464: 8, 556: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 789: 8, 791: 8, 800: 8, 805: 8, 807: 8, 816: 8, 821: 8, 832: 8, 837: 8, 841: 8, 848: 8, 853: 8, 854: 8, 858: 8, 860: 8, 873: 8, 882: 8, 889: 8, 890: 8, 891: 8, 892: 8, 893: 8, 896: 8, 899: 8, 901: 8, 917: 8, 919: 8, 1043: 8, 1045: 8, 1061: 8, 1072: 8, 1077: 8, 1088: 8, 1093: 8, 1120: 8, 1127: 8, 1160: 8, 1168: 8, 1171: 8, 1174: 8, 1175: 8, 1177: 8, 1296: 8, 1302: 8, 1320: 8, 1334: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8, 1428: 8, 1429: 8, 1430: 8, 1431: 8, 1432: 8, 1554: 8, 1584: 8, 1587: 8, 2022: 8
|
||||
}],
|
||||
}
|
||||
|
||||
FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = {
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
def checksum_lca_2_message(b0: int, b5: int) -> int:
|
||||
"""
|
||||
Compute checksum for VCU1 CAN ID 0x69 from bytes b0 and b5.
|
||||
|
||||
b0: first data byte (MSB) of the frame (usually 0x18 in your logs)
|
||||
b5: sixth data byte of the frame (what you called Byte5)
|
||||
|
||||
Returns: checksum byte (0..255) that goes into byte index 6.
|
||||
"""
|
||||
if b0 == 0 and b5 == 128: # Hotfix openpilot test (don't know where this alleged test message comes from)
|
||||
return 0
|
||||
|
||||
# Masks per checksum bit (bit 0..7) for b0 and b5
|
||||
M0 = [0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00]
|
||||
M5 = [0x83, 0x86, 0xCF, 0xCD, 0x09, 0x02, 0x44, 0x89]
|
||||
|
||||
def parity8(x: int) -> int:
|
||||
# 1 if x has an odd number of bits set, else 0
|
||||
x ^= x >> 4
|
||||
x ^= x >> 2
|
||||
x ^= x >> 1
|
||||
return x & 1
|
||||
|
||||
b0 &= 0xFF
|
||||
b5 &= 0xFF
|
||||
|
||||
c = 0
|
||||
for bit in range(8):
|
||||
p = 0
|
||||
if M0[bit]:
|
||||
p ^= parity8(b0 & M0[bit])
|
||||
if M5[bit]:
|
||||
p ^= parity8(b5 & M5[bit])
|
||||
c |= (p << bit)
|
||||
|
||||
return c & 0xFF
|
||||
|
||||
def checksum_2_0x69_message(b0: int, b1: int, b3: int = 0, b4: int = 0) -> int:
|
||||
"""
|
||||
Compute checksum byte (b2) for CAN ID 0x69 (LCA_2 message).
|
||||
|
||||
The checksum depends on bytes 0, 1, 3, and 4. During normal driving (BYTE_1_MSBS_3=0),
|
||||
bytes 3-4 (NEW_SIGNAL_2) are always 0, so only b0 and b1 matter. During stability
|
||||
control events (aquaplaning, etc.), BYTE_1_MSBS_3 becomes non-zero and bytes 3-4
|
||||
contain non-zero values that affect the checksum.
|
||||
|
||||
Args:
|
||||
b0: Byte 0 (usually 0x18)
|
||||
b1: Byte 1 ([7:5] BYTE_1_MSBS_3 | [4] PILOT_ASSIST_ENGAGED | [3:0] COUNTER_1)
|
||||
b3: Byte 3 (NEW_SIGNAL_2 high byte, default 0)
|
||||
b4: Byte 4 (NEW_SIGNAL_2 low byte, default 0)
|
||||
|
||||
Returns:
|
||||
Checksum byte (0-255) for position 2
|
||||
"""
|
||||
b0 &= 0xFF
|
||||
b1 &= 0xFF
|
||||
b3 &= 0xFF
|
||||
b4 &= 0xFF
|
||||
|
||||
def bit(byte, pos):
|
||||
return (byte >> pos) & 1
|
||||
|
||||
c = 0
|
||||
# Bit 0
|
||||
c |= (bit(b0, 0) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^ bit(b1, 5) ^ bit(b4, 0) ^ bit(b4, 1)) << 0
|
||||
# Bit 1
|
||||
c |= (bit(b0, 1) ^ bit(b0, 3) ^ bit(b1, 0) ^ bit(b1, 3) ^ bit(b1, 5) ^ bit(b1, 6) ^
|
||||
bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 1) ^ bit(b4, 2)) << 1
|
||||
# Bit 2
|
||||
c |= (bit(b0, 0) ^ bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^ bit(b1, 5) ^ bit(b1, 6) ^
|
||||
bit(b3, 0) ^ bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 2) ^ bit(b4, 3)) << 2
|
||||
# Bit 3
|
||||
c |= (bit(b0, 0) ^ bit(b0, 1) ^ bit(b1, 0) ^ bit(b1, 4) ^ bit(b1, 6) ^
|
||||
bit(b3, 0) ^ bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 3) ^ bit(b4, 4)) << 3
|
||||
# Bit 4
|
||||
c |= (bit(b0, 0) ^ bit(b0, 1) ^ bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^
|
||||
bit(b4, 4) ^ bit(b4, 5)) << 4
|
||||
# Bit 5
|
||||
c |= (bit(b0, 1) ^ bit(b1, 0) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 5) ^
|
||||
bit(b3, 1) ^ bit(b4, 5) ^ bit(b4, 6)) << 5
|
||||
# Bit 6
|
||||
c |= (bit(b0, 3) ^ bit(b1, 0) ^ bit(b1, 1) ^ bit(b1, 3) ^ bit(b1, 6) ^
|
||||
bit(b3, 0) ^ bit(b4, 6) ^ bit(b4, 7)) << 6
|
||||
# Bit 7
|
||||
c |= (bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 4) ^ bit(b4, 0) ^ bit(b4, 7)) << 7
|
||||
return c & 0xFF
|
||||
|
||||
def checksum_1_pscm_related_message(b1, b2):
|
||||
"""
|
||||
Computes checksum #1 (goes in byte[0]) for PSCM-related 0x17 message.
|
||||
Depends only on (byte[1], byte[2]).
|
||||
|
||||
b1 = SIG1 counter (high nibble) | LCA_ENABLED_ECHO (low nibble)
|
||||
b2 = 0x80 | SIG1 counter replica (low nibble)
|
||||
|
||||
Linear over GF(2), same shape as checksum_lca_2_message: each output bit is
|
||||
the parity of a fixed mask over b1 and b2. Solved from 137,965 logged PSCM
|
||||
frames (60 distinct (b1,b2) keys, leave-one-out cross-validated 60/60).
|
||||
|
||||
This replaces a 45-entry lookup table that covered only LCA_ENABLED_ECHO in
|
||||
{0, 1, 4} and returned 0 on a miss. During an ESC intervention the rack
|
||||
reports ECHO=6, so openpilot transmitted 128 consecutive frames with an
|
||||
invalid checksum (0x00) before this fix.
|
||||
|
||||
Note: bit 3 of b1 (LCA_ENABLED_ECHO >= 8) has never been observed on the bus,
|
||||
so its contribution is unconstrained by the data and is taken to be zero.
|
||||
"""
|
||||
# Masks per checksum bit (bit 0..7) for b1 and b2
|
||||
M1 = [0x41, 0x82, 0x55, 0xF3, 0xA7, 0x46, 0x94, 0x20]
|
||||
M2 = [0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80]
|
||||
|
||||
def parity8(x: int) -> int:
|
||||
# 1 if x has an odd number of bits set, else 0
|
||||
x ^= x >> 4
|
||||
x ^= x >> 2
|
||||
x ^= x >> 1
|
||||
return x & 1
|
||||
|
||||
b1 &= 0xFF
|
||||
b2 &= 0xFF
|
||||
|
||||
c = 0
|
||||
for bit in range(8):
|
||||
p = 0
|
||||
if M1[bit]:
|
||||
p ^= parity8(b1 & M1[bit])
|
||||
if M2[bit]:
|
||||
p ^= parity8(b2 & M2[bit])
|
||||
c |= (p << bit)
|
||||
|
||||
return c & 0xFF
|
||||
|
||||
|
||||
def checksum_2_pscm_related_message(b2):
|
||||
"""
|
||||
Computes checksum #2 (goes in byte[3]) for PSCM-related 0x17 message.
|
||||
Depends only on byte[2].
|
||||
"""
|
||||
|
||||
lut = {
|
||||
0x80: 0xBF,
|
||||
0x81: 0xF3,
|
||||
0x82: 0x27,
|
||||
0x83: 0x6B,
|
||||
0x84: 0x92,
|
||||
0x85: 0xDE,
|
||||
0x86: 0x0A,
|
||||
0x87: 0x46,
|
||||
0x88: 0xE5,
|
||||
0x89: 0xA9,
|
||||
0x8A: 0x7D,
|
||||
0x8B: 0x31,
|
||||
0x8C: 0xC8,
|
||||
0x8D: 0x84,
|
||||
0x8E: 0x50,
|
||||
}
|
||||
|
||||
return lut.get(b2, 0)
|
||||
|
||||
|
||||
def checksum_lca_4_message(*args) -> int:
|
||||
"""
|
||||
Placeholder for LCA_4 (0x90) checksum calculation.
|
||||
|
||||
TODO: Implementation will be provided after checksum analysis is complete.
|
||||
For now, returns 0 as a placeholder.
|
||||
|
||||
Args:
|
||||
*args: Byte values needed for checksum calculation (TBD)
|
||||
|
||||
Returns:
|
||||
Checksum byte (0-255)
|
||||
"""
|
||||
# Placeholder - will be replaced with actual checksum algorithm
|
||||
return 0
|
||||
|
||||
|
||||
class LCA3CounterSync:
|
||||
"""
|
||||
Best-effort pattern synchronization for LCA_3 COUNTER_1.
|
||||
|
||||
The counter follows a 20-element pattern that cycles based on transmission count.
|
||||
We track recent observed counter values and match them against the pattern to
|
||||
determine the current index. While not synchronized, we pass through stock values.
|
||||
Once synchronized, we permanently use the pattern.
|
||||
"""
|
||||
|
||||
PATTERN = [2, 2, 1, 2, 2, 2, 1, 2, 2, 3, 0, 2, 3, 2, 0, 2, 3, 2, 0, 3]
|
||||
PATTERN_LEN = 20
|
||||
WINDOW_SIZE = 5 # Track last 5 values for matching
|
||||
MIN_CONFIDENCE = 4 # Need 4 consecutive matches to sync
|
||||
|
||||
def __init__(self):
|
||||
self.pattern_index = None # Current index in pattern (None = not synced)
|
||||
self.observed_window = [] # Circular buffer of last N observed values
|
||||
self.confidence = 0 # Number of consecutive successful matches
|
||||
|
||||
def update(self, observed_counter: int) -> tuple:
|
||||
"""
|
||||
Update with newly observed counter value from stock message.
|
||||
|
||||
Args:
|
||||
observed_counter: Counter value from CS.msg_lca_3['COUNTER_1']
|
||||
|
||||
Returns:
|
||||
Tuple of (counter_to_send, is_synchronized)
|
||||
"""
|
||||
# If already synchronized, ignore stock and use our pattern permanently
|
||||
if self.pattern_index is not None:
|
||||
counter_to_send = self.PATTERN[self.pattern_index]
|
||||
self.pattern_index = (self.pattern_index + 1) % self.PATTERN_LEN
|
||||
return counter_to_send, True
|
||||
|
||||
# Not synchronized yet - try to find pattern index
|
||||
self.observed_window.append(observed_counter)
|
||||
if len(self.observed_window) > self.WINDOW_SIZE:
|
||||
self.observed_window.pop(0)
|
||||
|
||||
# Attempt to sync if we have enough samples
|
||||
if len(self.observed_window) >= 3:
|
||||
self._attempt_sync()
|
||||
|
||||
# While not synced, pass through stock counter
|
||||
return observed_counter, False
|
||||
|
||||
def _attempt_sync(self):
|
||||
"""Try to find current pattern index based on observed window."""
|
||||
# Try to match observation window against all positions in pattern
|
||||
best_match_idx = None
|
||||
best_match_len = 0
|
||||
|
||||
for start_idx in range(self.PATTERN_LEN):
|
||||
match_len = self._count_match(start_idx)
|
||||
if match_len > best_match_len:
|
||||
best_match_len = match_len
|
||||
best_match_idx = start_idx
|
||||
|
||||
# Require MIN_CONFIDENCE matching values to declare sync
|
||||
if best_match_len >= self.MIN_CONFIDENCE:
|
||||
# The match tells us where we WERE in the pattern
|
||||
# We need to set index to NEXT position for next transmission
|
||||
self.pattern_index = (best_match_idx + len(self.observed_window)) % self.PATTERN_LEN
|
||||
self.confidence = best_match_len
|
||||
|
||||
def _count_match(self, pattern_start_idx: int) -> int:
|
||||
"""
|
||||
Count how many values in observed_window match pattern starting at pattern_start_idx.
|
||||
|
||||
Returns:
|
||||
Number of consecutive matching values from start
|
||||
"""
|
||||
match_count = 0
|
||||
for i, observed in enumerate(self.observed_window):
|
||||
pattern_idx = (pattern_start_idx + i) % self.PATTERN_LEN
|
||||
if observed == self.PATTERN[pattern_idx]:
|
||||
match_count += 1
|
||||
else:
|
||||
break # Stop at first mismatch
|
||||
return match_count
|
||||
|
||||
def is_synchronized(self) -> bool:
|
||||
"""Returns True if we have synchronized to the pattern."""
|
||||
return self.pattern_index is not None
|
||||
|
||||
def checksum_lca_5_message(byte0: int, byte1: int, byte3: int, byte4: int, byte5: int) -> int:
|
||||
"""
|
||||
Calculate checksum for LCA_5 message 0x67 (byte 2)
|
||||
|
||||
Args:
|
||||
byte0: Byte 0 (0-255)
|
||||
byte1: Byte 1 (0-255)
|
||||
byte3: Byte 3 (0-255)
|
||||
byte4: Byte 4 (0-255)
|
||||
byte5: Byte 5 (0-255)
|
||||
|
||||
Returns:
|
||||
int: Checksum value (0-255) for byte 2
|
||||
|
||||
Example:
|
||||
>>> checksum = checksum_lca_5_message(0x80, 0x00, 0x4F, 0x00, 0x00)
|
||||
>>> print(f"0x{checksum:02X}")
|
||||
0x32
|
||||
"""
|
||||
|
||||
# Helper function to extract a bit (LSB = bit 0)
|
||||
def bit(byte_val, pos):
|
||||
return (byte_val >> pos) & 1
|
||||
|
||||
checksum = 0
|
||||
|
||||
# Bit 0: XOR of 13 bits
|
||||
checksum |= (
|
||||
bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 5) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 2) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 6) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 1) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 3) ^ bit(byte5, 6)
|
||||
) << 0
|
||||
|
||||
# Bit 1: XOR of 12 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 3) ^ bit(byte0, 4) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 0) ^ bit(byte1, 3) ^
|
||||
bit(byte3, 5) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 2) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 4) ^ bit(byte5, 7)
|
||||
) << 1
|
||||
|
||||
# Bit 2: XOR of 17 bits
|
||||
checksum |= (
|
||||
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 4) ^
|
||||
bit(byte1, 0) ^ bit(byte1, 1) ^ bit(byte1, 2) ^ bit(byte1, 4) ^
|
||||
bit(byte3, 4) ^
|
||||
bit(byte4, 1) ^ bit(byte4, 3) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 1) ^ bit(byte5, 3) ^ bit(byte5, 5) ^ bit(byte5, 6)
|
||||
) << 2
|
||||
|
||||
# Bit 3: XOR of 19 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 4) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 1) ^ bit(byte1, 3) ^ bit(byte1, 5) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 5) ^ bit(byte3, 6) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 1) ^ bit(byte4, 2) ^ bit(byte4, 4) ^ bit(byte4, 5) ^
|
||||
bit(byte5, 1) ^ bit(byte5, 2) ^ bit(byte5, 3) ^ bit(byte5, 4) ^ bit(byte5, 7)
|
||||
) << 3
|
||||
|
||||
# Bit 4: XOR of 16 bits
|
||||
checksum |= (
|
||||
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 4) ^ bit(byte1, 6) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 5) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 1) ^ bit(byte4, 2) ^ bit(byte4, 3) ^
|
||||
bit(byte5, 2) ^ bit(byte5, 4) ^ bit(byte5, 5) ^ bit(byte5, 6)
|
||||
) << 4
|
||||
|
||||
# Bit 5: XOR of 15 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 4) ^
|
||||
bit(byte1, 5) ^ bit(byte1, 7) ^
|
||||
bit(byte3, 5) ^ bit(byte3, 6) ^
|
||||
bit(byte4, 2) ^ bit(byte4, 3) ^ bit(byte4, 4) ^
|
||||
bit(byte5, 3) ^ bit(byte5, 5) ^ bit(byte5, 6) ^ bit(byte5, 7)
|
||||
) << 5
|
||||
|
||||
# Bit 6: XOR of 19 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 1) ^ bit(byte0, 3) ^ bit(byte0, 4) ^ bit(byte0, 5) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 0) ^ bit(byte1, 6) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 6) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 3) ^ bit(byte4, 4) ^ bit(byte4, 5) ^
|
||||
bit(byte5, 1) ^ bit(byte5, 4) ^ bit(byte5, 6) ^ bit(byte5, 7)
|
||||
) << 6
|
||||
|
||||
# Bit 7: XOR of 15 bits
|
||||
checksum |= (
|
||||
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 4) ^ bit(byte0, 5) ^
|
||||
bit(byte1, 1) ^ bit(byte1, 7) ^
|
||||
bit(byte3, 5) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 4) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 2) ^ bit(byte5, 5) ^ bit(byte5, 7)
|
||||
) << 7
|
||||
|
||||
return checksum
|
||||
|
||||
|
||||
# Test examples
|
||||
if __name__ == "__main__":
|
||||
print("CAN 0x67 Checksum Calculator")
|
||||
print("=" * 60)
|
||||
|
||||
# Test cases
|
||||
tests = [
|
||||
([0x80, 0x00, 0x4F, 0x00, 0x00, 0xBA, 0x00], 0x32),
|
||||
([0x80, 0x00, 0x8F, 0x00, 0x00, 0xBA, 0x00], 0x89),
|
||||
([0x80, 0x00, 0xCF, 0x00, 0x00, 0xBA, 0x00], 0xE0),
|
||||
([0x80, 0x00, 0x1F, 0x00, 0x00, 0xBA, 0x00], 0x06),
|
||||
]
|
||||
|
||||
all_passed = True
|
||||
for i, (bytes_list, expected) in enumerate(tests, 1):
|
||||
calculated = checksum_lca_5_message(*bytes_list[:5])
|
||||
status = "✓" if calculated == expected else "✗"
|
||||
|
||||
print(f"\nTest {i}: {status}")
|
||||
print(f" Bytes: {' '.join(f'{b:02X}' for b in bytes_list)}")
|
||||
print(f" Expected: 0x{expected:02X}")
|
||||
print(f" Calculated: 0x{calculated:02X}")
|
||||
|
||||
if calculated != expected:
|
||||
all_passed = False
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
if all_passed:
|
||||
print("All tests passed! ✓")
|
||||
else:
|
||||
print("Some tests failed! ✗")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user