mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-06 08:03:44 +08:00
Compare commits
45 Commits
bluescreensonly3
...
ELMo
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d0e3acb61 | |||
| 3fba6a4968 | |||
| 604a433ee4 | |||
| 87d007eb10 | |||
| ab77a59497 | |||
| 54a03a91b0 | |||
| cecc9bc8b6 | |||
| cec1a0fb62 | |||
| 097d63caef | |||
| de9cb64165 | |||
| 53e5c5246d | |||
| b5ab54ab6d | |||
| f55ad9162d | |||
| 901b93ac57 | |||
| 6850a8cdba | |||
| dd7ac353bd | |||
| fed4ce6ee0 | |||
| ab6351541f | |||
| 26ce46ba1d | |||
| c4ce84d037 | |||
| fbb0fccb0e | |||
| f43cbab52e | |||
| 4a50ca040f | |||
| 815267797c | |||
| 02e0f1ec9a | |||
| 509e6876f1 | |||
| b321dd71e6 | |||
| cc8c0dfb16 | |||
| ffb2b2c1e3 | |||
| 91d4b27aa3 | |||
| 693df09c88 | |||
| d2fb362876 | |||
| f18cf22104 | |||
| 352b23ddf5 | |||
| c7a3e3297a | |||
| b390513ad1 | |||
| 6f5e267493 | |||
| bb3b1429eb | |||
| 3b4a570564 | |||
| a3bdcf2417 | |||
| f553b8071d | |||
| f7fad2a4d5 | |||
| 5fe8b17467 | |||
| 51d8062c36 | |||
| 5122b8df42 |
Binary file not shown.
+32
-1
@@ -259,6 +259,32 @@ 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}},
|
||||
@@ -484,6 +510,9 @@ 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}},
|
||||
@@ -518,6 +547,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"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}},
|
||||
@@ -679,7 +710,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruAvhOnAtStartup", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruRedneckCruise", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
|
||||
Binary file not shown.
+125
-161
@@ -1,146 +1,128 @@
|
||||
# StarPilot Unified Model Rebuild
|
||||
# StarPilot Model Rebuild
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Safety
|
||||
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.
|
||||
|
||||
- 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.
|
||||
## Release Contract
|
||||
|
||||
## Workspace
|
||||
- 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.
|
||||
|
||||
The default workspace is:
|
||||
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:
|
||||
|
||||
```text
|
||||
/Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/
|
||||
hf://buckets/StarPilot-Driving/StarPilot-Resources/onnx/<source-id>/
|
||||
```
|
||||
|
||||
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
|
||||
Stage one model at a time in `/data/openpilot/uncompiledmodels`; this avoids
|
||||
filling the comma and prevents `./models` from selecting stale input files.
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py init
|
||||
python3 scripts/model_rebuild_pipeline.py extract \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
./models --<model-id> --version <behavior-version>
|
||||
./models --<gpu-model-id> --version v16 --gpu
|
||||
```
|
||||
|
||||
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:
|
||||
The default input is a single supercombo ONNX. For legacy sources use:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py extract \
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
./models --<model-id> --input-format split --version <behavior-version>
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Compile
|
||||
|
||||
Compile one model:
|
||||
The resumable bulk helper is:
|
||||
|
||||
```bash
|
||||
STAR_PILOT_MODEL_REMOTE=comma@192.168.3.110 \
|
||||
python3 scripts/model_rebuild_pipeline.py compile \
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
--workspace /Volumes/T5/StarPilot-Model-Rebuild \
|
||||
--source-map scripts/model_source_map_v25.json \
|
||||
--base-manifest ~/StarPilot-Resources/model_names_v25.json
|
||||
```
|
||||
|
||||
Compile or resume the full catalog:
|
||||
Failures are recorded under `results/`; rerun the same command to resume.
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py compile \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
## Driver Monitoring And Default
|
||||
|
||||
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:
|
||||
Driver monitoring is built once per tinygrad generation:
|
||||
|
||||
```bash
|
||||
./models --dm \
|
||||
@@ -148,61 +130,43 @@ Stage the current DM ONNX in `uncompiledmodels`, then run:
|
||||
--output-dir /tmp/dm_artifacts
|
||||
```
|
||||
|
||||
This builds:
|
||||
Replace these four files together:
|
||||
|
||||
- `dmonitoring_model_tinygrad.pkl`
|
||||
- `dmonitoring_model_metadata.pkl`
|
||||
- `dm_warp_1928x1208_tinygrad.pkl`
|
||||
- `dm_warp_1344x760_tinygrad.pkl`
|
||||
|
||||
All four files must be updated together.
|
||||
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.
|
||||
|
||||
## Manifest
|
||||
## Validation
|
||||
|
||||
Generate the base manifest after compilation, then namespace the release artifacts as v23:
|
||||
Run repository tests first:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py manifest \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
./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
|
||||
```
|
||||
|
||||
```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
|
||||
```
|
||||
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.
|
||||
|
||||
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.
|
||||
## Device Migration
|
||||
|
||||
After importing newly compiled sources, normalize the release namespace before
|
||||
copying files into either resource repository:
|
||||
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.
|
||||
|
||||
```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.
|
||||
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.
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ fi
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="19.6.16"
|
||||
export AGNOS_VERSION="19.6.19"
|
||||
fi
|
||||
|
||||
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
|
||||
|
||||
@@ -18,6 +18,26 @@ 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))
|
||||
@@ -85,6 +105,7 @@ class CarController(CarControllerBase):
|
||||
self.ford_lateral = None if CP.flags & FordFlags.LKA_STEERING else FordLateralController(CP)
|
||||
self.ford_shadow_curvature = 0.0
|
||||
self.ford_lateral_announced_mode = FordLateralMode.native
|
||||
self.stock_cruise_button = FordStockCruiseButton()
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
can_sends = []
|
||||
@@ -100,9 +121,23 @@ 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))
|
||||
|
||||
@@ -9,6 +9,7 @@ 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
|
||||
@@ -19,6 +20,24 @@ 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)
|
||||
|
||||
@@ -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,6 +256,17 @@ 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
|
||||
@@ -1003,14 +1014,13 @@ 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_for_powertrain and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
|
||||
if long_pitch_enabled 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))
|
||||
@@ -1031,7 +1041,7 @@ class CarController(CarControllerBase):
|
||||
if self.apply_brake > 0:
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
else:
|
||||
if long_pitch_for_powertrain and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
|
||||
if long_pitch_enabled 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
|
||||
@@ -1048,6 +1058,7 @@ 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(
|
||||
|
||||
@@ -28,6 +28,9 @@ 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):
|
||||
@@ -336,6 +339,28 @@ 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
|
||||
@@ -350,12 +375,15 @@ 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.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.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
|
||||
|
||||
cruise_btn = stabilize_bolt_cc_button(controller, CS.CP, cruise_btn)
|
||||
if cruise_btn == CruiseButtons.CANCEL:
|
||||
|
||||
@@ -500,9 +500,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.flags |= GMFlags.PEDAL_LONG.value
|
||||
|
||||
elif candidate in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC):
|
||||
# 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
|
||||
ret.minEnableSpeed = 0.
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.CHEVROLET_EQUINOX, CAR.CHEVROLET_EQUINOX_CC):
|
||||
@@ -666,7 +664,8 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
ret.openpilotLongitudinalControl = not disable_openpilot_long
|
||||
ret.pcmCruise = False
|
||||
ret.minEnableSpeed = 24 * CV.MPH_TO_MS
|
||||
if candidate not in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC):
|
||||
ret.minEnableSpeed = 24 * CV.MPH_TO_MS
|
||||
ret.radarUnavailable = True
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_CC_LONG.value
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ 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,
|
||||
@@ -895,6 +896,20 @@ 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)
|
||||
|
||||
|
||||
@@ -303,11 +303,36 @@ 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()
|
||||
@@ -603,6 +628,13 @@ 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)
|
||||
@@ -625,6 +657,60 @@ 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)
|
||||
|
||||
@@ -8,7 +8,7 @@ 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, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
@@ -24,6 +24,9 @@ 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
|
||||
@@ -454,6 +457,7 @@ 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
|
||||
@@ -717,6 +721,8 @@ 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,
|
||||
@@ -777,10 +783,12 @@ class CarController(CarControllerBase):
|
||||
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))
|
||||
|
||||
# Button messages
|
||||
if not self.long_active_ecu:
|
||||
if CC.cruiseControl.cancel:
|
||||
if self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
|
||||
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
|
||||
elif CC.cruiseControl.resume:
|
||||
# send resume at a max freq of 10Hz
|
||||
@@ -850,9 +858,6 @@ 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)
|
||||
@@ -1047,7 +1052,7 @@ class CarController(CarControllerBase):
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
|
||||
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info))
|
||||
self.last_button_frame = self.frame
|
||||
else:
|
||||
elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
|
||||
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
|
||||
|
||||
@@ -36,6 +36,8 @@ 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:
|
||||
@@ -142,6 +144,7 @@ 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
|
||||
@@ -242,7 +245,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.HYUNDAI_SONATA:
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
self.lda_button = int(cp.vl["BCM_PO_11"]["RAY_LKAS_BTN"] != 0) \
|
||||
if cp.ts_nanos["BCM_PO_11"]["RAY_LKAS_BTN"] > 0 else 0
|
||||
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
|
||||
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0
|
||||
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
|
||||
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
|
||||
@@ -347,9 +353,7 @@ class CarState(CarStateBase):
|
||||
|
||||
# cruise state
|
||||
no_scc = bool(self.CP.flags & HyundaiFlags.NON_SCC)
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
pass
|
||||
elif no_scc:
|
||||
if no_scc:
|
||||
cruise_available_msg, cruise_available_sig, cruise_enabled_msg, cruise_enabled_sig, cruise_speed_msg, cruise_speed_sig = get_non_scc_cruise_signals(self.CP)
|
||||
ret.cruiseState.available = cp.vl[cruise_available_msg][cruise_available_sig] != 0
|
||||
ret.cruiseState.enabled = cp.vl[cruise_enabled_msg][cruise_enabled_sig] != 0
|
||||
@@ -440,6 +444,8 @@ 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:
|
||||
@@ -722,6 +728,12 @@ 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))
|
||||
@@ -730,7 +742,7 @@ class CarState(CarStateBase):
|
||||
|
||||
parsers = {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], msgs, 0),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS12", 0)], 2),
|
||||
}
|
||||
if CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS:
|
||||
parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1)
|
||||
|
||||
@@ -81,6 +81,10 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
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
|
||||
|
||||
@@ -102,6 +106,19 @@ 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])
|
||||
|
||||
@@ -63,61 +63,6 @@ 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)
|
||||
@@ -156,13 +101,6 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
|
||||
if lka_icon is None:
|
||||
lka_icon = 2 if enabled else 1
|
||||
|
||||
if CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
ret = []
|
||||
if CP.openpilotLongitudinalControl:
|
||||
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LFA", CAN.ECAN, enabled, lat_active, apply_torque))
|
||||
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LKAS", CAN.ACAN, enabled, lat_active, apply_torque))
|
||||
return ret
|
||||
|
||||
angle_lkas_alt = CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
|
||||
|
||||
control_values = {
|
||||
|
||||
@@ -48,7 +48,7 @@ def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
|
||||
def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
ret.startAccel = 1.4
|
||||
ret.longitudinalActuatorDelay = 0.35
|
||||
ret.longitudinalActuatorDelay = 0.5
|
||||
ret.vEgoStarting = 0.5
|
||||
|
||||
|
||||
@@ -209,7 +209,9 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.enableBsm = 0x58b in fingerprint[CAN.ECAN]
|
||||
|
||||
# Send LFA message on cars with HDA
|
||||
if 0x485 in fingerprint[CAN.CAM]:
|
||||
if 0x485 in fingerprint[CAN.CAM] and (
|
||||
candidate != CAR.KIA_RAY_EV or fingerprint[CAN.CAM][0x485] == 4
|
||||
):
|
||||
ret.flags |= HyundaiFlags.SEND_LFA.value
|
||||
|
||||
# These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
|
||||
|
||||
@@ -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, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
|
||||
from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
|
||||
EV9LongitudinalTuningState, update_ev9_longitudinal_tuning, \
|
||||
BlindspotWarningState, update_blindspot_warning, \
|
||||
reset_egmp_longitudinal_tuning, \
|
||||
@@ -710,7 +710,7 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_LdwsActivemode"] == 0
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 0
|
||||
|
||||
def test_kia_ray_ev_preserves_stock_lkas_option(self):
|
||||
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)
|
||||
@@ -718,15 +718,46 @@ class TestHyundaiFingerprint:
|
||||
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, parser.vl["LKAS11"], False, 4, False,
|
||||
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"] == 1
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
|
||||
|
||||
def test_kia_ray_ev_delays_first_lkas11(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
@@ -752,6 +783,36 @@ class TestHyundaiFingerprint:
|
||||
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)
|
||||
@@ -796,6 +857,60 @@ 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 not (CP.flags & HyundaiFlags.SEND_LFA)
|
||||
|
||||
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
|
||||
@@ -864,7 +979,7 @@ 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, ("E_EMS11",), ("LABEL11", "EMS12", "SCC11", "SCC12")),
|
||||
(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()
|
||||
@@ -883,6 +998,66 @@ 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(2, 4)
|
||||
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
|
||||
|
||||
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):
|
||||
@@ -1176,7 +1351,7 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert CP.startAccel == pytest.approx(1.4)
|
||||
assert CP.vEgoStarting == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.35)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
|
||||
assert CP.vEgoStopping == pytest.approx(0.3)
|
||||
assert CP.stoppingDecelRate == pytest.approx(0.4)
|
||||
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin)
|
||||
@@ -1205,7 +1380,7 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert CP.startAccel == pytest.approx(1.4)
|
||||
assert CP.vEgoStarting == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.35)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
|
||||
|
||||
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, testing_ground_active=True)
|
||||
assert not kia_ev6_gt_line_longitudinal_tuning(CAR.KIA_EV6_2025, CP.carVin, testing_ground_active=True)
|
||||
@@ -2288,7 +2463,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_synthesizes_lkas_status_payload(self):
|
||||
def test_gv70_electrified_uses_generic_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)
|
||||
@@ -2331,9 +2506,11 @@ 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"] == 100
|
||||
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 0
|
||||
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)
|
||||
|
||||
@@ -124,6 +124,7 @@ class HyundaiStarPilotSafetyFlags(IntFlag):
|
||||
class HyundaiStarPilotFlags(IntFlag):
|
||||
SPEED_LIMIT_AVAILABLE = 1
|
||||
MAIN_CRUISE_STATE_TRACKING = 2 ** 2
|
||||
HAS_LKAS12 = 2 ** 9
|
||||
|
||||
|
||||
class HyundaiFlags(IntFlag):
|
||||
|
||||
@@ -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, SubaruSafetyFlags
|
||||
from opendbc.car.subaru.values import CAR as SUBARU, SUBARU_REDNECK_CRUISE_CARS, SubaruSafetyFlags
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags
|
||||
from opendbc.car.values import PLATFORMS
|
||||
from opendbc.can import CANParser
|
||||
@@ -240,6 +240,9 @@ 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
|
||||
@@ -297,6 +300,14 @@ class CarInterfaceBase(ABC):
|
||||
if getattr(starpilot_toggles, "subaru_sng", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value
|
||||
|
||||
fp_ret.redneckCruiseAvailable = candidate in SUBARU_REDNECK_CRUISE_CARS
|
||||
if fp_ret.redneckCruiseAvailable and params.get_bool("SubaruRedneckCruise") and \
|
||||
not CP.openpilotLongitudinalControl:
|
||||
fp_ret.pcmCruiseSpeed = False
|
||||
CP.openpilotLongitudinalControl = True
|
||||
CP.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
|
||||
|
||||
return fp_ret
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -4,7 +4,7 @@ from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, structs
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_std_steer_angle_limits, apply_steer_angle_limits_vm, common_fault_avoidance
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.subaru import subarucan
|
||||
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, SUBARU_AVH_CARS, SUBARU_STOP_START_CARS, CanBus, CarControllerParams, SubaruFlags
|
||||
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, CanBus, CarControllerParams, SubaruFlags
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
|
||||
@@ -37,8 +37,8 @@ _STOP_START_STARTUP_DELAY_FRAMES = 100
|
||||
_STOP_START_STARTUP_DEADLINE_FRAMES = 1000
|
||||
_STOP_START_PULSE_FRAMES = 30
|
||||
_STOP_START_PULSE_PERIOD_FRAMES = 5
|
||||
_AVH_STARTUP_DELAY_FRAMES = _STOP_START_STARTUP_DELAY_FRAMES
|
||||
_AVH_STARTUP_DEADLINE_FRAMES = _STOP_START_STARTUP_DEADLINE_FRAMES
|
||||
_REDNECK_BUTTON_INTERVAL_FRAMES = 10
|
||||
_REDNECK_BUTTON_COPIES = 2
|
||||
|
||||
|
||||
def get_safety_CP():
|
||||
@@ -89,9 +89,7 @@ class CarController(CarControllerBase):
|
||||
self.stop_start_initial_state = None
|
||||
self.stop_start_counter = 0
|
||||
self.stop_start_acknowledged = False
|
||||
self.avh_attempted = False
|
||||
self.avh_request_started = False
|
||||
self.avh_last_counter = None
|
||||
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.
|
||||
@@ -148,50 +146,6 @@ class CarController(CarControllerBase):
|
||||
self.stop_start_counter = (self.stop_start_counter + 1) % 0x10
|
||||
return msg
|
||||
|
||||
def _avh_on_request(self, CC, CS, starpilot_toggles):
|
||||
"""Send one bounded Subaru AVH ON request after ignition.
|
||||
|
||||
The AVH button frame was identified on the 2025 Legacy only. Keep this
|
||||
independent from Stop/Start so the existing Outback request is unchanged.
|
||||
"""
|
||||
if self.CP.carFingerprint not in SUBARU_AVH_CARS or \
|
||||
not getattr(starpilot_toggles, "subaru_avh_on", False) or self.avh_attempted:
|
||||
return None
|
||||
|
||||
if self.frame > _AVH_STARTUP_DEADLINE_FRAMES or getattr(CC, "enabled", False):
|
||||
self.avh_attempted = True
|
||||
return None
|
||||
|
||||
if self.frame < _AVH_STARTUP_DELAY_FRAMES or not getattr(getattr(CS, "out", None), "canValid", True):
|
||||
return None
|
||||
|
||||
out = CS.out
|
||||
if not getattr(out, "standstill", False) or out.gearShifter not in (
|
||||
structs.CarState.GearShifter.park,
|
||||
structs.CarState.GearShifter.neutral,
|
||||
):
|
||||
return None
|
||||
|
||||
avh_msg = getattr(CS, "avh_msg", None)
|
||||
avh_dat = getattr(CS, "avh_dat", None)
|
||||
if not avh_msg or not avh_dat:
|
||||
return None
|
||||
|
||||
if not self.avh_request_started:
|
||||
self.avh_request_started = True
|
||||
self.avh_last_counter = int(avh_msg.get("COUNTER", 0)) % 0x10
|
||||
|
||||
counter = int(avh_msg.get("COUNTER", 0)) % 0x10
|
||||
if counter == self.avh_last_counter:
|
||||
return None
|
||||
|
||||
self.avh_attempted = True
|
||||
msg = subarucan.create_avh_control(
|
||||
self.packer, avh_msg, raw_dat=avh_dat,
|
||||
counter=counter, bus=CanBus.alt_for_cp(self.CP),
|
||||
)
|
||||
return msg
|
||||
|
||||
def _reset_legacy_2025_handoff(self):
|
||||
self.driver_override = False
|
||||
self.angle_override_confirm_frames = 0
|
||||
@@ -458,6 +412,10 @@ 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 = []
|
||||
|
||||
@@ -465,10 +423,6 @@ class CarController(CarControllerBase):
|
||||
if stop_start_msg is not None:
|
||||
can_sends.append(stop_start_msg)
|
||||
|
||||
avh_msg = self._avh_on_request(CC, CS, starpilot_toggles)
|
||||
if avh_msg is not None:
|
||||
can_sends.append(avh_msg)
|
||||
|
||||
# *** steering ***
|
||||
if (self.frame % self.p.STEER_STEP) == 0:
|
||||
if self.CP.flags & SubaruFlags.LKAS_ANGLE:
|
||||
@@ -526,7 +480,8 @@ 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, CC.longActive, hud_control.leadVisible,
|
||||
self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise,
|
||||
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,
|
||||
@@ -544,7 +499,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:
|
||||
if self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise:
|
||||
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))
|
||||
@@ -560,6 +515,20 @@ 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,12 +1,20 @@
|
||||
import copy
|
||||
from cereal import custom
|
||||
from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car import Bus, create_button_events, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.subaru.values import DBC, CanBus, SUBARU_AVH_CARS, SUBARU_STOP_START_CARS, SubaruFlags
|
||||
from opendbc.car.subaru.values import DBC, CanBus, SUBARU_REDNECK_CRUISE_CARS, SUBARU_STOP_START_CARS, SubaruFlags
|
||||
from opendbc.car import CanSignalRateCalculator
|
||||
|
||||
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):
|
||||
@@ -18,8 +26,8 @@ class CarState(CarStateBase):
|
||||
self.dashlights_msg = {}
|
||||
self.dashlights_dat = b""
|
||||
self.stop_start_state = 0
|
||||
self.avh_msg = {}
|
||||
self.avh_dat = b""
|
||||
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]
|
||||
@@ -35,11 +43,6 @@ class CarState(CarStateBase):
|
||||
self.dashlights_dat = stop_start_cp.vl_raw["Dashlights"]
|
||||
self.stop_start_state = stop_start_cp.vl["Engine_Stop_Start"]["STOP_START_STATE"]
|
||||
|
||||
if self.CP.carFingerprint in SUBARU_AVH_CARS:
|
||||
avh_cp = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
|
||||
self.avh_msg = copy.copy(avh_cp.vl["AVH"])
|
||||
self.avh_dat = avh_cp.vl_raw["AVH"]
|
||||
|
||||
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"]
|
||||
ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
@@ -143,6 +146,17 @@ 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"])
|
||||
|
||||
@@ -163,11 +177,10 @@ class CarState(CarStateBase):
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
avh_messages = [("AVH", 0)] if CP.carFingerprint in SUBARU_AVH_CARS else []
|
||||
parsers = {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main_for_cp(CP)),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.camera),
|
||||
Bus.alt: CANParser(DBC[CP.carFingerprint][Bus.pt], avh_messages, CanBus.alt_for_cp(CP))
|
||||
Bus.alt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.alt_for_cp(CP))
|
||||
}
|
||||
if CP.flags & SubaruFlags.D_PLATFORM:
|
||||
parsers[Bus.main] = CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main)
|
||||
|
||||
@@ -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_AVH_CARS, SUBARU_STOP_START_CARS, SubaruFlags, SubaruSafetyFlags
|
||||
from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, SubaruFlags, SubaruSafetyFlags
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@@ -42,8 +42,6 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value
|
||||
if candidate in SUBARU_STOP_START_CARS:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
|
||||
if candidate in SUBARU_AVH_CARS:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.AVH_BUTTON.value
|
||||
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ 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 = {
|
||||
@@ -67,6 +71,19 @@ def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long
|
||||
return packer.make_can_msg("ES_Distance", bus, values)
|
||||
|
||||
|
||||
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 [
|
||||
@@ -208,31 +225,6 @@ def create_stop_start_control(packer, dashlights_msg, raw_dat=None, counter=None
|
||||
return packer.make_can_msg("Dashlights", bus, values)
|
||||
|
||||
|
||||
def create_avh_control(packer, avh_msg, raw_dat=None, counter=None, bus=CanBus.alt):
|
||||
"""Create the supported Subaru Legacy AVH ON request.
|
||||
|
||||
AVH is carried in the live 0x32b frame. Preserve the other bytes and update
|
||||
only the rolling counter, AVH bit, and Subaru additive checksum.
|
||||
"""
|
||||
if raw_dat:
|
||||
dat = bytearray(raw_dat)
|
||||
if len(dat) != 8:
|
||||
raise ValueError(f"AVH frame must be 8 bytes, got {len(dat)}")
|
||||
if counter is None:
|
||||
counter = (int(avh_msg.get("COUNTER", 0)) + 1) % 0x10
|
||||
dat[1] = (dat[1] & 0xF0) | (counter % 0x10)
|
||||
dat[5] |= 0x20 # AVH, big-endian bit 45
|
||||
dat[0] = ((0x32B & 0xFF) + ((0x32B >> 8) & 0xFF) + sum(dat[1:])) & 0xFF
|
||||
return 0x32B, bytes(dat), bus
|
||||
|
||||
values = dict(avh_msg)
|
||||
if counter is None:
|
||||
counter = (int(values.get("COUNTER", 0)) + 1) % 0x10
|
||||
values["COUNTER"] = counter % 0x10
|
||||
values["AVH"] = 1
|
||||
return packer.make_can_msg("AVH", bus, values)
|
||||
|
||||
|
||||
def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value, bus=CanBus.main):
|
||||
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, structs
|
||||
from opendbc.car import Bus, fw_versions, gen_empty_fingerprint, structs
|
||||
from opendbc.car.fw_query_definitions import StdQueries
|
||||
from opendbc.car.subaru import subarucan
|
||||
from opendbc.car.subaru.carcontroller import CarController
|
||||
@@ -67,6 +67,56 @@ def test_preglobal_sng_does_not_send_standstill_keepalive_without_manual_toggle(
|
||||
assert speed_cmd is False
|
||||
|
||||
|
||||
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]
|
||||
@@ -194,7 +244,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.AVH_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
|
||||
@@ -225,21 +274,6 @@ def test_stop_start_inputs_are_captured_for_supported_models(platform):
|
||||
assert car_state.stop_start_state == 3
|
||||
|
||||
|
||||
def test_avh_inputs_are_captured_for_legacy_2025():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
|
||||
car_state = CarState(CP, None)
|
||||
parsers = car_state.get_can_parsers(CP)
|
||||
raw_avh = bytes.fromhex("230f1c4208800000")
|
||||
parsers[Bus.alt].vl["AVH"]["COUNTER"] = 15
|
||||
parsers[Bus.alt].vl["AVH"]["AVH"] = 0
|
||||
parsers[Bus.alt].vl_raw["AVH"] = raw_avh
|
||||
|
||||
car_state.update(parsers, SimpleNamespace(subaru_sng=False))
|
||||
|
||||
assert car_state.avh_msg["COUNTER"] == 15
|
||||
assert car_state.avh_dat == raw_avh
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform, expected_bus, start_frame", [
|
||||
(CAR.SUBARU_OUTBACK_2023, CanBus.alt, 101),
|
||||
(CAR.SUBARU_LEGACY_2025, CanBus.alt, 401),
|
||||
@@ -292,61 +326,6 @@ def test_stop_start_request_is_bounded_and_uses_live_dashlights(platform, expect
|
||||
assert controller.stop_start_acknowledged
|
||||
|
||||
|
||||
def test_avh_request_sets_observed_bit_and_is_bounded():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
|
||||
controller = CarController({}, CP)
|
||||
controller.frame = 101
|
||||
|
||||
class TestActuators:
|
||||
steeringAngleDeg = 0.0
|
||||
|
||||
def as_builder(self):
|
||||
return SimpleNamespace(steeringAngleDeg=self.steeringAngleDeg)
|
||||
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=False,
|
||||
longActive=False,
|
||||
actuators=TestActuators(),
|
||||
hudControl=SimpleNamespace(leadVisible=False),
|
||||
cruiseControl=SimpleNamespace(cancel=False),
|
||||
)
|
||||
CS = SimpleNamespace(
|
||||
canValid=True,
|
||||
avh_msg={"COUNTER": 15, "AVH": 0},
|
||||
avh_dat=bytes.fromhex("230f1c4208800000"),
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
gearShifter=structs.CarState.GearShifter.park,
|
||||
),
|
||||
)
|
||||
toggles = SimpleNamespace(subaru_stop_start_off=False, subaru_avh_on=True, subaru_sng=False)
|
||||
|
||||
# Start the request from the current live counter. AVH is a native 10 Hz
|
||||
# frame, so the controller waits for each next live counter before sending
|
||||
# its matching button frame.
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
avh_msgs = [msg for msg in can_sends if msg[0] == 0x32b]
|
||||
assert not avh_msgs
|
||||
|
||||
CS.avh_msg["COUNTER"] = 0
|
||||
CS.avh_dat = bytes.fromhex("14001c4208800000")
|
||||
controller.frame = 103
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
avh_msgs = [msg for msg in can_sends if msg[0] == 0x32b]
|
||||
assert avh_msgs == [(0x32b, bytes.fromhex("34001c4208a00000"), CanBus.alt)]
|
||||
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("AVH", 0)], CanBus.alt)
|
||||
parser.update([(CanBus.alt, avh_msgs)])
|
||||
assert parser.vl["AVH"]["AVH"] == 1
|
||||
assert parser.vl["AVH"]["COUNTER"] == 0
|
||||
|
||||
controller.frame = 131
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
assert not any(msg[0] == 0x32b for msg in can_sends)
|
||||
assert controller.avh_attempted
|
||||
|
||||
|
||||
def test_legacy_2025_uses_gen2_angle_bus_layout():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
|
||||
parsers = CarState.get_can_parsers(CP)
|
||||
@@ -358,7 +337,6 @@ def test_legacy_2025_uses_gen2_angle_bus_layout():
|
||||
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 CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.AVH_BUTTON
|
||||
assert CanBus.main_for_cp(CP) == CanBus.main
|
||||
assert CanBus.angle_for_cp(CP) == CanBus.main
|
||||
assert parsers[Bus.pt].bus == CanBus.main
|
||||
|
||||
@@ -89,7 +89,7 @@ class SubaruSafetyFlags(IntFlag):
|
||||
D_PLATFORM_CAMERA = 64
|
||||
FIXED_ANGLE_LIMITS = 128
|
||||
STOP_START_BUTTON = 256
|
||||
AVH_BUTTON = 512
|
||||
REDNECK_CRUISE = 512
|
||||
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
|
||||
|
||||
|
||||
@@ -276,11 +276,10 @@ SUBARU_STOP_START_CARS = (
|
||||
CAR.SUBARU_LEGACY_2025,
|
||||
)
|
||||
|
||||
SUBARU_AVH_CARS = (
|
||||
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]) + \
|
||||
|
||||
@@ -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
|
||||
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, RADAR_ACC_CAR, SECOC_CAR
|
||||
from opendbc.can import CANPacker
|
||||
|
||||
Ecu = structs.CarParams.Ecu
|
||||
@@ -49,6 +49,8 @@ MAX_USER_TORQUE = 500
|
||||
PARK = structs.CarState.GearShifter.park
|
||||
REVERSE = structs.CarState.GearShifter.reverse
|
||||
|
||||
TOYOTA_AUTO_HOLD_CARS = TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR
|
||||
|
||||
# Lock / unlock door commands - Credit goes to AlexandreSato!
|
||||
LOCK_CMD = b"\x40\x05\x30\x11\x00\x80\x00\x00"
|
||||
UNLOCK_CMD = b"\x40\x05\x30\x11\x00\x40\x00\x00"
|
||||
@@ -72,6 +74,14 @@ def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
|
||||
) 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_long_tune(CP, params):
|
||||
kiBP = [2., 5.]
|
||||
kiV = [0.5, 0.25]
|
||||
@@ -243,11 +253,8 @@ 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):
|
||||
@@ -299,15 +306,12 @@ 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:
|
||||
if brake_hold_allowed and not self.brake_hold_active and CS.out.brakePressed:
|
||||
self._brake_hold_counter += 1
|
||||
self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer 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_active = self._brake_hold_counter > brake_hold_allowed_timer
|
||||
elif not brake_hold_allowed:
|
||||
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))
|
||||
@@ -409,8 +413,11 @@ class CarController(CarControllerBase):
|
||||
# *** gas and brake ***
|
||||
|
||||
self._update_standstill_request(CC, CS, actuators, starpilot_toggles)
|
||||
if self.auto_brake_hold:
|
||||
if supports_toyota_auto_hold(self.CP, getattr(starpilot_toggles, "toyota_auto_hold", False)):
|
||||
can_sends.extend(self.create_auto_brake_hold_messages(CS))
|
||||
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)
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class CarState(CarStateBase):
|
||||
self.distance_button = 0
|
||||
|
||||
self.pcm_follow_distance = 0
|
||||
self.pcm_acc_status = 0
|
||||
|
||||
self.acc_type = 1
|
||||
self.lkas_hud = {}
|
||||
@@ -208,6 +209,7 @@ 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
|
||||
@@ -264,8 +266,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, False, {1: ButtonType.accelCruise}),
|
||||
*create_button_events(self.pcm_acc_status == 10, False, {1: ButtonType.decelCruise}),
|
||||
*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}),
|
||||
]
|
||||
|
||||
fp_ret.dashboardSpeedLimit = calculate_speed_limit(cp_cam)
|
||||
|
||||
@@ -12,7 +12,8 @@ from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_fee
|
||||
get_prius_positive_feedforward_scale, \
|
||||
limit_interceptor_pcm_accel, \
|
||||
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
|
||||
limit_prius_stopping_accel, should_bypass_toyota_long_pid, update_permit_braking
|
||||
limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
|
||||
update_permit_braking
|
||||
from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_interceptor_gas_pressed, create_lkas_button_events
|
||||
from opendbc.car.toyota.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.toyota.interface import CarInterface
|
||||
@@ -205,6 +206,22 @@ class TestToyotaInterfaces:
|
||||
assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
|
||||
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
|
||||
|
||||
def test_auto_hold_is_disabled_by_default(self):
|
||||
params = Params()
|
||||
params.remove("ToyotaAutoHold")
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.TOYOTA_CAMRY_TSS2,
|
||||
{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,
|
||||
@@ -750,6 +767,74 @@ 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 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)
|
||||
|
||||
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)
|
||||
|
||||
@@ -893,14 +978,12 @@ class TestToyotaCarController:
|
||||
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=False,
|
||||
brakePressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
),
|
||||
pre_collision_2={},
|
||||
|
||||
@@ -11,7 +11,6 @@ TransmissionType = structs.CarParams.TransmissionType
|
||||
# Must match VOLVO_SPEED_TO_MS in opendbc/safety/modes/volvo.h.
|
||||
SPEED_TO_MS = 0.003977
|
||||
STEERING_PRESSED_THRESHOLD = 2
|
||||
STEERING_DISENGAGE_THRESHOLD = 5
|
||||
|
||||
|
||||
class CarState(CarStateBase):
|
||||
@@ -75,11 +74,9 @@ class CarState(CarStateBase):
|
||||
ret.steeringAngleDeg = cp_party.vl['PSCM']['PSCM_ANGLE_SENSOR'] # openpilot expects a negative value for a right turn
|
||||
#ret.steeringAngleDeg = cp_party.vl['SAS']['SAS_ANGLE_SENSOR']
|
||||
|
||||
# Driver steering torque feedback (used for driver override detection)
|
||||
ret.steeringTorque = -cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'] # Car right turn is negative, openpilot right turn is positive
|
||||
driver_input = abs(cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'])
|
||||
ret.steeringPressed = driver_input > STEERING_PRESSED_THRESHOLD
|
||||
ret.steeringDisengage = driver_input > STEERING_DISENGAGE_THRESHOLD
|
||||
|
||||
# EPS status - placeholder until actual signal is found
|
||||
self.eps_active = True # Assume EPS is active for now
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
CM_ "IMPORT _subaru_global.dbc";
|
||||
|
||||
BO_ 811 AVH: 8 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ AVH : 45|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 72 Transmission: 8 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
@@ -1497,9 +1497,11 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
|
||||
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
|
||||
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
|
||||
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ RAY_LKAS_BTN : 0|2@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 1426 LABEL11: 8 XXX
|
||||
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
|
||||
SG_ CC_Engaged : 35|1@1+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 910 WHL_SPD12_FS: 5 iBAU
|
||||
SG_ CRC : 0|8@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
@@ -1497,9 +1497,11 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
|
||||
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
|
||||
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
|
||||
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ RAY_LKAS_BTN : 0|2@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 1426 LABEL11: 8 XXX
|
||||
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
|
||||
SG_ CC_Engaged : 35|1@1+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 910 WHL_SPD12_FS: 5 iBAU
|
||||
SG_ CRC : 0|8@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
@@ -307,11 +307,6 @@ VAL_ 544 AEB_Status 12 "AEB related" 8 "AEB actuation" 4 "AEB related" 0 "No AEB
|
||||
|
||||
CM_ "subaru_global_2017.dbc starts here";
|
||||
|
||||
BO_ 811 AVH: 8 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ AVH : 45|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 72 Transmission: 8 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
@@ -89,6 +89,8 @@ static bool ford_get_quality_flag_valid(const CANPacket_t *msg) {
|
||||
static bool ford_lka_steering = false;
|
||||
static bool ford_extended_lateral = false;
|
||||
static bool ford_angle_mode = false;
|
||||
static bool ford_longitudinal = false;
|
||||
static bool ford_cancel_resume_button = false;
|
||||
static int16_t ford_shadow_curvature = 0;
|
||||
|
||||
// Curvature rate limits
|
||||
@@ -219,6 +221,10 @@ static void ford_rx_hook(const CANPacket_t *msg) {
|
||||
|
||||
acc_main_on = (cruise_state == 3U) || cruise_engaged;
|
||||
}
|
||||
|
||||
if (msg->addr == FORD_Steering_Data_FD1) {
|
||||
ford_cancel_resume_button = ((msg->data[2] >> 5) & 1U) != 0U;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +282,8 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
|
||||
// if cancel button is pressed when cruise isn't engaged.
|
||||
bool violation = false;
|
||||
violation |= ((msg->data[1] >> 0) & 1U) && !cruise_engaged_prev; // Signal: CcAslButtnCnclPress (cancel)
|
||||
violation |= ((msg->data[3] >> 1) & 1U) && !controls_allowed; // Signal: CcAsllButtnResPress (resume)
|
||||
bool stock_resume_from_driver = !ford_longitudinal && acc_main_on && ford_cancel_resume_button;
|
||||
violation |= ((msg->data[3] >> 1) & 1U) && !(controls_allowed || stock_resume_from_driver); // Signal: CcAsllButtnResPress (resume)
|
||||
|
||||
if (violation) {
|
||||
tx = false;
|
||||
@@ -415,6 +422,7 @@ static safety_config ford_init(uint16_t param) {
|
||||
{.msg = {{FORD_Yaw_Data_FD1, 0, 8, 100U, .max_counter = 255U}, { 0 }, { 0 }}},
|
||||
// These messages have no counter or checksum
|
||||
{.msg = {{FORD_EngBrakeData, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{FORD_Steering_Data_FD1, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{FORD_EngVehicleSpThrottle, 0, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{FORD_DesiredTorqBrk, 0, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
@@ -454,10 +462,11 @@ static safety_config ford_init(uint16_t param) {
|
||||
ford_lka_steering = GET_FLAG(param, FORD_PARAM_LKA_STEERING);
|
||||
ford_extended_lateral = false;
|
||||
ford_angle_mode = false;
|
||||
ford_cancel_resume_button = false;
|
||||
ford_shadow_curvature = 0;
|
||||
ford_desired_path_angle_last = 0;
|
||||
|
||||
bool ford_longitudinal = false;
|
||||
ford_longitudinal = false;
|
||||
|
||||
#ifdef ALLOW_DEBUG
|
||||
const uint16_t FORD_PARAM_LONGITUDINAL = 1;
|
||||
|
||||
@@ -29,6 +29,7 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = {
|
||||
{0x340, 0, 8, .check_relay = true}, /* LKAS11 Bus 0 */ \
|
||||
{0x4F1, scc_bus, 4, .check_relay = false}, /* CLU11 Bus 0 (radar-SCC) or 2 (camera-SCC) */ \
|
||||
{0x485, 0, (can_refresh) ? 8 : 4, .check_relay = true}, /* LFAHDA_MFC Bus 0 */ \
|
||||
{0x53E, 0, 6, .check_relay = false}, /* LKAS12 replacement after camera advertises it */ \
|
||||
|
||||
#define HYUNDAI_LONG_COMMON_TX_MSGS(scc_bus, can_refresh) \
|
||||
HYUNDAI_COMMON_TX_MSGS(scc_bus, can_refresh) \
|
||||
@@ -140,6 +141,12 @@ static uint32_t hyundai_get_checksum(const CANPacket_t *msg) {
|
||||
return chksum;
|
||||
}
|
||||
|
||||
static void hyundai_rx_all_hook(const CANPacket_t *msg) {
|
||||
if ((msg->addr == 0x53EU) && (msg->bus == 2U) && (GET_LEN(msg) == 6U)) {
|
||||
hyundai_has_lkas12 = true;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t hyundai_compute_checksum(const CANPacket_t *msg) {
|
||||
uint8_t chksum = 0;
|
||||
if (msg->addr == 0x386U) {
|
||||
@@ -284,6 +291,10 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
|
||||
|
||||
bool tx = true;
|
||||
|
||||
if ((msg->addr == 0x53EU) && !hyundai_has_lkas12) {
|
||||
tx = false;
|
||||
}
|
||||
|
||||
// FCA11: Block any potential actuation. The blended HDA II layout uses
|
||||
// different static fields, but its explicit AEB/FCA request bits stay zero.
|
||||
if (msg->addr == 0x38DU) {
|
||||
@@ -695,6 +706,7 @@ static safety_config hyundai_legacy_init(uint16_t param) {
|
||||
const safety_hooks hyundai_hooks = {
|
||||
.init = hyundai_init,
|
||||
.rx = hyundai_rx_hook,
|
||||
.rx_all = hyundai_rx_all_hook,
|
||||
.tx = hyundai_tx_hook,
|
||||
.get_counter = hyundai_get_counter,
|
||||
.get_checksum = hyundai_get_checksum,
|
||||
@@ -704,6 +716,7 @@ const safety_hooks hyundai_hooks = {
|
||||
const safety_hooks hyundai_legacy_hooks = {
|
||||
.init = hyundai_legacy_init,
|
||||
.rx = hyundai_rx_hook,
|
||||
.rx_all = hyundai_rx_all_hook,
|
||||
.tx = hyundai_tx_hook,
|
||||
.get_counter = hyundai_get_counter,
|
||||
.get_checksum = hyundai_get_checksum,
|
||||
|
||||
@@ -63,6 +63,9 @@ bool hyundai_cancel_button_enable = false;
|
||||
extern bool hyundai_can_refresh_msgs;
|
||||
bool hyundai_can_refresh_msgs = false;
|
||||
|
||||
extern bool hyundai_has_lkas12;
|
||||
bool hyundai_has_lkas12 = false;
|
||||
|
||||
extern bool hyundai_elantra_hev_2024;
|
||||
bool hyundai_elantra_hev_2024 = false;
|
||||
|
||||
@@ -106,6 +109,7 @@ void hyundai_common_init(uint16_t param) {
|
||||
hyundai_non_scc = GET_FLAG(param, HYUNDAI_PARAM_NON_SCC);
|
||||
hyundai_cancel_button_enable = GET_FLAG(param, HYUNDAI_PARAM_CANCEL_BTN_ENABLE);
|
||||
hyundai_can_refresh_msgs = GET_FLAG(param, HYUNDAI_PARAM_CAN_REFRESH_MSGS);
|
||||
hyundai_has_lkas12 = false;
|
||||
hyundai_elantra_hev_2024 = hyundai_can_refresh_msgs && hyundai_hybrid_gas_signal && hyundai_camera_scc;
|
||||
hyundai_aol_main_lkas_sync = false;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#define MSG_SUBARU_ES_DashStatus 0x321U
|
||||
#define MSG_SUBARU_ES_LKAS_State 0x322U
|
||||
#define MSG_SUBARU_ES_Infotainment 0x323U
|
||||
#define MSG_SUBARU_Cruise_Buttons 0x146U
|
||||
|
||||
#define MSG_SUBARU_ES_UDS_Request 0x787U
|
||||
|
||||
@@ -42,7 +43,6 @@
|
||||
#define MSG_SUBARU_ES_STATIC_1 0x22aU
|
||||
#define MSG_SUBARU_ES_STATIC_2 0x325U
|
||||
#define MSG_SUBARU_Dashlights 0x390U
|
||||
#define MSG_SUBARU_AVH 0x32bU
|
||||
|
||||
#define SUBARU_MAIN_BUS 0U
|
||||
#define SUBARU_ALT_BUS 1U
|
||||
@@ -57,6 +57,9 @@
|
||||
#define SUBARU_COMMON_TX_MSGS(alt_bus) \
|
||||
{MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = false}, \
|
||||
|
||||
#define SUBARU_REDNECK_TX_MSGS() \
|
||||
{MSG_SUBARU_Cruise_Buttons, SUBARU_MAIN_BUS, 8, .check_relay = false}, \
|
||||
|
||||
#define SUBARU_D_PLATFORM_ANGLE_TX_MSGS(bus) \
|
||||
{MSG_SUBARU_ES_LKAS_ANGLE, bus, 8, .check_relay = true}, \
|
||||
{MSG_SUBARU_ES_DashStatus, bus, 8, .check_relay = true}, \
|
||||
@@ -66,13 +69,6 @@
|
||||
#define SUBARU_STOP_START_TX_MSGS(bus) \
|
||||
{MSG_SUBARU_Dashlights, bus, 8, .check_relay = false}, \
|
||||
|
||||
#define SUBARU_AVH_TX_MSGS(bus) \
|
||||
{MSG_SUBARU_AVH, bus, 8, .check_relay = false}, \
|
||||
|
||||
#define SUBARU_STOP_START_AVH_TX_MSGS(bus) \
|
||||
SUBARU_STOP_START_TX_MSGS(bus) \
|
||||
SUBARU_AVH_TX_MSGS(bus)
|
||||
|
||||
#define SUBARU_COMMON_LONG_TX_MSGS(alt_bus) \
|
||||
{MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = true}, \
|
||||
{MSG_SUBARU_ES_Brake, alt_bus, 8, .check_relay = true}, \
|
||||
@@ -121,7 +117,7 @@ static bool subaru_lkas_angle = false;
|
||||
static bool subaru_d_platform = false;
|
||||
static bool subaru_fixed_angle_limits = false;
|
||||
static bool subaru_stop_start_button = false;
|
||||
static bool subaru_avh_button = false;
|
||||
static bool subaru_redneck_cruise = false;
|
||||
|
||||
static uint32_t subaru_get_checksum(const CANPacket_t *msg) {
|
||||
return (uint8_t)msg->data[0];
|
||||
@@ -306,10 +302,9 @@ static bool subaru_tx_hook(const CANPacket_t *msg) {
|
||||
violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg);
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_SUBARU_AVH) {
|
||||
violation |= !subaru_avh_button;
|
||||
violation |= msg->bus != (subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS);
|
||||
violation |= !GET_BIT(msg, 45U);
|
||||
if (msg->addr == MSG_SUBARU_Cruise_Buttons) {
|
||||
violation |= !subaru_redneck_cruise;
|
||||
violation |= msg->bus != SUBARU_MAIN_BUS;
|
||||
violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg);
|
||||
}
|
||||
|
||||
@@ -325,6 +320,19 @@ static safety_config subaru_init(uint16_t param) {
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_REDNECK_TX_MSGS_CONFIG[] = {
|
||||
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
SUBARU_REDNECK_TX_MSGS()
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_REDNECK_STOP_AND_GO_TX_MSGS_CONFIG[] = {
|
||||
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
SUBARU_REDNECK_TX_MSGS()
|
||||
SUBARU_STOP_AND_GO_ADDITIONAL_TX_MSGS()
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_LONG_TX_MSGS[] = {
|
||||
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
|
||||
SUBARU_COMMON_LONG_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
@@ -363,12 +371,6 @@ static safety_config subaru_init(uint16_t param) {
|
||||
SUBARU_STOP_START_TX_MSGS(SUBARU_ALT_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_GEN2_LKAS_ANGLE_STOP_START_AVH_TX_MSGS[] = {
|
||||
SUBARU_BASE_TX_MSGS(SUBARU_ALT_BUS, MSG_SUBARU_ES_LKAS_ANGLE)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
SUBARU_STOP_START_AVH_TX_MSGS(SUBARU_ALT_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS[] = {
|
||||
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
@@ -380,12 +382,6 @@ static safety_config subaru_init(uint16_t param) {
|
||||
SUBARU_STOP_START_TX_MSGS(SUBARU_ALT_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_D_PLATFORM_ANGLE_STOP_START_AVH_MAIN_TX_MSGS[] = {
|
||||
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
SUBARU_STOP_START_AVH_TX_MSGS(SUBARU_ALT_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS[] = {
|
||||
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_CAM_BUS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
@@ -433,8 +429,8 @@ static safety_config subaru_init(uint16_t param) {
|
||||
const uint16_t SUBARU_PARAM_STOP_START_BUTTON = 256;
|
||||
subaru_stop_start_button = GET_FLAG(param, SUBARU_PARAM_STOP_START_BUTTON);
|
||||
|
||||
const uint16_t SUBARU_PARAM_AVH_BUTTON = 512;
|
||||
subaru_avh_button = GET_FLAG(param, SUBARU_PARAM_AVH_BUTTON);
|
||||
const uint16_t SUBARU_PARAM_REDNECK_CRUISE = 512;
|
||||
subaru_redneck_cruise = GET_FLAG(param, SUBARU_PARAM_REDNECK_CRUISE);
|
||||
|
||||
#ifdef ALLOW_DEBUG
|
||||
const uint16_t SUBARU_PARAM_LONGITUDINAL = 2;
|
||||
@@ -443,19 +439,19 @@ static safety_config subaru_init(uint16_t param) {
|
||||
|
||||
safety_config ret;
|
||||
if (subaru_lkas_angle) {
|
||||
ret = subaru_d_platform ? (subaru_stop_start_button ? (subaru_avh_button ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_STOP_START_AVH_MAIN_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_STOP_START_MAIN_TX_MSGS)) : \
|
||||
ret = subaru_d_platform ? (subaru_stop_start_button ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_STOP_START_MAIN_TX_MSGS) : \
|
||||
(subaru_d_platform_camera ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS))) : \
|
||||
subaru_gen2 ? (subaru_stop_start_button ? (subaru_avh_button ? BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_STOP_START_AVH_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_STOP_START_TX_MSGS)) : \
|
||||
subaru_gen2 ? (subaru_stop_start_button ? BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_STOP_START_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_TX_MSGS)) : \
|
||||
BUILD_SAFETY_CFG(subaru_lkas_angle_rx_checks, SUBARU_LKAS_ANGLE_TX_MSGS);
|
||||
} else if (subaru_gen2) {
|
||||
ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_LONG_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_TX_MSGS);
|
||||
} else {
|
||||
ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_LONG_TX_MSGS) : \
|
||||
ret = subaru_redneck_cruise ? (subaru_stop_and_go ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_REDNECK_STOP_AND_GO_TX_MSGS_CONFIG) : \
|
||||
BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_REDNECK_TX_MSGS_CONFIG)) : \
|
||||
subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_LONG_TX_MSGS) : \
|
||||
subaru_stop_and_go ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_STOP_AND_GO_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_TX_MSGS);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#define VOLVO_ANGLE_DEG_TO_CAN 17.869907f
|
||||
#define VOLVO_MAX_ANGLE_CAN 9650
|
||||
#define VOLVO_RELAY_ANGLE_TOLERANCE 54 // approximately 3 degrees
|
||||
#define VOLVO_DRIVER_OVERRIDE 5
|
||||
|
||||
|
||||
// CAN bus definitions for Volvo
|
||||
@@ -83,8 +82,6 @@ static const AngleSteeringLimits VOLVO_ANGLE_STEERING_LIMITS = {
|
||||
};
|
||||
|
||||
static void volvo_rx_hook(const CANPacket_t *msg) {
|
||||
// Monitor the vehicle state required for cruise, disengagement, and angle
|
||||
// safety. All steering TX frames are separately constrained in volvo_tx_hook.
|
||||
|
||||
// Main bus (bus 0) messages
|
||||
if (msg->bus == VOLVO_MAIN_BUS) {
|
||||
@@ -148,13 +145,11 @@ static void volvo_rx_hook(const CANPacket_t *msg) {
|
||||
|
||||
// DRIVER_INPUT is the signal consumed by carstate.py for driver torque.
|
||||
// The PSCM frame's DRIVER_INPUT_DEVIATION is a different signal and must
|
||||
// not be substituted here: doing so leaves the hardware disengage path blind.
|
||||
if (msg->addr == VOLVO_DRIVER_INPUT) {
|
||||
// STEERING_DRIVER_INPUT is a Motorola signal starting at bit 55. The
|
||||
// DBC also carries a +1 offset, so its raw byte is data[6].
|
||||
const int driver_input = to_signed(msg->data[6], 8) + 1;
|
||||
update_sample(&torque_driver, driver_input);
|
||||
steering_disengage = SAFETY_ABS(driver_input) > VOLVO_DRIVER_OVERRIDE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ class TestFordSafetyBase(common.CarSafetyTest):
|
||||
MSG_LateralMotionControl2, MSG_IPMA_Data]}
|
||||
|
||||
STEER_MESSAGE = 0
|
||||
STOCK_LONGITUDINAL = False
|
||||
|
||||
# Curvature control limits
|
||||
LKA_STEERING = False
|
||||
@@ -199,6 +200,17 @@ class TestFordSafetyBase(common.CarSafetyTest):
|
||||
}
|
||||
return self.packer.make_can_msg_safety("Steering_Data_FD1", bus, values)
|
||||
|
||||
def _combined_cancel_resume_msg(self, pressed: bool):
|
||||
values = {"CcAslButtnCnclResPress": int(pressed)}
|
||||
return self.packer.make_can_msg_safety("Steering_Data_FD1", 0, values)
|
||||
|
||||
def _pcm_main_on_msg(self, main_on: bool):
|
||||
values = {
|
||||
"BpedDrvAppl_D_Actl": 1,
|
||||
"CcStat_D_Actl": 3 if main_on else 0,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("EngBrakeData", 0, values)
|
||||
|
||||
def test_rx_hook(self):
|
||||
# checksum, counter, and quality flag checks
|
||||
for quality_flag in [True, False]:
|
||||
@@ -381,6 +393,25 @@ class TestFordSafetyBase(common.CarSafetyTest):
|
||||
for bus in (0, 2):
|
||||
self.assertEqual(enabled, self._tx(self._acc_button_msg(Buttons.CANCEL, bus)))
|
||||
|
||||
def test_stock_resume_relay_requires_physical_button_and_cruise_main(self):
|
||||
self.safety.set_controls_allowed(False)
|
||||
self._rx(self._pcm_main_on_msg(True))
|
||||
for bus in (0, 2):
|
||||
self.assertFalse(self._tx(self._acc_button_msg(Buttons.RESUME, bus)))
|
||||
|
||||
self._rx(self._combined_cancel_resume_msg(True))
|
||||
for bus in (0, 2):
|
||||
self.assertEqual(self.STOCK_LONGITUDINAL, self._tx(self._acc_button_msg(Buttons.RESUME, bus)))
|
||||
|
||||
self._rx(self._combined_cancel_resume_msg(False))
|
||||
for bus in (0, 2):
|
||||
self.assertFalse(self._tx(self._acc_button_msg(Buttons.RESUME, bus)))
|
||||
|
||||
self._rx(self._pcm_main_on_msg(False))
|
||||
self._rx(self._combined_cancel_resume_msg(True))
|
||||
for bus in (0, 2):
|
||||
self.assertFalse(self._tx(self._acc_button_msg(Buttons.RESUME, bus)))
|
||||
|
||||
def _toggle_aol(self, toggle_on):
|
||||
# EngBrakeData, CcStat_D_Actl is the cruise state
|
||||
# 3 is standby (main on), 5 is active (engaged)
|
||||
@@ -394,6 +425,7 @@ class TestFordSafetyBase(common.CarSafetyTest):
|
||||
|
||||
class TestFordCANFDStockSafety(TestFordSafetyBase):
|
||||
STEER_MESSAGE = MSG_LateralMotionControl2
|
||||
STOCK_LONGITUDINAL = True
|
||||
|
||||
TX_MSGS = [
|
||||
[MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
|
||||
@@ -446,6 +478,7 @@ class TestFordCANFDStockSafety(TestFordSafetyBase):
|
||||
|
||||
class TestFordStockSafety(TestFordSafetyBase):
|
||||
STEER_MESSAGE = MSG_LateralMotionControl
|
||||
STOCK_LONGITUDINAL = True
|
||||
|
||||
TX_MSGS = [
|
||||
[MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
|
||||
|
||||
@@ -427,6 +427,18 @@ def test_hyundai_starpilot_rx_sources():
|
||||
safety.safety_rx_hook(libsafety_py.make_CANPacket(0x421, 0, bytes(8)))
|
||||
|
||||
|
||||
def test_hyundai_lkas12_tx_requires_stock_camera_message():
|
||||
safety = libsafety_py.libsafety
|
||||
assert safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0) == 0
|
||||
safety.init_tests()
|
||||
|
||||
lkas12 = libsafety_py.make_CANPacket(0x53E, 0, bytes(6))
|
||||
assert not safety.safety_tx_hook(lkas12)
|
||||
|
||||
safety.safety_rx_hook(libsafety_py.make_CANPacket(0x53E, 2, bytes(6)))
|
||||
assert safety.safety_tx_hook(lkas12)
|
||||
|
||||
|
||||
class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety):
|
||||
TX_MSGS = [[0x340, 0], [0x4F1, 0], [0x485, 0], [0x420, 0], [0x421, 0], [0x50A, 0], [0x389, 0], [0x4A2, 0], [0x38D, 0], [0x483, 0], [0x7D0, 0]]
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class SubaruMsg(enum.IntEnum):
|
||||
ES_STATIC_1 = 0x22a
|
||||
ES_STATIC_2 = 0x325
|
||||
Dashlights = 0x390
|
||||
AVH = 0x32b
|
||||
|
||||
|
||||
SUBARU_MAIN_BUS = 0
|
||||
@@ -386,20 +385,6 @@ class TestSubaruGen2FixedAngleStopStartSafety(TestSubaruGen2FixedAngleSafety):
|
||||
self.assertFalse(self._tx(self._stop_start_msg(False)))
|
||||
|
||||
|
||||
class TestSubaruGen2FixedAngleStopStartAvhSafety(TestSubaruGen2FixedAngleStopStartSafety):
|
||||
FLAGS = TestSubaruGen2FixedAngleStopStartSafety.FLAGS | SubaruSafetyFlags.AVH_BUTTON
|
||||
TX_MSGS = TestSubaruGen2FixedAngleStopStartSafety.TX_MSGS + [[SubaruMsg.AVH, SUBARU_ALT_BUS]]
|
||||
|
||||
def _avh_msg(self, pressed):
|
||||
return self.packer.make_can_msg_safety(
|
||||
"AVH", SUBARU_ALT_BUS, {"COUNTER": 0, "AVH": pressed},
|
||||
)
|
||||
|
||||
def test_avh_tx_requires_pressed_bit(self):
|
||||
self.assertTrue(self._tx(self._avh_msg(True)))
|
||||
self.assertFalse(self._tx(self._avh_msg(False)))
|
||||
|
||||
|
||||
class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruAngleSafetyBase):
|
||||
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM
|
||||
ALT_MAIN_BUS = SUBARU_ALT_BUS
|
||||
|
||||
@@ -211,21 +211,17 @@ class TestVolvoSafetyBase(common.CarSafetyTest):
|
||||
self.assertTrue(self._tx(valid))
|
||||
self.assertFalse(self._tx(invalid))
|
||||
|
||||
def test_driver_override_disengages_controls(self):
|
||||
def test_driver_input_is_a_normal_override(self):
|
||||
def driver_input_msg(value):
|
||||
return self.mid_packer.make_can_msg_safety(
|
||||
"DRIVER_INPUT", VOLVO_PARTY_BUS, {"STEERING_DRIVER_INPUT": value})
|
||||
|
||||
for value in (2, 3, 5):
|
||||
for value in (2, 3, 5, 6, 20, -20):
|
||||
self._rx(driver_input_msg(0))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._rx(driver_input_msg(value))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), f"unexpected disengage at {value=}")
|
||||
|
||||
self._rx(driver_input_msg(0))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._rx(driver_input_msg(6))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self.assertTrue(self.safety.get_controls_allowed(), f"unexpected safety disengage at {value=}")
|
||||
self.assertFalse(self.safety.get_steering_disengage_prev())
|
||||
|
||||
# ---- Volvo-specific consistency tests ----
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
extern const uint8_t gitversion[19];
|
||||
const uint8_t gitversion[19] = "DEV-3ebc6b99-DEBUG";
|
||||
const uint8_t gitversion[19] = "DEV-cec1a0fb-DEBUG";
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user