mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-20 05:52:11 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 813ef1ee65 | |||
| 3749b49186 | |||
| 25b6f84bb0 | |||
| 73092daefc |
@@ -0,0 +1,18 @@
|
|||||||
|
**/.git
|
||||||
|
.DS_Store
|
||||||
|
*.dylib
|
||||||
|
*.DSYM
|
||||||
|
*.d
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.*.swp
|
||||||
|
.*.swo
|
||||||
|
.*.un~
|
||||||
|
*.tmp
|
||||||
|
*.o
|
||||||
|
*.o-*
|
||||||
|
*.os
|
||||||
|
*.os-*
|
||||||
|
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
+10
-1
@@ -11,4 +11,13 @@
|
|||||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
||||||
common/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
|
system/hardware/tici/updater_weston filter=lfs diff=lfs merge=lfs -text
|
||||||
|
system/hardware/tici/updater_magic filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/**/*.a filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/**/*.so filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/**/*.dylib filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text
|
||||||
|
third_party/catch2/include/catch2/catch.hpp filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement
|
||||||
|
about: For openpilot enhancement suggestions
|
||||||
|
title: ''
|
||||||
|
labels: 'enhancement'
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
@@ -34,10 +34,10 @@ jobs:
|
|||||||
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
||||||
echo "tinygrad_ref is $ref"
|
echo "tinygrad_ref is $ref"
|
||||||
|
|
||||||
- name: Checkout docs repo (sunnypilot-models, gh-pages)
|
- name: Checkout docs repo (sunnypilot-docs, gh-pages)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/sunnypilot-models
|
repository: sunnypilot/sunnypilot-docs
|
||||||
ref: gh-pages
|
ref: gh-pages
|
||||||
path: docs
|
path: docs
|
||||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
||||||
@@ -120,7 +120,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upstream_branch: ${{ matrix.model.ref }}
|
upstream_branch: ${{ matrix.model.ref }}
|
||||||
custom_name: ${{ matrix.model.display_name }}
|
custom_name: ${{ matrix.model.display_name }}
|
||||||
is_20hz: ${{ matrix.model.is_20hz }}
|
|
||||||
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
||||||
json_version: ${{ needs.setup.outputs.json_version }}
|
json_version: ${{ needs.setup.outputs.json_version }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
@@ -158,7 +157,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upstream_branch: ${{ matrix.model.ref }}
|
upstream_branch: ${{ matrix.model.ref }}
|
||||||
custom_name: ${{ matrix.model.display_name }}
|
custom_name: ${{ matrix.model.display_name }}
|
||||||
is_20hz: ${{ matrix.model.is_20hz }}
|
|
||||||
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
||||||
json_version: ${{ needs.setup.outputs.json_version }}
|
json_version: ${{ needs.setup.outputs.json_version }}
|
||||||
artifact_suffix: -retry
|
artifact_suffix: -retry
|
||||||
@@ -204,7 +202,7 @@ jobs:
|
|||||||
- name: Checkout docs repo
|
- name: Checkout docs repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/sunnypilot-models
|
repository: sunnypilot/sunnypilot-docs
|
||||||
ref: gh-pages
|
ref: gh-pages
|
||||||
path: docs
|
path: docs
|
||||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
||||||
|
|||||||
@@ -24,11 +24,6 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ''
|
default: ''
|
||||||
is_20hz:
|
|
||||||
description: 'Is this a 20Hz model'
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
bypass_push:
|
bypass_push:
|
||||||
description: 'Bypass pushing to GitLab for build-all'
|
description: 'Bypass pushing to GitLab for build-all'
|
||||||
required: false
|
required: false
|
||||||
@@ -44,11 +39,6 @@ on:
|
|||||||
description: 'Custom name for the model (no date, only name)'
|
description: 'Custom name for the model (no date, only name)'
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
is_20hz:
|
|
||||||
description: 'Is this a 20Hz model'
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
recompiled_dir:
|
recompiled_dir:
|
||||||
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
||||||
required: true
|
required: true
|
||||||
@@ -92,7 +82,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upstream_branch: ${{ inputs.upstream_branch }}
|
upstream_branch: ${{ inputs.upstream_branch }}
|
||||||
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
||||||
is_20hz: ${{ inputs.is_20hz }}
|
is_20hz: true
|
||||||
artifact_suffix: ${{ inputs.artifact_suffix }}
|
artifact_suffix: ${{ inputs.artifact_suffix }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
@@ -129,7 +119,7 @@ jobs:
|
|||||||
- name: Checkout docs repo
|
- name: Checkout docs repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/sunnypilot-models
|
repository: sunnypilot/sunnypilot-docs
|
||||||
ref: gh-pages
|
ref: gh-pages
|
||||||
path: docs
|
path: docs
|
||||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
||||||
|
|||||||
@@ -23,62 +23,56 @@ env:
|
|||||||
CI: 1
|
CI: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
generate_cereal_artifact:
|
||||||
|
name: Generate cereal validation artifacts
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- run: ./tools/op.sh setup
|
||||||
|
- name: Build openpilot
|
||||||
|
run: scons -j$(nproc) cereal
|
||||||
|
- name: Generate the log file
|
||||||
|
run: |
|
||||||
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
|
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema_instances.bin
|
||||||
|
- name: 'Prepare artifact'
|
||||||
|
run: |
|
||||||
|
mkdir -p "cereal/messaging/tests/cereal_validations"
|
||||||
|
cp cereal/messaging/tests/validate_sp_cereal_upstream.py "cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py"
|
||||||
|
cp schema_instances.bin "cereal/messaging/tests/cereal_validations/schema_instances.bin"
|
||||||
|
- name: 'Upload Artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: cereal_validations
|
||||||
|
path: cereal/messaging/tests/cereal_validations
|
||||||
|
|
||||||
validate_cereal_with_upstream:
|
validate_cereal_with_upstream:
|
||||||
name: Validate cereal with Upstream
|
name: Validate cereal with Upstream
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
needs: generate_cereal_artifact
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sunnypilot cereal
|
- name: Checkout sunnypilot
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
|
||||||
sparse-checkout: cereal
|
|
||||||
|
|
||||||
- name: Init sunnypilot opendbc submodule
|
|
||||||
run: git submodule update --init --depth 1 opendbc_repo
|
|
||||||
|
|
||||||
- name: Checkout upstream openpilot
|
- name: Checkout upstream openpilot
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: 'commaai/openpilot'
|
repository: 'commaai/openpilot'
|
||||||
path: upstream_openpilot
|
path: openpilot
|
||||||
|
submodules: true
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
|
- run: ./tools/op.sh setup
|
||||||
- name: Init upstream opendbc submodule
|
- name: Build openpilot
|
||||||
working-directory: upstream_openpilot
|
working-directory: openpilot
|
||||||
run: git submodule update --init --depth 1 opendbc_repo
|
run: scons -j$(nproc) cereal
|
||||||
|
- name: Download build artifacts
|
||||||
- name: Locate upstream capnp paths
|
uses: actions/download-artifact@v4
|
||||||
id: locate-capnp
|
with:
|
||||||
|
name: cereal_validations
|
||||||
|
path: openpilot/cereal/messaging/tests/cereal_validations
|
||||||
|
- name: 'Run the validation'
|
||||||
run: |
|
run: |
|
||||||
CEREAL_DIR=$(find upstream_openpilot -maxdepth 4 -name log.capnp -path '*/cereal/log.capnp' -printf '%h\n' -quit)
|
export PYTHONPATH=${{ github.workspace }}/openpilot
|
||||||
if [ -z "$CEREAL_DIR" ]; then
|
chmod +x openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py
|
||||||
echo "::error::Could not locate cereal/log.capnp in upstream openpilot"
|
python3 openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f openpilot/cereal/messaging/tests/cereal_validations/schema_instances.bin
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "cereal_dir=$CEREAL_DIR" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Found upstream cereal at: $CEREAL_DIR"
|
|
||||||
|
|
||||||
IMPORT_ARGS=""
|
|
||||||
CAR_CAPNP=$(find upstream_openpilot -maxdepth 5 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
|
|
||||||
if [ -n "$CAR_CAPNP" ]; then
|
|
||||||
IMPORT_ARGS="-I $CAR_CAPNP"
|
|
||||||
echo "Found car.capnp at: $CAR_CAPNP"
|
|
||||||
fi
|
|
||||||
echo "import_args=$IMPORT_ARGS" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
run: pip install uv
|
|
||||||
|
|
||||||
- name: Generate sunnypilot schema
|
|
||||||
run: |
|
|
||||||
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
|
||||||
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
|
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
|
||||||
-g -f /tmp/sp_schema.json --cereal-dir cereal
|
|
||||||
|
|
||||||
- name: Validate against upstream
|
|
||||||
run: |
|
|
||||||
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
|
||||||
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
|
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
|
||||||
-r -f /tmp/sp_schema.json --cereal-dir ${{ steps.locate-capnp.outputs.cereal_dir }} \
|
|
||||||
${{ steps.locate-capnp.outputs.import_args }}
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
name: diff report
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
comment:
|
|
||||||
name: comment
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
actions: read
|
|
||||||
steps:
|
|
||||||
- name: Wait for process replay
|
|
||||||
id: wait
|
|
||||||
continue-on-error: true
|
|
||||||
uses: lewagon/wait-on-check-action@v1.3.4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
check-name: process replay
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
allowed-conclusions: success,failure
|
|
||||||
wait-interval: 20
|
|
||||||
- name: Download diff
|
|
||||||
if: steps.wait.outcome == 'success'
|
|
||||||
uses: dawidd6/action-download-artifact@v6
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
workflow: tests.yaml
|
|
||||||
workflow_conclusion: ''
|
|
||||||
pr: ${{ github.event.number }}
|
|
||||||
name: diff_report_${{ github.event.number }}
|
|
||||||
path: .
|
|
||||||
allow_forks: true
|
|
||||||
- name: Comment on PR
|
|
||||||
if: steps.wait.outcome == 'success'
|
|
||||||
uses: thollander/actions-comment-pull-request@v2
|
|
||||||
with:
|
|
||||||
filePath: diff_report.txt
|
|
||||||
comment_tag: diff_report
|
|
||||||
pr_number: ${{ github.event.number }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -29,9 +29,9 @@ jobs:
|
|||||||
# Build
|
# Build
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: |
|
run: |
|
||||||
git lfs pull
|
# TODO: can we install just the "docs" dependency group without the normal deps?
|
||||||
pip install zensical
|
pip install mkdocs
|
||||||
python scripts/docs.py build
|
mkdocs build
|
||||||
|
|
||||||
# Push to docs.comma.ai
|
# Push to docs.comma.ai
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
running-workflow-name: 'build __nightly'
|
running-workflow-name: 'build __nightly'
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$
|
check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|||||||
@@ -9,6 +9,28 @@ env:
|
|||||||
PYTHONPATH: ${{ github.workspace }}
|
PYTHONPATH: ${{ github.workspace }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
update_translations:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- run: ./tools/op.sh setup
|
||||||
|
- name: Update translations
|
||||||
|
run: python3 selfdrive/ui/update_translations.py --vanish
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0
|
||||||
|
with:
|
||||||
|
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||||
|
commit-message: "Update translations"
|
||||||
|
title: "[bot] Update translations"
|
||||||
|
body: "Automatic PR from repo-maintenance -> update_translations"
|
||||||
|
branch: "update-translations"
|
||||||
|
base: "master"
|
||||||
|
delete-branch: true
|
||||||
|
labels: bot
|
||||||
|
|
||||||
package_updates:
|
package_updates:
|
||||||
name: package_updates
|
name: package_updates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -50,6 +72,7 @@ jobs:
|
|||||||
git add .
|
git add .
|
||||||
- name: update car docs
|
- name: update car docs
|
||||||
run: |
|
run: |
|
||||||
|
scons -j$(nproc) --minimal opendbc_repo
|
||||||
python selfdrive/car/docs.py
|
python selfdrive/car/docs.py
|
||||||
git add docs/CARS.md
|
git add docs/CARS.md
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
|||||||
@@ -164,54 +164,18 @@ jobs:
|
|||||||
source /etc/profile
|
source /etc/profile
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
|
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}"
|
||||||
|
|
||||||
COMPILE_MODELD="${{ github.workspace }}/sunnypilot/modeld_v2/compile_modeld.py"
|
# Loop through all .onnx files
|
||||||
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
|
||||||
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
|
||||||
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
|
||||||
|
|
||||||
# Generate metadata for all ONNX files
|
|
||||||
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
||||||
echo "Generating metadata: $onnx_file"
|
base_name=$(basename "$onnx_file" .onnx)
|
||||||
env ${TG_FLAGS} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
output_file="${{ env.MODELS_DIR }}/${base_name}_tinygrad.pkl"
|
||||||
|
|
||||||
|
echo "Compiling: $onnx_file -> $output_file"
|
||||||
|
QCOM=1 python3 "${{ env.TINYGRAD_PATH }}/examples/openpilot/compile3.py" "$onnx_file" "$output_file"
|
||||||
|
DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Detect model type and build compile args
|
|
||||||
VISION_ONNX="${{ env.MODELS_DIR }}/driving_vision.onnx"
|
|
||||||
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
|
|
||||||
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
|
|
||||||
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
|
|
||||||
SUPERCOMBO_ONNX="${{ env.MODELS_DIR }}/supercombo.onnx"
|
|
||||||
|
|
||||||
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
|
||||||
if [ -f "$VISION_ONNX" ]; then
|
|
||||||
ONNX_ARGS="--vision-onnx $VISION_ONNX"
|
|
||||||
if [ -f "$ON_POLICY_ONNX" ] && [ -f "$OFF_POLICY_ONNX" ]; then
|
|
||||||
MODEL_TYPE=vision_multi_policy
|
|
||||||
ONNX_ARGS="$ONNX_ARGS --off-policy-onnx $OFF_POLICY_ONNX --on-policy-onnx $ON_POLICY_ONNX"
|
|
||||||
elif [ -f "$OFF_POLICY_ONNX" ] && [ -f "$POLICY_ONNX" ]; then
|
|
||||||
MODEL_TYPE=vision_multi_policy
|
|
||||||
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX --off-policy-onnx $OFF_POLICY_ONNX"
|
|
||||||
elif [ -f "$POLICY_ONNX" ]; then
|
|
||||||
MODEL_TYPE=vision_policy
|
|
||||||
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX"
|
|
||||||
fi
|
|
||||||
elif [ -f "$SUPERCOMBO_ONNX" ]; then
|
|
||||||
MODEL_TYPE=supercombo
|
|
||||||
ONNX_ARGS="--supercombo-onnx $SUPERCOMBO_ONNX"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$MODEL_TYPE" ]; then
|
|
||||||
echo "Detected: $MODEL_TYPE -> driving_tinygrad.pkl"
|
|
||||||
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
|
|
||||||
--model-type $MODEL_TYPE \
|
|
||||||
--model-size $MODEL_SIZE \
|
|
||||||
--camera-resolutions $CAMERA_RES \
|
|
||||||
$ONNX_ARGS \
|
|
||||||
--output "${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Validate Model Outputs
|
- name: Validate Model Outputs
|
||||||
run: |
|
run: |
|
||||||
source /etc/profile
|
source /etc/profile
|
||||||
@@ -230,8 +194,6 @@ jobs:
|
|||||||
rsync -avm \
|
rsync -avm \
|
||||||
--include='*.dlc' \
|
--include='*.dlc' \
|
||||||
--include='*.pkl' \
|
--include='*.pkl' \
|
||||||
--include='*.chunk*' \
|
|
||||||
--include='*.chunkmanifest' \
|
|
||||||
--include='*.onnx' \
|
--include='*.onnx' \
|
||||||
--exclude='*' \
|
--exclude='*' \
|
||||||
--delete-excluded \
|
--delete-excluded \
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ jobs:
|
|||||||
echo "Building sunnypilot's locationd..."
|
echo "Building sunnypilot's locationd..."
|
||||||
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd
|
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd
|
||||||
echo "Building openpilot's locationd..."
|
echo "Building openpilot's locationd..."
|
||||||
scons -j1 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd
|
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd
|
||||||
echo "Building rest of sunnypilot"
|
echo "Building rest of sunnypilot"
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
|
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
|
||||||
touch ${BUILD_DIR}/prebuilt
|
touch ${BUILD_DIR}/prebuilt
|
||||||
@@ -215,8 +215,8 @@ jobs:
|
|||||||
--exclude='**/SConstruct' \
|
--exclude='**/SConstruct' \
|
||||||
--exclude='**/SConscript' \
|
--exclude='**/SConscript' \
|
||||||
--exclude='**/.venv/' \
|
--exclude='**/.venv/' \
|
||||||
--exclude='selfdrive/modeld/models/*.onnx*' \
|
--exclude='selfdrive/modeld/models/driving_vision.onnx' \
|
||||||
--exclude='sunnypilot/modeld*/models/*.onnx*' \
|
--exclude='selfdrive/modeld/models/driving_policy.onnx' \
|
||||||
--exclude='third_party/*x86*' \
|
--exclude='third_party/*x86*' \
|
||||||
--exclude='third_party/*Darwin*' \
|
--exclude='third_party/*Darwin*' \
|
||||||
--delete-excluded \
|
--delete-excluded \
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons -j$(nproc)
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
|
||||||
run: |
|
run: |
|
||||||
@@ -147,7 +147,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons -j$(nproc)
|
||||||
- name: Run replay
|
- name: Run replay
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
|
||||||
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
|
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
|
||||||
@@ -156,22 +156,12 @@ jobs:
|
|||||||
id: print-diff
|
id: print-diff
|
||||||
if: always()
|
if: always()
|
||||||
run: cat selfdrive/test/process_replay/diff.txt
|
run: cat selfdrive/test/process_replay/diff.txt
|
||||||
- name: Print diff report
|
|
||||||
if: always()
|
|
||||||
run: cat selfdrive/test/process_replay/diff_report.txt
|
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v6
|
||||||
if: always()
|
if: always()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: process_replay_diff.txt
|
name: process_replay_diff.txt
|
||||||
path: selfdrive/test/process_replay/diff.txt
|
path: selfdrive/test/process_replay/diff.txt
|
||||||
- name: Upload diff report
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
if: always() && github.event_name == 'pull_request'
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
name: diff_report_${{ github.event.number }}
|
|
||||||
path: selfdrive/test/process_replay/diff_report.txt
|
|
||||||
- name: Checkout ci-artifacts
|
- name: Checkout ci-artifacts
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -179,7 +169,7 @@ jobs:
|
|||||||
repository: commaai/ci-artifacts
|
repository: commaai/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
path: ${{ github.workspace }}/ci-artifacts
|
path: ${{ github.workspace }}/ci-artifacts
|
||||||
- name: Prepare refs
|
- name: Push refs
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
||||||
working-directory: ${{ github.workspace }}/ci-artifacts
|
working-directory: ${{ github.workspace }}/ci-artifacts
|
||||||
run: |
|
run: |
|
||||||
@@ -191,13 +181,7 @@ jobs:
|
|||||||
echo "${{ github.sha }}" > ref_commit
|
echo "${{ github.sha }}" > ref_commit
|
||||||
git add .
|
git add .
|
||||||
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
|
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
|
||||||
- name: Push refs
|
git push origin process-replay
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
|
||||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
|
||||||
with:
|
|
||||||
timeout_minutes: 2
|
|
||||||
max_attempts: 3
|
|
||||||
command: cd ${{ github.workspace }}/ci-artifacts && git push origin process-replay --force
|
|
||||||
- name: Run regen
|
- name: Run regen
|
||||||
if: false
|
if: false
|
||||||
timeout-minutes: 4
|
timeout-minutes: 4
|
||||||
@@ -220,7 +204,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons -j$(nproc)
|
||||||
- name: Driving test
|
- name: Driving test
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
run: |
|
run: |
|
||||||
@@ -241,7 +225,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons -j$(nproc)
|
||||||
- name: Create UI Report
|
- name: Create UI Report
|
||||||
run: |
|
run: |
|
||||||
source selfdrive/test/setup_xvfb.sh
|
source selfdrive/test/setup_xvfb.sh
|
||||||
|
|||||||
+26
-15
@@ -13,13 +13,13 @@ venv/
|
|||||||
a.out
|
a.out
|
||||||
.hypothesis
|
.hypothesis
|
||||||
.cache/
|
.cache/
|
||||||
bin/
|
|
||||||
|
/docs_site/
|
||||||
|
|
||||||
*.mp4
|
*.mp4
|
||||||
*.dylib
|
*.dylib
|
||||||
*.DSYM
|
*.DSYM
|
||||||
*.d
|
*.d
|
||||||
*.pem
|
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
.*.swp
|
.*.swp
|
||||||
@@ -39,15 +39,11 @@ bin/
|
|||||||
*.mo
|
*.mo
|
||||||
*_pyx.cpp
|
*_pyx.cpp
|
||||||
*.stats
|
*.stats
|
||||||
*.pkl
|
|
||||||
*.pkl*
|
|
||||||
config.json
|
config.json
|
||||||
|
clcache
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
compare_runtime*.html
|
compare_runtime*.html
|
||||||
selfdrive/modeld/models/tg_input_devices.json
|
|
||||||
|
|
||||||
# build artifacts
|
|
||||||
docs_site/
|
|
||||||
selfdrive/pandad/pandad
|
selfdrive/pandad/pandad
|
||||||
cereal/services.h
|
cereal/services.h
|
||||||
cereal/gen
|
cereal/gen
|
||||||
@@ -60,36 +56,51 @@ system/camerad/test/ae_gray_test
|
|||||||
.coverage*
|
.coverage*
|
||||||
coverage.xml
|
coverage.xml
|
||||||
htmlcov
|
htmlcov
|
||||||
|
pandaextra
|
||||||
|
|
||||||
|
.mypy_cache/
|
||||||
|
flycheck_*
|
||||||
|
|
||||||
|
cppcheck_report.txt
|
||||||
|
comma*.sh
|
||||||
|
|
||||||
|
selfdrive/modeld/models/*.pkl*
|
||||||
|
sunnypilot/modeld*/models/*.pkl
|
||||||
|
|
||||||
# openpilot log files
|
# openpilot log files
|
||||||
*.bz2
|
*.bz2
|
||||||
*.zst
|
*.zst
|
||||||
*.rlog
|
|
||||||
|
|
||||||
build/
|
build/
|
||||||
|
|
||||||
!**/.gitkeep
|
!**/.gitkeep
|
||||||
|
|
||||||
|
poetry.toml
|
||||||
|
Pipfile
|
||||||
|
|
||||||
### VisualStudioCode ###
|
### VisualStudioCode ###
|
||||||
*.vsix
|
|
||||||
.history
|
|
||||||
.ionide
|
|
||||||
.vscode/*
|
.vscode/*
|
||||||
.history/
|
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/*.code-snippets
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
# agents
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
|
||||||
.claude/
|
.claude/
|
||||||
.context/
|
.context/
|
||||||
PLAN.md
|
PLAN.md
|
||||||
TASK.md
|
TASK.md
|
||||||
CLAUDE.md
|
|
||||||
SKILL.md
|
|
||||||
|
|
||||||
### JetBrains ###
|
### JetBrains ###
|
||||||
!.idea/customTargets.xml
|
!.idea/customTargets.xml
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
url = https://github.com/sunnypilot/opendbc.git
|
url = https://github.com/sunnypilot/opendbc.git
|
||||||
[submodule "msgq"]
|
[submodule "msgq"]
|
||||||
path = msgq_repo
|
path = msgq_repo
|
||||||
url = https://github.com/sunnypilot/msgq.git
|
url = https://github.com/commaai/msgq.git
|
||||||
[submodule "rednose_repo"]
|
[submodule "rednose_repo"]
|
||||||
path = rednose_repo
|
path = rednose_repo
|
||||||
url = https://github.com/commaai/rednose.git
|
url = https://github.com/commaai/rednose.git
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
3.12.13
|
|
||||||
Vendored
-3
@@ -52,9 +52,6 @@
|
|||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
"pid": "${command:pickMyProcess}",
|
"pid": "${command:pickMyProcess}",
|
||||||
"sourceMap": {
|
|
||||||
".": "${workspaceFolder}/opendbc/safety"
|
|
||||||
},
|
|
||||||
"initCommands": [
|
"initCommands": [
|
||||||
"script import time; time.sleep(3)"
|
"script import time; time.sleep(3)"
|
||||||
]
|
]
|
||||||
|
|||||||
Vendored
+1
@@ -21,6 +21,7 @@
|
|||||||
"common/**",
|
"common/**",
|
||||||
"selfdrive/**",
|
"selfdrive/**",
|
||||||
"system/**",
|
"system/**",
|
||||||
|
"third_party/**",
|
||||||
"tools/**",
|
"tools/**",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-118
@@ -1,36 +1,4 @@
|
|||||||
sunnypilot Version 2026.002.000 (2026-06-28)
|
sunnypilot Version 2026.001.000 (2026-03-xx)
|
||||||
========================
|
|
||||||
* What's Changed (sunnypilot/sunnypilot)
|
|
||||||
* ui: update gates for certain toggles by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1830
|
|
||||||
* release: ignore upstream IsReleaseBranch by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1831
|
|
||||||
* manager: disable DEVELOPMENT_ONLY reset by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1833
|
|
||||||
* sunnylink: fix max time offroad values by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1835
|
|
||||||
* ui: show default model name by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1837
|
|
||||||
* sunnylink: add CarParams fallback for brand-specific capabilities by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1839
|
|
||||||
* sunnylink SDUI: tweak DisableUpdate param for clarity by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1842
|
|
||||||
* Revert "DM: Lancia Delta HF Integrale model" by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1849
|
|
||||||
* modeld_v2: safe model validation by @Discountchubbs in https://github.com/sunnypilot/sunnypilot/pull/1855
|
|
||||||
* Revert "deprecate `carState.brake`" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/sunnypilot/pull/1860
|
|
||||||
* sunnylink: deprecate legacy params metadata by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1862
|
|
||||||
* ui: reset Enforce Torque Control and NNLC if both are enabled by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1863
|
|
||||||
* What's Changed (sunnypilot/opendbc)
|
|
||||||
* Rivian: suppress ACM hold-the-wheel warning during MADS-only lateral by @lukasloetkolben in https://github.com/sunnypilot/opendbc/pull/465
|
|
||||||
* Sync: `commaai/opendbc:master` → `sunnypilot/opendbc:master` by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/479
|
|
||||||
* safety: add option to ignore frequency check for RX checks by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/480
|
|
||||||
* Revert "deprecate carState.brake" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/opendbc/pull/481
|
|
||||||
* New Contributors (sunnypilot/sunnypilot)
|
|
||||||
* @mvl-boston made their first contribution in https://github.com/sunnypilot/sunnypilot/pull/1860
|
|
||||||
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2026.001.007...v2026.002.000
|
|
||||||
************************
|
|
||||||
* Synced with commaai's openpilot (v0.11.1)
|
|
||||||
* master commit 69e2c321e49760e52f7983eaa0a5f77cb95de637 (June 02, 2026)
|
|
||||||
* New driver monitoring model
|
|
||||||
* Improved image processing pipeline for driver camera
|
|
||||||
* Improved thermal policy for comma four
|
|
||||||
* Acura MDX 2022-24 support thanks to mvl-boston!
|
|
||||||
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
|
||||||
|
|
||||||
sunnypilot Version 2026.001.000 (2026-05-06)
|
|
||||||
========================
|
========================
|
||||||
* What's Changed (sunnypilot/sunnypilot)
|
* What's Changed (sunnypilot/sunnypilot)
|
||||||
* Complete rewrite of the user interface from Qt C++ to Raylib Python
|
* Complete rewrite of the user interface from Qt C++ to Raylib Python
|
||||||
@@ -98,64 +66,6 @@ sunnypilot Version 2026.001.000 (2026-05-06)
|
|||||||
* Pause Lateral Control with Blinker: Post-Blinker Delay by @CHaucke89
|
* Pause Lateral Control with Blinker: Post-Blinker Delay by @CHaucke89
|
||||||
* SCC-V: Use p97 for predicted lateral accel by @yasu-oh
|
* SCC-V: Use p97 for predicted lateral accel by @yasu-oh
|
||||||
* Controls: Support for Torque Lateral Control v0 Tune by @sunnyhaibin
|
* Controls: Support for Torque Lateral Control v0 Tune by @sunnyhaibin
|
||||||
* [TIZI/TICI] ui: ensure null checks for `CarParams` and `CarParamsSP` by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: use `vCruiseCluster` and `vEgoCluster` for SLA `preActive` by @sunnyhaibin
|
|
||||||
* Fix display of values when using use_float_scaling by @CHaucke89
|
|
||||||
* models: fix default & index "0" by @nayan8teen
|
|
||||||
* [TIZI/TICI] visuals: Improved speed limit by @angaz
|
|
||||||
* ICBM: ensure button timers update on disable to clear stale presses by @jamesmikesell
|
|
||||||
* [TIZI/TICI] ui: simplify Smart Cruise Control text rendering by @sunnyhaibin
|
|
||||||
* controlsd: fix steer_limited_by_safety not updating under MADS by @zephleggett
|
|
||||||
* soundd: trigger timeout warning during MADS lateral-only by @zephleggett
|
|
||||||
* pandad: flasher for Rivian long upgrade module by @lukasloetkolben
|
|
||||||
* modeld_v2: tinygrad transformation warp by @Discountchubbs
|
|
||||||
* tools: block `manage_sunnylinkd` in sim startup script by @sunnyhaibin
|
|
||||||
* [MICI] ui: need superclass `_render` in `HudRendererSP` by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: Speed Limit Assist active status by @sunnyhaibin
|
|
||||||
* ui: reimplement "Screen Off" option to Onroad Brightness by @sunnyhaibin
|
|
||||||
* ui: don't hide steering wheel when blindspot disabled by @royjr
|
|
||||||
* ui: Speed Limit Assist `preActive` improvements by @sunnyhaibin
|
|
||||||
* ui: consolidate Speed Limit Assist `preActive` status rendering by @sunnyhaibin
|
|
||||||
* [MICI] ui: Speed Limit Assist `preActive` status by @sunnyhaibin
|
|
||||||
* sunnypilot modeld: remove thneed modeld by @Discountchubbs
|
|
||||||
* modeld_v2: decouple planplus scaling from accel by @Discountchubbs
|
|
||||||
* sunnylink: Handle exceptions in `getParamsAllKeysV1` to log crashes by @devtekve
|
|
||||||
* [TIZI/TICI] ui: Developer UI cleanup by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: dynamic alert size by @nayan8teen
|
|
||||||
* i18n(fr): Add French translations by @didlawowo
|
|
||||||
* Toyota: Stop and Go Hack (Alpha) by @sunnyhaibin
|
|
||||||
* ui: `AlertFadeAnimator` for longitudinal-related statuses by @sunnyhaibin
|
|
||||||
* pandad: gate unsupported pandas before flashing by @sunnyhaibin
|
|
||||||
* Rivian: Flash xnor's Longitudinal Upgrade Kit prior supported panda check by @lukasloetkolben
|
|
||||||
* [TIZI/TICI] ui: add back gate steering arc behind toggle by @sunnyhaibin
|
|
||||||
* ui: gate Onroad Brightness Delay on readiness by @sunnyhaibin
|
|
||||||
* ui: add new timer options for Onroad Brightness Delay by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: branch switcher is always available by @sunnyhaibin
|
|
||||||
* pandad: always prioritize internal panda by @sunnyhaibin
|
|
||||||
* sunnylinkd: fetch compressed params schema by @sunnyhaibin
|
|
||||||
* sunnypilot locationd: remove unused car_ekf filter by @sunnyhaibin
|
|
||||||
* modeld_v2: update deprecated temporalPose ref by @sunnyhaibin
|
|
||||||
* NNLC: restore pre-v1 PID gains in torque extension by @mmmorks
|
|
||||||
* MADS safety: enable heartbeat and lateral controls mismatch checks by @sunnyhaibin
|
|
||||||
* [MICI] ui: models panel enhancements by @nayan8teen
|
|
||||||
* [TIZI/TICI] ui: fix unintended selection while scrolling in TreeOptionDialog by @TheSecurityDev
|
|
||||||
* tools: script for video concatenation by @Discountchubbs
|
|
||||||
* tools: profile memory usage by @Discountchubbs
|
|
||||||
* [TIZI/TICI] ui: remove per-frame param sync by @sunnyhaibin
|
|
||||||
* [MICI] ui: always offroad by @nayan8teen
|
|
||||||
* controls: always default Torque Lateral Control to v0 Tune by @sunnyhaibin
|
|
||||||
* Revert "controls: always default Torque Lateral Control to v0 Tune" by @sunnyhaibin
|
|
||||||
* Reapply "controls: always default Torque Lateral Control to v0 Tune" (#1806) by @sunnyhaibin
|
|
||||||
* [MICI] ui: add sunnylink info & connectivity check by @nayan8teen
|
|
||||||
* sunnylink: Remove unused API endpoint by @devtekve
|
|
||||||
* DM: wheel touch enforcement in MADS by @sunnyhaibin
|
|
||||||
* torque: show static override values in Dev UI & gate `useParams` on custom torque tune by @sunnyhaibin
|
|
||||||
* MADS: suppress espActive event when long is not engaged by @sunnyhaibin
|
|
||||||
* sunnylink: SDUI by @sunnyhaibin
|
|
||||||
* [MICI] ui: align upstream changes with sunnypilot settings buttons by @nayan8teen
|
|
||||||
* ui: fix cellular toggles by @AmyJeanes
|
|
||||||
* sunnylink: switch athena domain by @devtekve
|
|
||||||
* Platform List: dynamically migrate CarPlatformBundle by @sunnyhaibin
|
|
||||||
* What's Changed (sunnypilot/opendbc)
|
* What's Changed (sunnypilot/opendbc)
|
||||||
* Honda: DBC for Accord 9th Generation by @mvl-boston
|
* Honda: DBC for Accord 9th Generation by @mvl-boston
|
||||||
* FCA: update tire stiffness values for `RAM_HD` by @dparring
|
* FCA: update tire stiffness values for `RAM_HD` by @dparring
|
||||||
@@ -174,25 +84,12 @@ sunnypilot Version 2026.001.000 (2026-05-06)
|
|||||||
* Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC by @mvl-boston
|
* Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC by @mvl-boston
|
||||||
* GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` by @sunnyhaibin
|
* GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` by @sunnyhaibin
|
||||||
* GM: remove `CHEVROLET_BOLT_NON_ACC_2ND_GEN` from `dashcamOnly` by @sunnyhaibin
|
* GM: remove `CHEVROLET_BOLT_NON_ACC_2ND_GEN` from `dashcamOnly` by @sunnyhaibin
|
||||||
* Hyundai Longitudinal: deprecate ramp update for dynamic tune by @Discountchubbs
|
|
||||||
* Rivian: long upgrade messages on bus 1 by @lukasloetkolben
|
|
||||||
* Toyota: Stop and Go Hack (Alpha) by @sunnyhaibin
|
|
||||||
* Toyota: gate Smart DSU behind Alpha Longitudinal by @sunnyhaibin
|
|
||||||
* Toyota: Gas Interceptor always set `standstill_req` by @sunnyhaibin
|
|
||||||
* MADS safety: dedicated `controls_allowed_lateral` by @sunnyhaibin
|
|
||||||
* Platform List: include community supported platforms by @sunnyhaibin
|
|
||||||
* New Contributors (sunnypilot/sunnypilot)
|
* New Contributors (sunnypilot/sunnypilot)
|
||||||
* @TheSecurityDev made their first contribution in "ui: fix sidebar scroll in UI screenshots"
|
* @TheSecurityDev made their first contribution in "ui: fix sidebar scroll in UI screenshots"
|
||||||
* @zikeji made their first contribution in "sunnylink: block remote modification of SSH key parameters"
|
* @zikeji made their first contribution in "sunnylink: block remote modification of SSH key parameters"
|
||||||
* @Candy0707 made their first contribution in "[TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar"
|
* @Candy0707 made their first contribution in "[TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar"
|
||||||
* @CHaucke89 made their first contribution in "Pause Lateral Control with Blinker: Post-Blinker Delay"
|
* @CHaucke89 made their first contribution in "Pause Lateral Control with Blinker: Post-Blinker Delay"
|
||||||
* @yasu-oh made their first contribution in "SCC-V: Use p97 for predicted lateral accel"
|
* @yasu-oh made their first contribution in "SCC-V: Use p97 for predicted lateral accel"
|
||||||
* @angaz made their first contribution in "[TIZI/TICI] visuals: Improved speed limit"
|
|
||||||
* @jamesmikesell made their first contribution in "ICBM: ensure button timers update on disable to clear stale presses"
|
|
||||||
* @zephleggett made their first contribution in "controlsd: fix steer_limited_by_safety not updating under MADS"
|
|
||||||
* @lukasloetkolben made their first contribution in "pandad: flasher for Rivian long upgrade module"
|
|
||||||
* @didlawowo made their first contribution in "i18n(fr): Add French translations"
|
|
||||||
* @mmmorks made their first contribution in "NNLC: restore pre-v1 PID gains in torque extension"
|
|
||||||
* New Contributors (sunnypilot/opendbc)
|
* New Contributors (sunnypilot/opendbc)
|
||||||
* @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled"
|
* @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled"
|
||||||
* @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name"
|
* @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name"
|
||||||
@@ -202,20 +99,6 @@ sunnypilot Version 2026.001.000 (2026-05-06)
|
|||||||
* @royjr made their first contribution in "HKG: add KIA_FORTE_2019_NON_SCC fingerprint"
|
* @royjr made their first contribution in "HKG: add KIA_FORTE_2019_NON_SCC fingerprint"
|
||||||
* @ssysm made their first contribution in "Tesla: remove `TESLA_MODEL_X` from `dashcamOnly`"
|
* @ssysm made their first contribution in "Tesla: remove `TESLA_MODEL_X` from `dashcamOnly`"
|
||||||
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.002.000...v2026.001.000
|
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.002.000...v2026.001.000
|
||||||
************************
|
|
||||||
* Synced with commaai's openpilot (v0.11.1)
|
|
||||||
* master commit c001f3c9b490a80e69539f0af6022f6e07ceb721 (April 16, 2026)
|
|
||||||
* New driver monitoring model
|
|
||||||
* Improved image processing pipeline for driver camera
|
|
||||||
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
|
||||||
* New driving model #36798
|
|
||||||
* Fully trained using a learned simulator
|
|
||||||
* Improved longitudinal performance in Experimental mode
|
|
||||||
* Reduce comma four standby power usage by 77% to 52 mW
|
|
||||||
* Kia K7 2017 support thanks to royjr!
|
|
||||||
* Lexus LS 2018 support thanks to Hacheoy!
|
|
||||||
* Improved inter-process communication memory efficiency
|
|
||||||
* comma four support
|
|
||||||
|
|
||||||
sunnypilot Version 2025.002.000 (2025-11-06)
|
sunnypilot Version 2025.002.000 (2025-11-06)
|
||||||
========================
|
========================
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends sudo tzdata locales && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||||
|
ENV LANG=en_US.UTF-8
|
||||||
|
ENV LANGUAGE=en_US:en
|
||||||
|
ENV LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
|
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
|
||||||
|
|
||||||
|
ARG USER=batman
|
||||||
|
ARG USER_UID=1001
|
||||||
|
RUN useradd -m -s /bin/bash -u $USER_UID $USER
|
||||||
|
RUN usermod -aG sudo $USER
|
||||||
|
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||||
|
USER $USER
|
||||||
|
|
||||||
|
ENV OPENPILOT_PATH=/home/$USER/openpilot
|
||||||
|
RUN mkdir -p ${OPENPILOT_PATH}
|
||||||
|
WORKDIR ${OPENPILOT_PATH}
|
||||||
|
|
||||||
|
COPY --chown=$USER . ${OPENPILOT_PATH}/
|
||||||
|
|
||||||
|
ENV UV_BIN="/home/$USER/.local/bin/"
|
||||||
|
ENV VIRTUAL_ENV=${OPENPILOT_PATH}/.venv
|
||||||
|
ENV PATH="$UV_BIN:$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
RUN tools/setup_dependencies.sh && \
|
||||||
|
sudo rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN git config --global --add safe.directory '*'
|
||||||
Vendored
+9
-8
@@ -12,7 +12,7 @@ def retryWithDelay(int maxRetries, int delay, Closure body) {
|
|||||||
def device(String ip, String step_label, String cmd) {
|
def device(String ip, String step_label, String cmd) {
|
||||||
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||||
def ssh_cmd = """
|
def ssh_cmd = """
|
||||||
ssh -o ControlMaster=auto -o ControlPath=/tmp/ssh_control_%C -o ControlPersist=yes -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
ssh -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -166,8 +166,8 @@ node {
|
|||||||
env.GIT_BRANCH = checkout(scm).GIT_BRANCH
|
env.GIT_BRANCH = checkout(scm).GIT_BRANCH
|
||||||
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
||||||
|
|
||||||
def excludeBranches = ['__nightly', 'devel', 'devel-staging',
|
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
||||||
'release-tizi', 'release-tizi-staging', 'release-mici', 'release-mici-staging', 'testing-closet*', 'hotfix-*']
|
'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*']
|
||||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||||
|
|
||||||
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
||||||
@@ -179,7 +179,7 @@ node {
|
|||||||
try {
|
try {
|
||||||
if (env.BRANCH_NAME == 'devel-staging') {
|
if (env.BRANCH_NAME == 'devel-staging') {
|
||||||
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
|
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
|
||||||
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging,release-mici-staging $SOURCE_DIR/release/build_release.sh"),
|
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ node {
|
|||||||
'HW + Unit Tests': {
|
'HW + Unit Tests': {
|
||||||
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test power draw", "pytest -s selfdrive/test//test_power_draw.py"),
|
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||||
])
|
])
|
||||||
@@ -218,14 +218,14 @@ node {
|
|||||||
'camerad OX03C10': {
|
'camerad OX03C10': {
|
||||||
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'camerad OS04C10': {
|
'camerad OS04C10': {
|
||||||
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@@ -246,7 +246,8 @@ node {
|
|||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||||
step("test amp", "pytest common/hardware/tici/tests/test_amplifier.py"),
|
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||||
|
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+2
-17
@@ -1,23 +1,8 @@
|
|||||||
Version 0.11.2 (2026-06-15)
|
Version 0.10.4 (2026-02-17)
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
|
||||||
Version 0.11.1 (2026-05-18)
|
|
||||||
========================
|
|
||||||
* New driver monitoring model
|
|
||||||
* Improved image processing pipeline for driver camera
|
|
||||||
* Improved thermal policy for comma four
|
|
||||||
* Acura MDX 2022-24 support thanks to mvl-boston!
|
|
||||||
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
|
||||||
|
|
||||||
Version 0.11.0 (2026-03-17)
|
|
||||||
========================
|
|
||||||
* New driving model #36798
|
|
||||||
* Fully trained using a learned simulator
|
|
||||||
* Improved longitudinal performance in Experimental mode
|
|
||||||
* Reduce comma four standby power usage by 77% to 52 mW
|
|
||||||
* Kia K7 2017 support thanks to royjr!
|
* Kia K7 2017 support thanks to royjr!
|
||||||
* Lexus LS 2018 support thanks to Hacheoy!
|
* Lexus LS 2018 support thanks to Hacheoy!
|
||||||
|
* Reduce comma four standby power usage by 77% to 52 mW
|
||||||
|
|
||||||
Version 0.10.3 (2025-12-17)
|
Version 0.10.3 (2025-12-17)
|
||||||
========================
|
========================
|
||||||
|
|||||||
+71
-135
@@ -4,34 +4,32 @@ import sys
|
|||||||
import sysconfig
|
import sysconfig
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
import importlib
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import SCons.Errors
|
import SCons.Errors
|
||||||
from SCons.Defaults import _stripixes
|
|
||||||
|
|
||||||
TICI = os.path.isfile('/TICI')
|
|
||||||
|
|
||||||
SCons.Warnings.warningAsException(True)
|
SCons.Warnings.warningAsException(True)
|
||||||
|
|
||||||
Decider('MD5-timestamp')
|
Decider('MD5-timestamp')
|
||||||
|
|
||||||
SetOption('num_jobs', max(1, int(os.cpu_count()/(1 if "CI" in os.environ else 2))))
|
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
|
||||||
|
|
||||||
|
AddOption('--asan', action='store_true', help='turn on ASAN')
|
||||||
|
AddOption('--ubsan', action='store_true', help='turn on UBSan')
|
||||||
|
AddOption('--mutation', action='store_true', help='generate mutation-ready code')
|
||||||
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
|
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
|
||||||
AddOption('--verbose', action='store_true', default=False, help='show full build commands')
|
AddOption('--verbose', action='store_true', default=False, help='show full build commands')
|
||||||
release = not os.path.exists(File('#.gitattributes').abspath) # file absent on release branch, see release_files.py
|
|
||||||
AddOption('--minimal',
|
AddOption('--minimal',
|
||||||
action='store_false',
|
action='store_false',
|
||||||
dest='extras',
|
dest='extras',
|
||||||
default=(not TICI and not release),
|
default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS)
|
||||||
help='the minimum build to run openpilot. no tests, tools, etc.')
|
help='the minimum build to run openpilot. no tests, tools, etc.')
|
||||||
|
|
||||||
# Detect platform
|
# Detect platform
|
||||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
arch = "Darwin"
|
arch = "Darwin"
|
||||||
elif arch == "aarch64" and TICI:
|
elif arch == "aarch64" and os.path.isfile('/TICI'):
|
||||||
arch = "larch64"
|
arch = "larch64"
|
||||||
assert arch in [
|
assert arch in [
|
||||||
"larch64", # linux tici arm64
|
"larch64", # linux tici arm64
|
||||||
@@ -40,61 +38,32 @@ assert arch in [
|
|||||||
"Darwin", # macOS arm64 (x86 not supported)
|
"Darwin", # macOS arm64 (x86 not supported)
|
||||||
]
|
]
|
||||||
|
|
||||||
pkg_names = ['acados', 'bzip2', 'capnproto', 'catch2', 'eigen', 'ffmpeg', 'json11', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd']
|
if arch != "larch64":
|
||||||
pkgs = [importlib.import_module(name) for name in pkg_names]
|
import bzip2
|
||||||
acados = pkgs[pkg_names.index('acados')]
|
import capnproto
|
||||||
acados_include_dirs = [
|
import eigen
|
||||||
acados.INCLUDE_DIR,
|
import ffmpeg as ffmpeg_pkg
|
||||||
os.path.join(acados.INCLUDE_DIR, "blasfeo", "include"),
|
import libjpeg
|
||||||
os.path.join(acados.INCLUDE_DIR, "hpipm", "include"),
|
import libyuv
|
||||||
]
|
import ncurses
|
||||||
|
import openssl3
|
||||||
|
import python3_dev
|
||||||
# ***** enforce a whitelist of system libraries *****
|
import zeromq
|
||||||
# this prevents silently relying on a 3rd party package,
|
import zstd
|
||||||
# e.g. apt-installed libusb. all libraries should either
|
pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd]
|
||||||
# be distributed with all Linux distros and macOS, or
|
py_include = python3_dev.INCLUDE_DIR
|
||||||
# vendored in commaai/dependencies.
|
else:
|
||||||
allowed_system_libs = {
|
# TODO: remove when AGNOS has our new vendor pkgs
|
||||||
"EGL", "GLESv2", "GL",
|
pkgs = []
|
||||||
"Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets",
|
py_include = sysconfig.get_paths()['include']
|
||||||
"dl", "drm", "gbm", "m", "pthread",
|
|
||||||
}
|
|
||||||
|
|
||||||
def _resolve_lib(env, name):
|
|
||||||
for d in env.Flatten(env.get('LIBPATH', [])):
|
|
||||||
p = Dir(str(d)).abspath
|
|
||||||
for ext in ('.a', '.so', '.dylib'):
|
|
||||||
f = File(os.path.join(p, f'lib{name}{ext}'))
|
|
||||||
if f.exists() or f.has_builder():
|
|
||||||
return name
|
|
||||||
if name in allowed_system_libs:
|
|
||||||
return name
|
|
||||||
raise SCons.Errors.UserError(f"Unexpected non-vendored library '{name}'")
|
|
||||||
|
|
||||||
def _libflags(target, source, env, for_signature):
|
|
||||||
libs = []
|
|
||||||
lp = env.subst('$LIBLITERALPREFIX')
|
|
||||||
for lib in env.Flatten(env.get('LIBS', [])):
|
|
||||||
if isinstance(lib, str):
|
|
||||||
if os.sep in lib or lib.startswith('#'):
|
|
||||||
libs.append(File(lib))
|
|
||||||
elif lib.startswith('-') or (lp and lib.startswith(lp)):
|
|
||||||
libs.append(lib)
|
|
||||||
else:
|
|
||||||
libs.append(_resolve_lib(env, lib))
|
|
||||||
else:
|
|
||||||
libs.append(lib)
|
|
||||||
return _stripixes(env['LIBLINKPREFIX'], libs, env['LIBLINKSUFFIX'],
|
|
||||||
env['LIBPREFIXES'], env['LIBSUFFIXES'], env, env['LIBLITERALPREFIX'])
|
|
||||||
|
|
||||||
env = Environment(
|
env = Environment(
|
||||||
ENV={
|
ENV={
|
||||||
"PATH": os.environ['PATH'],
|
"PATH": os.environ['PATH'],
|
||||||
"PYTHONPATH": Dir("#").abspath,
|
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
||||||
"ACADOS_SOURCE_DIR": acados.DIR,
|
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
||||||
"ACADOS_PYTHON_INTERFACE_PATH": acados.TEMPLATE_DIR,
|
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
||||||
"TERA_PATH": acados.TERA_PATH
|
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
||||||
},
|
},
|
||||||
CCFLAGS=[
|
CCFLAGS=[
|
||||||
"-g",
|
"-g",
|
||||||
@@ -114,14 +83,22 @@ env = Environment(
|
|||||||
CPPPATH=[
|
CPPPATH=[
|
||||||
"#",
|
"#",
|
||||||
"#msgq",
|
"#msgq",
|
||||||
acados_include_dirs,
|
"#third_party",
|
||||||
|
"#third_party/json11",
|
||||||
|
"#third_party/linux/include",
|
||||||
|
"#third_party/acados/include",
|
||||||
|
"#third_party/acados/include/blasfeo/include",
|
||||||
|
"#third_party/acados/include/hpipm/include",
|
||||||
|
"#third_party/catch2/include",
|
||||||
[x.INCLUDE_DIR for x in pkgs],
|
[x.INCLUDE_DIR for x in pkgs],
|
||||||
],
|
],
|
||||||
LIBPATH=[
|
LIBPATH=[
|
||||||
"#common",
|
"#common",
|
||||||
"#msgq_repo",
|
"#msgq_repo",
|
||||||
|
"#third_party",
|
||||||
"#selfdrive/pandad",
|
"#selfdrive/pandad",
|
||||||
"#rednose/helpers",
|
"#rednose/helpers",
|
||||||
|
f"#third_party/acados/{arch}/lib",
|
||||||
[x.LIB_DIR for x in pkgs],
|
[x.LIB_DIR for x in pkgs],
|
||||||
],
|
],
|
||||||
RPATH=[],
|
RPATH=[],
|
||||||
@@ -131,14 +108,14 @@ env = Environment(
|
|||||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||||
)
|
)
|
||||||
if arch != "larch64":
|
|
||||||
env['_LIBFLAGS'] = _libflags
|
|
||||||
|
|
||||||
# Arch-specific flags and paths
|
# Arch-specific flags and paths
|
||||||
if arch == "larch64":
|
if arch == "larch64":
|
||||||
env["CC"] = "clang"
|
env["CC"] = "clang"
|
||||||
env["CXX"] = "clang++"
|
env["CXX"] = "clang++"
|
||||||
env.Append(LIBPATH=[
|
env.Append(LIBPATH=[
|
||||||
|
"/usr/local/lib",
|
||||||
|
"/system/vendor/lib64",
|
||||||
"/usr/lib/aarch64-linux-gnu",
|
"/usr/lib/aarch64-linux-gnu",
|
||||||
])
|
])
|
||||||
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"]
|
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"]
|
||||||
@@ -150,6 +127,19 @@ elif arch == "Darwin":
|
|||||||
])
|
])
|
||||||
env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
||||||
env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
||||||
|
else:
|
||||||
|
env.Append(LIBPATH=[
|
||||||
|
"/usr/lib",
|
||||||
|
"/usr/local/lib",
|
||||||
|
])
|
||||||
|
|
||||||
|
# Sanitizers and extra CCFLAGS from CLI
|
||||||
|
if GetOption('asan'):
|
||||||
|
env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"])
|
||||||
|
env.Append(LINKFLAGS=["-fsanitize=address"])
|
||||||
|
elif GetOption('ubsan'):
|
||||||
|
env.Append(CCFLAGS=["-fsanitize=undefined"])
|
||||||
|
env.Append(LINKFLAGS=["-fsanitize=undefined"])
|
||||||
|
|
||||||
_extra_cc = shlex.split(GetOption('ccflags') or '')
|
_extra_cc = shlex.split(GetOption('ccflags') or '')
|
||||||
if _extra_cc:
|
if _extra_cc:
|
||||||
@@ -175,9 +165,19 @@ if not GetOption('verbose'):
|
|||||||
):
|
):
|
||||||
env[f"{action}COMSTR"] = f" [{short}] $TARGET"
|
env[f"{action}COMSTR"] = f" [{short}] $TARGET"
|
||||||
|
|
||||||
|
# progress output
|
||||||
|
node_interval = 5
|
||||||
|
node_count = 0
|
||||||
|
def progress_function(node):
|
||||||
|
global node_count
|
||||||
|
node_count += node_interval
|
||||||
|
sys.stderr.write("progress: %d\n" % node_count)
|
||||||
|
if os.environ.get('SCONS_PROGRESS'):
|
||||||
|
Progress(progress_function, interval=node_interval)
|
||||||
|
|
||||||
# ********** Cython build environment **********
|
# ********** Cython build environment **********
|
||||||
envCython = env.Clone()
|
envCython = env.Clone()
|
||||||
envCython["CPPPATH"] += [sysconfig.get_paths()['include'], np.get_include()]
|
envCython["CPPPATH"] += [py_include, np.get_include()]
|
||||||
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
||||||
envCython["CCFLAGS"].remove("-Werror")
|
envCython["CCFLAGS"].remove("-Werror")
|
||||||
|
|
||||||
@@ -190,24 +190,14 @@ else:
|
|||||||
np_version = SCons.Script.Value(np.__version__)
|
np_version = SCons.Script.Value(np.__version__)
|
||||||
Export('envCython', 'np_version')
|
Export('envCython', 'np_version')
|
||||||
|
|
||||||
Export('env', 'arch', 'acados', 'release')
|
Export('env', 'arch')
|
||||||
|
|
||||||
# Setup cache dir
|
# Setup cache dir
|
||||||
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
||||||
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
|
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
|
||||||
cache_size_limit = 4e9 if "CI" in os.environ else 2e9
|
|
||||||
CacheDir(cache_dir)
|
CacheDir(cache_dir)
|
||||||
Clean(["."], cache_dir)
|
Clean(["."], cache_dir)
|
||||||
|
|
||||||
def prune_cache_dir(target=None, source=None, env=None):
|
|
||||||
cache_files = sorted((os.path.join(root, f) for root, _, files in os.walk(cache_dir) for f in files), key=os.path.getmtime)
|
|
||||||
cache_size = sum(os.path.getsize(f) for f in cache_files)
|
|
||||||
for f in cache_files:
|
|
||||||
if cache_size < cache_size_limit:
|
|
||||||
break
|
|
||||||
cache_size -= os.path.getsize(f)
|
|
||||||
os.unlink(f)
|
|
||||||
|
|
||||||
# ********** start building stuff **********
|
# ********** start building stuff **********
|
||||||
|
|
||||||
# Build common module
|
# Build common module
|
||||||
@@ -221,6 +211,7 @@ Export('common')
|
|||||||
env_swaglog = env.Clone()
|
env_swaglog = env.Clone()
|
||||||
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
||||||
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
||||||
|
SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog})
|
||||||
|
|
||||||
SConscript(['cereal/SConscript'])
|
SConscript(['cereal/SConscript'])
|
||||||
|
|
||||||
@@ -243,70 +234,15 @@ SConscript([
|
|||||||
if arch == "larch64":
|
if arch == "larch64":
|
||||||
SConscript(['system/camerad/SConscript'])
|
SConscript(['system/camerad/SConscript'])
|
||||||
|
|
||||||
# Build selfdrive
|
# Build openpilot
|
||||||
SConscript([
|
SConscript(['third_party/SConscript'])
|
||||||
'selfdrive/pandad/SConscript',
|
|
||||||
'selfdrive/controls/lib/lateral_mpc_lib/SConscript',
|
SConscript(['selfdrive/SConscript'])
|
||||||
'selfdrive/controls/lib/longitudinal_mpc_lib/SConscript',
|
|
||||||
'selfdrive/locationd/SConscript',
|
|
||||||
'selfdrive/modeld/SConscript',
|
|
||||||
'selfdrive/ui/SConscript',
|
|
||||||
])
|
|
||||||
|
|
||||||
SConscript(['sunnypilot/SConscript'])
|
SConscript(['sunnypilot/SConscript'])
|
||||||
|
|
||||||
# Build desktop-only tools
|
if Dir('#tools/cabana/').exists() and arch != "larch64":
|
||||||
if GetOption('extras') and arch != "larch64":
|
SConscript(['tools/cabana/SConscript'])
|
||||||
SConscript([
|
|
||||||
'tools/replay/SConscript',
|
|
||||||
'tools/cabana/SConscript',
|
|
||||||
'tools/jotpluggler/SConscript',
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
env.CompilationDatabase('compile_commands.json')
|
env.CompilationDatabase('compile_commands.json')
|
||||||
|
|
||||||
# progress output
|
|
||||||
def count_scons_nodes(nodes):
|
|
||||||
seen = set()
|
|
||||||
stack = list(nodes)
|
|
||||||
|
|
||||||
while stack:
|
|
||||||
node = stack.pop().disambiguate()
|
|
||||||
if node in seen:
|
|
||||||
continue
|
|
||||||
seen.add(node)
|
|
||||||
if hasattr(node, 'has_builder') and node.has_builder():
|
|
||||||
build_product_nodes.add(node)
|
|
||||||
executor = node.get_executor()
|
|
||||||
if executor is not None:
|
|
||||||
stack += executor.get_all_prerequisites() + executor.get_all_children()
|
|
||||||
|
|
||||||
return len(seen)
|
|
||||||
|
|
||||||
progress_interval = 5
|
|
||||||
progress_count = 0
|
|
||||||
build_product_nodes = set()
|
|
||||||
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
|
|
||||||
|
|
||||||
def progress_function(node):
|
|
||||||
global progress_count
|
|
||||||
if progress_count >= progress_total:
|
|
||||||
return
|
|
||||||
progress_count = min(progress_count + progress_interval, progress_total)
|
|
||||||
progress = round(100. * progress_count / progress_total, 1)
|
|
||||||
sys.stderr.write("\rBuilding: %5.1f%%" % progress if sys.stderr.isatty() else "progress: %.1f\n" % progress)
|
|
||||||
if progress == 100. and sys.stderr.isatty():
|
|
||||||
sys.stderr.write("\n")
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
Progress(progress_function, interval=progress_interval)
|
|
||||||
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
|
|
||||||
|
|
||||||
def check_build_product_size(target, source, env):
|
|
||||||
limit = 50 * 1024 * 1024 # GitHub max size
|
|
||||||
for t in target:
|
|
||||||
if hasattr(t, 'isfile') and t.isfile() and (size := os.path.getsize(t.abspath)) > limit:
|
|
||||||
raise SCons.Errors.UserError(f"{t} is {size / (1024 * 1024):.1f} MiB, exceeding the {limit / (1024 * 1024):.1f} MiB limit")
|
|
||||||
if not GetOption('extras'):
|
|
||||||
AddPostAction(list(build_product_nodes), Action(check_build_product_size, None))
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ cereal_dir = Dir('.')
|
|||||||
gen_dir = Dir('gen')
|
gen_dir = Dir('gen')
|
||||||
|
|
||||||
# Build cereal
|
# Build cereal
|
||||||
schema_files = ['log.capnp', 'car.capnp', 'deprecated.capnp', 'custom.capnp']
|
schema_files = ['log.capnp', 'car.capnp', 'legacy.capnp', 'custom.capnp']
|
||||||
env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files],
|
env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files],
|
||||||
schema_files,
|
schema_files,
|
||||||
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
|
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
vision @2;
|
vision @2;
|
||||||
policy @3;
|
policy @3;
|
||||||
offPolicy @4;
|
offPolicy @4;
|
||||||
onPolicy @5;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ $Cxx.namespace("cereal");
|
|||||||
|
|
||||||
@0x80ef1ec4889c2a63;
|
@0x80ef1ec4889c2a63;
|
||||||
|
|
||||||
# deprecated.capnp: a home for deprecated structs
|
# legacy.capnp: a home for deprecated structs
|
||||||
|
|
||||||
struct LogRotate @0x9811e1f38f62f2d1 {
|
struct LogRotate @0x9811e1f38f62f2d1 {
|
||||||
segmentNum @0 :Int32;
|
segmentNum @0 :Int32;
|
||||||
@@ -571,207 +571,4 @@ struct LidarPts @0xe3d6685d4e9d8f7a {
|
|||||||
pkt @4 :Data;
|
pkt @4 :Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveTracksDEPRECATED @0xb16f60103159415a {
|
|
||||||
trackId @0 :Int32;
|
|
||||||
dRel @1 :Float32;
|
|
||||||
yRel @2 :Float32;
|
|
||||||
vRel @3 :Float32;
|
|
||||||
aRel @4 :Float32;
|
|
||||||
timeStamp @5 :Float32;
|
|
||||||
status @6 :Float32;
|
|
||||||
currentTime @7 :Float32;
|
|
||||||
stationary @8 :Bool;
|
|
||||||
oncoming @9 :Bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LiveMpcData @0x92a5e332a85f32a0 {
|
|
||||||
x @0 :List(Float32);
|
|
||||||
y @1 :List(Float32);
|
|
||||||
psi @2 :List(Float32);
|
|
||||||
curvature @3 :List(Float32);
|
|
||||||
qpIterations @4 :UInt32;
|
|
||||||
calculationTime @5 :UInt64;
|
|
||||||
cost @6 :Float64;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LiveLongitudinalMpcData @0xe7e17c434f865ae2 {
|
|
||||||
xEgo @0 :List(Float32);
|
|
||||||
vEgo @1 :List(Float32);
|
|
||||||
aEgo @2 :List(Float32);
|
|
||||||
xLead @3 :List(Float32);
|
|
||||||
vLead @4 :List(Float32);
|
|
||||||
aLead @5 :List(Float32);
|
|
||||||
aLeadTau @6 :Float32; # lead accel time constant
|
|
||||||
qpIterations @7 :UInt32;
|
|
||||||
mpcId @8 :UInt32;
|
|
||||||
calculationTime @9 :UInt64;
|
|
||||||
cost @10 :Float64;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 {
|
|
||||||
frameId @0 :UInt32;
|
|
||||||
modelExecutionTime @14 :Float32;
|
|
||||||
dspExecutionTime @16 :Float32;
|
|
||||||
rawPredictions @15 :Data;
|
|
||||||
|
|
||||||
faceOrientation @3 :List(Float32);
|
|
||||||
facePosition @4 :List(Float32);
|
|
||||||
faceProb @5 :Float32;
|
|
||||||
leftEyeProb @6 :Float32;
|
|
||||||
rightEyeProb @7 :Float32;
|
|
||||||
leftBlinkProb @8 :Float32;
|
|
||||||
rightBlinkProb @9 :Float32;
|
|
||||||
faceOrientationStd @11 :List(Float32);
|
|
||||||
facePositionStd @12 :List(Float32);
|
|
||||||
sunglassesProb @13 :Float32;
|
|
||||||
poorVision @17 :Float32;
|
|
||||||
partialFace @18 :Float32;
|
|
||||||
distractedPose @19 :Float32;
|
|
||||||
distractedEyes @20 :Float32;
|
|
||||||
eyesOnRoad @21 :Float32;
|
|
||||||
phoneUse @22 :Float32;
|
|
||||||
occludedProb @23 :Float32;
|
|
||||||
|
|
||||||
readyProb @24 :List(Float32);
|
|
||||||
notReadyProb @25 :List(Float32);
|
|
||||||
|
|
||||||
irPwrDEPRECATED @10 :Float32;
|
|
||||||
descriptorDEPRECATED @1 :List(Float32);
|
|
||||||
stdDEPRECATED @2 :Float32;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NavModelData @0xac3de5c437be057a {
|
|
||||||
frameId @0 :UInt32;
|
|
||||||
locationMonoTime @6 :UInt64;
|
|
||||||
modelExecutionTime @1 :Float32;
|
|
||||||
dspExecutionTime @2 :Float32;
|
|
||||||
features @3 :List(Float32);
|
|
||||||
# predicted future position
|
|
||||||
position @4 :XYData;
|
|
||||||
desirePrediction @5 :List(Float32);
|
|
||||||
|
|
||||||
# All SI units and in device frame
|
|
||||||
struct XYData @0xbe09e615b2507e26 {
|
|
||||||
x @0 :List(Float32);
|
|
||||||
y @1 :List(Float32);
|
|
||||||
xStd @2 :List(Float32);
|
|
||||||
yStd @3 :List(Float32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AndroidBuildInfo @0xfe2919d5c21f426c {
|
|
||||||
board @0 :Text;
|
|
||||||
bootloader @1 :Text;
|
|
||||||
brand @2 :Text;
|
|
||||||
device @3 :Text;
|
|
||||||
display @4 :Text;
|
|
||||||
fingerprint @5 :Text;
|
|
||||||
hardware @6 :Text;
|
|
||||||
host @7 :Text;
|
|
||||||
id @8 :Text;
|
|
||||||
manufacturer @9 :Text;
|
|
||||||
model @10 :Text;
|
|
||||||
product @11 :Text;
|
|
||||||
radioVersion @12 :Text;
|
|
||||||
serial @13 :Text;
|
|
||||||
supportedAbis @14 :List(Text);
|
|
||||||
tags @15 :Text;
|
|
||||||
time @16 :Int64;
|
|
||||||
type @17 :Text;
|
|
||||||
user @18 :Text;
|
|
||||||
|
|
||||||
versionCodename @19 :Text;
|
|
||||||
versionRelease @20 :Text;
|
|
||||||
versionSdk @21 :Int32;
|
|
||||||
versionSecurityPatch @22 :Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AndroidSensor @0x9b513b93a887dbcd {
|
|
||||||
id @0 :Int32;
|
|
||||||
name @1 :Text;
|
|
||||||
vendor @2 :Text;
|
|
||||||
version @3 :Int32;
|
|
||||||
handle @4 :Int32;
|
|
||||||
type @5 :Int32;
|
|
||||||
maxRange @6 :Float32;
|
|
||||||
resolution @7 :Float32;
|
|
||||||
power @8 :Float32;
|
|
||||||
minDelay @9 :Int32;
|
|
||||||
fifoReservedEventCount @10 :UInt32;
|
|
||||||
fifoMaxEventCount @11 :UInt32;
|
|
||||||
stringType @12 :Text;
|
|
||||||
maxDelay @13 :Int32;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IosBuildInfo @0xd97e3b28239f5580 {
|
|
||||||
appVersion @0 :Text;
|
|
||||||
appBuild @1 :UInt32;
|
|
||||||
osVersion @2 :Text;
|
|
||||||
deviceModel @3 :Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FrameTypeDEPRECATED @0xa37f0d8558e193fd {
|
|
||||||
unknown @0;
|
|
||||||
neo @1;
|
|
||||||
chffrAndroid @2;
|
|
||||||
front @3;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AndroidCaptureResult @0xbcc3efbac41d2048 {
|
|
||||||
sensitivity @0 :Int32;
|
|
||||||
frameDuration @1 :Int64;
|
|
||||||
exposureTime @2 :Int64;
|
|
||||||
rollingShutterSkew @3 :UInt64;
|
|
||||||
colorCorrectionTransform @4 :List(Int32);
|
|
||||||
colorCorrectionGains @5 :List(Float32);
|
|
||||||
displayRotation @6 :Int8;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UsbPowerModeDEPRECATED @0xa8883583b32c9877 {
|
|
||||||
none @0;
|
|
||||||
client @1;
|
|
||||||
cdp @2;
|
|
||||||
dcp @3;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LateralINDIState @0x939463348632375e {
|
|
||||||
active @0 :Bool;
|
|
||||||
steeringAngleDeg @1 :Float32;
|
|
||||||
steeringRateDeg @2 :Float32;
|
|
||||||
steeringAccelDeg @3 :Float32;
|
|
||||||
rateSetPoint @4 :Float32;
|
|
||||||
accelSetPoint @5 :Float32;
|
|
||||||
accelError @6 :Float32;
|
|
||||||
delayedOutput @7 :Float32;
|
|
||||||
delta @8 :Float32;
|
|
||||||
output @9 :Float32;
|
|
||||||
saturated @10 :Bool;
|
|
||||||
steeringAngleDesiredDeg @11 :Float32;
|
|
||||||
steeringRateDesiredDeg @12 :Float32;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LateralLQRState @0x9024e2d790c82ade {
|
|
||||||
active @0 :Bool;
|
|
||||||
steeringAngleDeg @1 :Float32;
|
|
||||||
i @2 :Float32;
|
|
||||||
output @3 :Float32;
|
|
||||||
lqrOutput @4 :Float32;
|
|
||||||
saturated @5 :Bool;
|
|
||||||
steeringAngleDesiredDeg @6 :Float32;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LateralPlannerSolution @0x84caeca5a6b4acfe {
|
|
||||||
x @0 :List(Float32);
|
|
||||||
y @1 :List(Float32);
|
|
||||||
yaw @2 :List(Float32);
|
|
||||||
yawRate @3 :List(Float32);
|
|
||||||
xStd @4 :List(Float32);
|
|
||||||
yStd @5 :List(Float32);
|
|
||||||
yawStd @6 :List(Float32);
|
|
||||||
yawRateStd @7 :List(Float32);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GpsTrajectory @0x8cfeb072f5301000 {
|
|
||||||
x @0 :List(Float32);
|
|
||||||
y @1 :List(Float32);
|
|
||||||
}
|
|
||||||
+470
-406
File diff suppressed because it is too large
Load Diff
@@ -259,11 +259,11 @@ class PubMaster:
|
|||||||
self.sock[s].send(dat)
|
self.sock[s].send(dat)
|
||||||
|
|
||||||
def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool:
|
def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool:
|
||||||
try:
|
for _ in range(int(timeout*(1./dt))):
|
||||||
self.sock[s].wait_for_readers(timeout=timeout, interval=dt)
|
if self.sock[s].all_readers_updated():
|
||||||
return True
|
return True
|
||||||
except TimeoutError:
|
time.sleep(dt)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def all_readers_updated(self, s: str) -> bool:
|
def all_readers_updated(self, s: str) -> bool:
|
||||||
return self.sock[s].all_readers_updated()
|
return self.sock[s].all_readers_updated() # type: ignore
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def zmq_sleep(t=1):
|
|||||||
|
|
||||||
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
||||||
def random_carstate():
|
def random_carstate():
|
||||||
fields = ["vEgo", "aEgo", "steeringTorque", "steeringAngleDeg"]
|
fields = ["vEgo", "aEgo", "brake", "steeringAngleDeg"]
|
||||||
msg = messaging.new_message("carState")
|
msg = messaging.new_message("carState")
|
||||||
cs = msg.carState
|
cs = msg.carState
|
||||||
for f in fields:
|
for f in fields:
|
||||||
|
|||||||
@@ -1,165 +1,222 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Validate sunnypilot routes are parseable by stock commaai/openpilot.
|
|
||||||
|
|
||||||
Cap'n Proto is wire-compatible across renames, type relocations, and
|
|
||||||
additive fields. The only breaking change is a union variant that
|
|
||||||
upstream doesn't recognize — an unknown discriminant makes the entire
|
|
||||||
union unreadable.
|
|
||||||
|
|
||||||
This script checks: for every struct with a union that exists in both
|
|
||||||
schemas, does sunnypilot introduce union variants upstream doesn't have?
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any, List, Tuple
|
||||||
|
|
||||||
NO_DISCRIMINANT = 0xFFFF
|
DEBUG = False
|
||||||
|
|
||||||
|
|
||||||
def hex_id(value: int) -> str:
|
def print_debug(string: str) -> None:
|
||||||
return f"0x{value:016x}"
|
if DEBUG:
|
||||||
|
print(string)
|
||||||
|
|
||||||
|
|
||||||
def encode_struct(schema: Any) -> dict:
|
def create_schema_instance(struct: Any, prop: Tuple[str, Any]) -> Any:
|
||||||
node = schema.node
|
"""
|
||||||
fields = []
|
Create a new instance of a schema type, handling different field types.
|
||||||
for name, field in schema.fields.items():
|
|
||||||
proto = field.proto
|
Args:
|
||||||
ordinal = proto.ordinal.explicit if proto.ordinal.which() == "explicit" else None
|
struct: The Cap'n Proto schema structure
|
||||||
discriminant = proto.discriminantValue if proto.discriminantValue != NO_DISCRIMINANT else None
|
prop: A tuple containing the field name and field metadata
|
||||||
fields.append({"name": name, "ordinal": ordinal, "discriminant": discriminant})
|
|
||||||
return {
|
Returns:
|
||||||
"typeId": hex_id(node.id),
|
A new initialized schema instance
|
||||||
"displayName": node.displayName,
|
"""
|
||||||
"hasUnion": node.struct.discriminantCount > 0,
|
struct_instance = struct.new_message()
|
||||||
"fields": fields,
|
field_name, field_metadata = prop
|
||||||
}
|
|
||||||
|
try:
|
||||||
|
field_type = field_metadata.proto.slot.type.which()
|
||||||
|
|
||||||
|
# Initialize different types of fields
|
||||||
|
if field_type in ('list', 'text', 'data'):
|
||||||
|
struct_instance.init(field_name, 1)
|
||||||
|
print_debug(f"Initialized list/text/data field: {field_name}")
|
||||||
|
elif field_type in ('struct', 'object'):
|
||||||
|
struct_instance.init(field_name)
|
||||||
|
print_debug(f"Initialized struct/object field: {field_name}")
|
||||||
|
|
||||||
|
return struct_instance
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating instance for {field_name}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _child_struct_schema(field: Any) -> Any:
|
def get_schema_fields(schema_struct: Any) -> List[Tuple[str, Any]]:
|
||||||
proto = field.proto
|
"""
|
||||||
if proto.which() == "group":
|
Retrieve all fields from a given schema structure.
|
||||||
return field.schema
|
|
||||||
type_node = proto.slot.type
|
Args:
|
||||||
which = type_node.which()
|
schema_struct: The Cap'n Proto schema structure
|
||||||
if which == "struct":
|
|
||||||
return field.schema
|
Returns:
|
||||||
if which == "list":
|
A list of field names and their metadata
|
||||||
container = field.schema
|
"""
|
||||||
element_type = type_node.list.elementType
|
try:
|
||||||
while element_type.which() == "list":
|
# Get all fields from the schema
|
||||||
container = container.elementType
|
schema_fields = list(schema_struct.schema.fields.items())
|
||||||
element_type = element_type.list.elementType
|
|
||||||
if element_type.which() == "struct":
|
print_debug("Discovered schema fields:")
|
||||||
return container.elementType
|
for field_name, field_metadata in schema_fields:
|
||||||
return None
|
print_debug(f"- {field_name}")
|
||||||
|
|
||||||
|
return schema_fields
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error retrieving schema fields: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def collect_schema(root: Any) -> dict[str, dict]:
|
def generate_schema_instances(schema_struct: Any) -> List[Any]:
|
||||||
structs: dict[str, dict] = {}
|
"""
|
||||||
stack = [root]
|
Generate instances for all fields in a given schema.
|
||||||
while stack:
|
|
||||||
schema = stack.pop()
|
Args:
|
||||||
type_id = hex_id(schema.node.id)
|
schema_struct: The Cap'n Proto schema structure
|
||||||
if type_id in structs:
|
|
||||||
continue
|
Returns:
|
||||||
structs[type_id] = encode_struct(schema)
|
A list of schema instances
|
||||||
for _name, field in schema.fields.items():
|
"""
|
||||||
try:
|
schema_fields = get_schema_fields(schema_struct)
|
||||||
child = _child_struct_schema(field)
|
instances = []
|
||||||
except Exception:
|
|
||||||
child = None
|
for field_prop in schema_fields:
|
||||||
if child is not None:
|
try:
|
||||||
stack.append(child)
|
instance = create_schema_instance(schema_struct, field_prop)
|
||||||
return structs
|
if instance is not None:
|
||||||
|
instances.append(instance)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Skipping field due to error: {e}")
|
||||||
|
|
||||||
|
print(f"Generated {len(instances)} schema instances")
|
||||||
|
return instances
|
||||||
|
|
||||||
|
|
||||||
def load_log(cereal_dir: str, extra_imports: list[str] | None = None) -> Any:
|
def persist_instances(instances: List[Any], filename: str) -> None:
|
||||||
import capnp
|
"""
|
||||||
cereal_dir = os.path.abspath(cereal_dir)
|
Write schema instances to a binary file.
|
||||||
capnp.remove_import_hook()
|
|
||||||
imports = [cereal_dir] + [os.path.abspath(p) for p in (extra_imports or [])]
|
Args:
|
||||||
return capnp.load(os.path.join(cereal_dir, "log.capnp"), imports=imports)
|
instances: List of schema instances
|
||||||
|
filename: Output file path
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
for instance in instances:
|
||||||
|
f.write(instance.to_bytes())
|
||||||
|
|
||||||
|
print(f"Successfully wrote {len(instances)} instances to {filename}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error persisting instances: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def dump_schema(cereal_dir: str, path: str, extra_imports: list[str] | None = None) -> None:
|
def read_instances(filename: str, schema_type: Any) -> List[Any]:
|
||||||
log = load_log(cereal_dir, extra_imports)
|
"""
|
||||||
payload = {
|
Read schema instances from a binary file.
|
||||||
"root": hex_id(log.Event.schema.node.id),
|
|
||||||
"structs": collect_schema(log.Event.schema),
|
Args:
|
||||||
}
|
filename: Input file path
|
||||||
with open(path, "w", encoding="utf-8") as handle:
|
schema_type: The schema type to use for reading
|
||||||
json.dump(payload, handle, indent=2, sort_keys=True)
|
|
||||||
print(f"wrote schema dump with {len(payload['structs'])} structs to {path}")
|
Returns:
|
||||||
|
A list of read schema instances
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
instances = list(schema_type.read_multiple_bytes(data))
|
||||||
|
|
||||||
|
print(f"Read {len(instances)} instances from {filename}")
|
||||||
|
return instances
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading instances: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def compare(sunnypilot_dump: dict, upstream_dump: dict) -> list[str]:
|
def compare_schemas(original_instances: List[Any], read_instances: List[Any]) -> bool:
|
||||||
violations: list[str] = []
|
"""
|
||||||
sunnypilot_structs = sunnypilot_dump["structs"]
|
Compare original and read-back instances to detect potential breaking changes.
|
||||||
upstream_structs = upstream_dump["structs"]
|
|
||||||
|
|
||||||
for type_id, sp_struct in sunnypilot_structs.items():
|
Args:
|
||||||
if not sp_struct["hasUnion"]:
|
original_instances: List of originally generated instances
|
||||||
continue
|
read_instances: List of instances read back from file
|
||||||
up_struct = upstream_structs.get(type_id)
|
|
||||||
if up_struct is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
up_ordinals = {f["ordinal"] for f in up_struct["fields"] if f.get("discriminant") is not None}
|
Returns:
|
||||||
display = sp_struct["displayName"]
|
Boolean indicating whether schemas appear compatible
|
||||||
|
"""
|
||||||
|
if len(original_instances) != len(read_instances):
|
||||||
|
print("❌ Schema Compatibility Warning: Instance count mismatch")
|
||||||
|
return False
|
||||||
|
|
||||||
for field in sp_struct["fields"]:
|
compatible = True
|
||||||
if field.get("discriminant") is None:
|
for struct in read_instances:
|
||||||
continue
|
try:
|
||||||
if field["ordinal"] not in up_ordinals:
|
getattr(struct, struct.which()) # Attempting to access the field to validate readability
|
||||||
violations.append(
|
except Exception as e:
|
||||||
f"{display} @{field['ordinal']} '{field['name']}': "
|
print(f"❌ Structural change detected: {struct.which()} is not readable.\nFull error: {e}")
|
||||||
f"union variant not present upstream (discriminant={field['discriminant']})"
|
compatible = False
|
||||||
)
|
|
||||||
|
|
||||||
return violations
|
return compatible
|
||||||
|
|
||||||
|
|
||||||
def run_read(cereal_dir: str, peer_path: str, extra_imports: list[str] | None = None) -> int:
|
def main():
|
||||||
log = load_log(cereal_dir, extra_imports)
|
"""
|
||||||
with open(peer_path, "r", encoding="utf-8") as f:
|
CLI entry point for schema compatibility testing.
|
||||||
peer_dump = json.load(f)
|
"""
|
||||||
local_dump = {
|
# Setup argument parser
|
||||||
"root": hex_id(log.Event.schema.node.id),
|
parser = argparse.ArgumentParser(
|
||||||
"structs": collect_schema(log.Event.schema),
|
description='Cap\'n Proto Schema Compatibility Testing Tool',
|
||||||
}
|
epilog='Test schema compatibility by generating and reading back instances.'
|
||||||
violations = compare(sunnypilot_dump=peer_dump, upstream_dump=local_dump)
|
)
|
||||||
|
|
||||||
if not violations:
|
# Add mutually exclusive group for generation or reading mode
|
||||||
print("cereal compat OK: upstream can parse sunnypilot routes.")
|
mode_group = parser.add_mutually_exclusive_group(required=True)
|
||||||
return 0
|
mode_group.add_argument('-g', '--generate', action='store_true',
|
||||||
|
help='Generate schema instances')
|
||||||
|
mode_group.add_argument('-r', '--read', action='store_true',
|
||||||
|
help='Read and validate schema instances')
|
||||||
|
|
||||||
print(f"cereal compat FAIL ({len(violations)} leaked union variant(s)):")
|
# Common arguments
|
||||||
for v in violations:
|
parser.add_argument('-f', '--file',
|
||||||
print(f" {v}")
|
default='schema_instances.bin',
|
||||||
return 1
|
help='Output/input binary file (default: schema_instances.bin)')
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
def main() -> int:
|
|
||||||
parser = argparse.ArgumentParser(description="sunnypilot cereal upstream compat check")
|
|
||||||
mode = parser.add_mutually_exclusive_group(required=True)
|
|
||||||
mode.add_argument("-g", "--generate", action="store_true", help="dump local schema to JSON")
|
|
||||||
mode.add_argument("-r", "--read", action="store_true", help="validate against peer schema")
|
|
||||||
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path")
|
|
||||||
parser.add_argument("--cereal-dir", required=True, help="path to cereal directory")
|
|
||||||
parser.add_argument("-I", "--import-path", action="append", default=[], help="extra capnp import paths")
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Import the schema dynamically
|
||||||
|
try:
|
||||||
|
from cereal import log
|
||||||
|
schema_type = log.Event
|
||||||
|
except ImportError:
|
||||||
|
print("Error: Unable to import schema. Ensure 'cereal' is installed.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Execute based on mode
|
||||||
if args.generate:
|
if args.generate:
|
||||||
dump_schema(args.cereal_dir, args.file, args.import_path)
|
print("🔧 Generating Schema Instances")
|
||||||
return 0
|
instances = generate_schema_instances(schema_type)
|
||||||
return run_read(args.cereal_dir, args.file, args.import_path)
|
persist_instances(instances, args.file)
|
||||||
|
print("✅ Instance generation complete")
|
||||||
|
|
||||||
|
elif args.read:
|
||||||
|
print("🔍 Reading and Validating Schema Instances")
|
||||||
|
generated_instances = generate_schema_instances(schema_type)
|
||||||
|
read_back_instances = read_instances(args.file, schema_type)
|
||||||
|
|
||||||
|
# Compare schemas
|
||||||
|
if compare_schemas(generated_instances, read_back_instances):
|
||||||
|
print("✅ Schema Compatibility: No breaking changes detected")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("❌ Potential Schema Breaking Changes Detected")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
main()
|
||||||
|
|||||||
+12
-3
@@ -24,7 +24,10 @@ _services: dict[str, tuple] = {
|
|||||||
# note: the "EncodeIdx" packets will still be in the log
|
# note: the "EncodeIdx" packets will still be in the log
|
||||||
"gyroscope": (True, 104., 104),
|
"gyroscope": (True, 104., 104),
|
||||||
"accelerometer": (True, 104., 104),
|
"accelerometer": (True, 104., 104),
|
||||||
|
"magnetometer": (True, 25.),
|
||||||
|
"lightSensor": (True, 100., 100),
|
||||||
"temperatureSensor": (True, 2., 200),
|
"temperatureSensor": (True, 2., 200),
|
||||||
|
"gpsNMEA": (True, 9.),
|
||||||
"deviceState": (True, 2., 1),
|
"deviceState": (True, 2., 1),
|
||||||
"touch": (True, 20., 1),
|
"touch": (True, 20., 1),
|
||||||
"can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment
|
"can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment
|
||||||
@@ -36,8 +39,8 @@ _services: dict[str, tuple] = {
|
|||||||
"roadEncodeIdx": (False, 20., 1),
|
"roadEncodeIdx": (False, 20., 1),
|
||||||
"liveTracks": (True, 20.),
|
"liveTracks": (True, 20.),
|
||||||
"sendcan": (True, 100., 139, QueueSize.MEDIUM),
|
"sendcan": (True, 100., 139, QueueSize.MEDIUM),
|
||||||
"logMessage": (True, 0., None, QueueSize.BIG),
|
"logMessage": (True, 0.),
|
||||||
"errorLogMessage": (True, 0., 1, QueueSize.BIG),
|
"errorLogMessage": (True, 0., 1),
|
||||||
"liveCalibration": (True, 4., 4),
|
"liveCalibration": (True, 4., 4),
|
||||||
"liveTorqueParameters": (True, 4., 1),
|
"liveTorqueParameters": (True, 4., 1),
|
||||||
"liveDelay": (True, 4., 1),
|
"liveDelay": (True, 4., 1),
|
||||||
@@ -46,13 +49,13 @@ _services: dict[str, tuple] = {
|
|||||||
"carControl": (True, 100., 10),
|
"carControl": (True, 100., 10),
|
||||||
"carOutput": (True, 100., 10),
|
"carOutput": (True, 100., 10),
|
||||||
"longitudinalPlan": (True, 20., 10),
|
"longitudinalPlan": (True, 20., 10),
|
||||||
"lateralManeuverPlan": (True, 20.),
|
|
||||||
"driverAssistance": (True, 20., 20),
|
"driverAssistance": (True, 20., 20),
|
||||||
"procLog": (True, 0.5, 15, QueueSize.BIG),
|
"procLog": (True, 0.5, 15, QueueSize.BIG),
|
||||||
"gpsLocationExternal": (True, 10., 10),
|
"gpsLocationExternal": (True, 10., 10),
|
||||||
"gpsLocation": (True, 1., 1),
|
"gpsLocation": (True, 1., 1),
|
||||||
"ubloxGnss": (True, 10.),
|
"ubloxGnss": (True, 10.),
|
||||||
"qcomGnss": (True, 2.),
|
"qcomGnss": (True, 2.),
|
||||||
|
"gnssMeasurements": (True, 10., 10),
|
||||||
"clocks": (True, 0.1, 1),
|
"clocks": (True, 0.1, 1),
|
||||||
"ubloxRaw": (True, 20.),
|
"ubloxRaw": (True, 20.),
|
||||||
"livePose": (True, 20., 4),
|
"livePose": (True, 20., 4),
|
||||||
@@ -71,6 +74,10 @@ _services: dict[str, tuple] = {
|
|||||||
"drivingModelData": (True, 20., 10),
|
"drivingModelData": (True, 20., 10),
|
||||||
"modelV2": (True, 20., None, QueueSize.BIG),
|
"modelV2": (True, 20., None, QueueSize.BIG),
|
||||||
"managerState": (True, 2., 1),
|
"managerState": (True, 2., 1),
|
||||||
|
"uploaderState": (True, 0., 1),
|
||||||
|
"navInstruction": (True, 1., 10),
|
||||||
|
"navRoute": (True, 0.),
|
||||||
|
"navThumbnail": (True, 0.),
|
||||||
"qRoadEncodeIdx": (False, 20.),
|
"qRoadEncodeIdx": (False, 20.),
|
||||||
"userBookmark": (True, 0., 1),
|
"userBookmark": (True, 0., 1),
|
||||||
"soundPressure": (True, 10., 10),
|
"soundPressure": (True, 10., 10),
|
||||||
@@ -106,6 +113,8 @@ _services: dict[str, tuple] = {
|
|||||||
"livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
"livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
||||||
"livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
"livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
||||||
"customReservedRawData0": (True, 0.),
|
"customReservedRawData0": (True, 0.),
|
||||||
|
"customReservedRawData1": (True, 0.),
|
||||||
|
"customReservedRawData2": (True, 0.),
|
||||||
}
|
}
|
||||||
SERVICE_LIST = {name: Service(*vals) for
|
SERVICE_LIST = {name: Service(*vals) for
|
||||||
idx, (name, vals) in enumerate(_services.items())}
|
idx, (name, vals) in enumerate(_services.items())}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
*.cpp
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
Import('env', 'envCython')
|
Import('env', 'envCython', 'arch')
|
||||||
|
|
||||||
common_libs = [
|
common_libs = [
|
||||||
'params.cc',
|
'params.cc',
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ import os
|
|||||||
import requests
|
import requests
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from datetime import datetime, timedelta, UTC
|
from datetime import datetime, timedelta, UTC
|
||||||
from openpilot.common.hardware.hw import Paths
|
from openpilot.system.hardware.hw import Paths
|
||||||
from openpilot.system.version import get_version
|
from openpilot.system.version import get_version
|
||||||
|
|
||||||
# name: jwt signature algorithm
|
# name: jwt signature algorithm
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from openpilot.common.esim.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
|
|
||||||
|
|
||||||
__all__ = ["LPABase", "LPAError", "LPAProfileNotFoundError", "Profile"]
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
|
|
||||||
class LPAError(RuntimeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LPAProfileNotFoundError(LPAError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Profile:
|
|
||||||
iccid: str
|
|
||||||
nickname: str
|
|
||||||
enabled: bool
|
|
||||||
provider: str
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_comma(self) -> bool:
|
|
||||||
return self.provider == 'Webbing' and self.iccid.startswith('8985235')
|
|
||||||
|
|
||||||
|
|
||||||
class LPABase(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def list_profiles(self) -> list[Profile]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_active_profile(self) -> Profile | None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def delete_profile(self, iccid: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def download_profile(self, qr: str, nickname: str | None = None) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def nickname_profile(self, iccid: str, nickname: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def switch_profile(self, iccid: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def process_notifications(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def is_euicc(self) -> bool:
|
|
||||||
pass
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
from openpilot.common.hardware import HARDWARE
|
|
||||||
from openpilot.common.esim.base import LPABase, Profile
|
|
||||||
|
|
||||||
|
|
||||||
def sorted_profiles(lpa: LPABase) -> list[Profile]:
|
|
||||||
return sorted(lpa.list_profiles(), key=lambda p: p.iccid)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_iccid(lpa: LPABase, ref: str) -> str:
|
|
||||||
# ref is either a 1-based index into the sorted list, or a literal iccid
|
|
||||||
if ref.isdigit():
|
|
||||||
profiles = sorted_profiles(lpa)
|
|
||||||
idx = int(ref) - 1
|
|
||||||
if not 0 <= idx < len(profiles):
|
|
||||||
raise SystemExit(f'no profile at index {ref} (have {len(profiles)})')
|
|
||||||
return profiles[idx].iccid
|
|
||||||
return ref
|
|
||||||
|
|
||||||
|
|
||||||
def print_profiles(lpa: LPABase) -> None:
|
|
||||||
profiles = sorted_profiles(lpa)
|
|
||||||
print(f'\n{len(profiles)} profile{"s" if len(profiles) != 1 else ""}:')
|
|
||||||
for i, p in enumerate(profiles, start=1):
|
|
||||||
print(f'{i}. {p.iccid} (nickname: {p.nickname or "<none provided>"}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai')
|
|
||||||
sub = parser.add_subparsers(dest='cmd')
|
|
||||||
|
|
||||||
sub.add_parser('list', help='list profiles')
|
|
||||||
|
|
||||||
p_switch = sub.add_parser('switch', help='switch to profile')
|
|
||||||
p_switch.add_argument('profile', help='iccid or 1-based index from `list`')
|
|
||||||
|
|
||||||
p_delete = sub.add_parser('delete', help='delete profile (warning: this cannot be undone)')
|
|
||||||
p_delete.add_argument('profile', help='iccid or 1-based index from `list`')
|
|
||||||
|
|
||||||
p_download = sub.add_parser('download', help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)')
|
|
||||||
p_download.add_argument('qr')
|
|
||||||
p_download.add_argument('name')
|
|
||||||
|
|
||||||
p_nickname = sub.add_parser('nickname', help='update the nickname for a profile')
|
|
||||||
p_nickname.add_argument('profile', help='iccid or 1-based index from `list`')
|
|
||||||
p_nickname.add_argument('name')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
lpa = HARDWARE.get_sim_lpa()
|
|
||||||
if args.cmd == 'switch':
|
|
||||||
lpa.switch_profile(resolve_iccid(lpa, args.profile))
|
|
||||||
elif args.cmd == 'delete':
|
|
||||||
iccid = resolve_iccid(lpa, args.profile)
|
|
||||||
confirm = input(f'are you sure you want to delete profile {iccid}? (y/N) ')
|
|
||||||
if confirm == 'y':
|
|
||||||
lpa.delete_profile(iccid)
|
|
||||||
else:
|
|
||||||
print('cancelled')
|
|
||||||
exit(0)
|
|
||||||
elif args.cmd == 'download':
|
|
||||||
lpa.download_profile(args.qr, args.name)
|
|
||||||
elif args.cmd == 'nickname':
|
|
||||||
lpa.nickname_profile(resolve_iccid(lpa, args.profile), args.name)
|
|
||||||
else:
|
|
||||||
if args.cmd is None:
|
|
||||||
parser.print_help()
|
|
||||||
print_profiles(lpa)
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# GSMA Certificate Issuer (CI) bundle for eSIM RSP
|
|
||||||
# Source: https://euicc-manual.osmocom.org/docs/pki/ci/bundle.pem
|
|
||||||
|
|
||||||
issuer=
|
|
||||||
countryName = CH
|
|
||||||
organizationName = OISTE Foundation
|
|
||||||
commonName = OISTE GSMA CI G1
|
|
||||||
notBefore=2024-01-16 23:17:39Z
|
|
||||||
notAfter=2059-01-07 23:17:38Z
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw
|
|
||||||
QzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xGTAXBgNV
|
|
||||||
BAMMEE9JU1RFIEdTTUEgQ0kgRzEwIBcNMjQwMTE2MjMxNzM5WhgPMjA1OTAxMDcy
|
|
||||||
MzE3MzhaMEMxCzAJBgNVBAYTAkNIMRkwFwYDVQQKDBBPSVNURSBGb3VuZGF0aW9u
|
|
||||||
MRkwFwYDVQQDDBBPSVNURSBHU01BIENJIEcxMFkwEwYHKoZIzj0CAQYIKoZIzj0D
|
|
||||||
AQcDQgAEvZ3s3PFC4NgrCcCMmHJ6DJ66uzAHuLcvjJnOn+TtBNThS7YHLDyHCa2v
|
|
||||||
7D+zTP+XTtgqgcLoB56Gha9EQQQ4xKNtMGswDwYDVR0TAQH/BAUwAwEB/zAQBgNV
|
|
||||||
HREECTAHiAVghXQFDjAXBgNVHSABAf8EDTALMAkGB2eBEgECAQAwHQYDVR0OBBYE
|
|
||||||
FEwnlnrSDBSzkelgHkHmBK1XwCIvMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD
|
|
||||||
AgNIADBFAiBVcywTj017jKpAQ+gwy4MqK2hQvzve6lkvQkgSP6ykHwIhAI0KFwCD
|
|
||||||
jnPbmcJsG41hUrWNlf+IcrMvFuYii0DasBNi
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
issuer=
|
|
||||||
organizationName = GSM Association
|
|
||||||
commonName = GSM Association - RSP2 Root CI1
|
|
||||||
notBefore=2017-02-22 00:00:00Z
|
|
||||||
notAfter=2052-02-21 23:59:59Z
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICSTCCAe+gAwIBAgIQbmhWeneg7nyF7hg5Y9+qejAKBggqhkjOPQQDAjBEMRgw
|
|
||||||
FgYDVQQKEw9HU00gQXNzb2NpYXRpb24xKDAmBgNVBAMTH0dTTSBBc3NvY2lhdGlv
|
|
||||||
biAtIFJTUDIgUm9vdCBDSTEwIBcNMTcwMjIyMDAwMDAwWhgPMjA1MjAyMjEyMzU5
|
|
||||||
NTlaMEQxGDAWBgNVBAoTD0dTTSBBc3NvY2lhdGlvbjEoMCYGA1UEAxMfR1NNIEFz
|
|
||||||
c29jaWF0aW9uIC0gUlNQMiBSb290IENJMTBZMBMGByqGSM49AgEGCCqGSM49AwEH
|
|
||||||
A0IABJ1qutL0HCMX52GJ6/jeibsAqZfULWj/X10p/Min6seZN+hf5llovbCNuB2n
|
|
||||||
unLz+O8UD0SUCBUVo8e6n9X1TuajgcAwgb0wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
|
|
||||||
EwEB/wQFMAMBAf8wEwYDVR0RBAwwCogIKwYBBAGC6WAwFwYDVR0gAQH/BA0wCzAJ
|
|
||||||
BgdngRIBAgEAME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9nc21hLWNybC5zeW1h
|
|
||||||
dXRoLmNvbS9vZmZsaW5lY2EvZ3NtYS1yc3AyLXJvb3QtY2kxLmNybDAdBgNVHQ4E
|
|
||||||
FgQUgTcPUSXQsdQI1MOyMubSXnlb6/swCgYIKoZIzj0EAwIDSAAwRQIgIJdYsOMF
|
|
||||||
WziPK7l8nh5mu0qiRiVf25oa9ullG/OIASwCIQDqCmDrYf+GziHXBOiwJwnBaeBO
|
|
||||||
aFsiLzIEOaUuZwdNUw==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
issuer=
|
|
||||||
countryName = US
|
|
||||||
organizationName = Entrust, Inc.
|
|
||||||
organizationalUnitName = See www.entrust.net/legal-terms
|
|
||||||
organizationalUnitName = (c) 2016 Entrust, Inc. - for authorized use only
|
|
||||||
commonName = Entrust eSIM Certification Authority
|
|
||||||
notBefore=2016-11-16 16:04:02Z
|
|
||||||
notAfter=2051-10-16 16:34:02Z
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIC6DCCAo2gAwIBAgIRAIy4GT7M5nHsAAAAAFgsinowCgYIKoZIzj0EAwIwgbkx
|
|
||||||
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9T
|
|
||||||
ZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAx
|
|
||||||
NiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxLTArBgNV
|
|
||||||
BAMTJEVudHJ1c3QgZVNJTSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAgFw0xNjEx
|
|
||||||
MTYxNjA0MDJaGA8yMDUxMTAxNjE2MzQwMlowgbkxCzAJBgNVBAYTAlVTMRYwFAYD
|
|
||||||
VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
|
|
||||||
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNiBFbnRydXN0LCBJbmMuIC0g
|
|
||||||
Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxLTArBgNVBAMTJEVudHJ1c3QgZVNJTSBD
|
|
||||||
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
|
|
||||||
BAdzwGHeQ1Wb2f4DmHTByR5/IWL3JugQ1U3908a++bHdlt+TTA7K4c5cYZ+51Yz/
|
|
||||||
hg/bacxguPDh9uQUK6Wg3a6jcjBwMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
|
|
||||||
BAQDAgEGMBcGA1UdIAEB/wQNMAswCQYHZ4ESAQIBADAVBgNVHREEDjAMiApghkgB
|
|
||||||
hvpsFAoAMB0GA1UdDgQWBBQWcEt/NR42B/GMS3AAXDoAPf1BSjAKBggqhkjOPQQD
|
|
||||||
AgNJADBGAiEAspjXMvaBZyAg86Z0AAtT0yBRAi1EyaAfNz9kDJeAE04CIQC3efj8
|
|
||||||
ATL7/tDBOhANy3cK8PS/1NIlu9vqMLCZsZvJ0Q==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
issuer=
|
|
||||||
countryName = FR
|
|
||||||
organizationName = OBERTHUR TECHNOLOGIES
|
|
||||||
organizationalUnitName = TELECOM
|
|
||||||
commonName = MC4 OT ROOT CI v1
|
|
||||||
notBefore=2016-11-15 00:00:01Z
|
|
||||||
notAfter=2046-11-08 23:59:59Z
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICOjCCAeGgAwIBAgIBATAKBggqhkjOPQQDAjBbMQswCQYDVQQGEwJGUjEeMBwG
|
|
||||||
A1UEChMVT0JFUlRIVVIgVEVDSE5PTE9HSUVTMRAwDgYDVQQLEwdURUxFQ09NMRow
|
|
||||||
GAYDVQQDExFNQzQgT1QgUk9PVCBDSSB2MTAeFw0xNjExMTUwMDAwMDFaFw00NjEx
|
|
||||||
MDgyMzU5NTlaMFsxCzAJBgNVBAYTAkZSMR4wHAYDVQQKExVPQkVSVEhVUiBURUNI
|
|
||||||
Tk9MT0dJRVMxEDAOBgNVBAsTB1RFTEVDT00xGjAYBgNVBAMTEU1DNCBPVCBST09U
|
|
||||||
IENJIHYxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHb/Gajt3OZxuaDSklBQE
|
|
||||||
D4lOd6PGPLSvtfkM952ubdyy45tJwAeA0eEii0CLrFT6tcfXkW+H/5mQyMRXaAUk
|
|
||||||
T6OBlTCBkjAfBgNVHSMEGDAWgBTNbmC3LXoGPLyEYluR6A/jBAbhPjAdBgNVHQ4E
|
|
||||||
FgQUzW5gty16Bjy8hGJbkegP4wQG4T4wDgYDVR0PAQH/BAQDAgAGMBcGA1UdIAEB
|
|
||||||
/wQNMAswCQYHZ4ESAQIBADAWBgNVHREEDzANiAsrBgEEAYHvb7OITTAPBgNVHRMB
|
|
||||||
Af8EBTADAQH/MAoGCCqGSM49BAMCA0cAMEQCIEw4Nc7f2fDtoH+6ON/bknfDQxmT
|
|
||||||
ikThXjhpLtSrSKN2AiAxHxgC87L0FDnH8dJNlkdGX9c0JIx6oLheIplfS6k+jg==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
issuer=
|
|
||||||
commonName = SubMan V4.2 CI Google Pixel
|
|
||||||
organizationName = Giesecke and Devrient GmbH
|
|
||||||
organizationalUnitName = Mobile Security
|
|
||||||
countryName = DE
|
|
||||||
notBefore=2017-05-10 00:00:00Z
|
|
||||||
notAfter=2027-05-10 00:00:00Z
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICaTCCAg6gAwIBAgICASwwCgYIKoZIzj0EAwIwczElMCMGA1UEAxMcIFN1Yk1h
|
|
||||||
biBWNC4yIENJIEdvb2dsZSBQaXhlbDEjMCEGA1UEChMaR2llc2Vja2UgYW5kIERl
|
|
||||||
dnJpZW50IEdtYkgxGDAWBgNVBAsTD01vYmlsZSBTZWN1cml0eTELMAkGA1UEBhMC
|
|
||||||
REUwHhcNMTcwNTEwMDAwMDAwWhcNMjcwNTEwMDAwMDAwWjBzMSUwIwYDVQQDExwg
|
|
||||||
U3ViTWFuIFY0LjIgQ0kgR29vZ2xlIFBpeGVsMSMwIQYDVQQKExpHaWVzZWNrZSBh
|
|
||||||
bmQgRGV2cmllbnQgR21iSDEYMBYGA1UECxMPTW9iaWxlIFNlY3VyaXR5MQswCQYD
|
|
||||||
VQQGEwJERTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHNorfaJsGzqWNawyAhl
|
|
||||||
IAv9QL2/+b9RsUoso06t/dKX1MRr5CUJ51acvv5TAFhQKIml+dwLbFnV5aO+8W6Z
|
|
||||||
wxajgZEwgY4wHwYDVR0jBBgwFoAUtg8LiX/WMLiM/tYWH46oCMU4KsMwHQYDVR0O
|
|
||||||
BBYEFLYPC4l/1jC4jP7WFh+OqAjFOCrDMA4GA1UdDwEB/wQEAwIBBjAXBgNVHSAB
|
|
||||||
Af8EDTALMAkGB2eBEgECAQAwDwYDVR0TAQH/BAUwAwEB/zASBgNVHREECzAJiAcr
|
|
||||||
BgEEAdwPMAoGCCqGSM49BAMCA0kAMEYCIQDpoZcuAQrjATW8U+AWqMUJ0dY6nWW1
|
|
||||||
R1QmFzVZ1yMXSwIhALCvRqkCtgiavdeFeSgsSNbY5Fhd+QoCltuSh1U4TE7A
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
issuer=
|
|
||||||
countryName = DE
|
|
||||||
commonName = SubMan V4.2 CI
|
|
||||||
organizationName = Giesecke and Devrient
|
|
||||||
organizationalUnitName = Mobile Security
|
|
||||||
notBefore=2016-08-12 13:51:48Z
|
|
||||||
notAfter=2026-08-12 13:51:48Z
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICUjCCAfigAwIBAgIDQgAAMAoGCCqGSM49BAMCMGAxCzAJBgNVBAYTAkRFMRcw
|
|
||||||
FQYDVQQDEw5TdWJNYW4gVjQuMiBDSTEeMBwGA1UEChMVR2llc2Vja2UgYW5kIERl
|
|
||||||
dnJpZW50MRgwFgYDVQQLEw9Nb2JpbGUgU2VjdXJpdHkwHhcNMTYwODEyMTM1MTQ4
|
|
||||||
WhcNMjYwODEyMTM1MTQ4WjBgMQswCQYDVQQGEwJERTEXMBUGA1UEAxMOU3ViTWFu
|
|
||||||
IFY0LjIgQ0kxHjAcBgNVBAoTFUdpZXNlY2tlIGFuZCBEZXZyaWVudDEYMBYGA1UE
|
|
||||||
CxMPTW9iaWxlIFNlY3VyaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYIgl
|
|
||||||
VQr9wbXOlwPp8qMg5Df08Cli9Mc+lpr3Lwa9PlVA3QWlLeX4GfD4H3phLBqVIa17
|
|
||||||
yHttmtheTxi0KoEqhKOBoDCBnTAdBgNVHQ4EFgQU6lOt7zMpuVCa/XVf1Ei4LcG8
|
|
||||||
7P8wDgYDVR0PAQH/BAQDAgEGMBcGA1UdIAEB/wQNMAswCQYHZ4ESAQIBADAPBgNV
|
|
||||||
HRMBAf8EBTADAQH/MBIGA1UdEQQLMAmIBysGAQQB3A8wLgYDVR0fBCcwJTAjoCGg
|
|
||||||
H4YdaHR0cDovL2dpLWRlLmNvbS90ZXN0LmNybC5wZW0wCgYIKoZIzj0EAwIDSAAw
|
|
||||||
RQIhAMMx2L/VHDiOW+Fl/OuFmhCdizYM17Yn9zAVieKO2T0iAiANWtCMmY+DzkqK
|
|
||||||
yHxBFX0U2tBd682zP4DpgRt8j3Ylew==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,791 +0,0 @@
|
|||||||
# SGP.22 v2.3: https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2021/07/SGP.22-v2.3.pdf
|
|
||||||
|
|
||||||
import atexit
|
|
||||||
import base64
|
|
||||||
import fcntl
|
|
||||||
import hashlib
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
import serial
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import termios
|
|
||||||
import time
|
|
||||||
|
|
||||||
from collections.abc import Callable, Generator
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from openpilot.common.time_helpers import system_time_valid
|
|
||||||
from openpilot.common.esim.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
|
|
||||||
|
|
||||||
GSMA_CI_BUNDLE = str(Path(__file__).parent / "gsma_ci_bundle.pem")
|
|
||||||
|
|
||||||
DEFAULT_DEVICE = "/dev/modem_at0"
|
|
||||||
DEFAULT_BAUD = 9600
|
|
||||||
DEFAULT_TIMEOUT = 5.0
|
|
||||||
# https://euicc-manual.osmocom.org/docs/lpa/applet-id/
|
|
||||||
ISDR_AID = "A0000005591010FFFFFFFF8900000100"
|
|
||||||
ES10X_MSS = 120
|
|
||||||
HTTP_TIMEOUT = 30
|
|
||||||
OPEN_ISDR_RETRIES = 10
|
|
||||||
OPEN_ISDR_RETRY_DELAY_S = 0.25
|
|
||||||
OPEN_ISDR_RESET_ATTEMPT = 5
|
|
||||||
SEND_APDU_RETRIES = 3
|
|
||||||
LOCK_FILE = '/dev/shm/modem.lock'
|
|
||||||
DEBUG = os.environ.get("DEBUG") == "1"
|
|
||||||
|
|
||||||
|
|
||||||
# TLV Tags
|
|
||||||
TAG_ICCID = 0x5A
|
|
||||||
TAG_STATUS = 0x80
|
|
||||||
TAG_EUICC_INFO = 0xBF20
|
|
||||||
TAG_PREPARE_DOWNLOAD = 0xBF21
|
|
||||||
TAG_BPP_COMMAND = 0xBF23
|
|
||||||
TAG_PROFILE_METADATA = 0xBF25
|
|
||||||
TAG_INSTALL_RESULT_DATA = 0xBF27
|
|
||||||
TAG_LIST_NOTIFICATION = 0xBF28
|
|
||||||
TAG_SET_NICKNAME = 0xBF29
|
|
||||||
TAG_RETRIEVE_NOTIFICATION = 0xBF2B
|
|
||||||
TAG_PROFILE_INFO_LIST = 0xBF2D
|
|
||||||
TAG_EUICC_CHALLENGE = 0xBF2E
|
|
||||||
TAG_NOTIFICATION_METADATA = 0xBF2F
|
|
||||||
TAG_NOTIFICATION_SENT = 0xBF30
|
|
||||||
TAG_ENABLE_PROFILE = 0xBF31
|
|
||||||
TAG_DELETE_PROFILE = 0xBF33
|
|
||||||
TAG_BPP = 0xBF36
|
|
||||||
TAG_PROFILE_INSTALL_RESULT = 0xBF37
|
|
||||||
TAG_AUTH_SERVER = 0xBF38
|
|
||||||
TAG_CANCEL_SESSION = 0xBF41
|
|
||||||
TAG_OK = 0xA0
|
|
||||||
|
|
||||||
PROFILE_OK = 0x00
|
|
||||||
PROFILE_NOT_IN_DISABLED_STATE = 0x02
|
|
||||||
PROFILE_CAT_BUSY = 0x05
|
|
||||||
|
|
||||||
PROFILE_ERROR_CODES = {
|
|
||||||
0x01: "iccidOrAidNotFound", PROFILE_NOT_IN_DISABLED_STATE: "profileNotInDisabledState",
|
|
||||||
0x03: "disallowedByPolicy", 0x04: "wrongProfileReenabling",
|
|
||||||
PROFILE_CAT_BUSY: "catBusy", 0x06: "undefinedError",
|
|
||||||
}
|
|
||||||
AUTH_SERVER_ERROR_CODES = {
|
|
||||||
0x01: "eUICCVerificationFailed", 0x02: "eUICCCertificateExpired",
|
|
||||||
0x03: "eUICCCertificateRevoked", 0x05: "invalidServerSignature",
|
|
||||||
0x06: "euiccCiPKUnknown", 0x0A: "matchingIdRefused",
|
|
||||||
0x10: "insufficientMemory",
|
|
||||||
}
|
|
||||||
BPP_COMMAND_NAMES = {
|
|
||||||
0: "initialiseSecureChannel", 1: "configureISDP", 2: "storeMetadata",
|
|
||||||
3: "storeMetadata2", 4: "replaceSessionKeys", 5: "loadProfileElements",
|
|
||||||
}
|
|
||||||
BPP_ERROR_REASONS = {
|
|
||||||
1: "incorrectInputValues", 2: "invalidSignature", 3: "invalidTransactionId",
|
|
||||||
4: "unsupportedCrtValues", 5: "unsupportedRemoteOperationType",
|
|
||||||
6: "unsupportedProfileClass", 7: "scp03tStructureError", 8: "scp03tSecurityError",
|
|
||||||
9: "iccidAlreadyExistsOnEuicc", 10: "insufficientMemoryForProfile",
|
|
||||||
11: "installInterrupted", 12: "peProcessingError", 13: "dataMismatch",
|
|
||||||
14: "invalidNAA",
|
|
||||||
}
|
|
||||||
BPP_ERROR_MESSAGES = {
|
|
||||||
9: "This eSIM profile is already installed on this device.",
|
|
||||||
10: "Not enough memory on the eUICC to install this profile.",
|
|
||||||
12: "Profile installation failed. The QR code may have already been used.",
|
|
||||||
}
|
|
||||||
|
|
||||||
# SGP.22 §5.2.6 SM-DP+ reason/subject codes mapped to user-friendly messages
|
|
||||||
ES9P_ERROR_MESSAGES: dict[tuple[str, str], str] = {
|
|
||||||
('3.8', '8.2.6'): "This eSIM profile is already installed on another device. Please use a new QR code.",
|
|
||||||
('3.8', '8.2.1'): "This eSIM profile has expired. Please request a new QR code.",
|
|
||||||
('3.8', '8.1'): "The SM-DP+ server refused this request.",
|
|
||||||
('3.1', '8.2.6'): "This eSIM profile has been revoked by the carrier.",
|
|
||||||
('3.9', '8.2.6'): "This eSIM profile download has already been completed.",
|
|
||||||
('2.1', '8.8'): "The device is not compatible with this eSIM profile.",
|
|
||||||
('1.2', '8.1'): "The SM-DP+ server is temporarily unavailable. Try again later.",
|
|
||||||
}
|
|
||||||
|
|
||||||
NOTIFICATION_OPERATIONS = {0x80: "install", 0x40: "enable", 0x20: "disable", 0x10: "delete"}
|
|
||||||
|
|
||||||
STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"}
|
|
||||||
ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"}
|
|
||||||
CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"}
|
|
||||||
|
|
||||||
# TLV tag -> (field_name, decoder)
|
|
||||||
FieldMap = dict[int, tuple[str, Callable[[bytes], Any]]]
|
|
||||||
|
|
||||||
|
|
||||||
def b64e(data: bytes) -> str:
|
|
||||||
return base64.b64encode(data).decode("ascii")
|
|
||||||
|
|
||||||
|
|
||||||
def base64_trim(s: str) -> str:
|
|
||||||
return "".join(c for c in s if c not in "\n\r \t")
|
|
||||||
|
|
||||||
|
|
||||||
def b64d(s: str) -> bytes:
|
|
||||||
return base64.b64decode(base64_trim(s))
|
|
||||||
|
|
||||||
|
|
||||||
class AtClient:
|
|
||||||
def __init__(self, device: str, baud: int, timeout: float) -> None:
|
|
||||||
self.channel: str | None = None
|
|
||||||
self._device = device
|
|
||||||
self._baud = baud
|
|
||||||
self._timeout = timeout
|
|
||||||
self._serial: serial.Serial | None = None
|
|
||||||
|
|
||||||
def send_raw(self, data: bytes) -> None:
|
|
||||||
self._ensure_serial()
|
|
||||||
self._serial.reset_input_buffer()
|
|
||||||
self._serial.write(data)
|
|
||||||
self._serial.flush()
|
|
||||||
|
|
||||||
def close(self) -> None:
|
|
||||||
try:
|
|
||||||
if self.channel:
|
|
||||||
try:
|
|
||||||
self.query(f"AT+CCHC={self.channel}")
|
|
||||||
except (RuntimeError, TimeoutError):
|
|
||||||
pass
|
|
||||||
self.channel = None
|
|
||||||
finally:
|
|
||||||
if self._serial:
|
|
||||||
self._serial.close()
|
|
||||||
|
|
||||||
def _send(self, cmd: str) -> None:
|
|
||||||
if DEBUG:
|
|
||||||
print(f"SER >> {cmd}", file=sys.stderr)
|
|
||||||
self._serial.write((cmd + "\r").encode("ascii"))
|
|
||||||
|
|
||||||
def _expect(self) -> list[str]:
|
|
||||||
lines: list[str] = []
|
|
||||||
while True:
|
|
||||||
raw = self._serial.readline()
|
|
||||||
if not raw:
|
|
||||||
raise TimeoutError("AT command timed out")
|
|
||||||
line = raw.decode(errors="ignore").strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
if DEBUG:
|
|
||||||
print(f"SER << {line}", file=sys.stderr)
|
|
||||||
if line == "OK":
|
|
||||||
return lines
|
|
||||||
if line == "ERROR" or line.startswith("+CME ERROR"):
|
|
||||||
raise RuntimeError(f"AT command failed: {line}")
|
|
||||||
lines.append(line)
|
|
||||||
|
|
||||||
def _ensure_serial(self, reconnect: bool = False) -> None:
|
|
||||||
if reconnect:
|
|
||||||
self.channel = None
|
|
||||||
try:
|
|
||||||
if self._serial:
|
|
||||||
self._serial.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self._serial = None
|
|
||||||
if self._serial is None:
|
|
||||||
self._serial = serial.Serial(self._device, baudrate=self._baud, timeout=self._timeout)
|
|
||||||
|
|
||||||
def query(self, cmd: str) -> list[str]:
|
|
||||||
self._ensure_serial()
|
|
||||||
try:
|
|
||||||
self._send(cmd)
|
|
||||||
return self._expect()
|
|
||||||
except serial.SerialException:
|
|
||||||
self._ensure_serial(reconnect=True)
|
|
||||||
self._send(cmd)
|
|
||||||
return self._expect()
|
|
||||||
|
|
||||||
def _open_isdr_once(self) -> None:
|
|
||||||
if self.channel:
|
|
||||||
try:
|
|
||||||
self.query(f"AT+CCHC={self.channel}")
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
self.channel = None
|
|
||||||
# drain any unsolicited responses before opening
|
|
||||||
if self._serial:
|
|
||||||
try:
|
|
||||||
self._serial.reset_input_buffer()
|
|
||||||
except (OSError, serial.SerialException, termios.error):
|
|
||||||
self._ensure_serial(reconnect=True)
|
|
||||||
for line in self.query(f'AT+CCHO="{ISDR_AID}"'):
|
|
||||||
if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()):
|
|
||||||
self.channel = ch
|
|
||||||
return
|
|
||||||
raise RuntimeError("Failed to open ISD-R application")
|
|
||||||
|
|
||||||
def _reset_modem(self) -> None:
|
|
||||||
if self._serial:
|
|
||||||
try:
|
|
||||||
self._serial.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self._serial = None
|
|
||||||
subprocess.run(['/usr/comma/lte/lte.sh', 'start'], capture_output=True)
|
|
||||||
|
|
||||||
def open_isdr(self) -> None:
|
|
||||||
for attempt in range(OPEN_ISDR_RETRIES):
|
|
||||||
try:
|
|
||||||
self._open_isdr_once()
|
|
||||||
return
|
|
||||||
except (RuntimeError, TimeoutError, termios.error, serial.SerialException):
|
|
||||||
time.sleep(OPEN_ISDR_RETRY_DELAY_S)
|
|
||||||
if attempt == OPEN_ISDR_RESET_ATTEMPT:
|
|
||||||
self._reset_modem()
|
|
||||||
raise RuntimeError("Failed to open ISD-R after retries")
|
|
||||||
|
|
||||||
def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]:
|
|
||||||
for attempt in range(SEND_APDU_RETRIES):
|
|
||||||
try:
|
|
||||||
if not self.channel:
|
|
||||||
self.open_isdr()
|
|
||||||
hex_payload = apdu.hex().upper()
|
|
||||||
for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'):
|
|
||||||
if line.startswith("+CGLA:"):
|
|
||||||
parts = line.split(":", 1)[1].split(",", 1)
|
|
||||||
if len(parts) == 2:
|
|
||||||
data = bytes.fromhex(parts[1].strip().strip('"'))
|
|
||||||
if len(data) >= 2:
|
|
||||||
return data[:-2], data[-2], data[-1]
|
|
||||||
raise RuntimeError("Missing +CGLA response")
|
|
||||||
except (RuntimeError, ValueError):
|
|
||||||
self.channel = None
|
|
||||||
if attempt == SEND_APDU_RETRIES - 1:
|
|
||||||
raise
|
|
||||||
raise RuntimeError("send_apdu failed")
|
|
||||||
|
|
||||||
|
|
||||||
# --- TLV utilities ---
|
|
||||||
|
|
||||||
def iter_tlv(data: bytes, with_positions: bool = False) -> Generator:
|
|
||||||
idx, length = 0, len(data)
|
|
||||||
while idx < length:
|
|
||||||
start_pos = idx
|
|
||||||
tag = data[idx]
|
|
||||||
idx += 1
|
|
||||||
if tag & 0x1F == 0x1F: # Multi-byte tag
|
|
||||||
tag_value = tag
|
|
||||||
while idx < length:
|
|
||||||
next_byte = data[idx]
|
|
||||||
idx += 1
|
|
||||||
tag_value = (tag_value << 8) | next_byte
|
|
||||||
if not (next_byte & 0x80):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
tag_value = tag
|
|
||||||
if idx >= length:
|
|
||||||
break
|
|
||||||
size = data[idx]
|
|
||||||
idx += 1
|
|
||||||
if size & 0x80: # Multi-byte length
|
|
||||||
num_bytes = size & 0x7F
|
|
||||||
if idx + num_bytes > length:
|
|
||||||
break
|
|
||||||
size = int.from_bytes(data[idx : idx + num_bytes], "big")
|
|
||||||
idx += num_bytes
|
|
||||||
if idx + size > length:
|
|
||||||
break
|
|
||||||
value = data[idx : idx + size]
|
|
||||||
idx += size
|
|
||||||
yield (tag_value, value, start_pos, idx) if with_positions else (tag_value, value)
|
|
||||||
|
|
||||||
|
|
||||||
def find_tag(data: bytes, target: int) -> bytes | None:
|
|
||||||
return next((v for t, v in iter_tlv(data) if t == target), None)
|
|
||||||
|
|
||||||
|
|
||||||
def require_tag(data: bytes, target: int, label: str = "") -> bytes:
|
|
||||||
v = find_tag(data, target)
|
|
||||||
if v is None:
|
|
||||||
raise RuntimeError(f"Missing {label or f'tag 0x{target:X}'}")
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
def tbcd_to_string(raw: bytes) -> str:
|
|
||||||
return "".join(str(n) for b in raw for n in (b & 0x0F, b >> 4) if n <= 9)
|
|
||||||
|
|
||||||
|
|
||||||
def string_to_tbcd(s: str) -> bytes:
|
|
||||||
digits = [int(c) for c in s if c.isdigit()]
|
|
||||||
return bytes(digits[i] | ((digits[i + 1] if i + 1 < len(digits) else 0xF) << 4) for i in range(0, len(digits), 2))
|
|
||||||
|
|
||||||
|
|
||||||
def encode_tlv(tag: int, value: bytes) -> bytes:
|
|
||||||
tag_bytes = bytes([(tag >> 8) & 0xFF, tag & 0xFF]) if tag > 255 else bytes([tag])
|
|
||||||
vlen = len(value)
|
|
||||||
if vlen <= 127:
|
|
||||||
return tag_bytes + bytes([vlen]) + value
|
|
||||||
length_bytes = vlen.to_bytes((vlen.bit_length() + 7) // 8, "big")
|
|
||||||
return tag_bytes + bytes([0x80 | len(length_bytes)]) + length_bytes + value
|
|
||||||
|
|
||||||
|
|
||||||
def int_bytes(n: int) -> bytes:
|
|
||||||
"""Encode a positive integer as minimal big-endian bytes (at least 1 byte)."""
|
|
||||||
return n.to_bytes((n.bit_length() + 7) // 8 or 1, "big")
|
|
||||||
|
|
||||||
|
|
||||||
PROFILE: FieldMap = {
|
|
||||||
TAG_ICCID: ("iccid", tbcd_to_string),
|
|
||||||
0x4F: ("isdpAid", lambda v: v.hex().upper()),
|
|
||||||
0x9F70: ("profileState", lambda v: STATE_LABELS.get(v[0], "unknown")),
|
|
||||||
0x90: ("profileNickname", lambda v: v.decode("utf-8", errors="ignore") or None),
|
|
||||||
0x91: ("serviceProviderName", lambda v: v.decode("utf-8", errors="ignore") or None),
|
|
||||||
0x92: ("profileName", lambda v: v.decode("utf-8", errors="ignore") or None),
|
|
||||||
0x93: ("iconType", lambda v: ICON_LABELS.get(v[0], "unknown")),
|
|
||||||
0x94: ("icon", b64e),
|
|
||||||
0x95: ("profileClass", lambda v: CLASS_LABELS.get(v[0], "unknown")),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def decode_struct(data: bytes, field_map: FieldMap) -> dict[str, Any]:
|
|
||||||
"""Parse TLV data using a {tag: (field_name, decoder)} map into a dict."""
|
|
||||||
result: dict[str, Any] = {name: None for name, _ in field_map.values()}
|
|
||||||
for tag, value in iter_tlv(data):
|
|
||||||
if (field := field_map.get(tag)):
|
|
||||||
result[field[0]] = field[1](value)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# --- ES10x command transport ---
|
|
||||||
|
|
||||||
def es10x_command(client: AtClient, data: bytes) -> bytes:
|
|
||||||
response = bytearray()
|
|
||||||
sequence = 0
|
|
||||||
offset = 0
|
|
||||||
while offset < len(data):
|
|
||||||
chunk = data[offset : offset + ES10X_MSS]
|
|
||||||
offset += len(chunk)
|
|
||||||
is_last = offset == len(data)
|
|
||||||
apdu = bytes([0x80, 0xE2, 0x91 if is_last else 0x11, sequence & 0xFF, len(chunk)]) + chunk
|
|
||||||
segment, sw1, sw2 = client.send_apdu(apdu)
|
|
||||||
response.extend(segment)
|
|
||||||
while True:
|
|
||||||
if sw1 == 0x61: # More data available
|
|
||||||
segment, sw1, sw2 = client.send_apdu(bytes([0x80, 0xC0, 0x00, 0x00, sw2 or 0]))
|
|
||||||
response.extend(segment)
|
|
||||||
continue
|
|
||||||
if (sw1 & 0xF0) == 0x90:
|
|
||||||
break
|
|
||||||
raise RuntimeError(f"APDU failed with SW={sw1:02X}{sw2:02X}")
|
|
||||||
sequence += 1
|
|
||||||
return bytes(response)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Profile operations ---
|
|
||||||
|
|
||||||
NOTIFICATION: FieldMap = {
|
|
||||||
TAG_STATUS: ("seqNumber", lambda v: int.from_bytes(v, "big")),
|
|
||||||
0x81: ("profileManagementOperation",
|
|
||||||
lambda v: NOTIFICATION_OPERATIONS.get(next((m for m in NOTIFICATION_OPERATIONS if len(v) >= 2 and v[1] & m), 0), "unknown")),
|
|
||||||
0x0C: ("notificationAddress", lambda v: v.decode("utf-8", errors="ignore")),
|
|
||||||
TAG_ICCID: ("iccid", tbcd_to_string),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def decode_profiles(blob: bytes) -> list[dict]:
|
|
||||||
root = require_tag(blob, TAG_PROFILE_INFO_LIST, "ProfileInfoList")
|
|
||||||
list_ok = find_tag(root, TAG_OK)
|
|
||||||
if list_ok is None:
|
|
||||||
return []
|
|
||||||
return [decode_struct(value, PROFILE) for tag, value in iter_tlv(list_ok) if tag == 0xE3]
|
|
||||||
|
|
||||||
|
|
||||||
def list_profiles(client: AtClient) -> list[dict]:
|
|
||||||
return decode_profiles(es10x_command(client, TAG_PROFILE_INFO_LIST.to_bytes(2, "big") + b"\x00"))
|
|
||||||
|
|
||||||
|
|
||||||
def set_profile_nickname(client: AtClient, iccid: str, nickname: str) -> None:
|
|
||||||
nickname_bytes = nickname.encode("utf-8")
|
|
||||||
if len(nickname_bytes) > 64:
|
|
||||||
raise ValueError("Profile nickname must be 64 bytes or less")
|
|
||||||
content = encode_tlv(TAG_ICCID, string_to_tbcd(iccid)) + encode_tlv(0x90, nickname_bytes)
|
|
||||||
response = es10x_command(client, encode_tlv(TAG_SET_NICKNAME, content))
|
|
||||||
code = require_tag(require_tag(response, TAG_SET_NICKNAME, "SetNicknameResponse"), TAG_STATUS, "SetNickname status")[0]
|
|
||||||
if code == 0x01:
|
|
||||||
raise LPAError(f"profile {iccid} not found")
|
|
||||||
if code != 0x00:
|
|
||||||
raise RuntimeError(f"SetNickname failed with status 0x{code:02X}")
|
|
||||||
|
|
||||||
|
|
||||||
# --- ES9P HTTP ---
|
|
||||||
|
|
||||||
def es9p_request(smdp_address: str, endpoint: str, payload: dict, error_prefix: str = "Request", session: requests.Session | None = None) -> dict:
|
|
||||||
url = f"https://{smdp_address}/gsma/rsp2/es9plus/{endpoint}"
|
|
||||||
headers = {"User-Agent": "gsma-rsp-lpad", "X-Admin-Protocol": "gsma/rsp/v2.3.0", "Content-Type": "application/json"}
|
|
||||||
http = session or requests
|
|
||||||
resp = http.post(url, json=payload, headers=headers, timeout=HTTP_TIMEOUT, verify=GSMA_CI_BUNDLE)
|
|
||||||
resp.raise_for_status()
|
|
||||||
if not resp.content:
|
|
||||||
return {}
|
|
||||||
data = resp.json()
|
|
||||||
if "header" in data and "functionExecutionStatus" in data["header"]:
|
|
||||||
status = data["header"]["functionExecutionStatus"]
|
|
||||||
if status.get("status") == "Failed":
|
|
||||||
sd = status.get("statusCodeData", {})
|
|
||||||
reason = sd.get("reasonCode", "unknown")
|
|
||||||
subject = sd.get("subjectCode", "unknown")
|
|
||||||
msg = ES9P_ERROR_MESSAGES.get((reason, subject),
|
|
||||||
f"{error_prefix} failed: {reason}/{subject} - {sd.get('message', 'unknown')}")
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
# --- Notifications ---
|
|
||||||
|
|
||||||
def list_notifications(client: AtClient) -> list[dict]:
|
|
||||||
response = es10x_command(client, encode_tlv(TAG_LIST_NOTIFICATION, b""))
|
|
||||||
root = require_tag(response, TAG_LIST_NOTIFICATION, "ListNotificationResponse")
|
|
||||||
metadata_list = find_tag(root, TAG_OK)
|
|
||||||
if metadata_list is None:
|
|
||||||
return []
|
|
||||||
return [decode_struct(value, NOTIFICATION) for tag, value in iter_tlv(metadata_list) if tag == TAG_NOTIFICATION_METADATA]
|
|
||||||
|
|
||||||
|
|
||||||
def process_notifications(client: AtClient) -> None:
|
|
||||||
for notification in list_notifications(client):
|
|
||||||
seq_number, smdp_address = notification["seqNumber"], notification["notificationAddress"]
|
|
||||||
try:
|
|
||||||
request = encode_tlv(TAG_RETRIEVE_NOTIFICATION, encode_tlv(TAG_OK, encode_tlv(TAG_STATUS, int_bytes(seq_number))))
|
|
||||||
response = es10x_command(client, request)
|
|
||||||
content = require_tag(require_tag(response, TAG_RETRIEVE_NOTIFICATION, "RetrieveNotificationsListResponse"),
|
|
||||||
TAG_OK, "RetrieveNotificationsListResponse")
|
|
||||||
pending_notif = next((v for t, v in iter_tlv(content) if t in (TAG_PROFILE_INSTALL_RESULT, 0x30)), None)
|
|
||||||
if pending_notif is None:
|
|
||||||
raise RuntimeError("Missing PendingNotification")
|
|
||||||
|
|
||||||
es9p_request(smdp_address, "handleNotification", {"pendingNotification": b64e(pending_notif)}, "HandleNotification")
|
|
||||||
|
|
||||||
response = es10x_command(client, encode_tlv(TAG_NOTIFICATION_SENT, encode_tlv(TAG_STATUS, int_bytes(seq_number))))
|
|
||||||
root = require_tag(response, TAG_NOTIFICATION_SENT, "NotificationSentResponse")
|
|
||||||
if int.from_bytes(require_tag(root, TAG_STATUS, "RemoveNotificationFromList status"), "big") != 0:
|
|
||||||
raise RuntimeError("RemoveNotificationFromList failed")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"notification {seq_number} failed: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Authentication & Download ---
|
|
||||||
|
|
||||||
def get_challenge_and_info(client: AtClient) -> tuple[bytes, bytes]:
|
|
||||||
challenge_resp = es10x_command(client, encode_tlv(TAG_EUICC_CHALLENGE, b""))
|
|
||||||
challenge = require_tag(require_tag(challenge_resp, TAG_EUICC_CHALLENGE, "GetEuiccDataResponse"),
|
|
||||||
TAG_STATUS, "challenge in response")
|
|
||||||
info_resp = es10x_command(client, encode_tlv(TAG_EUICC_INFO, b""))
|
|
||||||
require_tag(info_resp, TAG_EUICC_INFO, "GetEuiccInfo1Response")
|
|
||||||
return challenge, info_resp
|
|
||||||
|
|
||||||
|
|
||||||
def authenticate_server(client: AtClient, b64_signed1: str, b64_sig1: str, b64_pk_id: str, b64_cert: str, matching_id: str) -> str:
|
|
||||||
tac = bytes([0x35, 0x29, 0x06, 0x11])
|
|
||||||
device_info = encode_tlv(TAG_STATUS, tac) + encode_tlv(0xA1, b"")
|
|
||||||
ctx_inner = encode_tlv(TAG_STATUS, matching_id.encode("utf-8")) + encode_tlv(0xA1, device_info)
|
|
||||||
content = b64d(b64_signed1) + b64d(b64_sig1) + b64d(b64_pk_id) + b64d(b64_cert) + encode_tlv(0xA0, ctx_inner)
|
|
||||||
response = es10x_command(client, encode_tlv(TAG_AUTH_SERVER, content))
|
|
||||||
root = require_tag(response, TAG_AUTH_SERVER, "AuthenticateServerResponse")
|
|
||||||
error_tag = find_tag(root, 0xA1)
|
|
||||||
if error_tag is not None:
|
|
||||||
code = int.from_bytes(error_tag, "big") if error_tag else 0
|
|
||||||
raise RuntimeError(f"AuthenticateServer rejected by eUICC: {AUTH_SERVER_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})")
|
|
||||||
return b64e(response)
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_download(client: AtClient, b64_signed2: str, b64_sig2: str, b64_cert: str, cc: str | None = None) -> str:
|
|
||||||
smdp_signed2 = b64d(b64_signed2)
|
|
||||||
smdp_signature2 = b64d(b64_sig2)
|
|
||||||
smdp_certificate = b64d(b64_cert)
|
|
||||||
smdp_signed2_root = find_tag(smdp_signed2, 0x30)
|
|
||||||
if smdp_signed2_root is None:
|
|
||||||
raise RuntimeError("Invalid smdpSigned2")
|
|
||||||
transaction_id = find_tag(smdp_signed2_root, TAG_STATUS)
|
|
||||||
cc_required_flag = find_tag(smdp_signed2_root, 0x01)
|
|
||||||
if transaction_id is None or cc_required_flag is None:
|
|
||||||
raise RuntimeError("Invalid smdpSigned2")
|
|
||||||
content = smdp_signed2 + smdp_signature2
|
|
||||||
if int.from_bytes(cc_required_flag, "big") != 0:
|
|
||||||
if not cc:
|
|
||||||
raise RuntimeError("Confirmation code required but not provided")
|
|
||||||
content += encode_tlv(0x04, hashlib.sha256(hashlib.sha256(cc.encode("utf-8")).digest() + transaction_id).digest())
|
|
||||||
content += smdp_certificate
|
|
||||||
response = es10x_command(client, encode_tlv(TAG_PREPARE_DOWNLOAD, content))
|
|
||||||
require_tag(response, TAG_PREPARE_DOWNLOAD, "PrepareDownloadResponse")
|
|
||||||
return b64e(response)
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_tlv_header_len(data: bytes) -> int:
|
|
||||||
tag_len = 2 if data[0] & 0x1F == 0x1F else 1
|
|
||||||
length_byte = data[tag_len]
|
|
||||||
return tag_len + (1 + (length_byte & 0x7F) if length_byte & 0x80 else 1)
|
|
||||||
|
|
||||||
|
|
||||||
def _split_bpp(bpp: bytes) -> list[bytes]:
|
|
||||||
"""Split a BoundProfilePackage into APDU chunks per SGP.22 §5.7.6."""
|
|
||||||
root_value = None
|
|
||||||
for tag, value, start, end in iter_tlv(bpp, with_positions=True):
|
|
||||||
if tag == TAG_BPP:
|
|
||||||
root_value = value
|
|
||||||
val_start = start + _parse_tlv_header_len(bpp[start:end])
|
|
||||||
break
|
|
||||||
if root_value is None:
|
|
||||||
raise RuntimeError("Invalid BoundProfilePackage")
|
|
||||||
|
|
||||||
chunks: list[bytes] = []
|
|
||||||
for tag, value, start, end in iter_tlv(root_value, with_positions=True):
|
|
||||||
if tag == TAG_BPP_COMMAND:
|
|
||||||
chunks.append(bpp[0 : val_start + end])
|
|
||||||
elif tag in (0xA0, 0xA2):
|
|
||||||
chunks.append(bpp[val_start + start : val_start + end])
|
|
||||||
elif tag in (0xA1, 0xA3):
|
|
||||||
hdr_len = _parse_tlv_header_len(root_value[start:end])
|
|
||||||
chunks.append(bpp[val_start + start : val_start + start + hdr_len])
|
|
||||||
for _, _, cs, ce in iter_tlv(value, with_positions=True):
|
|
||||||
chunks.append(value[cs:ce])
|
|
||||||
return chunks
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_install_result(response: bytes) -> dict[str, Any] | None:
|
|
||||||
"""Parse a ProfileInstallResult from an APDU response, or None if not present."""
|
|
||||||
root = find_tag(response, TAG_PROFILE_INSTALL_RESULT)
|
|
||||||
if not root:
|
|
||||||
return None
|
|
||||||
result_data = find_tag(root, TAG_INSTALL_RESULT_DATA)
|
|
||||||
if not result_data:
|
|
||||||
return None
|
|
||||||
result: dict[str, Any] = {"seqNumber": 0, "success": False, "bppCommandId": None, "errorReason": None}
|
|
||||||
notif_meta = find_tag(result_data, TAG_NOTIFICATION_METADATA)
|
|
||||||
if notif_meta:
|
|
||||||
seq_num = find_tag(notif_meta, TAG_STATUS)
|
|
||||||
if seq_num:
|
|
||||||
result["seqNumber"] = int.from_bytes(seq_num, "big")
|
|
||||||
final_result = find_tag(result_data, 0xA2)
|
|
||||||
if final_result:
|
|
||||||
for tag, value in iter_tlv(final_result):
|
|
||||||
if tag == 0xA0:
|
|
||||||
result["success"] = True
|
|
||||||
elif tag == 0xA1:
|
|
||||||
bpp_cmd = find_tag(value, TAG_STATUS)
|
|
||||||
if bpp_cmd:
|
|
||||||
result["bppCommandId"] = int.from_bytes(bpp_cmd, "big")
|
|
||||||
err = find_tag(value, 0x81)
|
|
||||||
if err:
|
|
||||||
result["errorReason"] = int.from_bytes(err, "big")
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def load_bpp(client: AtClient, b64_bpp: str) -> dict:
|
|
||||||
bpp = b64d(b64_bpp)
|
|
||||||
result = None
|
|
||||||
for chunk in _split_bpp(bpp):
|
|
||||||
response = es10x_command(client, chunk)
|
|
||||||
if response and (parsed := _parse_install_result(response)):
|
|
||||||
result = parsed
|
|
||||||
break
|
|
||||||
|
|
||||||
if result is None:
|
|
||||||
raise RuntimeError("Profile installation failed: no result from eUICC")
|
|
||||||
if not result["success"] and result["errorReason"] is not None:
|
|
||||||
msg = BPP_ERROR_MESSAGES.get(result["errorReason"])
|
|
||||||
if not msg:
|
|
||||||
cmd_name = BPP_COMMAND_NAMES.get(result["bppCommandId"], f"unknown({result['bppCommandId']})")
|
|
||||||
err_name = BPP_ERROR_REASONS.get(result["errorReason"], f"unknown({result['errorReason']})")
|
|
||||||
msg = f"Profile installation failed at {cmd_name}: {err_name}"
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
if not result["success"]:
|
|
||||||
raise RuntimeError("Profile installation failed: no result from eUICC")
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def parse_metadata(b64_metadata: str) -> dict:
|
|
||||||
root = find_tag(b64d(b64_metadata), TAG_PROFILE_METADATA)
|
|
||||||
if root is None:
|
|
||||||
raise RuntimeError("Invalid profileMetadata")
|
|
||||||
return decode_struct(root, PROFILE)
|
|
||||||
|
|
||||||
|
|
||||||
def cancel_session(client: AtClient, transaction_id: bytes, reason: int = 127) -> str:
|
|
||||||
content = encode_tlv(0x80, transaction_id) + encode_tlv(0x81, bytes([reason]))
|
|
||||||
response = es10x_command(client, encode_tlv(TAG_CANCEL_SESSION, content))
|
|
||||||
return b64e(response)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_lpa_activation_code(activation_code: str) -> tuple[str, str]:
|
|
||||||
"""Parse 'LPA:1$smdp.example.com$MATCHING-ID' into (smdp_address, matching_id)."""
|
|
||||||
if not activation_code.startswith("LPA:"):
|
|
||||||
raise ValueError("Invalid activation code format")
|
|
||||||
parts = activation_code[4:].split("$")
|
|
||||||
if len(parts) != 3:
|
|
||||||
raise ValueError("Invalid activation code format")
|
|
||||||
return parts[1], parts[2]
|
|
||||||
|
|
||||||
|
|
||||||
def _b64_field(data: dict, key: str) -> str:
|
|
||||||
return base64_trim(data[key])
|
|
||||||
|
|
||||||
|
|
||||||
def _cancel_session_safe(client: AtClient, smdp: str, tx_id: str, session: requests.Session) -> None:
|
|
||||||
b64_cancel = ""
|
|
||||||
try:
|
|
||||||
b64_cancel = cancel_session(client, b64d(tx_id))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
es9p_request(smdp, "cancelSession", {"transactionId": tx_id, "cancelSessionResponse": b64_cancel}, "CancelSession", session=session)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def download_profile(client: AtClient, activation_code: str) -> str:
|
|
||||||
"""Download and install an eSIM profile. Returns the ICCID of the installed profile."""
|
|
||||||
if not system_time_valid():
|
|
||||||
raise RuntimeError("System time is not set; TLS certificate validation requires a valid clock")
|
|
||||||
smdp, matching_id = parse_lpa_activation_code(activation_code)
|
|
||||||
challenge, euicc_info = get_challenge_and_info(client)
|
|
||||||
session = requests.Session()
|
|
||||||
tx_id = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# step 1: initiate authentication
|
|
||||||
auth = es9p_request(smdp, "initiateAuthentication", {
|
|
||||||
"smdpAddress": smdp, "euiccChallenge": b64e(challenge),
|
|
||||||
"euiccInfo1": b64e(euicc_info), "matchingId": matching_id,
|
|
||||||
}, "Authentication", session=session)
|
|
||||||
tx_id = _b64_field(auth, "transactionId")
|
|
||||||
|
|
||||||
# step 2: authenticate server
|
|
||||||
b64_auth = authenticate_server(client,
|
|
||||||
_b64_field(auth, "serverSigned1"), _b64_field(auth, "serverSignature1"),
|
|
||||||
_b64_field(auth, "euiccCiPKIdToBeUsed"), _b64_field(auth, "serverCertificate"),
|
|
||||||
matching_id)
|
|
||||||
|
|
||||||
# step 3: authenticate client + get metadata
|
|
||||||
cli = es9p_request(smdp, "authenticateClient", {
|
|
||||||
"transactionId": tx_id, "authenticateServerResponse": b64_auth,
|
|
||||||
}, "Authentication", session=session)
|
|
||||||
iccid = parse_metadata(_b64_field(cli, "profileMetadata"))["iccid"]
|
|
||||||
|
|
||||||
# step 4: prepare download
|
|
||||||
b64_prep = prepare_download(client,
|
|
||||||
_b64_field(cli, "smdpSigned2"), _b64_field(cli, "smdpSignature2"),
|
|
||||||
_b64_field(cli, "smdpCertificate"))
|
|
||||||
|
|
||||||
# step 5: get and install bound profile package
|
|
||||||
bpp = es9p_request(smdp, "getBoundProfilePackage", {
|
|
||||||
"transactionId": tx_id, "prepareDownloadResponse": b64_prep,
|
|
||||||
}, "GetBoundProfilePackage", session=session)
|
|
||||||
load_bpp(client, _b64_field(bpp, "boundProfilePackage"))
|
|
||||||
return iccid
|
|
||||||
except Exception:
|
|
||||||
if tx_id:
|
|
||||||
_cancel_session_safe(client, smdp, tx_id, session)
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
|
|
||||||
class TiciLPA(LPABase):
|
|
||||||
def __init__(self):
|
|
||||||
if hasattr(self, '_client'):
|
|
||||||
return
|
|
||||||
self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT)
|
|
||||||
atexit.register(self._client.close)
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def _acquire_lock(self):
|
|
||||||
fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR)
|
|
||||||
try:
|
|
||||||
fcntl.flock(fd, fcntl.LOCK_EX)
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
|
||||||
os.close(fd)
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def _acquire_channel(self):
|
|
||||||
with self._acquire_lock():
|
|
||||||
try:
|
|
||||||
self._client.open_isdr()
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
if self._client.channel:
|
|
||||||
try:
|
|
||||||
self._client.query(f"AT+CCHC={self._client.channel}")
|
|
||||||
except (RuntimeError, TimeoutError):
|
|
||||||
pass
|
|
||||||
self._client.channel = None
|
|
||||||
|
|
||||||
def list_profiles(self) -> list[Profile]:
|
|
||||||
with self._acquire_channel():
|
|
||||||
return [
|
|
||||||
Profile(
|
|
||||||
iccid=p.get("iccid", ""),
|
|
||||||
nickname=p.get("profileNickname") or "",
|
|
||||||
enabled=p.get("profileState") == "enabled",
|
|
||||||
provider=p.get("serviceProviderName") or "",
|
|
||||||
)
|
|
||||||
for p in list_profiles(self._client)
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_active_profile(self) -> Profile | None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def process_notifications(self) -> None:
|
|
||||||
if not system_time_valid():
|
|
||||||
raise RuntimeError("System time is not set; TLS certificate validation requires a valid clock")
|
|
||||||
with self._acquire_channel():
|
|
||||||
process_notifications(self._client)
|
|
||||||
|
|
||||||
def delete_profile(self, iccid: str) -> None:
|
|
||||||
profile = next((p for p in self.list_profiles() if p.iccid == iccid), None)
|
|
||||||
if profile is None:
|
|
||||||
raise LPAProfileNotFoundError(f"profile not found: {iccid}")
|
|
||||||
if profile.is_comma:
|
|
||||||
raise LPAError("refusing to delete a comma profile")
|
|
||||||
with self._acquire_channel():
|
|
||||||
request = encode_tlv(TAG_DELETE_PROFILE, encode_tlv(TAG_ICCID, string_to_tbcd(iccid)))
|
|
||||||
response = es10x_command(self._client, request)
|
|
||||||
code = require_tag(require_tag(response, TAG_DELETE_PROFILE, "DeleteProfileResponse"), TAG_STATUS, "DeleteProfile status")[0]
|
|
||||||
if code != PROFILE_OK:
|
|
||||||
raise LPAError(f"DeleteProfile failed: {PROFILE_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})")
|
|
||||||
|
|
||||||
def download_profile(self, qr: str, nickname: str | None = None) -> None:
|
|
||||||
with self._acquire_channel():
|
|
||||||
iccid = download_profile(self._client, qr)
|
|
||||||
if nickname and iccid:
|
|
||||||
set_profile_nickname(self._client, iccid, nickname)
|
|
||||||
|
|
||||||
def nickname_profile(self, iccid: str, nickname: str) -> None:
|
|
||||||
with self._acquire_channel():
|
|
||||||
set_profile_nickname(self._client, iccid, nickname)
|
|
||||||
|
|
||||||
def _enable_profile(self, iccid: str) -> int:
|
|
||||||
inner = encode_tlv(TAG_OK, encode_tlv(TAG_ICCID, string_to_tbcd(iccid)))
|
|
||||||
inner += b'\x01\x01\x01' # refreshFlag=1
|
|
||||||
response = es10x_command(self._client, encode_tlv(TAG_ENABLE_PROFILE, inner))
|
|
||||||
return require_tag(require_tag(response, TAG_ENABLE_PROFILE, "EnableProfileResponse"), TAG_STATUS, "EnableProfile status")[0]
|
|
||||||
|
|
||||||
def switch_profile(self, iccid: str) -> None:
|
|
||||||
with self._acquire_channel():
|
|
||||||
code = self._enable_profile(iccid)
|
|
||||||
if code == PROFILE_CAT_BUSY: # stale eUICC transaction, reset and retry
|
|
||||||
self._client._reset_modem()
|
|
||||||
self._client.open_isdr()
|
|
||||||
code = self._enable_profile(iccid)
|
|
||||||
if code not in (PROFILE_OK, PROFILE_NOT_IN_DISABLED_STATE):
|
|
||||||
raise LPAError(f"EnableProfile failed: {PROFILE_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})")
|
|
||||||
|
|
||||||
def is_euicc(self) -> bool:
|
|
||||||
# +CCHO:<n> -> ISD-R applet present, eUICC. Any error -> non-eUICC.
|
|
||||||
with self._acquire_lock():
|
|
||||||
try:
|
|
||||||
lines = self._client.query(f'AT+CCHO="{ISDR_AID}"')
|
|
||||||
except RuntimeError:
|
|
||||||
return False
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()):
|
|
||||||
try:
|
|
||||||
self._client.query(f"AT+CCHC={ch}")
|
|
||||||
except (RuntimeError, TimeoutError):
|
|
||||||
pass
|
|
||||||
self._client.channel = None
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
Executable → Regular
+2
-20
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -12,12 +10,9 @@ def get_chunk_name(name, idx, num_chunks):
|
|||||||
def get_manifest_path(name):
|
def get_manifest_path(name):
|
||||||
return f"{name}.chunkmanifest"
|
return f"{name}.chunkmanifest"
|
||||||
|
|
||||||
def _chunk_paths(path, num_chunks):
|
def get_chunk_paths(path, file_size):
|
||||||
return [get_manifest_path(path)] + [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)]
|
|
||||||
|
|
||||||
def get_chunk_targets(path, file_size):
|
|
||||||
num_chunks = math.ceil(file_size / CHUNK_SIZE)
|
num_chunks = math.ceil(file_size / CHUNK_SIZE)
|
||||||
return _chunk_paths(path, num_chunks)
|
return [get_manifest_path(path)] + [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)]
|
||||||
|
|
||||||
def chunk_file(path, targets):
|
def chunk_file(path, targets):
|
||||||
manifest_path, *chunk_paths = targets
|
manifest_path, *chunk_paths = targets
|
||||||
@@ -31,13 +26,6 @@ def chunk_file(path, targets):
|
|||||||
Path(manifest_path).write_text(str(len(chunk_paths)))
|
Path(manifest_path).write_text(str(len(chunk_paths)))
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
def get_existing_chunks(path):
|
|
||||||
if os.path.isfile(path):
|
|
||||||
return [path]
|
|
||||||
if os.path.isfile(manifest := get_manifest_path(path)):
|
|
||||||
num_chunks = int(Path(manifest).read_text().strip())
|
|
||||||
return _chunk_paths(path, num_chunks)
|
|
||||||
raise FileNotFoundError(path)
|
|
||||||
|
|
||||||
def read_file_chunked(path):
|
def read_file_chunked(path):
|
||||||
manifest_path = get_manifest_path(path)
|
manifest_path = get_manifest_path(path)
|
||||||
@@ -47,9 +35,3 @@ def read_file_chunked(path):
|
|||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
return Path(path).read_bytes()
|
return Path(path).read_bytes()
|
||||||
raise FileNotFoundError(path)
|
raise FileNotFoundError(path)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
path = sys.argv[1]
|
|
||||||
chunk_paths = get_chunk_targets(path, os.path.getsize(path))
|
|
||||||
chunk_file(path, chunk_paths)
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class BounceFilter(FirstOrderFilter):
|
|||||||
scale = self.dt / (1.0 / 60.0) # tuned at 60 fps
|
scale = self.dt / (1.0 / 60.0) # tuned at 60 fps
|
||||||
self.velocity.x += (x - self.x) * self.bounce * scale * self.dt
|
self.velocity.x += (x - self.x) * self.bounce * scale * self.dt
|
||||||
self.velocity.update(0.0)
|
self.velocity.update(0.0)
|
||||||
if abs(self.velocity.x) < 1e-3:
|
if abs(self.velocity.x) < 1e-5:
|
||||||
self.velocity.x = 0.0
|
self.velocity.x = 0.0
|
||||||
self.x += self.velocity.x
|
self.x += self.velocity.x
|
||||||
return self.x
|
return self.x
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from cereal import log
|
|
||||||
from openpilot.common.hardware.base import HardwareBase
|
|
||||||
|
|
||||||
class Pc(HardwareBase):
|
|
||||||
def get_device_type(self):
|
|
||||||
return "pc"
|
|
||||||
|
|
||||||
def get_network_type(self):
|
|
||||||
# some stuff is gated on wifi, so just assume for now
|
|
||||||
return log.DeviceState.NetworkType.wifi
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "xbl",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/xbl-e8acf2a9cc7f0ce84cb803bfea9477f765c0d7b4daf26048e59651b9e6a7bfbb.img.xz",
|
|
||||||
"hash": "e8acf2a9cc7f0ce84cb803bfea9477f765c0d7b4daf26048e59651b9e6a7bfbb",
|
|
||||||
"hash_raw": "e8acf2a9cc7f0ce84cb803bfea9477f765c0d7b4daf26048e59651b9e6a7bfbb",
|
|
||||||
"size": 3282256,
|
|
||||||
"sparse": false,
|
|
||||||
"full_check": true,
|
|
||||||
"has_ab": true,
|
|
||||||
"ondevice_hash": "bea7f1a24428c3ededf672fa4fc78baf180cfbd8aafb77c974655b38517283e3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "xbl_config",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/xbl_config-758552ecf92b5569677197783bf0ccb73d7f961685308e45d3276ac9dd974f85.img.xz",
|
|
||||||
"hash": "758552ecf92b5569677197783bf0ccb73d7f961685308e45d3276ac9dd974f85",
|
|
||||||
"hash_raw": "758552ecf92b5569677197783bf0ccb73d7f961685308e45d3276ac9dd974f85",
|
|
||||||
"size": 98124,
|
|
||||||
"sparse": false,
|
|
||||||
"full_check": true,
|
|
||||||
"has_ab": true,
|
|
||||||
"ondevice_hash": "fb18cde08a98a168961ecd357e92474823046752b94e112f59fe51a6acd7197d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "abl",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/abl-b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c.img.xz",
|
|
||||||
"hash": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c",
|
|
||||||
"hash_raw": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c",
|
|
||||||
"size": 274432,
|
|
||||||
"sparse": false,
|
|
||||||
"full_check": true,
|
|
||||||
"has_ab": true,
|
|
||||||
"ondevice_hash": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "aop",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/aop-78b2287ca219a0811b3004c523fa0f4749e4d1fd92be3aba61699305b7943ad1.img.xz",
|
|
||||||
"hash": "78b2287ca219a0811b3004c523fa0f4749e4d1fd92be3aba61699305b7943ad1",
|
|
||||||
"hash_raw": "78b2287ca219a0811b3004c523fa0f4749e4d1fd92be3aba61699305b7943ad1",
|
|
||||||
"size": 184364,
|
|
||||||
"sparse": false,
|
|
||||||
"full_check": true,
|
|
||||||
"has_ab": true,
|
|
||||||
"ondevice_hash": "6c9135446bd3fc075fcee59b887a12e49029ab1f98ed8d6d1e32c73569d47de3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "devcfg",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/devcfg-f71df3a86958c093ba3969254c4db025187eef9385427f1ade946742939b43cc.img.xz",
|
|
||||||
"hash": "f71df3a86958c093ba3969254c4db025187eef9385427f1ade946742939b43cc",
|
|
||||||
"hash_raw": "f71df3a86958c093ba3969254c4db025187eef9385427f1ade946742939b43cc",
|
|
||||||
"size": 40336,
|
|
||||||
"sparse": false,
|
|
||||||
"full_check": true,
|
|
||||||
"has_ab": true,
|
|
||||||
"ondevice_hash": "2a67971602012c1b43544964709da13c322786b456a8e78568b117e8b1540ce3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "boot",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8.img.xz",
|
|
||||||
"hash": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
|
||||||
"hash_raw": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
|
||||||
"size": 17487872,
|
|
||||||
"sparse": false,
|
|
||||||
"full_check": true,
|
|
||||||
"has_ab": true,
|
|
||||||
"ondevice_hash": "edca8bee1531e66953d107eeceeed2dc7b3ca46417e49d55508f94e58bf95db8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "system",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img.xz",
|
|
||||||
"hash": "78acfe16a7b62a3a91fc7a81f40a693e4468cec1c69df7d0b1e550aacc646113",
|
|
||||||
"hash_raw": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
|
||||||
"size": 4718592000,
|
|
||||||
"sparse": true,
|
|
||||||
"full_check": false,
|
|
||||||
"has_ab": true,
|
|
||||||
"ondevice_hash": "743142c5a898f27b2a1029cca42c8a5d5d1fc0096414422b850fe84c8d0b8342",
|
|
||||||
"alt": {
|
|
||||||
"hash": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img",
|
|
||||||
"size": 4718592000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,419 +0,0 @@
|
|||||||
import configparser
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
from functools import cached_property, lru_cache
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from cereal import log
|
|
||||||
from openpilot.common.utils import sudo_read, sudo_write
|
|
||||||
from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action
|
|
||||||
from openpilot.common.esim.base import LPABase
|
|
||||||
from openpilot.common.hardware.base import HardwareBase, ThermalConfig, ThermalZone
|
|
||||||
from openpilot.common.esim.lpa import TiciLPA
|
|
||||||
from openpilot.common.hardware.tici.pins import GPIO
|
|
||||||
from openpilot.common.hardware.tici.amplifier import Amplifier
|
|
||||||
|
|
||||||
MODEM_STATE_PATH = "/dev/shm/modem"
|
|
||||||
|
|
||||||
NetworkType = log.DeviceState.NetworkType
|
|
||||||
NetworkStrength = log.DeviceState.NetworkStrength
|
|
||||||
|
|
||||||
|
|
||||||
def affine_irq(val, action):
|
|
||||||
irqs = get_irqs_for_action(action)
|
|
||||||
if len(irqs) == 0:
|
|
||||||
print(f"No IRQs found for '{action}'")
|
|
||||||
return
|
|
||||||
|
|
||||||
for i in irqs:
|
|
||||||
sudo_write(str(val), f"/proc/irq/{i}/smp_affinity_list")
|
|
||||||
|
|
||||||
@lru_cache
|
|
||||||
def get_device_type():
|
|
||||||
# lru_cache and cache can cause memory leaks when used in classes
|
|
||||||
with open("/sys/firmware/devicetree/base/model") as f:
|
|
||||||
model = f.read().strip('\x00')
|
|
||||||
return model.split('comma ')[-1]
|
|
||||||
|
|
||||||
def wpa_supplicant_cmd(cmd: str, timeout: float = 0.2) -> dict[str, str]:
|
|
||||||
with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock:
|
|
||||||
sock.settimeout(timeout)
|
|
||||||
sock.bind(f"\0openpilot-wpa-{os.getpid()}-{time.monotonic_ns()}")
|
|
||||||
sock.connect("/run/wpa_supplicant/wlan0")
|
|
||||||
sock.send(cmd.encode())
|
|
||||||
|
|
||||||
while True:
|
|
||||||
out = sock.recv(8192).decode("utf-8", "replace")
|
|
||||||
if out.startswith("<"):
|
|
||||||
continue
|
|
||||||
if out.startswith("FAIL"):
|
|
||||||
return {}
|
|
||||||
return dict(l.split("=", 1) for l in out.splitlines() if "=" in l)
|
|
||||||
|
|
||||||
def get_default_route_iface():
|
|
||||||
with open("/proc/net/route") as f:
|
|
||||||
routes = [(int(route[6]), route[0]) for line in f.readlines()[1:] if (route := line.split())[1] == "00000000" and int(route[3], 16) & 0x1]
|
|
||||||
return min(routes)[1] if routes else None
|
|
||||||
|
|
||||||
class Tici(HardwareBase):
|
|
||||||
@cached_property
|
|
||||||
def amplifier(self):
|
|
||||||
if self.get_device_type() == "mici":
|
|
||||||
return None
|
|
||||||
return Amplifier()
|
|
||||||
|
|
||||||
def get_modem_state(self) -> dict:
|
|
||||||
try:
|
|
||||||
with open(MODEM_STATE_PATH) as f:
|
|
||||||
return json.load(f)
|
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_os_version(self):
|
|
||||||
with open("/VERSION") as f:
|
|
||||||
return f.read().strip()
|
|
||||||
|
|
||||||
def get_device_type(self):
|
|
||||||
return get_device_type()
|
|
||||||
|
|
||||||
def reboot(self, reason=None):
|
|
||||||
subprocess.check_output(["sudo", "reboot"])
|
|
||||||
|
|
||||||
def uninstall(self):
|
|
||||||
Path("/data/__system_reset__").touch()
|
|
||||||
os.sync()
|
|
||||||
self.reboot()
|
|
||||||
|
|
||||||
def get_serial(self):
|
|
||||||
return self.get_cmdline()['androidboot.serialno']
|
|
||||||
|
|
||||||
def get_voltage(self):
|
|
||||||
with open("/sys/class/hwmon/hwmon1/in1_input") as f:
|
|
||||||
return int(f.read())
|
|
||||||
|
|
||||||
def get_current(self):
|
|
||||||
with open("/sys/class/hwmon/hwmon1/curr1_input") as f:
|
|
||||||
return int(f.read())
|
|
||||||
|
|
||||||
def set_ir_power(self, percent: int):
|
|
||||||
if self.get_device_type() == "tizi":
|
|
||||||
return
|
|
||||||
|
|
||||||
value = int((percent / 100) * 300)
|
|
||||||
with open("/sys/class/leds/led:switch_2/brightness", "w") as f:
|
|
||||||
f.write("0\n")
|
|
||||||
with open("/sys/class/leds/led:torch_2/brightness", "w") as f:
|
|
||||||
f.write(f"{value}\n")
|
|
||||||
with open("/sys/class/leds/led:switch_2/brightness", "w") as f:
|
|
||||||
f.write(f"{value}\n")
|
|
||||||
|
|
||||||
def get_network_type(self):
|
|
||||||
try:
|
|
||||||
if (iface := get_default_route_iface()):
|
|
||||||
if iface.startswith('wlan'):
|
|
||||||
return NetworkType.wifi
|
|
||||||
if iface.startswith('eth'):
|
|
||||||
return NetworkType.ethernet
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
ms = self.get_modem_state()
|
|
||||||
if ms.get('connected'):
|
|
||||||
nt = ms.get('network_type', '')
|
|
||||||
if nt == 'nr':
|
|
||||||
return NetworkType.cell5G
|
|
||||||
elif nt == 'lte':
|
|
||||||
return NetworkType.cell4G
|
|
||||||
elif nt in ('utran', 'umts'):
|
|
||||||
return NetworkType.cell3G
|
|
||||||
elif nt == 'gsm':
|
|
||||||
return NetworkType.cell2G
|
|
||||||
return NetworkType.none
|
|
||||||
|
|
||||||
def get_sim_info(self):
|
|
||||||
ms = self.get_modem_state()
|
|
||||||
sim_id = ms.get('iccid', '')
|
|
||||||
return {
|
|
||||||
'sim_id': sim_id,
|
|
||||||
'mcc_mnc': ms.get('mcc_mnc') or None,
|
|
||||||
'network_type': ["Unknown"],
|
|
||||||
'sim_state': ["ABSENT"] if not sim_id else ["READY"],
|
|
||||||
'data_connected': ms.get('connected', False),
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_sim_lpa(self) -> LPABase:
|
|
||||||
return TiciLPA()
|
|
||||||
|
|
||||||
def get_imei(self):
|
|
||||||
return self.get_modem_state().get('imei', '')
|
|
||||||
|
|
||||||
def get_network_info(self):
|
|
||||||
if self.get_device_type() == "mici":
|
|
||||||
return None
|
|
||||||
|
|
||||||
ms = self.get_modem_state()
|
|
||||||
return {
|
|
||||||
'technology': ms.get('network_type', '').upper() if ms.get('network_type') else '',
|
|
||||||
'operator': ms.get('operator', ''),
|
|
||||||
'band': ms.get('band', ''),
|
|
||||||
'channel': ms.get('channel', 0),
|
|
||||||
'extra': ms.get('extra', ''),
|
|
||||||
'state': ms.get('state', 'UNKNOWN'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse_strength(self, percentage):
|
|
||||||
if percentage < 25:
|
|
||||||
return NetworkStrength.poor
|
|
||||||
elif percentage < 50:
|
|
||||||
return NetworkStrength.moderate
|
|
||||||
elif percentage < 75:
|
|
||||||
return NetworkStrength.good
|
|
||||||
else:
|
|
||||||
return NetworkStrength.great
|
|
||||||
|
|
||||||
def get_network_strength(self, network_type):
|
|
||||||
network_strength = NetworkStrength.unknown
|
|
||||||
|
|
||||||
try:
|
|
||||||
if network_type == NetworkType.none:
|
|
||||||
pass
|
|
||||||
elif network_type == NetworkType.ethernet:
|
|
||||||
network_strength = NetworkStrength.great
|
|
||||||
elif network_type == NetworkType.wifi:
|
|
||||||
rssi = wpa_supplicant_cmd("SIGNAL_POLL").get("RSSI")
|
|
||||||
if rssi is not None:
|
|
||||||
dbm = int(rssi)
|
|
||||||
if -100 < dbm <= 0:
|
|
||||||
network_strength = self.parse_strength(120 + max(-100, min(-20, dbm)))
|
|
||||||
else: # Cellular
|
|
||||||
network_strength = self.parse_strength(self.get_modem_state().get('signal_quality', 0))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return network_strength
|
|
||||||
|
|
||||||
def get_network_metered(self, network_type) -> bool:
|
|
||||||
if network_type in (NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G):
|
|
||||||
from openpilot.common.params import Params
|
|
||||||
return Params().get_bool("GsmMetered")
|
|
||||||
try:
|
|
||||||
if network_type == NetworkType.wifi:
|
|
||||||
ssid = wpa_supplicant_cmd("STATUS").get("ssid", "")
|
|
||||||
if ssid:
|
|
||||||
# wpa_supplicant escapes non-printable bytes as \xNN; NM keyfile stores ASCII SSIDs as a literal and others as a byte;byte; list
|
|
||||||
ssid_bytes = ssid.encode().decode('unicode_escape').encode('latin-1')
|
|
||||||
ssid_keyfile_list = ';'.join(str(b) for b in ssid_bytes) + ';'
|
|
||||||
|
|
||||||
nm_dirs = ("/run/NetworkManager/system-connections", "/data/etc/NetworkManager/system-connections")
|
|
||||||
for fpath in (p for d in nm_dirs for p in Path(d).glob("*.nmconnection")):
|
|
||||||
raw = sudo_read(str(fpath))
|
|
||||||
if not raw:
|
|
||||||
continue
|
|
||||||
cp = configparser.ConfigParser(interpolation=None)
|
|
||||||
try:
|
|
||||||
cp.read_string(raw)
|
|
||||||
keyfile_ssid = cp.get("wifi", "ssid", fallback="")
|
|
||||||
if keyfile_ssid != ssid and keyfile_ssid != ssid_keyfile_list:
|
|
||||||
continue
|
|
||||||
metered = cp.getint("connection", "metered", fallback=0)
|
|
||||||
except (configparser.Error, ValueError):
|
|
||||||
continue
|
|
||||||
if metered == 1: # NM_METERED_YES
|
|
||||||
return True
|
|
||||||
if metered == 2: # NM_METERED_NO
|
|
||||||
return False
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return super().get_network_metered(network_type)
|
|
||||||
|
|
||||||
def get_modem_temperatures(self):
|
|
||||||
return self.get_modem_state().get('temperatures', [])
|
|
||||||
|
|
||||||
def get_current_power_draw(self):
|
|
||||||
return (self.read_param_file("/sys/class/hwmon/hwmon1/power1_input", int) / 1e6)
|
|
||||||
|
|
||||||
def get_som_power_draw(self):
|
|
||||||
return (self.read_param_file("/sys/class/power_supply/bms/voltage_now", int) * self.read_param_file("/sys/class/power_supply/bms/current_now", int) / 1e12)
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
os.system("sudo poweroff")
|
|
||||||
|
|
||||||
def get_thermal_config(self):
|
|
||||||
intake, exhaust, gnss, bottomSoc = None, None, None, None
|
|
||||||
if self.get_device_type() == "mici":
|
|
||||||
gnss = ThermalZone("gnss")
|
|
||||||
intake = ThermalZone("intake")
|
|
||||||
exhaust = ThermalZone("exhaust")
|
|
||||||
bottomSoc = ThermalZone("bottom_soc")
|
|
||||||
return ThermalConfig(cpu=[ThermalZone(f"cpu{i}-silver-usr") for i in range(4)] +
|
|
||||||
[ThermalZone(f"cpu{i}-gold-usr") for i in range(4)],
|
|
||||||
gpu=[ThermalZone("gpu0-usr"), ThermalZone("gpu1-usr")],
|
|
||||||
dsp=ThermalZone("compute-hvx-usr"),
|
|
||||||
memory=ThermalZone("ddr-usr"),
|
|
||||||
pmic=[ThermalZone("pm8998_tz"), ThermalZone("pm8005_tz")],
|
|
||||||
intake=intake,
|
|
||||||
exhaust=exhaust,
|
|
||||||
gnss=gnss,
|
|
||||||
bottomSoc=bottomSoc)
|
|
||||||
|
|
||||||
def set_display_power(self, on):
|
|
||||||
try:
|
|
||||||
with open("/sys/class/backlight/panel0-backlight/bl_power", "w") as f:
|
|
||||||
f.write("0" if on else "4")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_screen_brightness(self, percentage):
|
|
||||||
try:
|
|
||||||
with open("/sys/class/backlight/panel0-backlight/max_brightness") as f:
|
|
||||||
max_brightness = float(f.read().strip())
|
|
||||||
|
|
||||||
val = int(percentage * (max_brightness / 100.))
|
|
||||||
with open("/sys/class/backlight/panel0-backlight/brightness", "w") as f:
|
|
||||||
f.write(str(val))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_screen_brightness(self):
|
|
||||||
try:
|
|
||||||
with open("/sys/class/backlight/panel0-backlight/max_brightness") as f:
|
|
||||||
max_brightness = float(f.read().strip())
|
|
||||||
|
|
||||||
with open("/sys/class/backlight/panel0-backlight/brightness") as f:
|
|
||||||
return int(float(f.read()) / (max_brightness / 100.))
|
|
||||||
except Exception:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def set_power_save(self, powersave_enabled):
|
|
||||||
# amplifier, 100mW at idle
|
|
||||||
if self.amplifier is not None:
|
|
||||||
self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled)
|
|
||||||
if not powersave_enabled:
|
|
||||||
self.amplifier.initialize_configuration()
|
|
||||||
|
|
||||||
# *** CPU config ***
|
|
||||||
|
|
||||||
# offline big cluster
|
|
||||||
for i in range(4, 8):
|
|
||||||
val = '0' if powersave_enabled else '1'
|
|
||||||
sudo_write(val, f'/sys/devices/system/cpu/cpu{i}/online')
|
|
||||||
|
|
||||||
for n in ('0', '4'):
|
|
||||||
if powersave_enabled and n == '4':
|
|
||||||
continue
|
|
||||||
gov = 'ondemand' if powersave_enabled else 'performance'
|
|
||||||
sudo_write(gov, f'/sys/devices/system/cpu/cpufreq/policy{n}/scaling_governor')
|
|
||||||
if not powersave_enabled:
|
|
||||||
# cap max core freq to 1689 Mhz
|
|
||||||
sudo_write('1689600', f'/sys/devices/system/cpu/cpufreq/policy{n}/scaling_max_freq')
|
|
||||||
|
|
||||||
# *** IRQ config ***
|
|
||||||
|
|
||||||
# GPU, modeld core
|
|
||||||
affine_irq(7, "kgsl-3d0")
|
|
||||||
|
|
||||||
# camerad core
|
|
||||||
camera_irqs = ("a5", "cci", "cpas_camnoc", "cpas-cdm", "csid", "ife", "csid-lite", "ife-lite")
|
|
||||||
for n in camera_irqs:
|
|
||||||
affine_irq(6, n)
|
|
||||||
|
|
||||||
def get_gpu_usage_percent(self):
|
|
||||||
try:
|
|
||||||
with open('/sys/class/kgsl/kgsl-3d0/gpubusy') as f:
|
|
||||||
used, total = f.read().strip().split()
|
|
||||||
return 100.0 * int(used) / int(total)
|
|
||||||
except Exception:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def initialize_hardware(self):
|
|
||||||
if self.amplifier is not None:
|
|
||||||
self.amplifier.initialize_configuration()
|
|
||||||
|
|
||||||
# Allow hardwared to write engagement status to kmsg
|
|
||||||
os.system("sudo chmod a+w /dev/kmsg")
|
|
||||||
|
|
||||||
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
|
|
||||||
gpio_init(GPIO.SOM_ST_IO, True)
|
|
||||||
gpio_set(GPIO.SOM_ST_IO, 1)
|
|
||||||
|
|
||||||
# *** IRQ config ***
|
|
||||||
|
|
||||||
# mask off big cluster from default affinity
|
|
||||||
sudo_write("f", "/proc/irq/default_smp_affinity")
|
|
||||||
|
|
||||||
# move these off the default core
|
|
||||||
affine_irq(1, "msm_vidc") # encoders
|
|
||||||
affine_irq(1, "i2c_geni") # sensors
|
|
||||||
|
|
||||||
# *** GPU config ***
|
|
||||||
# https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216
|
|
||||||
affine_irq(5, "fts_ts") # touch
|
|
||||||
affine_irq(5, "msm_drm") # display
|
|
||||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel")
|
|
||||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/max_pwrlevel")
|
|
||||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_bus_on")
|
|
||||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_clk_on")
|
|
||||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_rail_on")
|
|
||||||
sudo_write("1000", "/sys/class/kgsl/kgsl-3d0/idle_timer")
|
|
||||||
sudo_write("performance", "/sys/class/kgsl/kgsl-3d0/devfreq/governor")
|
|
||||||
sudo_write("710", "/sys/class/kgsl/kgsl-3d0/max_clock_mhz")
|
|
||||||
|
|
||||||
# setup governors
|
|
||||||
sudo_write("performance", "/sys/class/devfreq/soc:qcom,cpubw/governor")
|
|
||||||
sudo_write("performance", "/sys/class/devfreq/soc:qcom,memlat-cpu0/governor")
|
|
||||||
sudo_write("performance", "/sys/class/devfreq/soc:qcom,memlat-cpu4/governor")
|
|
||||||
|
|
||||||
# *** VIDC (encoder) config ***
|
|
||||||
sudo_write("N", "/sys/kernel/debug/msm_vidc/clock_scaling")
|
|
||||||
sudo_write("Y", "/sys/kernel/debug/msm_vidc/disable_thermal_mitigation")
|
|
||||||
|
|
||||||
# pandad core
|
|
||||||
affine_irq(3, "spi_geni") # SPI
|
|
||||||
try:
|
|
||||||
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
|
|
||||||
subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
|
|
||||||
subprocess.call(["sudo", "taskset", "-pc", "3", pid])
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(str(e))
|
|
||||||
|
|
||||||
def get_modem_data_usage(self):
|
|
||||||
ms = self.get_modem_state()
|
|
||||||
return ms.get('tx_bytes', -1), ms.get('rx_bytes', -1)
|
|
||||||
|
|
||||||
def reset_internal_panda(self):
|
|
||||||
gpio_init(GPIO.STM_RST_N, True)
|
|
||||||
gpio_init(GPIO.STM_BOOT0, True)
|
|
||||||
|
|
||||||
gpio_set(GPIO.STM_RST_N, 1)
|
|
||||||
gpio_set(GPIO.STM_BOOT0, 0)
|
|
||||||
time.sleep(0.01)
|
|
||||||
gpio_set(GPIO.STM_RST_N, 0)
|
|
||||||
|
|
||||||
def recover_internal_panda(self):
|
|
||||||
gpio_init(GPIO.STM_RST_N, True)
|
|
||||||
gpio_init(GPIO.STM_BOOT0, True)
|
|
||||||
|
|
||||||
gpio_set(GPIO.STM_RST_N, 1)
|
|
||||||
gpio_set(GPIO.STM_BOOT0, 1)
|
|
||||||
time.sleep(0.01)
|
|
||||||
gpio_set(GPIO.STM_RST_N, 0)
|
|
||||||
time.sleep(0.01)
|
|
||||||
gpio_set(GPIO.STM_BOOT0, 0)
|
|
||||||
|
|
||||||
def booted(self):
|
|
||||||
# this normally boots within 8s, but on rare occasions takes 30+s
|
|
||||||
encoder_state = sudo_read("/sys/kernel/debug/msm_vidc/core0/info")
|
|
||||||
if "Core state: 0" in encoder_state and (time.monotonic() < 60*2):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
t = Tici()
|
|
||||||
t.initialize_hardware()
|
|
||||||
t.set_power_save(False)
|
|
||||||
print(t.get_sim_info())
|
|
||||||
@@ -1,587 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import fcntl
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import serial
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import time
|
|
||||||
|
|
||||||
from ipaddress import IPv4Address, AddressValueError
|
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format="%(asctime)s.%(msecs)03d %(levelname)-7s modem: %(message)s",
|
|
||||||
datefmt="%H:%M:%S",
|
|
||||||
)
|
|
||||||
|
|
||||||
AT_PORT = "/dev/modem_at0"
|
|
||||||
PPP_PORT = "/dev/modem_at1"
|
|
||||||
STATE_PATH = "/dev/shm/modem"
|
|
||||||
AT_LOCK = "/dev/shm/modem.lock" # shared with LPA
|
|
||||||
AT_INIT = [
|
|
||||||
"ATE0", # disable command echo
|
|
||||||
"ATV1", # verbose result codes (CONNECT/BUSY/NO CARRIER, not numeric)
|
|
||||||
"AT+CMEE=1", # numeric +CME ERROR codes on failures (per 3GPP 27.007)
|
|
||||||
"ATX4", # extended result codes: busy + dial tone detection, line speed in CONNECT
|
|
||||||
"AT&C1", # DCD pin follows carrier state (V.250 default)
|
|
||||||
"AT+CREG=2", # registration URCs include location info
|
|
||||||
"AT+CGREG=2", # GPRS registration URCs include location info
|
|
||||||
]
|
|
||||||
CREG = {0: "not_registered", 1: "home", 2: "searching", 3: "denied", 4: "unknown", 5: "roaming"}
|
|
||||||
# 3GPP TS 27.007 +COPS <AcT> -> network type
|
|
||||||
NETWORK_TYPE = {0: "gsm", 1: "gsm", 3: "gsm", 8: "gsm",
|
|
||||||
2: "utran", 4: "utran", 5: "utran", 6: "utran",
|
|
||||||
7: "lte", 9: "lte", 10: "lte",
|
|
||||||
11: "nr", 12: "nr", 13: "nr"}
|
|
||||||
|
|
||||||
DIAL_CID = 1
|
|
||||||
WEBBING_ICCID_PREFIX = "8985235"
|
|
||||||
|
|
||||||
PPPD_CMD = [
|
|
||||||
"sudo", "pppd", PPP_PORT, "460800", "noauth", "nodetach", "noipdefault", "usepeerdns",
|
|
||||||
"nodefaultroute", "connect",
|
|
||||||
"/usr/sbin/chat -v ABORT 'NO CARRIER' ABORT 'NO DIALTONE' ABORT 'BUSY' " +
|
|
||||||
f"ABORT 'NO ANSWER' ABORT 'ERROR' TIMEOUT 5 '' AT OK ATD*99***{DIAL_CID}# CONNECT ''",
|
|
||||||
"lcp-echo-interval", "30", "lcp-echo-failure", "4", "mtu", "1500", "mru", "1500",
|
|
||||||
"novj", "novjccomp", "ipcp-accept-local", "ipcp-accept-remote", "nomagic",
|
|
||||||
"user", '""', "password", '""',
|
|
||||||
]
|
|
||||||
INITIAL_STATE = {
|
|
||||||
"seconds_since_boot": 0,
|
|
||||||
"state": "INITIALIZING",
|
|
||||||
"connected": False, "ip_address": "",
|
|
||||||
"iccid": "", "mcc_mnc": "", "imei": "", "modem_version": "",
|
|
||||||
"signal_strength": 0, "signal_quality": 0,
|
|
||||||
"network_type": "unknown", "operator": "", "band": "", "channel": 0,
|
|
||||||
"registration": "unknown", "temperatures": [], "extra": "",
|
|
||||||
"tx_bytes": 0, "rx_bytes": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class State(Enum):
|
|
||||||
INITIALIZING = "INITIALIZING"
|
|
||||||
SEARCHING = "SEARCHING"
|
|
||||||
CONNECTING = "CONNECTING"
|
|
||||||
CONNECTED = "CONNECTED"
|
|
||||||
DISCONNECTING = "DISCONNECTING"
|
|
||||||
|
|
||||||
|
|
||||||
STATE_WAIT = 1.0 # seconds to wait after each state handler returns
|
|
||||||
|
|
||||||
|
|
||||||
class PPPSession:
|
|
||||||
"""Owns pppd lifecycle, fail tracking, and PPP routing."""
|
|
||||||
MAX_FAILS = 3
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._proc: subprocess.Popen | None = None
|
|
||||||
self._fails = 0
|
|
||||||
self._peer = ""
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self._proc = subprocess.Popen(PPPD_CMD, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
||||||
self._peer = ""
|
|
||||||
logging.info(f"PPP dialing CID {DIAL_CID}")
|
|
||||||
|
|
||||||
def kill(self):
|
|
||||||
subprocess.run(["sudo", "killall", "-9", "pppd"], capture_output=True)
|
|
||||||
self._peer = ""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def reset_data_port():
|
|
||||||
"""Drop DTR on PPP_PORT so the modem terminates any stuck PPP session."""
|
|
||||||
try:
|
|
||||||
with serial.Serial(PPP_PORT, 460800, timeout=1) as s:
|
|
||||||
s.dtr = False
|
|
||||||
time.sleep(0.2)
|
|
||||||
s.dtr = True
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(f"data port reset failed: {e}")
|
|
||||||
|
|
||||||
def has_exited(self) -> bool:
|
|
||||||
return self._proc is not None and self._proc.poll() is not None
|
|
||||||
|
|
||||||
def reset_fail_counter(self):
|
|
||||||
self._fails = 0
|
|
||||||
|
|
||||||
def record_fail(self) -> bool:
|
|
||||||
"""Bump fail counter; return True if at the give-up limit."""
|
|
||||||
self._fails += 1
|
|
||||||
return self._fails >= self.MAX_FAILS
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fails(self) -> int:
|
|
||||||
return self._fails
|
|
||||||
|
|
||||||
def maybe_install_routes(self, ip: str, peer: str) -> bool:
|
|
||||||
"""Install routes if peer changed; kill the session on failure so the state machine reconnects."""
|
|
||||||
if not peer or peer == self._peer:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
IPv4Address(ip)
|
|
||||||
IPv4Address(peer)
|
|
||||||
except AddressValueError:
|
|
||||||
logging.warning(f"refusing route install with non-IPv4 ip={ip!r} peer={peer!r}")
|
|
||||||
self.kill()
|
|
||||||
return False
|
|
||||||
self.cleanup_routes()
|
|
||||||
cmds = [
|
|
||||||
["sudo", "ip", "route", "add", "default", "via", peer, "dev", "ppp0", "metric", "1000"],
|
|
||||||
["sudo", "ip", "route", "add", "default", "via", peer, "dev", "ppp0", "table", "1000"],
|
|
||||||
["sudo", "ip", "rule", "add", "from", ip, "table", "1000"],
|
|
||||||
]
|
|
||||||
for cmd in cmds:
|
|
||||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
if r.returncode != 0:
|
|
||||||
logging.warning(f"route install failed ({' '.join(cmd[1:])}): {r.stderr.strip()}")
|
|
||||||
self.cleanup_routes()
|
|
||||||
self.kill()
|
|
||||||
return False
|
|
||||||
logging.info(f"route set up for {ip} via {peer}")
|
|
||||||
self._peer = peer
|
|
||||||
return True
|
|
||||||
|
|
||||||
def maybe_install_dns(self, dns_servers: list[str]) -> bool:
|
|
||||||
"""Register DNS servers with systemd-resolved; kill the session on failure to force a retry."""
|
|
||||||
if not dns_servers:
|
|
||||||
return False
|
|
||||||
for cmd in (["sudo", "resolvectl", "dns", "ppp0", *dns_servers],
|
|
||||||
["sudo", "resolvectl", "default-route", "ppp0", "yes"]):
|
|
||||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
if r.returncode != 0:
|
|
||||||
logging.warning(f"resolvectl failed ({' '.join(cmd[1:])}): {r.stderr.strip()}")
|
|
||||||
self.kill()
|
|
||||||
return False
|
|
||||||
logging.info(f"resolvectl: ppp0 DNS = {dns_servers}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cleanup_routes():
|
|
||||||
subprocess.run(["sudo", "ip", "route", "del", "default", "dev", "ppp0"], capture_output=True)
|
|
||||||
subprocess.run(["sudo", "ip", "route", "flush", "table", "1000"], capture_output=True)
|
|
||||||
# rules don't have a flush; delete until none remain
|
|
||||||
while subprocess.run(["sudo", "ip", "rule", "del", "table", "1000"], capture_output=True).returncode == 0:
|
|
||||||
pass
|
|
||||||
subprocess.run(["sudo", "resolvectl", "revert", "ppp0"], capture_output=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Modem:
|
|
||||||
def __init__(self):
|
|
||||||
self._ppp = PPPSession()
|
|
||||||
self._sim_change = False
|
|
||||||
self._apn = "" # blank = network-provided via PCO
|
|
||||||
self._roaming_allowed = True
|
|
||||||
self.running = True
|
|
||||||
self.S = INITIAL_STATE.copy()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _read_param(key):
|
|
||||||
try:
|
|
||||||
with open(f"/data/params/d/{key}") as f:
|
|
||||||
return f.read().strip()
|
|
||||||
except FileNotFoundError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _parse_reg(v: str) -> str:
|
|
||||||
try:
|
|
||||||
return CREG.get(int(v.split(",")[1].strip('"')), "unknown")
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
return "unknown"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _has_modem_manager() -> bool:
|
|
||||||
return os.path.isfile("/lib/systemd/system/ModemManager.service")
|
|
||||||
|
|
||||||
def _is_roaming_allowed(self) -> bool:
|
|
||||||
if self.S["iccid"].startswith(WEBBING_ICCID_PREFIX):
|
|
||||||
return True
|
|
||||||
return self._read_param("GsmRoaming") == "1"
|
|
||||||
|
|
||||||
def _publish_state(self, **kwargs):
|
|
||||||
self.S.update(kwargs)
|
|
||||||
self.S["seconds_since_boot"] = time.monotonic()
|
|
||||||
with tempfile.NamedTemporaryFile(mode="w", dir="/dev/shm", delete=False) as f:
|
|
||||||
json.dump(self.S, f, indent=2)
|
|
||||||
os.chmod(f.name, 0o644)
|
|
||||||
os.replace(f.name, STATE_PATH)
|
|
||||||
|
|
||||||
def _at(self, cmd):
|
|
||||||
"""Send AT command, return response lines. [] on error or if LPA holds port."""
|
|
||||||
fd = os.open(AT_LOCK, os.O_CREAT | os.O_RDWR, 0o666)
|
|
||||||
try:
|
|
||||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
||||||
except OSError:
|
|
||||||
os.close(fd)
|
|
||||||
return []
|
|
||||||
try:
|
|
||||||
with serial.Serial(AT_PORT, 9600, timeout=5) as ser:
|
|
||||||
ser.reset_input_buffer()
|
|
||||||
ser.write((cmd + "\r").encode())
|
|
||||||
lines = []
|
|
||||||
while True:
|
|
||||||
raw = ser.readline()
|
|
||||||
if not raw:
|
|
||||||
raise TimeoutError("AT timeout")
|
|
||||||
line = raw.decode(errors="ignore").strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
if line == "OK":
|
|
||||||
break
|
|
||||||
if line == "ERROR" or line.startswith("+CME ERROR"):
|
|
||||||
raise RuntimeError(line)
|
|
||||||
lines.append(line)
|
|
||||||
return lines
|
|
||||||
except (RuntimeError, TimeoutError, OSError) as e:
|
|
||||||
logging.info(f"AT {cmd} failed: {e}")
|
|
||||||
return []
|
|
||||||
finally:
|
|
||||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
|
||||||
os.close(fd)
|
|
||||||
|
|
||||||
def _atv(self, cmd, pfx):
|
|
||||||
for line in self._at(cmd):
|
|
||||||
if pfx in line and ":" in line:
|
|
||||||
return line.split(":", 1)[1].strip()
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _init_at_channel(self) -> bool:
|
|
||||||
"""Run AT_INIT and confirm ATE0 took effect. Returns False if echo is still on."""
|
|
||||||
for c in AT_INIT:
|
|
||||||
self._at(c)
|
|
||||||
r = self._at("AT+CGMI")
|
|
||||||
return bool(r) and not r[0].startswith("AT")
|
|
||||||
|
|
||||||
def _configure_modem(self, modem_version: str):
|
|
||||||
if not modem_version.startswith("EG25"):
|
|
||||||
return
|
|
||||||
cmds = [
|
|
||||||
# clear initial EPS bearer APN (some carriers reject the default)
|
|
||||||
'AT+CGDCONT=0,"IP",""',
|
|
||||||
|
|
||||||
# SIM hot swap
|
|
||||||
'AT+QSIMDET=1,0',
|
|
||||||
'AT+QSIMSTAT=1',
|
|
||||||
|
|
||||||
# configure modem as data-centric
|
|
||||||
'AT+QNVW=5280,0,"0102000000000000"',
|
|
||||||
'AT+QNVFW="/nv/item_files/ims/IMS_enable",00',
|
|
||||||
'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01',
|
|
||||||
]
|
|
||||||
for c in cmds:
|
|
||||||
self._at(c)
|
|
||||||
|
|
||||||
def _do_initializing(self):
|
|
||||||
if not os.path.exists(AT_PORT):
|
|
||||||
return State.INITIALIZING
|
|
||||||
logging.info("port found, initializing")
|
|
||||||
self._ppp.kill()
|
|
||||||
self._ppp.cleanup_routes()
|
|
||||||
|
|
||||||
if not self._init_at_channel():
|
|
||||||
logging.warning("AT echo still on, retrying")
|
|
||||||
return State.INITIALIZING
|
|
||||||
|
|
||||||
identity = self._read_identity()
|
|
||||||
if not identity["iccid"] or not identity["imei"]:
|
|
||||||
logging.warning(f"identity read incomplete: {identity}, retrying")
|
|
||||||
return State.INITIALIZING
|
|
||||||
|
|
||||||
self._configure_modem(identity["modem_version"])
|
|
||||||
|
|
||||||
self.S.update(identity)
|
|
||||||
self._apn = self._read_param("GsmApn")
|
|
||||||
self._roaming_allowed = self._is_roaming_allowed()
|
|
||||||
# blank APN lets the carrier supply one via PCO
|
|
||||||
self._at(f'AT+CGDCONT={DIAL_CID},"IP","{self._apn}"')
|
|
||||||
logging.info(f"APN '{self._apn or '(network-provided)'}' written to CID {DIAL_CID}, roaming={'on' if self._roaming_allowed else 'off'}")
|
|
||||||
|
|
||||||
self._sim_change = False # clear since we just re-read identity with the new SIM
|
|
||||||
self._publish_state(**identity)
|
|
||||||
return State.SEARCHING
|
|
||||||
|
|
||||||
def _read_identity(self):
|
|
||||||
def first_line(cmd):
|
|
||||||
r = self._at(cmd)
|
|
||||||
return r[0].strip() if r else ""
|
|
||||||
|
|
||||||
imei = first_line("AT+CGSN")
|
|
||||||
if not (imei.isdigit() and 14 <= len(imei) <= 17): # 3GPP TS 23.003
|
|
||||||
imei = ""
|
|
||||||
|
|
||||||
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
|
|
||||||
if not iccid.isdigit():
|
|
||||||
iccid = ""
|
|
||||||
|
|
||||||
imsi = first_line("AT+CIMI")
|
|
||||||
mcc_mnc = imsi[:6] if imsi.isdigit() and len(imsi) >= 6 else ""
|
|
||||||
|
|
||||||
modem_version = first_line("AT+GMR")
|
|
||||||
|
|
||||||
logging.info(f"imei={imei} iccid={iccid} mcc_mnc={mcc_mnc} ver={modem_version}")
|
|
||||||
return {"imei": imei, "iccid": iccid, "mcc_mnc": mcc_mnc, "modem_version": modem_version}
|
|
||||||
|
|
||||||
def _do_searching(self):
|
|
||||||
new_roaming = self._is_roaming_allowed()
|
|
||||||
if new_roaming != self._roaming_allowed:
|
|
||||||
logging.info(f"roaming changed: {self._roaming_allowed} -> {new_roaming}")
|
|
||||||
self._roaming_allowed = new_roaming
|
|
||||||
|
|
||||||
v = self._atv("AT+CREG?", "+CREG:")
|
|
||||||
if not v:
|
|
||||||
return self._searching_idle()
|
|
||||||
|
|
||||||
reg = self._parse_reg(v)
|
|
||||||
greg = self._parse_reg(self._atv("AT+CGREG?", "+CGREG:") or "")
|
|
||||||
logging.debug(f"creg={reg} cgreg={greg} roaming_allowed={self._roaming_allowed}")
|
|
||||||
|
|
||||||
if reg == "roaming" and not self._roaming_allowed:
|
|
||||||
self._publish_state(registration=reg)
|
|
||||||
return State.SEARCHING
|
|
||||||
|
|
||||||
if reg in ("home", "roaming") and greg in ("home", "roaming"):
|
|
||||||
self._publish_state(registration=reg)
|
|
||||||
return State.CONNECTING
|
|
||||||
|
|
||||||
if reg != self.S.get("registration"):
|
|
||||||
self._publish_state(registration=reg)
|
|
||||||
return self._searching_idle()
|
|
||||||
|
|
||||||
def _searching_idle(self):
|
|
||||||
if self._sim_change or not os.path.exists(AT_PORT):
|
|
||||||
logging.info(f"-> reconnecting (sim_change={self._sim_change} port={os.path.exists(AT_PORT)})")
|
|
||||||
return State.DISCONNECTING
|
|
||||||
return State.SEARCHING
|
|
||||||
|
|
||||||
def _do_connecting(self):
|
|
||||||
logging.info("starting pppd")
|
|
||||||
self._ppp.reset_fail_counter()
|
|
||||||
self._sim_change = False
|
|
||||||
self._ppp.start()
|
|
||||||
return State.CONNECTED
|
|
||||||
|
|
||||||
def _handle_pppd_exit(self):
|
|
||||||
if self._sim_change or not os.path.exists(AT_PORT):
|
|
||||||
return State.DISCONNECTING
|
|
||||||
give_up = self._ppp.record_fail()
|
|
||||||
if give_up:
|
|
||||||
logging.warning(f"PPP fail {self._ppp.fails}/{self._ppp.MAX_FAILS}, reconnecting")
|
|
||||||
return State.DISCONNECTING
|
|
||||||
logging.warning(f"PPP fail {self._ppp.fails}/{self._ppp.MAX_FAILS}, retrying")
|
|
||||||
self._ppp.reset_data_port()
|
|
||||||
if not os.path.exists(AT_PORT):
|
|
||||||
return State.DISCONNECTING
|
|
||||||
self._ppp.start()
|
|
||||||
return State.CONNECTED
|
|
||||||
|
|
||||||
def _params_changed(self) -> bool:
|
|
||||||
new_apn = self._read_param("GsmApn")
|
|
||||||
if new_apn != self._apn:
|
|
||||||
logging.info(f"GsmApn changed: '{self._apn}' -> '{new_apn}'")
|
|
||||||
return True
|
|
||||||
new_roaming = self._is_roaming_allowed()
|
|
||||||
if new_roaming != self._roaming_allowed:
|
|
||||||
logging.info(f"roaming changed: {self._roaming_allowed} -> {new_roaming}")
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _check_iccid(self, state):
|
|
||||||
if state in (State.INITIALIZING, State.DISCONNECTING) or not self.S["iccid"]:
|
|
||||||
return
|
|
||||||
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
|
|
||||||
if iccid and iccid != self.S["iccid"]:
|
|
||||||
logging.warning(f"iccid changed: {self.S['iccid']} -> {iccid}")
|
|
||||||
self._sim_change = True
|
|
||||||
|
|
||||||
def _do_connected(self):
|
|
||||||
if self._ppp.has_exited():
|
|
||||||
return self._handle_pppd_exit()
|
|
||||||
|
|
||||||
if self._sim_change or not os.path.exists(AT_PORT) or self._params_changed():
|
|
||||||
return State.DISCONNECTING
|
|
||||||
|
|
||||||
self._poll()
|
|
||||||
return State.CONNECTED
|
|
||||||
|
|
||||||
def _do_disconnecting(self):
|
|
||||||
logging.warning("reconnecting")
|
|
||||||
self._publish_state(**INITIAL_STATE)
|
|
||||||
self._ppp.kill()
|
|
||||||
self._ppp.cleanup_routes()
|
|
||||||
self._ppp.reset_data_port()
|
|
||||||
self._sim_change = False
|
|
||||||
return State.INITIALIZING
|
|
||||||
|
|
||||||
def _poll_signal(self) -> dict:
|
|
||||||
v = self._atv("AT+CSQ", "+CSQ:")
|
|
||||||
if not v:
|
|
||||||
return {}
|
|
||||||
try:
|
|
||||||
rssi = int(v.split(",")[0])
|
|
||||||
if rssi == 99:
|
|
||||||
return {}
|
|
||||||
return {"signal_strength": rssi, "signal_quality": min(100, int(rssi / 31 * 100))}
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _poll_operator(self) -> dict:
|
|
||||||
v = self._atv("AT+COPS?", "+COPS:")
|
|
||||||
if not v:
|
|
||||||
return {}
|
|
||||||
p = v.split(",")
|
|
||||||
out: dict = {}
|
|
||||||
try:
|
|
||||||
if len(p) >= 3:
|
|
||||||
out["operator"] = p[2].strip('"')
|
|
||||||
if len(p) >= 4:
|
|
||||||
out["network_type"] = NETWORK_TYPE.get(int(p[3]), "unknown")
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
pass
|
|
||||||
return out
|
|
||||||
|
|
||||||
def _poll_band(self) -> dict:
|
|
||||||
v = self._atv("AT+QNWINFO", "+QNWINFO:")
|
|
||||||
if not v:
|
|
||||||
return {}
|
|
||||||
info = v.replace('"', '').split(",")
|
|
||||||
try:
|
|
||||||
if len(info) >= 4:
|
|
||||||
return {"band": info[2], "channel": int(info[3])}
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _poll_extra(self) -> dict:
|
|
||||||
v = self._atv('AT+QENG="servingcell"', "+QENG:")
|
|
||||||
return {"extra": v.replace('"', '')} if v else {}
|
|
||||||
|
|
||||||
def _poll_temps(self) -> dict:
|
|
||||||
v = self._atv("AT+QTEMP", "+QTEMP:")
|
|
||||||
if not v:
|
|
||||||
return {}
|
|
||||||
try:
|
|
||||||
return {"temperatures": [t for t in (int(x) for x in v.split(",") if x.strip()) if t != 255]}
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _poll_iface(self) -> dict:
|
|
||||||
try:
|
|
||||||
r = subprocess.run(["ip", "-4", "addr", "show", "ppp0"], capture_output=True, text=True, timeout=2)
|
|
||||||
ip, peer = "", ""
|
|
||||||
for line in r.stdout.splitlines():
|
|
||||||
# `inet 10.x.x.x peer 10.64.64.64/32 ...`
|
|
||||||
parts = line.strip().split()
|
|
||||||
if "inet" in parts:
|
|
||||||
i = parts.index("inet")
|
|
||||||
ip = parts[i + 1].split("/")[0]
|
|
||||||
if "peer" in parts:
|
|
||||||
peer = parts[parts.index("peer") + 1].split("/")[0]
|
|
||||||
break
|
|
||||||
if ip:
|
|
||||||
if self._ppp.maybe_install_routes(ip, peer):
|
|
||||||
self._ppp.maybe_install_dns(self._read_cellular_dns())
|
|
||||||
return {"ip_address": ip, "connected": True}
|
|
||||||
if self.S["connected"]:
|
|
||||||
return {"connected": False, "ip_address": ""}
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _read_cellular_dns(self) -> list[str]:
|
|
||||||
v = self._atv(f"AT+CGCONTRDP={DIAL_CID}", "+CGCONTRDP:")
|
|
||||||
if not v:
|
|
||||||
return []
|
|
||||||
# +CGCONTRDP: <cid>,<bearer_id>,<apn>,<local_addr>,<gw_addr>,<dns_prim>,<dns_sec>,...
|
|
||||||
fields = [f.strip().strip('"') for f in v.split(",")]
|
|
||||||
dns_servers = []
|
|
||||||
for d in fields[5:7]:
|
|
||||||
try:
|
|
||||||
dns_servers.append(str(IPv4Address(d)))
|
|
||||||
except (AddressValueError, ValueError):
|
|
||||||
pass
|
|
||||||
if not dns_servers:
|
|
||||||
logging.warning(f"no cellular DNS servers reported by modem: {v!r}")
|
|
||||||
return dns_servers
|
|
||||||
|
|
||||||
def _poll_byte_counters(self) -> dict:
|
|
||||||
try:
|
|
||||||
with open("/sys/class/net/ppp0/statistics/tx_bytes") as f:
|
|
||||||
tx = int(f.read().strip())
|
|
||||||
with open("/sys/class/net/ppp0/statistics/rx_bytes") as f:
|
|
||||||
rx = int(f.read().strip())
|
|
||||||
except Exception:
|
|
||||||
return {}
|
|
||||||
return {"tx_bytes": tx, "rx_bytes": rx}
|
|
||||||
|
|
||||||
def _poll(self):
|
|
||||||
s: dict = {}
|
|
||||||
for fn in (self._poll_signal, self._poll_operator, self._poll_band,
|
|
||||||
self._poll_extra, self._poll_temps, self._poll_iface,
|
|
||||||
self._poll_byte_counters):
|
|
||||||
s.update(fn())
|
|
||||||
if s:
|
|
||||||
self._publish_state(**s)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
logging.info("starting")
|
|
||||||
self._publish_state(state=State.INITIALIZING.value)
|
|
||||||
if self._has_modem_manager():
|
|
||||||
subprocess.run(["sudo", "systemctl", "mask", "--runtime", "ModemManager"], capture_output=True)
|
|
||||||
subprocess.run(["sudo", "systemctl", "stop", "ModemManager"], capture_output=True)
|
|
||||||
self._ppp.kill()
|
|
||||||
|
|
||||||
state = State.INITIALIZING
|
|
||||||
|
|
||||||
handlers = {
|
|
||||||
State.INITIALIZING: self._do_initializing,
|
|
||||||
State.SEARCHING: self._do_searching,
|
|
||||||
State.CONNECTING: self._do_connecting,
|
|
||||||
State.CONNECTED: self._do_connected,
|
|
||||||
State.DISCONNECTING: self._do_disconnecting,
|
|
||||||
}
|
|
||||||
|
|
||||||
while self.running:
|
|
||||||
try:
|
|
||||||
self._check_iccid(state)
|
|
||||||
prev = state
|
|
||||||
state = handlers[state]()
|
|
||||||
if state != prev:
|
|
||||||
self._publish_state(state=state.value)
|
|
||||||
logging.info(f"{prev.value} -> {state.value}")
|
|
||||||
except Exception:
|
|
||||||
logging.exception(f"error in {state.value}")
|
|
||||||
state = State.DISCONNECTING
|
|
||||||
time.sleep(STATE_WAIT)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.running = False
|
|
||||||
self._ppp.kill()
|
|
||||||
self._ppp.cleanup_routes()
|
|
||||||
try:
|
|
||||||
os.remove(STATE_PATH)
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
if self._has_modem_manager():
|
|
||||||
subprocess.run(["sudo", "systemctl", "unmask", "--runtime", "ModemManager"], capture_output=True)
|
|
||||||
subprocess.run(["sudo", "systemctl", "start", "ModemManager"], capture_output=True)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
m = Modem()
|
|
||||||
|
|
||||||
def _sig(*_):
|
|
||||||
m.running = False
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, _sig)
|
|
||||||
signal.signal(signal.SIGTERM, _sig)
|
|
||||||
m.run()
|
|
||||||
m.stop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:3a94ab8395f20d20a9d5a2a2bacca0694f072df8421cf13adca6250d28065bdc
|
|
||||||
size 24709205
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#define DEFAULT_MODEL "CD210 (Default)"
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import pytest
|
import pytest
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
|
|
||||||
def _to_safe_name(s):
|
|
||||||
return re.sub(r"[^a-zA-Z0-9_]+", "_", str(s)).strip("_")
|
|
||||||
|
|
||||||
|
|
||||||
class parameterized:
|
class parameterized:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def expand(cases):
|
def expand(cases):
|
||||||
@@ -39,9 +34,7 @@ def parameterized_class(attrs, input_list=None):
|
|||||||
def decorator(cls):
|
def decorator(cls):
|
||||||
globs = sys._getframe(1).f_globals
|
globs = sys._getframe(1).f_globals
|
||||||
for i, params in enumerate(params_list):
|
for i, params in enumerate(params_list):
|
||||||
# append sanitized string param values so pytest -k can filter by them
|
name = f"{cls.__name__}_{i}"
|
||||||
suffix = "_".join(filter(None, (_to_safe_name(v) for v in params.values() if isinstance(v, str))))
|
|
||||||
name = f"{cls.__name__}_{i}" + (f"_{suffix}" if suffix else "")
|
|
||||||
new_cls = type(name, (cls,), dict(params))
|
new_cls = type(name, (cls,), dict(params))
|
||||||
new_cls.__module__ = cls.__module__
|
new_cls.__module__ = cls.__module__
|
||||||
new_cls.__test__ = True # override inherited False so pytest collects this subclass
|
new_cls.__test__ = True # override inherited False so pytest collects this subclass
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
#include "common/queue.h"
|
#include "common/queue.h"
|
||||||
#include "common/swaglog.h"
|
#include "common/swaglog.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "common/hardware/hw.h"
|
#include "system/hardware/hw.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,6 +14,6 @@ if __name__ == "__main__":
|
|||||||
if len(sys.argv) == 3:
|
if len(sys.argv) == 3:
|
||||||
val = sys.argv[2]
|
val = sys.argv[2]
|
||||||
print(f"SET: {key} = {val}")
|
print(f"SET: {key} = {val}")
|
||||||
params.put(key, val, block=True)
|
params.put(key, val)
|
||||||
elif len(sys.argv) == 2:
|
elif len(sys.argv) == 2:
|
||||||
print(f"GET: {key} = {params.get(key)}")
|
print(f"GET: {key} = {params.get(key)}")
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"IsDriverViewEnabled", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsDriverViewEnabled", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"IsEngaged", {PERSISTENT, BOOL}},
|
{"IsEngaged", {PERSISTENT, BOOL}},
|
||||||
{"IsLdwEnabled", {PERSISTENT | BACKUP, BOOL}},
|
{"IsLdwEnabled", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"IsLiveStreaming", {CLEAR_ON_MANAGER_START, BOOL}},
|
|
||||||
{"IsMetric", {PERSISTENT | BACKUP, BOOL}},
|
{"IsMetric", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"IsOffroad", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsOffroad", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"IsOnroad", {PERSISTENT, BOOL}},
|
{"IsOnroad", {PERSISTENT, BOOL}},
|
||||||
@@ -81,11 +80,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"LiveDelay", {PERSISTENT | BACKUP, BYTES}},
|
{"LiveDelay", {PERSISTENT | BACKUP, BYTES}},
|
||||||
{"LiveParameters", {PERSISTENT, JSON}},
|
{"LiveParameters", {PERSISTENT, JSON}},
|
||||||
{"LiveParametersV2", {PERSISTENT, BYTES}},
|
{"LiveParametersV2", {PERSISTENT, BYTES}},
|
||||||
{"LivestreamEncoderBitrate", {CLEAR_ON_MANAGER_START | DONT_LOG, INT}},
|
|
||||||
{"LivestreamRequestKeyframe", {CLEAR_ON_MANAGER_START | DONT_LOG, BOOL}},
|
|
||||||
{"LiveTorqueParameters", {PERSISTENT | DONT_LOG, BYTES}},
|
{"LiveTorqueParameters", {PERSISTENT | DONT_LOG, BYTES}},
|
||||||
{"LocationFilterInitialState", {PERSISTENT, BYTES}},
|
{"LocationFilterInitialState", {PERSISTENT, BYTES}},
|
||||||
{"LateralManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
|
||||||
{"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"LongitudinalPersonality", {PERSISTENT | BACKUP, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}},
|
{"LongitudinalPersonality", {PERSISTENT | BACKUP, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}},
|
||||||
{"NetworkMetered", {PERSISTENT | BACKUP, BOOL}},
|
{"NetworkMetered", {PERSISTENT | BACKUP, BOOL}},
|
||||||
@@ -106,6 +102,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"OpenpilotEnabledToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"OpenpilotEnabledToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
|
{"PandaSomResetTriggered", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
|
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
|
||||||
{"PrimeType", {PERSISTENT, INT}},
|
{"PrimeType", {PERSISTENT, INT}},
|
||||||
{"RecordAudio", {PERSISTENT | BACKUP, BOOL}},
|
{"RecordAudio", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"RecordAudioFeedback", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"RecordAudioFeedback", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
@@ -133,8 +131,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
|
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
|
||||||
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
|
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||||
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||||
{"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
|
||||||
{"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
|
||||||
{"Version", {PERSISTENT, STRING}},
|
{"Version", {PERSISTENT, STRING}},
|
||||||
|
|
||||||
// --- sunnypilot params --- //
|
// --- sunnypilot params --- //
|
||||||
@@ -176,7 +172,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
|
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"OnroadScreenOffBrightnessMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}},
|
{"OnroadScreenOffBrightnessMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}},
|
||||||
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
|
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
|
||||||
{"OnroadScreenOffTimerMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}},
|
|
||||||
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
@@ -207,7 +202,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
// sunnylink params
|
// sunnylink params
|
||||||
{"EnableSunnylinkUploader", {PERSISTENT | BACKUP, BOOL}},
|
{"EnableSunnylinkUploader", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"LastSunnylinkPingTime", {CLEAR_ON_MANAGER_START, INT}},
|
{"LastSunnylinkPingTime", {CLEAR_ON_MANAGER_START, INT}},
|
||||||
{"ParamsVersion", {PERSISTENT, INT}},
|
|
||||||
{"SunnylinkCache_Roles", {PERSISTENT, STRING}},
|
{"SunnylinkCache_Roles", {PERSISTENT, STRING}},
|
||||||
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
|
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
|
||||||
{"SunnylinkDongleId", {PERSISTENT, STRING}},
|
{"SunnylinkDongleId", {PERSISTENT, STRING}},
|
||||||
@@ -276,7 +270,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
|
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
|
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
{"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT, "0.0"}},
|
{"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT}},
|
||||||
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
|
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
|
||||||
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
|
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
|
||||||
|
|||||||
+18
-13
@@ -142,28 +142,33 @@ cdef class Params:
|
|||||||
cdef ParamKeyType t = self.p.getKeyType(k)
|
cdef ParamKeyType t = self.p.getKeyType(k)
|
||||||
return ensure_bytes(self.python2cpp(type(dat), t, dat, key))
|
return ensure_bytes(self.python2cpp(type(dat), t, dat, key))
|
||||||
|
|
||||||
def put(self, key, dat, bool block = False):
|
def put(self, key, dat):
|
||||||
"""
|
"""
|
||||||
Warning: block=True blocks until the param is written to disk!
|
Warning: This function blocks until the param is written to disk!
|
||||||
In very rare cases this can take over a second, and your code will hang.
|
In very rare cases this can take over a second, and your code will hang.
|
||||||
Use block=False in time sensitive code, but in general try to avoid
|
Use the put_nonblocking, put_bool_nonblocking in time sensitive code, but
|
||||||
writing params as much as possible.
|
in general try to avoid writing params as much as possible.
|
||||||
"""
|
"""
|
||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
cdef string dat_bytes = self._put_cast(key, dat)
|
cdef string dat_bytes = self._put_cast(key, dat)
|
||||||
with nogil:
|
with nogil:
|
||||||
if block:
|
self.p.put(k, dat_bytes)
|
||||||
self.p.put(k, dat_bytes)
|
|
||||||
else:
|
|
||||||
self.p.putNonBlocking(k, dat_bytes)
|
|
||||||
|
|
||||||
def put_bool(self, key, bool val, bool block = False):
|
def put_bool(self, key, bool val):
|
||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
with nogil:
|
with nogil:
|
||||||
if block:
|
self.p.putBool(k, val)
|
||||||
self.p.putBool(k, val)
|
|
||||||
else:
|
def put_nonblocking(self, key, dat):
|
||||||
self.p.putBoolNonBlocking(k, val)
|
cdef string k = self.check_key(key)
|
||||||
|
cdef string dat_bytes = self._put_cast(key, dat)
|
||||||
|
with nogil:
|
||||||
|
self.p.putNonBlocking(k, dat_bytes)
|
||||||
|
|
||||||
|
def put_bool_nonblocking(self, key, bool val):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
with nogil:
|
||||||
|
self.p.putBoolNonBlocking(k, val)
|
||||||
|
|
||||||
def remove(self, key):
|
def remove(self, key):
|
||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "common/params.h"
|
#include "common/params.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "common/hardware/hw.h"
|
#include "system/hardware/hw.h"
|
||||||
|
|
||||||
class OpenpilotPrefix {
|
class OpenpilotPrefix {
|
||||||
public:
|
public:
|
||||||
|
|||||||
+3
-3
@@ -5,9 +5,9 @@ import uuid
|
|||||||
|
|
||||||
|
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.hardware import PC
|
from openpilot.system.hardware import PC
|
||||||
from openpilot.common.hardware.hw import Paths
|
from openpilot.system.hardware.hw import Paths
|
||||||
from openpilot.common.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||||
|
|
||||||
class OpenpilotPrefix:
|
class OpenpilotPrefix:
|
||||||
def __init__(self, prefix: str | None = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
|
def __init__(self, prefix: str | None = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
|
||||||
|
|||||||
+1
-6
@@ -7,7 +7,7 @@ import time
|
|||||||
from setproctitle import getproctitle
|
from setproctitle import getproctitle
|
||||||
|
|
||||||
from openpilot.common.utils import MovingAverage
|
from openpilot.common.utils import MovingAverage
|
||||||
from openpilot.common.hardware import PC
|
from openpilot.system.hardware import PC
|
||||||
|
|
||||||
|
|
||||||
# time step for each process
|
# time step for each process
|
||||||
@@ -28,11 +28,6 @@ class Priority:
|
|||||||
CTRL_HIGH = 53
|
CTRL_HIGH = 53
|
||||||
|
|
||||||
|
|
||||||
def drop_realtime() -> None:
|
|
||||||
if sys.platform == 'linux' and not PC:
|
|
||||||
os.sched_setscheduler(0, os.SCHED_OTHER, os.sched_param(0))
|
|
||||||
|
|
||||||
|
|
||||||
def set_core_affinity(cores: list[int]) -> None:
|
def set_core_affinity(cores: list[int]) -> None:
|
||||||
if sys.platform == 'linux' and not PC:
|
if sys.platform == 'linux' and not PC:
|
||||||
os.sched_setaffinity(0, cores)
|
os.sched_setaffinity(0, cores)
|
||||||
|
|||||||
+2
-2
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
#include <zmq.h>
|
#include <zmq.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include "json11/json11.hpp"
|
#include "third_party/json11/json11.hpp"
|
||||||
#include "common/version.h"
|
#include "common/version.h"
|
||||||
#include "common/hardware/hw.h"
|
#include "system/hardware/hw.h"
|
||||||
|
|
||||||
#include "sunnypilot/common/version.h"
|
#include "sunnypilot/common/version.h"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ from logging.handlers import BaseRotatingHandler
|
|||||||
import zmq
|
import zmq
|
||||||
|
|
||||||
from openpilot.common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter
|
from openpilot.common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter
|
||||||
from openpilot.common.hardware.hw import Paths
|
from openpilot.system.hardware.hw import Paths
|
||||||
|
|
||||||
|
|
||||||
def get_file_handler():
|
def get_file_handler():
|
||||||
|
|||||||
+19
-19
@@ -12,17 +12,17 @@ class TestParams:
|
|||||||
self.params = Params()
|
self.params = Params()
|
||||||
|
|
||||||
def test_params_put_and_get(self):
|
def test_params_put_and_get(self):
|
||||||
self.params.put("DongleId", "cb38263377b873ee", block=True)
|
self.params.put("DongleId", "cb38263377b873ee")
|
||||||
assert self.params.get("DongleId") == "cb38263377b873ee"
|
assert self.params.get("DongleId") == "cb38263377b873ee"
|
||||||
|
|
||||||
def test_params_non_ascii(self):
|
def test_params_non_ascii(self):
|
||||||
st = b"\xe1\x90\xff"
|
st = b"\xe1\x90\xff"
|
||||||
self.params.put("CarParams", st, block=True)
|
self.params.put("CarParams", st)
|
||||||
assert self.params.get("CarParams") == st
|
assert self.params.get("CarParams") == st
|
||||||
|
|
||||||
def test_params_get_cleared_manager_start(self):
|
def test_params_get_cleared_manager_start(self):
|
||||||
self.params.put("CarParams", b"test", block=True)
|
self.params.put("CarParams", b"test")
|
||||||
self.params.put("DongleId", "cb38263377b873ee", block=True)
|
self.params.put("DongleId", "cb38263377b873ee")
|
||||||
assert self.params.get("CarParams") == b"test"
|
assert self.params.get("CarParams") == b"test"
|
||||||
|
|
||||||
undefined_param = self.params.get_param_path(uuid.uuid4().hex)
|
undefined_param = self.params.get_param_path(uuid.uuid4().hex)
|
||||||
@@ -36,15 +36,15 @@ class TestParams:
|
|||||||
assert not os.path.isfile(undefined_param)
|
assert not os.path.isfile(undefined_param)
|
||||||
|
|
||||||
def test_params_two_things(self):
|
def test_params_two_things(self):
|
||||||
self.params.put("DongleId", "bob", block=True)
|
self.params.put("DongleId", "bob")
|
||||||
self.params.put("AthenadPid", 123, block=True)
|
self.params.put("AthenadPid", 123)
|
||||||
assert self.params.get("DongleId") == "bob"
|
assert self.params.get("DongleId") == "bob"
|
||||||
assert self.params.get("AthenadPid") == 123
|
assert self.params.get("AthenadPid") == 123
|
||||||
|
|
||||||
def test_params_get_block(self):
|
def test_params_get_block(self):
|
||||||
def _delayed_writer():
|
def _delayed_writer():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.params.put("CarParams", b"test", block=True)
|
self.params.put("CarParams", b"test")
|
||||||
threading.Thread(target=_delayed_writer).start()
|
threading.Thread(target=_delayed_writer).start()
|
||||||
assert self.params.get("CarParams") is None
|
assert self.params.get("CarParams") is None
|
||||||
assert self.params.get("CarParams", block=True) == b"test"
|
assert self.params.get("CarParams", block=True) == b"test"
|
||||||
@@ -57,10 +57,10 @@ class TestParams:
|
|||||||
self.params.get_bool("swag")
|
self.params.get_bool("swag")
|
||||||
|
|
||||||
with pytest.raises(UnknownKeyName):
|
with pytest.raises(UnknownKeyName):
|
||||||
self.params.put("swag", "abc", block=True)
|
self.params.put("swag", "abc")
|
||||||
|
|
||||||
with pytest.raises(UnknownKeyName):
|
with pytest.raises(UnknownKeyName):
|
||||||
self.params.put_bool("swag", True, block=True)
|
self.params.put_bool("swag", True)
|
||||||
|
|
||||||
def test_remove_not_there(self):
|
def test_remove_not_there(self):
|
||||||
assert self.params.get("CarParams") is None
|
assert self.params.get("CarParams") is None
|
||||||
@@ -71,23 +71,23 @@ class TestParams:
|
|||||||
self.params.remove("IsMetric")
|
self.params.remove("IsMetric")
|
||||||
assert not self.params.get_bool("IsMetric")
|
assert not self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
self.params.put_bool("IsMetric", True, block=True)
|
self.params.put_bool("IsMetric", True)
|
||||||
assert self.params.get_bool("IsMetric")
|
assert self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
self.params.put_bool("IsMetric", False, block=True)
|
self.params.put_bool("IsMetric", False)
|
||||||
assert not self.params.get_bool("IsMetric")
|
assert not self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
self.params.put("IsMetric", True, block=True)
|
self.params.put("IsMetric", True)
|
||||||
assert self.params.get_bool("IsMetric")
|
assert self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
self.params.put("IsMetric", False, block=True)
|
self.params.put("IsMetric", False)
|
||||||
assert not self.params.get_bool("IsMetric")
|
assert not self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
def test_put_non_blocking_with_get_block(self):
|
def test_put_non_blocking_with_get_block(self):
|
||||||
q = Params()
|
q = Params()
|
||||||
def _delayed_writer():
|
def _delayed_writer():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
Params().put("CarParams", b"test")
|
Params().put_nonblocking("CarParams", b"test")
|
||||||
threading.Thread(target=_delayed_writer).start()
|
threading.Thread(target=_delayed_writer).start()
|
||||||
assert q.get("CarParams") is None
|
assert q.get("CarParams") is None
|
||||||
assert q.get("CarParams", True) == b"test"
|
assert q.get("CarParams", True) == b"test"
|
||||||
@@ -96,7 +96,7 @@ class TestParams:
|
|||||||
q = Params()
|
q = Params()
|
||||||
def _delayed_writer():
|
def _delayed_writer():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
Params().put_bool("CarParams", True)
|
Params().put_bool_nonblocking("CarParams", True)
|
||||||
threading.Thread(target=_delayed_writer).start()
|
threading.Thread(target=_delayed_writer).start()
|
||||||
assert q.get("CarParams") is None
|
assert q.get("CarParams") is None
|
||||||
assert q.get("CarParams", True) == b"1"
|
assert q.get("CarParams", True) == b"1"
|
||||||
@@ -123,19 +123,19 @@ class TestParams:
|
|||||||
|
|
||||||
def test_params_get_type(self):
|
def test_params_get_type(self):
|
||||||
# json
|
# json
|
||||||
self.params.put("ApiCache_FirehoseStats", {"a": 0}, block=True)
|
self.params.put("ApiCache_FirehoseStats", {"a": 0})
|
||||||
assert self.params.get("ApiCache_FirehoseStats") == {"a": 0}
|
assert self.params.get("ApiCache_FirehoseStats") == {"a": 0}
|
||||||
|
|
||||||
# int
|
# int
|
||||||
self.params.put("BootCount", 1441, block=True)
|
self.params.put("BootCount", 1441)
|
||||||
assert self.params.get("BootCount") == 1441
|
assert self.params.get("BootCount") == 1441
|
||||||
|
|
||||||
# bool
|
# bool
|
||||||
self.params.put("AdbEnabled", True, block=True)
|
self.params.put("AdbEnabled", True)
|
||||||
assert self.params.get("AdbEnabled")
|
assert self.params.get("AdbEnabled")
|
||||||
assert isinstance(self.params.get("AdbEnabled"), bool)
|
assert isinstance(self.params.get("AdbEnabled"), bool)
|
||||||
|
|
||||||
# time
|
# time
|
||||||
now = datetime.datetime.now(datetime.UTC)
|
now = datetime.datetime.now(datetime.UTC)
|
||||||
self.params.put("InstallDate", now, block=True)
|
self.params.put("InstallDate", now)
|
||||||
assert self.params.get("InstallDate") == now
|
assert self.params.get("InstallDate") == now
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
#include "common/swaglog.h"
|
#include "common/swaglog.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "common/version.h"
|
#include "common/version.h"
|
||||||
#include "common/hardware/hw.h"
|
#include "system/hardware/hw.h"
|
||||||
#include "json11/json11.hpp"
|
#include "third_party/json11/json11.hpp"
|
||||||
|
|
||||||
#include "sunnypilot/common/version.h"
|
#include "sunnypilot/common/version.h"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
MIN_DATE = datetime.datetime(year=2025, month=2, day=21)
|
MIN_DATE = datetime.datetime(year=2025, month=2, day=21)
|
||||||
MAX_DATE = datetime.datetime(year=2035, month=1, day=1)
|
|
||||||
|
|
||||||
def min_date():
|
def min_date():
|
||||||
# on systemd systems, the default time is the systemd build time
|
# on systemd systems, the default time is the systemd build time
|
||||||
@@ -13,4 +12,4 @@ def min_date():
|
|||||||
return MIN_DATE
|
return MIN_DATE
|
||||||
|
|
||||||
def system_time_valid():
|
def system_time_valid():
|
||||||
return min_date() < datetime.datetime.now() < MAX_DATE
|
return datetime.datetime.now() > min_date()
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
transformations
|
||||||
|
transformations.cpp
|
||||||
+3
-3
@@ -48,7 +48,7 @@ def sudo_write(val: str, path: str) -> None:
|
|||||||
|
|
||||||
def sudo_read(path: str) -> str:
|
def sudo_read(path: str) -> str:
|
||||||
try:
|
try:
|
||||||
return subprocess.check_output(["sudo", "cat", "--", path], encoding='utf8').strip()
|
return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -131,11 +131,11 @@ def get_upload_stream(filepath: str, should_compress: bool) -> tuple[io.Buffered
|
|||||||
return compressed_stream, compressed_size
|
return compressed_stream, compressed_size
|
||||||
|
|
||||||
|
|
||||||
# remove all keys that end in DEPRECATED, plus any "deprecated" group
|
# remove all keys that end in DEPRECATED
|
||||||
def strip_deprecated_keys(d):
|
def strip_deprecated_keys(d):
|
||||||
for k in list(d.keys()):
|
for k in list(d.keys()):
|
||||||
if isinstance(k, str):
|
if isinstance(k, str):
|
||||||
if k.endswith('DEPRECATED') or k == 'deprecated':
|
if k.endswith('DEPRECATED'):
|
||||||
d.pop(k)
|
d.pop(k)
|
||||||
elif isinstance(d[k], dict):
|
elif isinstance(d[k], dict):
|
||||||
strip_deprecated_keys(d[k])
|
strip_deprecated_keys(d[k])
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
#define COMMA_VERSION "0.11.2"
|
#define COMMA_VERSION "0.10.4"
|
||||||
|
|||||||
+21
-5
@@ -5,15 +5,19 @@ import pytest
|
|||||||
|
|
||||||
from openpilot.common.prefix import OpenpilotPrefix
|
from openpilot.common.prefix import OpenpilotPrefix
|
||||||
from openpilot.system.manager import manager
|
from openpilot.system.manager import manager
|
||||||
from openpilot.common.hardware import TICI, HARDWARE
|
from openpilot.system.hardware import TICI, HARDWARE
|
||||||
|
|
||||||
# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml
|
# TODO: pytest-cpp doesn't support FAIL, and we need to create test translations in sessionstart
|
||||||
|
# pending https://github.com/pytest-dev/pytest-cpp/pull/147
|
||||||
collect_ignore = [
|
collect_ignore = [
|
||||||
|
"selfdrive/ui/tests/test_translations",
|
||||||
"selfdrive/test/process_replay/test_processes.py",
|
"selfdrive/test/process_replay/test_processes.py",
|
||||||
"selfdrive/test/process_replay/test_regen.py",
|
"selfdrive/test/process_replay/test_regen.py",
|
||||||
# tinygrad JIT has process-global state. Other test files import modeld → tinygrad,
|
]
|
||||||
# which corrupts JIT captures for test_warp.py in the same process. Run separately in CI.
|
collect_ignore_glob = [
|
||||||
"sunnypilot/modeld_v2/tests/test_warp.py",
|
"selfdrive/debug/*.py",
|
||||||
|
"selfdrive/modeld/*.py",
|
||||||
|
"sunnypilot/modeld*/*.py",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -94,3 +98,15 @@ def pytest_collection_modifyitems(config, items):
|
|||||||
class_property_name = item.get_closest_marker('xdist_group_class_property').args[0]
|
class_property_name = item.get_closest_marker('xdist_group_class_property').args[0]
|
||||||
class_property_value = getattr(item.cls, class_property_name)
|
class_property_value = getattr(item.cls, class_property_name)
|
||||||
item.add_marker(pytest.mark.xdist_group(class_property_value))
|
item.add_marker(pytest.mark.xdist_group(class_property_value))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(trylast=True)
|
||||||
|
def pytest_configure(config):
|
||||||
|
config_line = "xdist_group_class_property: group tests by a property of the class that contains them"
|
||||||
|
config.addinivalue_line("markers", config_line)
|
||||||
|
|
||||||
|
config_line = "nocapture: don't capture test output"
|
||||||
|
config.addinivalue_line("markers", config_line)
|
||||||
|
|
||||||
|
config_line = "shared_download_cache: share download cache between tests"
|
||||||
|
config.addinivalue_line("markers", config_line)
|
||||||
|
|||||||
+166
-173
@@ -4,25 +4,24 @@
|
|||||||
|
|
||||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||||
|
|
||||||
# 341 Supported Cars
|
# 336 Supported Cars
|
||||||
|
|
||||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
||||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|
||||||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|
||||||
|Acura|MDX 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2022-24">Buy Here</a></sub></details>|||
|
|
||||||
|Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025-26">Buy Here</a></sub></details>|||
|
|Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025-26">Buy Here</a></sub></details>|||
|
||||||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|
||||||
|Acura|RDX 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Acura|TLX 2021-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2021-22">Buy Here</a></sub></details>|||
|
|Acura|TLX 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2021">Buy Here</a></sub></details>|||
|
||||||
|Acura|TLX 2025|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2025">Buy Here</a></sub></details>|||
|
|Acura|TLX 2025|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2025">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>12</sup>](#footnotes)|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>12</sup>](#footnotes)|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>12</sup>](#footnotes)|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q2 2018">Buy Here</a></sub></details>|||
|
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q2 2018">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>12</sup>](#footnotes)|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|
|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>12</sup>](#footnotes)|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi RS3 2018">Buy Here</a></sub></details>|||
|
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi RS3 2018">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>12</sup>](#footnotes)|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|
||||||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Silverado 1500 2020-21">Buy Here</a></sub></details>|||
|
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Silverado 1500 2020-21">Buy Here</a></sub></details>|||
|
||||||
@@ -32,34 +31,34 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2021-23">Buy Here</a></sub></details>|||
|
|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica Hybrid 2017-18">Buy Here</a></sub></details>|||
|
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica Hybrid 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica Hybrid 2019-25">Buy Here</a></sub></details>|||
|
|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica Hybrid 2019-25">Buy Here</a></sub></details>|||
|
||||||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None|<a href="https://youtu.be/VT-i3yRsX2s?t=2736" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None|<a href="https://youtu.be/VT-i3yRsX2s?t=2736" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|CUPRA[<sup>12</sup>](#footnotes)|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|
||||||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Dodge Durango 2020-21">Buy Here</a></sub></details>|||
|
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Dodge Durango 2020-21">Buy Here</a></sub></details>|||
|
||||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|
||||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Hybrid 2020-22">Buy Here</a></sub></details>|||
|
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Expedition 2022-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Expedition 2022-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Explorer 2020-24">Buy Here</a></sub></details>|||
|
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Explorer 2020-24">Buy Here</a></sub></details>|||
|
||||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Focus 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Focus 2018-22">Buy Here</a></sub></details>|||
|
|Ford|Focus 2018[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Focus 2018">Buy Here</a></sub></details>|||
|
||||||
|Ford|Focus Hybrid 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Focus Hybrid 2018-22">Buy Here</a></sub></details>|||
|
|Ford|Focus Hybrid 2018[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Focus Hybrid 2018">Buy Here</a></sub></details>|||
|
||||||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>|||
|
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick 2022">Buy Here</a></sub></details>|||
|
|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick 2022">Buy Here</a></sub></details>|||
|
||||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick 2023-24">Buy Here</a></sub></details>|||
|
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick 2023-24">Buy Here</a></sub></details>|||
|
||||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick Hybrid 2022">Buy Here</a></sub></details>|||
|
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick Hybrid 2022">Buy Here</a></sub></details>|||
|
||||||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick Hybrid 2023-24">Buy Here</a></sub></details>|||
|
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Maverick Hybrid 2023-24">Buy Here</a></sub></details>|||
|
||||||
|Ford|Mustang Mach-E 2021-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Mustang Mach-E 2021-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Mustang Mach-E 2021-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Mustang Mach-E 2021-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Ranger 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ford Ranger 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Genesis|G70 2018|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2018">Buy Here</a></sub></details>|||
|
|Genesis|G70 2018|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2018">Buy Here</a></sub></details>|||
|
||||||
|Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2019-21">Buy Here</a></sub></details>|||
|
|Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2022-23">Buy Here</a></sub></details>|||
|
|Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G70 2022-23">Buy Here</a></sub></details>|||
|
||||||
@@ -74,37 +73,36 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Genesis|GV70 Electrified (Australia Only) 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV70 Electrified (Australia Only) 2022">Buy Here</a></sub></details>|||
|
|Genesis|GV70 Electrified (Australia Only) 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV70 Electrified (Australia Only) 2022">Buy Here</a></sub></details>|||
|
||||||
|Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV70 Electrified (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
|Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV70 Electrified (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
||||||
|Genesis|GV80 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV80 2023">Buy Here</a></sub></details>|||
|
|Genesis|GV80 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV80 2023">Buy Here</a></sub></details>|||
|
||||||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Accord 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|
|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|
|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V Hybrid 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-26">Buy Here</a></sub></details>|||
|
|Honda|CR-V Hybrid 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|e 2020|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|
||||||
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
|
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
|
||||||
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2019-22">Buy Here</a></sub></details>|||
|
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|HR-V 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|
|Honda|HR-V 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Insight 2019-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|
|Honda|Insight 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|Inspire 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|
|Honda|Inspire 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|
||||||
|Honda|N-Box 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|11 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
|
|Honda|N-Box 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|11 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
|
||||||
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|Odyssey (Singapore) 2021|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey (Singapore) 2021">Buy Here</a></sub></details>|||
|
|
||||||
|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey (Taiwan) 2018-19">Buy Here</a></sub></details>|||
|
|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey (Taiwan) 2018-19">Buy Here</a></sub></details>|||
|
||||||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Passport 2026|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2026">Buy Here</a></sub></details>|||
|
|Honda|Passport 2026|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2026">Buy Here</a></sub></details>|||
|
||||||
@@ -117,16 +115,15 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Hyundai|Custin 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Custin 2023">Buy Here</a></sub></details>|||
|
|Hyundai|Custin 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Custin 2023">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2017-18">Buy Here</a></sub></details>|||
|
|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2019">Buy Here</a></sub></details>|||
|
|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2019">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra GT 2017-20">Buy Here</a></sub></details>|||
|
|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra GT 2017-20">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Genesis 2015-16">Buy Here</a></sub></details>|||
|
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Genesis 2015-16">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai i30 2017-19">Buy Here</a></sub></details>|||
|
|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai i30 2017-19">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (Southeast Asia and Europe only) 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (Southeast Asia and Europe only) 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 5 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 5 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 5 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 5 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 6 (with HDA II) 2023-24|Highway Driving Assist II|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 6 (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 6 (with HDA II) 2023-24|Highway Driving Assist II|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 6 (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 6 (without HDA II) 2023-24|Highway Driving Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 6 (without HDA II) 2023-24">Buy Here</a></sub></details>|||
|
|
||||||
|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Electric 2019">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Electric 2019">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq Electric 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Electric 2020">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Electric 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Electric 2020">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Hybrid 2017-19">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Hybrid 2017-19">Buy Here</a></sub></details>|||
|
||||||
@@ -137,17 +134,17 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022-23">Buy Here</a></sub></details>|||
|
|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2018-21">Buy Here</a></sub></details>|||
|
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2018-21">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2022-23">Buy Here</a></sub></details>|||
|
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Hybrid 2020">Buy Here</a></sub></details>|||
|
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Hybrid 2020">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Nexo 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Nexo 2021">Buy Here</a></sub></details>|||
|
|Hyundai|Nexo 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Nexo 2021">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Cruz 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Cruz 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Santa Fe 2019-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe 2019-20">Buy Here</a></sub></details>|<a href="https://youtu.be/bjDR0YjM__s" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Hyundai|Santa Fe 2019-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe 2019-20">Buy Here</a></sub></details>|<a href="https://youtu.be/bjDR0YjM__s" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Santa Fe 2021-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/VnHzSTygTS4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Hyundai|Santa Fe 2021-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/VnHzSTygTS4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe Hybrid 2022-23">Buy Here</a></sub></details>|||
|
|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe Hybrid 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe Plug-in Hybrid 2022-23">Buy Here</a></sub></details>|||
|
|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Fe Plug-in Hybrid 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata 2018-19">Buy Here</a></sub></details>|||
|
|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata 2018-19">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Sonata 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata 2020-23">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=ix63r9kE3Fw" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Hyundai|Sonata 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata 2020-23">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=ix63r9kE3Fw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata Hybrid 2020-23">Buy Here</a></sub></details>|||
|
|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Staria 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Staria 2023">Buy Here</a></sub></details>|||
|
|Hyundai|Staria 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Staria 2023">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson 2021">Buy Here</a></sub></details>|||
|
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson 2021">Buy Here</a></sub></details>|||
|
||||||
@@ -157,8 +154,8 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Hyundai|Tucson Hybrid 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Hybrid 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Tucson Hybrid 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Hybrid 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Plug-in Hybrid 2024">Buy Here</a></sub></details>|||
|
|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Plug-in Hybrid 2024">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Veloster 2019-20">Buy Here</a></sub></details>|||
|
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Veloster 2019-20">Buy Here</a></sub></details>|||
|
||||||
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=jBe4lWnRSu4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=jBe4lWnRSu4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
||||||
@@ -171,16 +168,16 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K5 Hybrid 2020-22">Buy Here</a></sub></details>|||
|
|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K5 Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Kia|K7 2017|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K7 2017">Buy Here</a></sub></details>|||
|
|Kia|K7 2017|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K7 2017">Buy Here</a></sub></details>|||
|
||||||
|Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K8 Hybrid (with HDA II) 2023">Buy Here</a></sub></details>|||
|
|Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K8 Hybrid (with HDA II) 2023">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro EV 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Niro EV 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Niro EV 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2020">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Niro EV 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2020">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Niro EV 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2021">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Niro EV 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2021">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Niro EV 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2022">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Niro EV 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV 2022">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Niro EV (with HDA II) 2024-25|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV (with HDA II) 2024-25">Buy Here</a></sub></details>|||
|
|Kia|Niro EV (with HDA II) 2025|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV (with HDA II) 2025">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro EV (without HDA II) 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV (without HDA II) 2023-25">Buy Here</a></sub></details>|||
|
|Kia|Niro EV (without HDA II) 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro EV (without HDA II) 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2018">Buy Here</a></sub></details>|||
|
|Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2018">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2021">Buy Here</a></sub></details>|||
|
|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2021">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2022">Buy Here</a></sub></details>|||
|
|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2022">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro Hybrid 2023-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2023-24">Buy Here</a></sub></details>|||
|
|Kia|Niro Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Hybrid 2023">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2018-19">Buy Here</a></sub></details>|||
|
|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2018-19">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2020">Buy Here</a></sub></details>|||
|
|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2020">Buy Here</a></sub></details>|||
|
||||||
|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2021">Buy Here</a></sub></details>|||
|
|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Niro Plug-in Hybrid 2021">Buy Here</a></sub></details>|||
|
||||||
@@ -189,21 +186,21 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima 2019-20">Buy Here</a></sub></details>|||
|
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima 2019-20">Buy Here</a></sub></details>|||
|
||||||
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|
||||||
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Seltos 2021">Buy Here</a></sub></details>|||
|
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Seltos 2021">Buy Here</a></sub></details>|||
|
||||||
|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Sorento 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2021-23">Buy Here</a></sub></details>|||
|
|Kia|Sorento 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Kia|Sorento Hybrid 2021-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento Hybrid 2021-23">Buy Here</a></sub></details>|||
|
|Kia|Sorento Hybrid 2021-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento Hybrid 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Kia|Sorento Plug-in Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento Plug-in Hybrid 2022-23">Buy Here</a></sub></details>|||
|
|Kia|Sorento Plug-in Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sorento Plug-in Hybrid 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Kia|Sportage 2023-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sportage 2023-24">Buy Here</a></sub></details>|||
|
|Kia|Sportage 2023-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sportage 2023-24">Buy Here</a></sub></details>|||
|
||||||
|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sportage Hybrid 2023">Buy Here</a></sub></details>|||
|
|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sportage Hybrid 2023">Buy Here</a></sub></details>|||
|
||||||
|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Stinger 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2022-23">Buy Here</a></sub></details>|||
|
|Kia|Stinger 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Telluride 2020-22">Buy Here</a></sub></details>|||
|
|Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Telluride 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus CT Hybrid 2017-18">Buy Here</a></sub></details>|||
|
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus CT Hybrid 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Lexus|ES 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2017-18">Buy Here</a></sub></details>|||
|
|Lexus|ES 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2019-25">Buy Here</a></sub></details>|||
|
|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2019-25">Buy Here</a></sub></details>|||
|
||||||
|Lexus|ES Hybrid 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES Hybrid 2017-18">Buy Here</a></sub></details>|||
|
|Lexus|ES Hybrid 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES Hybrid 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES Hybrid 2019-25">Buy Here</a></sub></details>|<a href="https://youtu.be/BZ29osRVJeg?t=12" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES Hybrid 2019-25">Buy Here</a></sub></details>|<a href="https://youtu.be/BZ29osRVJeg?t=12" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus GS F 2016">Buy Here</a></sub></details>|||
|
|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus GS F 2016">Buy Here</a></sub></details>|||
|
||||||
|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus IS 2017-19">Buy Here</a></sub></details>|||
|
|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus IS 2017-19">Buy Here</a></sub></details>|||
|
||||||
|Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus IS 2022-24">Buy Here</a></sub></details>|||
|
|Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus IS 2022-24">Buy Here</a></sub></details>|||
|
||||||
@@ -224,50 +221,48 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus UX Hybrid 2019-24">Buy Here</a></sub></details>|||
|
|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus UX Hybrid 2019-24">Buy Here</a></sub></details>|||
|
||||||
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|
||||||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||||
|MAN[<sup>12</sup>](#footnotes)|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|MAN[<sup>12</sup>](#footnotes)|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-5 2022-25">Buy Here</a></sub></details>|||
|
|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-5 2022-25">Buy Here</a></sub></details>|||
|
||||||
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Nissan[<sup>6</sup>](#footnotes)|Altima 2019-24|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Altima 2019-24">Buy Here</a></sub></details>|||
|
|Nissan[<sup>5</sup>](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Altima 2019-20, 2024">Buy Here</a></sub></details>|||
|
||||||
|Nissan[<sup>6</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Nissan[<sup>5</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Nissan[<sup>6</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
|Nissan[<sup>5</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Nissan[<sup>6</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
|Nissan[<sup>5</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
||||||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|
||||||
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 2500 2020-24">Buy Here</a></sub></details>|||
|
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 2500 2020-24">Buy Here</a></sub></details>|||
|
||||||
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 3500 2019-22">Buy Here</a></sub></details>|||
|
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 3500 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2025">Buy Here</a></sub></details>|||
|
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
||||||
|Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2025">Buy Here</a></sub></details>|||
|
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|
||||||
|SEAT[<sup>12</sup>](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
|Subaru|Ascent 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|SEAT[<sup>12</sup>](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Subaru|Ascent 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Forester 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Forester 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Legacy 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Legacy 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Outback 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Subaru|Outback 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Škoda|Fabia 2022-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Škoda|Kamiq 2021-23[<sup>11,13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||||
|Škoda|Fabia 2022-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
|Škoda|Karoq 2019-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
|
||||||
|Škoda|Kamiq 2021-23[<sup>13,15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
|Škoda|Kodiaq 2017-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kodiaq 2017-23">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>12</sup>](#footnotes)|Karoq 2019-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
|
|Škoda|Octavia 2015-19[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia 2015-19">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>12</sup>](#footnotes)|Kodiaq 2017-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kodiaq 2017-23">Buy Here</a></sub></details>|||
|
|Škoda|Octavia RS 2016[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia RS 2016">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>12</sup>](#footnotes)|Octavia 2015-19[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia 2015-19">Buy Here</a></sub></details>|||
|
|Škoda|Octavia Scout 2017-19[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>12</sup>](#footnotes)|Octavia RS 2016[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia RS 2016">Buy Here</a></sub></details>|||
|
|Škoda|Scala 2020-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Scala 2020-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||||
|Škoda[<sup>12</sup>](#footnotes)|Octavia Scout 2017-19[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|
|Škoda|Superb 2015-22[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Škoda|Scala 2020-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Scala 2020-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>12</sup>](#footnotes)|Superb 2015-22[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
||||||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
||||||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|
|
||||||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
|
||||||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard 2019-20">Buy Here</a></sub></details>|||
|
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard 2019-20">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Avalon 2016">Buy Here</a></sub></details>|||
|
|Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Avalon 2016">Buy Here</a></sub></details>|||
|
||||||
@@ -280,94 +275,92 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Toyota|C-HR 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota C-HR 2021">Buy Here</a></sub></details>|||
|
|Toyota|C-HR 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota C-HR 2021">Buy Here</a></sub></details>|||
|
||||||
|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota C-HR Hybrid 2017-20">Buy Here</a></sub></details>|||
|
|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota C-HR Hybrid 2017-20">Buy Here</a></sub></details>|||
|
||||||
|Toyota|C-HR Hybrid 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota C-HR Hybrid 2021-22">Buy Here</a></sub></details>|||
|
|Toyota|C-HR Hybrid 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota C-HR Hybrid 2021-22">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>11</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>10</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>11</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry 2021-24">Buy Here</a></sub></details>|||
|
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>10</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry 2021-24">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry Hybrid 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Q2DYY0AWKgk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry Hybrid 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Q2DYY0AWKgk" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry Hybrid 2021-24">Buy Here</a></sub></details>|||
|
|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Camry Hybrid 2021-24">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Corolla 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla 2017-19">Buy Here</a></sub></details>|||
|
|Toyota|Corolla 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla 2017-19">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla 2020-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=_66pXk0CBYA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla 2020-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=_66pXk0CBYA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Cross (Non-US only) 2020-23">Buy Here</a></sub></details>|||
|
|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Cross (Non-US only) 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Cross Hybrid (Non-US only) 2020-22">Buy Here</a></sub></details>|||
|
|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Cross Hybrid (Non-US only) 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Hatchback 2019-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=_66pXk0CBYA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Hatchback 2019-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=_66pXk0CBYA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Hybrid 2020-22">Buy Here</a></sub></details>|||
|
|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Hybrid (South America only) 2020-23">Buy Here</a></sub></details>|||
|
|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Corolla Hybrid (South America only) 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Highlander 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander 2017-19">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=0wS0wXSLzoo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Highlander 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander 2017-19">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=0wS0wXSLzoo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander 2020-23">Buy Here</a></sub></details>|||
|
|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Highlander Hybrid 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander Hybrid 2017-19">Buy Here</a></sub></details>|||
|
|Toyota|Highlander Hybrid 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander Hybrid 2017-19">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander Hybrid 2020-23">Buy Here</a></sub></details>|||
|
|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Highlander Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Mirai 2021">Buy Here</a></sub></details>|||
|
|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Mirai 2021">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Prius 2016|Toyota Safety Sense P|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius 2016">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Prius 2016|Toyota Safety Sense P|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius 2016">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Prius 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius 2017-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Prius 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius 2017-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius 2021-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=J58TvCpUd4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius 2021-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=J58TvCpUd4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Prius Prime 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius Prime 2017-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Prius Prime 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius Prime 2017-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius Prime 2021-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=J58TvCpUd4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius Prime 2021-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=J58TvCpUd4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Prius v 2017|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius v 2017">Buy Here</a></sub></details>|||
|
|Toyota|Prius v 2017|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Prius v 2017">Buy Here</a></sub></details>|||
|
||||||
|Toyota|RAV4 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2016">Buy Here</a></sub></details>|||
|
|Toyota|RAV4 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2016">Buy Here</a></sub></details>|||
|
||||||
|Toyota|RAV4 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2017-18">Buy Here</a></sub></details>|||
|
|Toyota|RAV4 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=wJxjDd42gGA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=wJxjDd42gGA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|RAV4 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2022">Buy Here</a></sub></details>|||
|
|Toyota|RAV4 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2022">Buy Here</a></sub></details>|||
|
||||||
|Toyota|RAV4 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2023-25">Buy Here</a></sub></details>|||
|
|Toyota|RAV4 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2016">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2016">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|RAV4 Hybrid 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|RAV4 Hybrid 2017-18|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2019-21">Buy Here</a></sub></details>|||
|
|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|
|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|
|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|
|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|
|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|
|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|
|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta 2019-23">Buy Here</a></sub></details>|||
|
|Volkswagen|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta 2019-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|
|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen|Passat 2015-22[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Passat 2015-22[<sup>12</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||||
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||||
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|
|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|
|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|
|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>12</sup>](#footnotes)|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
|
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
|
||||||
|
|
||||||
### Footnotes
|
### Footnotes
|
||||||
<sup>1</sup>openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `nightly-dev`. <br />
|
<sup>1</sup>openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `nightly-dev`. <br />
|
||||||
<sup>2</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br />
|
<sup>2</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br />
|
||||||
<sup>3</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/gm" target="_blank">GM</a>. <br />
|
<sup>3</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/gm" target="_blank">GM</a>. <br />
|
||||||
<sup>4</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
|
<sup>4</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
|
||||||
<sup>5</sup>Enabling longitudinal control (alpha) will disable all CMBS functionality, including AEB and FCW. <br />
|
<sup>5</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
|
||||||
<sup>6</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
|
<sup>6</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
|
||||||
<sup>7</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
|
<sup>7</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
|
||||||
<sup>8</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
|
<sup>8</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
|
||||||
<sup>9</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
|
<sup>9</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
|
||||||
<sup>10</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
|
<sup>10</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
||||||
<sup>11</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
<sup>11</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
|
||||||
<sup>12</sup>The J533 harness plugs in at the CAN gateway under the dashboard, just above the steering column. More information can be found at <a href="https://docs.howtocomma.com/docs/j533-harness-install" target="_blank">this guide</a>. <br />
|
<sup>12</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
|
||||||
<sup>13</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
|
<sup>13</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
|
||||||
<sup>14</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
|
<sup>14</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
|
||||||
<sup>15</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
|
<sup>15</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
|
||||||
<sup>16</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
|
|
||||||
<sup>17</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
|
|
||||||
|
|
||||||
## Community Maintained Cars
|
## Community Maintained Cars
|
||||||
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
|
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ All of these are examples of good PRs:
|
|||||||
### First contribution
|
### First contribution
|
||||||
|
|
||||||
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
|
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
|
||||||
There are a lot of bounties that don't require a comma four or a car.
|
There are a lot of bounties that don't require a comma 3X or a car.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# Docs development
|
|
||||||
|
|
||||||
The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai).
|
|
||||||
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
|
|
||||||
|
|
||||||
Those commands must be run in the root directory of openpilot, **not /docs**
|
|
||||||
|
|
||||||
**1. Install the docs dependencies**
|
|
||||||
``` bash
|
|
||||||
uv pip install .[docs]
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Build the new site**
|
|
||||||
``` bash
|
|
||||||
docs build
|
|
||||||
```
|
|
||||||
|
|
||||||
**3. Run the new site locally**
|
|
||||||
``` bash
|
|
||||||
docs serve
|
|
||||||
```
|
|
||||||
|
|
||||||
References:
|
|
||||||
* https://zensical.org/docs/
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# openpilot docs
|
||||||
|
|
||||||
|
This is the source for [docs.comma.ai](https://docs.comma.ai).
|
||||||
|
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
|
||||||
|
|
||||||
|
## Development
|
||||||
|
NOTE: Those commands must be run in the root directory of openpilot, **not /docs**
|
||||||
|
|
||||||
|
**1. Install the docs dependencies**
|
||||||
|
``` bash
|
||||||
|
uv pip install .[docs]
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Build the new site**
|
||||||
|
``` bash
|
||||||
|
mkdocs build
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Run the new site locally**
|
||||||
|
``` bash
|
||||||
|
mkdocs serve
|
||||||
|
```
|
||||||
|
|
||||||
|
References:
|
||||||
|
* https://www.mkdocs.org/getting-started/
|
||||||
|
* https://github.com/ntno/mkdocs-terminal
|
||||||
+2
-2
@@ -4,8 +4,8 @@ openpilot is an Adaptive Cruise Control (ACC) and Automated Lane Centering (ALC)
|
|||||||
Like other ACC and ALC systems, openpilot is a failsafe passive system and it requires the
|
Like other ACC and ALC systems, openpilot is a failsafe passive system and it requires the
|
||||||
driver to be alert and to pay attention at all times.
|
driver to be alert and to pay attention at all times.
|
||||||
|
|
||||||
To assist the driver in maintaining alertness, openpilot includes a driver monitoring feature
|
In order to enforce driver alertness, openpilot includes a driver monitoring feature
|
||||||
that alerts when it detects driver distraction.
|
that alerts the driver when distracted.
|
||||||
|
|
||||||
However, even with an attentive driver, we must make further efforts for the system to be
|
However, even with an attentive driver, we must make further efforts for the system to be
|
||||||
safe. We repeat, **driver alertness is necessary, but not sufficient, for openpilot to be
|
safe. We repeat, **driver alertness is necessary, but not sufficient, for openpilot to be
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../selfdrive/assets/icons_mici/settings/comma_icon.png
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Developing a car brand port
|
||||||
|
|
||||||
|
A brand port is a port of openpilot to a substantially new car brand or platform within a brand.
|
||||||
|
|
||||||
|
Here's an example of one: https://github.com/commaai/openpilot/pull/23331.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# CarState signals
|
||||||
|
|
||||||
|
## Required for basic lateral control
|
||||||
|
|
||||||
|
* `brakePressed`
|
||||||
|
* `cruiseState`
|
||||||
|
* `doorOpen`
|
||||||
|
* `espDisabled`
|
||||||
|
* `gasPressed`
|
||||||
|
* `gearShifter`
|
||||||
|
* `leftBlinker` / `rightBlinker`
|
||||||
|
* `seatbeltUnlatched`
|
||||||
|
* `standstill`
|
||||||
|
* `steeringAngleDeg`
|
||||||
|
* `steeringPressed`
|
||||||
|
* `steeringTorque`
|
||||||
|
* `steerFaultPermanent`
|
||||||
|
* `steerFaultTemporary`
|
||||||
|
* `vCruise`
|
||||||
|
* `wheelSpeeds.[fl|fr|rl|rr]`: Speed of each of the car's four wheels, in m/s. The car's CAN bus often broadcasts the
|
||||||
|
speed in kph, so the helper function `parse_wheel_speeds` performs this conversion by default.
|
||||||
|
|
||||||
|
## Recommended / Required for openpilot longitudinal control
|
||||||
|
|
||||||
|
* `accFaulted`
|
||||||
|
* `espActive`
|
||||||
|
* `parkingBrake`
|
||||||
|
|
||||||
|
## Application Dependent
|
||||||
|
|
||||||
|
* `blockPcmEnable`
|
||||||
|
* `buttonEnable`
|
||||||
|
* `brakeHoldActive`
|
||||||
|
* `carFaultedNonCritical`
|
||||||
|
* `invalidLkasSetting`
|
||||||
|
* `lowSpeedAlert`
|
||||||
|
* `regenBraking`
|
||||||
|
* `steeringAngleOffsetDeg`
|
||||||
|
* `steeringDisengage`
|
||||||
|
* `steeringTorqueEps`
|
||||||
|
* `stockLkas`
|
||||||
|
* `vCruiseCluster`
|
||||||
|
* `vEgoCluster`
|
||||||
|
* `vehicleSensorsInvalid`
|
||||||
|
|
||||||
|
## Automatically populated
|
||||||
|
|
||||||
|
* `buttonEvents`
|
||||||
|
|
||||||
|
These values are populated automatically by `parse_wheel_speeds`:
|
||||||
|
|
||||||
|
* `aEgo`: Acceleration of the ego vehicle, Kalman filtered derivative of `vEgo`.
|
||||||
|
* `vEgo`: Speed of the ego vehicle, Kalman filtered from `vEgoRaw`.
|
||||||
|
* `vEgoRaw`: Speed of the ego vehicle, based on the average of all four wheel speeds, unfiltered.
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
* `brake`
|
||||||
|
* `charging`
|
||||||
|
* `fuelGauge`
|
||||||
|
* `leftBlindspot` / `rightBlindspot`
|
||||||
|
* `steeringRateDeg`
|
||||||
|
* `stockAeb`
|
||||||
|
* `stockFcw`
|
||||||
|
* `yawRate`
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Developing a car model port
|
||||||
|
|
||||||
|
A model port is a port of openpilot to a new car model within an already supported brand. Model ports are easier than brand ports because the car's existing APIs are already known.
|
||||||
|
|
||||||
|
Here's an example of one: https://github.com/commaai/openpilot/pull/30672/.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Stimulus-Response Tests
|
||||||
|
|
||||||
|
These are example test drives that can help identify the CAN bus messaging necessary for ADAS control. Each scripted
|
||||||
|
test should be done in a separate route (ignition cycle). These tests are a guide, not necessarily exhaustive.
|
||||||
|
|
||||||
|
While testing, constant power to the comma device is highly recommended, using [comma power](https://comma.ai/shop/comma-power) if
|
||||||
|
necessary to make sure all test activity is fully captured and for ease of uploading. If constant power isn't
|
||||||
|
available, keep the ignition on for at least one minute after your test to make sure power loss doesn't result
|
||||||
|
in loss of the last minute of testing data.
|
||||||
|
|
||||||
|
## Stationary ignition-only tests, part 1
|
||||||
|
|
||||||
|
1. Ignition on, but don't start engine, remain in Park
|
||||||
|
2. Open and close each door in a defined order: driver, passenger, rear left, rear right
|
||||||
|
3. Re-enter the vehicle, close the driver's door, and fasten the driver's seatbelt
|
||||||
|
4. Slowly press and release the accelerator pedal 3 times
|
||||||
|
5. Slowly press and release the brake pedal 3 times
|
||||||
|
6. Hold the brake and move the gearshift to reverse, then neutral, then drive, then sport/eco/etc if applicable
|
||||||
|
7. Return to Park, ignition off
|
||||||
|
|
||||||
|
Brake-pressed information may show up in several messages and signals, both as on/off states and as a percentage or
|
||||||
|
pressure. It may reflect a switch on the driver's brake pedal, or a pressure-threshold state, or signals to turn on
|
||||||
|
the rear brake lights. Start by identifying all the potential signals, and confirm while driving with ACC later.
|
||||||
|
|
||||||
|
Locate signals for all four door states if possible, but some cars only expose the driver's door state on the ADAS bus.
|
||||||
|
Driver/passenger door signals may or may not change positions for LHD vs RHD cars. For cars where only the driver's
|
||||||
|
door signal is available, the same signal may follow the driver.
|
||||||
|
|
||||||
|
## Stationary ignition-only tests, part 2
|
||||||
|
|
||||||
|
1. Ignition on, but don't start engine, remain in Park
|
||||||
|
2. Press each ACC button in a defined order: main switch on/off, set, resume, cancel, accel, decel, gap adjust
|
||||||
|
3. Set the left turn signal for about five seconds
|
||||||
|
4. Operate the left turn signal one time in its touch-to-pass mode
|
||||||
|
5. Set the right turn signal for about five seconds
|
||||||
|
6. Operate the right turn signal one time in its touch-to-pass mode
|
||||||
|
7. Set the hazard / emergency indicator switch for about five seconds
|
||||||
|
8. Ignition off
|
||||||
|
|
||||||
|
Your vehicle may have a momentary-press main ACC switch or a physical toggle that remains set. Actual ACC engagement
|
||||||
|
isn't necessary for purposes of detecting the ACC button presses.
|
||||||
|
|
||||||
|
## Steering angle and steering torque tests
|
||||||
|
|
||||||
|
Power steering should be available. On ICE cars, engine RPM may be present.
|
||||||
|
|
||||||
|
1. Ignition on, start engine if applicable, remain in Park
|
||||||
|
2. Rotate the steering wheel as follows, with a few seconds pause between each step
|
||||||
|
* Start as close to exact center as possible
|
||||||
|
* Turn to 45 degrees right and hold
|
||||||
|
* Turn to 90 degrees right and hold
|
||||||
|
* Turn to 180 degrees right and hold
|
||||||
|
* Turn to full lock right and hold, with firm pressure against lock
|
||||||
|
* Release the wheel and allow it to bounce back slightly from lock
|
||||||
|
* Turn to 180 degrees left and hold
|
||||||
|
* Return to center and release
|
||||||
|
3. Ignition off
|
||||||
|
|
||||||
|
Performing the full test to the right, followed by an abbreviated test to the left, helps give additional confirmation
|
||||||
|
of signal scale, and sign/direction for both the steering wheel angle and driver input torque signals.
|
||||||
|
|
||||||
|
## Low speed / parking lot driving tests
|
||||||
|
|
||||||
|
Before this test, drive to a place like an empty parking lot where you are free to drive in a series of curves.
|
||||||
|
|
||||||
|
1. Ignition on, start engine if applicable, prepare to drive
|
||||||
|
2. Slowly (10-20mph at most) drive a figure-8 if possible, or at least one sharp left and one sharp right.
|
||||||
|
3. Come to a complete stop
|
||||||
|
4. When and where safe, drive in reverse for a short distance (10-15 feet)
|
||||||
|
5. Park the car in a safe place, ignition off
|
||||||
|
|
||||||
|
## High speed / highway driving tests
|
||||||
|
|
||||||
|
Select a place and time where you can safely set cruise control at normal travel speeds with little interference from
|
||||||
|
traffic ahead, and safely test the response of your factory lane guidance system.
|
||||||
|
|
||||||
|
1. Ignition on, start engine if applicable, prepare to drive
|
||||||
|
2. When safely able, engage adaptive cruise control below 50 mph
|
||||||
|
3. When safely able, use the ACC buttons to accelerate to 50mph, then 55mph, then 60mph
|
||||||
|
4. Disengage adaptive cruise
|
||||||
|
5. When safely able, allow your factory lane guidance to prevent lane departures, 2-3 times on both the left and right
|
||||||
|
|
||||||
|
The series of setpoints can be adjusted to local traffic regulations, and of course metric units. The specific cruise
|
||||||
|
setpoints are useful for locating the ACC HUD signals later, and confirming their precise scaling. When the car reaches
|
||||||
|
and holds the setpoint, that can also provide additional confirmation of wheel speed scaling.
|
||||||
@@ -8,7 +8,7 @@ A car port enables openpilot support on a particular car. Each car model openpil
|
|||||||
|
|
||||||
# Structure of a car port
|
# Structure of a car port
|
||||||
|
|
||||||
All car-specific code is contained in the [opendbc](https://github.com/commaai/opendbc) project.
|
Virtually all car-specific code is contained in two other repositories: [opendbc](https://github.com/commaai/opendbc) and [panda](https://github.com/commaai/panda).
|
||||||
|
|
||||||
## opendbc
|
## opendbc
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ Each car brand is supported by a standard interface structure in `opendbc/car/[b
|
|||||||
|
|
||||||
## safety
|
## safety
|
||||||
|
|
||||||
* `opendbc/safety/modes/[brand].h`: Brand-specific safety logic
|
* `opendbc_repo/opendbc/safety/modes/[brand].h`: Brand-specific safety logic
|
||||||
* `opendbc/safety/tests/test_[brand].py`: Brand-specific safety CI tests
|
* `opendbc_repo/opendbc/safety/tests/test_[brand].py`: Brand-specific safety CI tests
|
||||||
|
|
||||||
## openpilot
|
## openpilot
|
||||||
|
|
||||||
@@ -32,20 +32,8 @@ For historical reasons, openpilot still contains a small amount of car-specific
|
|||||||
|
|
||||||
* `selfdrive/car/car_specific.py`: Brand-specific event logic
|
* `selfdrive/car/car_specific.py`: Brand-specific event logic
|
||||||
|
|
||||||
# How do I port car?
|
# Overview
|
||||||
|
|
||||||
[Jason Young](https://github.com/jyoung8607) gave a talk at COMMA_CON with an overview of the car porting process. The talk is available on YouTube:
|
[Jason Young](https://github.com/jyoung8607) gave a talk at COMMA_CON with an overview of the car porting process. The talk is available on YouTube:
|
||||||
|
|
||||||
https://www.youtube.com/watch?v=XxPS5TpTUnI
|
https://www.youtube.com/watch?v=XxPS5TpTUnI
|
||||||
|
|
||||||
## Brand Port
|
|
||||||
|
|
||||||
A brand port is a port of openpilot to a substantially new car brand or platform within a brand.
|
|
||||||
|
|
||||||
Here's an example of one: https://github.com/commaai/openpilot/pull/23331.
|
|
||||||
|
|
||||||
## Model Port
|
|
||||||
|
|
||||||
A model port is a port of openpilot to a new car model within an already supported brand. Model ports are easier than brand ports because the car's existing APIs are already known.
|
|
||||||
|
|
||||||
Here's an example of one: https://github.com/commaai/openpilot/pull/30672/.
|
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
# openpilot glossary
|
# openpilot glossary
|
||||||
|
|
||||||
{{GLOSSARY_DEFINITIONS}}
|
* **onroad**: openpilot's system state while ignition is on
|
||||||
|
* **offroad**: openpilot's system state while ignition is off
|
||||||
|
* **route**: a route is a recording of an onroad session
|
||||||
|
* **segment**: routes are split into one minute chunks called segments.
|
||||||
|
* **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai).
|
||||||
|
* **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda).
|
||||||
|
* **comma 3X**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop).
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Check out our [Python library](https://github.com/commaai/openpilot/blob/master/
|
|||||||
|
|
||||||
For each segment, openpilot records the following log types:
|
For each segment, openpilot records the following log types:
|
||||||
|
|
||||||
## rlog.zst
|
## rlog.bz2
|
||||||
|
|
||||||
rlogs contain all the messages passed amongst openpilot's processes. See [cereal/services.py](https://github.com/commaai/openpilot/blob/master/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Cap’n Proto](https://capnproto.org/) messages.
|
rlogs contain all the messages passed amongst openpilot's processes. See [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for a list of all the logged services. They're a bzip2 archive of the serialized capnproto messages.
|
||||||
|
|
||||||
## {f,e,d}camera.hevc
|
## {f,e,d}camera.hevc
|
||||||
|
|
||||||
@@ -18,10 +18,12 @@ Each camera stream is H.265 encoded and written to its respective file.
|
|||||||
* `ecamera.hevc` is the wide road camera
|
* `ecamera.hevc` is the wide road camera
|
||||||
* `dcamera.hevc` is the driver camera
|
* `dcamera.hevc` is the driver camera
|
||||||
|
|
||||||
## qlog.zst & qcamera.ts
|
## qlog.bz2 & qcamera.ts
|
||||||
|
|
||||||
qlogs are a decimated subset of the rlogs. Check out [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for the decimation.
|
qlogs are a decimated subset of the rlogs. Check out [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for the decimation.
|
||||||
|
|
||||||
|
|
||||||
qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras.
|
qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras.
|
||||||
|
|
||||||
qlogs and qcameras are designed to be small enough to upload instantly on slow internet, yet useful enough for most analysis and debugging.
|
|
||||||
|
qlogs and qcameras are designed to be small enough to upload instantly on slow internet and store forever, yet useful enough for most analysis and debugging.
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
# How to Give Feedback
|
|
||||||
|
|
||||||
Feedback is one of the highest leverage ways to contribute to openpilot as a user.
|
|
||||||
|
|
||||||
## Driving
|
|
||||||
|
|
||||||
Got feedback about how your car drives?
|
|
||||||
Join the community Discord, then use the form in `#submit-feedback`.
|
|
||||||
|
|
||||||
Before posting feedback, please ensure:
|
|
||||||
|
|
||||||
- **openpilot is up to date** you should be on the latest openpilot release or nightly
|
|
||||||
- **both road-facing cameras have a clear view** your windshield is clean, lenses are clean, etc.
|
|
||||||
- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield
|
|
||||||
|
|
||||||
## Driver Monitoring
|
|
||||||
|
|
||||||
If you find DM annoying while being perfectly attentive, these are likely false positives and we want to fix them!
|
|
||||||
In general, driver monitoring feedback is very actionable, and we can fix your complaint within a release cycle.
|
|
||||||
|
|
||||||
To post your feedback:
|
|
||||||
|
|
||||||
1. Join the [community Discord](https://discord.comma.ai).
|
|
||||||
2. If driver camera recording is toggled off, temporarily enable driver camera recording in the settings until you reproduce the issue.
|
|
||||||
3. Using comma connect, identify the relevant segment and upload the segment's logs and driver camera.
|
|
||||||
4. Post the segment in the `#openpilot-experience` channel on Discord with a good description.
|
|
||||||
|
|
||||||
Before posting feedback, please ensure:
|
|
||||||
|
|
||||||
- **openpilot is up to date** you should be on the latest openpilot release or nightly
|
|
||||||
- **the driver camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc.
|
|
||||||
- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield
|
|
||||||
|
|
||||||
## Other bugs
|
|
||||||
|
|
||||||
Got an issue with something else? Open an issue on our [GitHub issue tracker](https://github.com/commaai/openpilot/issues/new/choose).
|
|
||||||
@@ -7,11 +7,25 @@ This is the roadmap for the next major openpilot releases. Also check out
|
|||||||
* [Bounties](https://comma.ai/bounties) for paid individual issues
|
* [Bounties](https://comma.ai/bounties) for paid individual issues
|
||||||
* [#current-projects](https://discord.com/channels/469524606043160576/1249579909739708446) in Discord for discussion on work-in-progress projects
|
* [#current-projects](https://discord.com/channels/469524606043160576/1249579909739708446) in Discord for discussion on work-in-progress projects
|
||||||
|
|
||||||
|
## openpilot 0.10
|
||||||
|
|
||||||
|
openpilot 0.10 will be the first release with a driving policy trained in
|
||||||
|
a [learned simulator](https://youtu.be/EqQNZXqzFSI).
|
||||||
|
|
||||||
|
* Driving model trained in a learned simulator
|
||||||
|
* Always-on driver monitoring (behind a toggle)
|
||||||
|
* GPS removed from the driving stack
|
||||||
|
* 100KB qlogs
|
||||||
|
* `nightly` pushed after 1000 hours of hardware-in-the-loop testing
|
||||||
|
* Car interface code moved into [opendbc](https://github.com/commaai/opendbc)
|
||||||
|
* openpilot on PC for Linux x86, Linux arm64, and Mac (Apple Silicon)
|
||||||
|
|
||||||
## openpilot 1.0
|
## openpilot 1.0
|
||||||
|
|
||||||
openpilot 1.0 will feature a fully end-to-end driving policy.
|
openpilot 1.0 will feature a fully end-to-end driving policy.
|
||||||
|
|
||||||
* End-to-end longitudinal control in Chill mode
|
* End-to-end longitudinal control in Chill mode
|
||||||
|
* Automatic Emergency Braking (AEB)
|
||||||
* Driver monitoring with sleep detection
|
* Driver monitoring with sleep detection
|
||||||
* Rolling updates/releases pushed out by CI
|
* Rolling updates/releases pushed out by CI
|
||||||
* [panda safety 1.0](https://github.com/orgs/commaai/projects/27)
|
* [panda safety 1.0](https://github.com/orgs/commaai/projects/27)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
[data-tooltip] {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
border-bottom: 1px dotted black;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip] .tooltip-content {
|
||||||
|
width: max-content;
|
||||||
|
max-width: 25em;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background-color: white;
|
||||||
|
color: #404040;
|
||||||
|
box-shadow: 0 4px 14px 0 rgba(0,0,0,.2), 0 0 0 1px rgba(0,0,0,.05);
|
||||||
|
padding: 10px;
|
||||||
|
font: 14px/1.5 Lato, proxima-nova, Helvetica Neue, Arial, sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.1s, visibility 0s;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none; /* Prevent accidental interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip]:hover .tooltip-content {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: auto; /* Allow interaction when visible */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-content .tooltip-glossary-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-content .tooltip-glossary-link:hover {
|
||||||
|
color: #0056b3;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
import posixpath
|
|
||||||
import re
|
|
||||||
import tomllib
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from markdown.extensions import Extension
|
|
||||||
from markdown.preprocessors import Preprocessor
|
|
||||||
from markdown.treeprocessors import Treeprocessor
|
|
||||||
|
|
||||||
from zensical.extensions.links import LinksTreeprocessor
|
|
||||||
|
|
||||||
GlossaryTerm = tuple[str, re.Pattern[str], str]
|
|
||||||
|
|
||||||
GLOSSARY_FILE = Path(__file__).with_name("glossary.toml")
|
|
||||||
GLOSSARY_PAGE = "concepts/glossary.md"
|
|
||||||
GLOSSARY_PLACEHOLDER = "{{GLOSSARY_DEFINITIONS}}"
|
|
||||||
|
|
||||||
SKIP_TAGS = {
|
|
||||||
"a",
|
|
||||||
"code",
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
"h6",
|
|
||||||
"kbd",
|
|
||||||
"pre",
|
|
||||||
"script",
|
|
||||||
"style",
|
|
||||||
}
|
|
||||||
|
|
||||||
def clean_tooltip(description: str) -> str:
|
|
||||||
text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
|
|
||||||
text = re.sub(r"`([^`]+)`", r"\1", text)
|
|
||||||
text = re.sub(r"[*_~]", "", text)
|
|
||||||
return re.sub(r"\s+", " ", text).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def load_glossary() -> tuple[list[GlossaryTerm], str]:
|
|
||||||
with GLOSSARY_FILE.open("rb") as f:
|
|
||||||
glossary_data = tomllib.load(f).get("glossary", {})
|
|
||||||
|
|
||||||
glossary: list[GlossaryTerm] = []
|
|
||||||
rendered = []
|
|
||||||
for key, value in glossary_data.items():
|
|
||||||
label = str(key).strip().replace("_", " ")
|
|
||||||
description = str(value).strip()
|
|
||||||
if not description:
|
|
||||||
continue
|
|
||||||
|
|
||||||
slug = label.replace(" ", "-").replace("_", "-").lower()
|
|
||||||
glossary.append((slug, re.compile(rf"(?<!\w){re.escape(label)}(?!\w)", re.IGNORECASE), clean_tooltip(description)))
|
|
||||||
rendered.append(f'* <span id="{slug}"></span>**{label}**: {description}')
|
|
||||||
|
|
||||||
return glossary, "\n".join(rendered)
|
|
||||||
|
|
||||||
|
|
||||||
class GlossaryPreprocessor(Preprocessor):
|
|
||||||
def __init__(self, md, glossary: str):
|
|
||||||
super().__init__(md)
|
|
||||||
self.glossary = glossary
|
|
||||||
|
|
||||||
def run(self, lines: list[str]) -> list[str]:
|
|
||||||
markdown = "\n".join(lines)
|
|
||||||
if GLOSSARY_PLACEHOLDER not in markdown:
|
|
||||||
return lines
|
|
||||||
return markdown.replace(GLOSSARY_PLACEHOLDER, self.glossary).splitlines()
|
|
||||||
|
|
||||||
|
|
||||||
class GlossaryTreeprocessor(Treeprocessor):
|
|
||||||
def __init__(self, md, glossary: list[GlossaryTerm]):
|
|
||||||
super().__init__(md)
|
|
||||||
self.glossary = glossary
|
|
||||||
self.seen: set[str] = set()
|
|
||||||
|
|
||||||
def run(self, root: ET.Element) -> None:
|
|
||||||
at = self.md.treeprocessors.get_index_for_name("zrelpath")
|
|
||||||
processor = self.md.treeprocessors[at]
|
|
||||||
if not isinstance(processor, LinksTreeprocessor):
|
|
||||||
raise TypeError("Links processor not registered")
|
|
||||||
if processor.path == GLOSSARY_PAGE:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.seen.clear()
|
|
||||||
glossary_href = f"{posixpath.relpath(GLOSSARY_PAGE, posixpath.dirname(processor.path) or '.')}#"
|
|
||||||
self._walk(root, glossary_href)
|
|
||||||
|
|
||||||
def _walk(self, element: ET.Element, glossary_href: str) -> None:
|
|
||||||
if element.tag in SKIP_TAGS or element.attrib.get("data-glossary-skip") is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._replace(element, glossary_href)
|
|
||||||
|
|
||||||
idx = 0
|
|
||||||
while idx < len(element):
|
|
||||||
child = element[idx]
|
|
||||||
self._walk(child, glossary_href)
|
|
||||||
idx = self._replace(element, glossary_href, idx) + 1
|
|
||||||
|
|
||||||
def _replace(self, parent: ET.Element, glossary_href: str, index: int | None = None) -> int:
|
|
||||||
child = None if index is None else parent[index]
|
|
||||||
text = parent.text if child is None else child.tail
|
|
||||||
pieces = self._pieces(text or "", glossary_href)
|
|
||||||
if not pieces:
|
|
||||||
return -1 if index is None else index
|
|
||||||
|
|
||||||
if child is None:
|
|
||||||
parent.text = pieces[0] if isinstance(pieces[0], str) else ""
|
|
||||||
# Insert replacements for parent.text before the first existing child.
|
|
||||||
insert_at = -1
|
|
||||||
else:
|
|
||||||
assert index is not None
|
|
||||||
child.tail = pieces[0] if isinstance(pieces[0], str) else ""
|
|
||||||
insert_at = index
|
|
||||||
|
|
||||||
start = 1 if isinstance(pieces[0], str) else 0
|
|
||||||
previous = child
|
|
||||||
|
|
||||||
for piece in pieces[start:]:
|
|
||||||
if isinstance(piece, str):
|
|
||||||
previous.tail = (previous.tail or "") + piece
|
|
||||||
continue
|
|
||||||
|
|
||||||
insert_at += 1
|
|
||||||
parent.insert(insert_at, piece)
|
|
||||||
previous = piece
|
|
||||||
|
|
||||||
return insert_at
|
|
||||||
|
|
||||||
def _pieces(self, text: str, glossary_href: str) -> list[str | ET.Element]:
|
|
||||||
if not text.strip():
|
|
||||||
return []
|
|
||||||
|
|
||||||
pieces: list[str | ET.Element] = []
|
|
||||||
cursor = 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
best = None
|
|
||||||
for slug, pattern, tooltip in self.glossary:
|
|
||||||
if slug in self.seen:
|
|
||||||
continue
|
|
||||||
|
|
||||||
found = pattern.search(text, cursor)
|
|
||||||
if found is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
candidate = (slug, tooltip, found.start(), found.end())
|
|
||||||
if best is None:
|
|
||||||
best = candidate
|
|
||||||
continue
|
|
||||||
|
|
||||||
_, _, best_start, best_end = best
|
|
||||||
_, _, current_start, current_end = candidate
|
|
||||||
if current_start < best_start:
|
|
||||||
best = candidate
|
|
||||||
continue
|
|
||||||
|
|
||||||
if current_start == best_start and current_end - current_start > best_end - best_start:
|
|
||||||
best = candidate
|
|
||||||
|
|
||||||
if best is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
slug, tooltip, start, end = best
|
|
||||||
if start > cursor:
|
|
||||||
pieces.append(text[cursor:start])
|
|
||||||
|
|
||||||
link = ET.Element(
|
|
||||||
"a",
|
|
||||||
{
|
|
||||||
"class": "glossary-term",
|
|
||||||
"data-glossary-term": "",
|
|
||||||
"href": f"{glossary_href}{slug}",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
ET.SubElement(link, "span", {"class": "glossary-term__label"}).text = text[start:end]
|
|
||||||
ET.SubElement(
|
|
||||||
link,
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
"class": "glossary-term__tooltip",
|
|
||||||
"data-search-exclude": "",
|
|
||||||
},
|
|
||||||
).text = tooltip
|
|
||||||
pieces.append(link)
|
|
||||||
self.seen.add(slug)
|
|
||||||
cursor = end
|
|
||||||
|
|
||||||
if not pieces:
|
|
||||||
return []
|
|
||||||
if cursor < len(text):
|
|
||||||
pieces.append(text[cursor:])
|
|
||||||
return pieces
|
|
||||||
|
|
||||||
|
|
||||||
class GlossaryExtension(Extension):
|
|
||||||
def extendMarkdown(self, md) -> None:
|
|
||||||
md.registerExtension(self)
|
|
||||||
glossary, rendered = load_glossary()
|
|
||||||
|
|
||||||
md.preprocessors.register(
|
|
||||||
GlossaryPreprocessor(md, rendered),
|
|
||||||
"docs-ext-glossary-preprocessor",
|
|
||||||
27,
|
|
||||||
)
|
|
||||||
md.treeprocessors.register(
|
|
||||||
GlossaryTreeprocessor(md, glossary),
|
|
||||||
"docs-ext-glossary-treeprocessor",
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def makeExtension(**kwargs) -> GlossaryExtension:
|
|
||||||
return GlossaryExtension(**kwargs)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[glossary]
|
|
||||||
onroad = "openpilot's system state while ignition is on."
|
|
||||||
offroad = "openpilot's system state while ignition is off."
|
|
||||||
route = "A route is a recording of an onroad session."
|
|
||||||
segment = "Routes are split into one minute chunks called segments."
|
|
||||||
"comma connect" = "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai)."
|
|
||||||
panda = "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda)."
|
|
||||||
"comma four" = "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four)."
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# What is openpilot?
|
||||||
|
|
||||||
|
[openpilot](http://github.com/commaai/openpilot) is an open source driver assistance system. Currently, openpilot performs the functions of Adaptive Cruise Control (ACC), Automated Lane Centering (ALC), Forward Collision Warning (FCW), and Lane Departure Warning (LDW) for a growing variety of [supported car makes, models, and model years](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). In addition, while openpilot is engaged, a camera-based Driver Monitoring (DM) feature alerts distracted and asleep drivers. See more about [the vehicle integration](https://github.com/commaai/openpilot/blob/master/docs/INTEGRATION.md) and [limitations](https://github.com/commaai/openpilot/blob/master/docs/LIMITATIONS.md).
|
||||||
|
|
||||||
|
|
||||||
|
## How do I use it?
|
||||||
|
|
||||||
|
openpilot is designed to be used on the comma 3X.
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
In short, openpilot uses the car's existing APIs for the built-in [ADAS](https://en.wikipedia.org/wiki/Advanced_driver-assistance_system) system and simply provides better acceleration, braking, and steering inputs than the stock system.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import re
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
def load_glossary(file_path="docs/glossary.toml"):
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
glossary_data = tomllib.load(f)
|
||||||
|
return glossary_data.get("glossary", {})
|
||||||
|
|
||||||
|
def generate_anchor_id(name):
|
||||||
|
return name.replace(" ", "-").replace("_", "-").lower()
|
||||||
|
|
||||||
|
def format_markdown_term(name, definition):
|
||||||
|
anchor_id = generate_anchor_id(name)
|
||||||
|
markdown = f"* [**{name.replace('_', ' ').title()}**](#{anchor_id})"
|
||||||
|
if definition.get("abbreviation"):
|
||||||
|
markdown += f" *({definition['abbreviation']})*"
|
||||||
|
if definition.get("description"):
|
||||||
|
markdown += f": {definition['description']}\n"
|
||||||
|
return markdown
|
||||||
|
|
||||||
|
def glossary_markdown(vocabulary):
|
||||||
|
markdown = ""
|
||||||
|
for category, terms in vocabulary.items():
|
||||||
|
markdown += f"## {category.replace('_', ' ').title()}\n\n"
|
||||||
|
for name, definition in terms.items():
|
||||||
|
markdown += format_markdown_term(name, definition)
|
||||||
|
return markdown
|
||||||
|
|
||||||
|
def format_tooltip_html(term_key, definition, html):
|
||||||
|
display_term = term_key.replace("_", " ").title()
|
||||||
|
clean_description = re.sub(r"\[(.+)]\(.+\)", r"\1", definition["description"])
|
||||||
|
glossary_link = (
|
||||||
|
f"<a href='/concepts/glossary#{term_key}' class='tooltip-glossary-link' title='View in glossary'>Glossary🔗</a>"
|
||||||
|
)
|
||||||
|
return re.sub(
|
||||||
|
re.escape(display_term),
|
||||||
|
lambda
|
||||||
|
match: f"<span data-tooltip>{match.group(0)}<span class='tooltip-content'>{clean_description} {glossary_link}</span></span>",
|
||||||
|
html,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply_tooltip(_term_key, _definition, pattern, html):
|
||||||
|
return re.sub(
|
||||||
|
pattern,
|
||||||
|
lambda match: format_tooltip_html(_term_key, _definition, match.group(0)),
|
||||||
|
html,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
def tooltip_html(vocabulary, html):
|
||||||
|
for _category, terms in vocabulary.items():
|
||||||
|
for term_key, definition in terms.items():
|
||||||
|
if definition.get("description"):
|
||||||
|
pattern = rf"(?<!\w){re.escape(term_key.replace('_', ' ').title())}(?![^<]*<\/a>)(?!\([^)]*\))"
|
||||||
|
html = apply_tooltip(term_key, definition, pattern, html)
|
||||||
|
return html
|
||||||
|
|
||||||
|
# Page Hooks
|
||||||
|
def on_page_markdown(markdown, **kwargs):
|
||||||
|
glossary = load_glossary()
|
||||||
|
return markdown.replace("{{GLOSSARY_DEFINITIONS}}", glossary_markdown(glossary))
|
||||||
|
|
||||||
|
def on_page_content(html, **kwargs):
|
||||||
|
if kwargs.get("page").title == "Glossary":
|
||||||
|
return html
|
||||||
|
glossary = load_glossary()
|
||||||
|
return tooltip_html(glossary, html)
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
# connect to a comma 3X or comma four
|
# connect to a comma 3X
|
||||||
|
|
||||||
A comma device is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
|
A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
|
||||||
|
|
||||||
## Serial Console
|
## Serial Console
|
||||||
|
|
||||||
On the comma 3X, the serial console is accessible from the main OBD-C port, forwarded through the panda.
|
On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
|
||||||
Access it using `panda/scripts/som_debug.sh`.
|
Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
|
||||||
|
|
||||||
comma four also exposes a serial console, albeit through an internal debug connector. Dedicated debug hardware coming soon to the comma shop.
|
On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect.
|
||||||
|
|
||||||
Login to the default user with:
|
On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script.
|
||||||
|
|
||||||
* Username: `comma`
|
* Username: `comma`
|
||||||
* Password: `comma`
|
* Password: `comma`
|
||||||
@@ -25,7 +25,7 @@ In order to SSH into your device, you'll need a GitHub account with SSH keys. Se
|
|||||||
* Port: `22`
|
* Port: `22`
|
||||||
|
|
||||||
Here's an example command for connecting to your device using its tethered connection:<br />
|
Here's an example command for connecting to your device using its tethered connection:<br />
|
||||||
`ssh comma@192.168.43.1 -i ~/.ssh/my_github_key`
|
`ssh comma@192.168.43.1`
|
||||||
|
|
||||||
For doing development work on device, it's recommended to use [SSH agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding).
|
For doing development work on device, it's recommended to use [SSH agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding).
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u
|
|||||||
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
|
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The default port for ADB is 5555.
|
> The default port for ADB is 5555 on the comma 3X.
|
||||||
|
|
||||||
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).
|
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ The public keys are only fetched from your GitHub account once. In order to upda
|
|||||||
|
|
||||||
The `id_rsa` key in this directory only works while your device is in the setup state with no software installed. After installation, that default key will be removed.
|
The `id_rsa` key in this directory only works while your device is in the setup state with no software installed. After installation, that default key will be removed.
|
||||||
|
|
||||||
## ssh.comma.ai proxy
|
#### ssh.comma.ai proxy
|
||||||
|
|
||||||
With a [comma prime subscription](https://comma.ai/connect), you can SSH into your comma device from anywhere.
|
With a [comma prime subscription](https://comma.ai/connect), you can SSH into your comma device from anywhere.
|
||||||
|
|
||||||
@@ -79,7 +79,6 @@ Host ssh.comma.ai
|
|||||||
```
|
```
|
||||||
ssh -i ~/.ssh/my_github_key -o ProxyCommand="ssh -i ~/.ssh/my_github_key -W %h:%p -p %p %h@ssh.comma.ai" comma@ffffffffffffffff
|
ssh -i ~/.ssh/my_github_key -o ProxyCommand="ssh -i ~/.ssh/my_github_key -W %h:%p -p %p %h@ssh.comma.ai" comma@ffffffffffffffff
|
||||||
```
|
```
|
||||||
|
|
||||||
(Replace `ffffffffffffffff` with your dongle_id)
|
(Replace `ffffffffffffffff` with your dongle_id)
|
||||||
|
|
||||||
### ssh.comma.ai host key fingerprint
|
### ssh.comma.ai host key fingerprint
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging.
|
|||||||
Just run `tools/replay/replay --demo`.
|
Just run `tools/replay/replay --demo`.
|
||||||
|
|
||||||
## Replaying CAN data
|
## Replaying CAN data
|
||||||
*Hardware required: jungle and comma four*
|
*Hardware required: jungle and comma 3X*
|
||||||
|
|
||||||
1. Connect your PC to a jungle.
|
1. Connect your PC to a jungle.
|
||||||
2.
|
2.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
In 30 minutes, we'll get an openpilot development environment set up on your computer and make some changes to openpilot's UI.
|
In 30 minutes, we'll get an openpilot development environment set up on your computer and make some changes to openpilot's UI.
|
||||||
|
|
||||||
And if you have a comma four, we'll deploy the change to your device for testing.
|
And if you have a comma 3X, we'll deploy the change to your device for testing.
|
||||||
|
|
||||||
## 1. Set up your development environment
|
## 1. Set up your development environment
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ source .venv/bin/activate
|
|||||||
|
|
||||||
Then, compile openpilot:
|
Then, compile openpilot:
|
||||||
```bash
|
```bash
|
||||||
scons
|
scons -j$(nproc)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Run replay
|
## 2. Run replay
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# What is openpilot?
|
|
||||||
|
|
||||||
[openpilot](http://github.com/commaai/openpilot) is an open source driver assistance system. Currently, openpilot performs the functions of Adaptive Cruise Control (ACC), Automated Lane Centering (ALC), Forward Collision Warning (FCW), and Lane Departure Warning (LDW) for a growing variety of [supported car makes, models, and model years](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). In addition, while openpilot is engaged, a camera-based Driver Monitoring (DM) feature alerts distracted and asleep drivers. See more about [the vehicle integration](https://github.com/commaai/openpilot/blob/master/docs/INTEGRATION.md) and [limitations](https://github.com/commaai/openpilot/blob/master/docs/LIMITATIONS.md).
|
|
||||||
|
|
||||||
|
|
||||||
## How do I use it?
|
|
||||||
|
|
||||||
openpilot is designed to be used on the comma four.
|
|
||||||
|
|
||||||
## How does it work?
|
|
||||||
|
|
||||||
In short, openpilot uses the car's existing APIs for the built-in [ADAS](https://en.wikipedia.org/wiki/Advanced_driver-assistance_system) system and simply provides better acceleration, braking, and steering inputs than the stock system.
|
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
getting-started/what-is-openpilot.md
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
.md-logo img {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term {
|
|
||||||
position: relative;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term__label {
|
|
||||||
border-bottom: 1px dotted currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term__tooltip {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 0.4rem);
|
|
||||||
left: 50%;
|
|
||||||
width: max-content;
|
|
||||||
max-width: min(30rem, 80vw);
|
|
||||||
padding: 0.65rem 0.8rem;
|
|
||||||
border-radius: 0.6rem;
|
|
||||||
background: rgb(26 26 26 / 96%);
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
line-height: 1.45;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translateX(-50%) translateY(-0.15rem);
|
|
||||||
transition: opacity 120ms ease, transform 120ms ease;
|
|
||||||
visibility: hidden;
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term:hover .glossary-term__tooltip,
|
|
||||||
.glossary-term:focus-visible .glossary-term__tooltip,
|
|
||||||
.glossary-term:focus-within .glossary-term__tooltip {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(-50%) translateY(0);
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
+2
-3
@@ -7,7 +7,6 @@ source "$DIR/launch_env.sh"
|
|||||||
function agnos_init {
|
function agnos_init {
|
||||||
# TODO: move this to agnos
|
# TODO: move this to agnos
|
||||||
sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta
|
sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta
|
||||||
rm -f /data/scons_cache/config.lock
|
|
||||||
|
|
||||||
# set success flag for current boot slot
|
# set success flag for current boot slot
|
||||||
sudo abctl --set_success
|
sudo abctl --set_success
|
||||||
@@ -19,12 +18,12 @@ function agnos_init {
|
|||||||
|
|
||||||
# Check if AGNOS update is required
|
# Check if AGNOS update is required
|
||||||
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
||||||
AGNOS_PY="$DIR/common/hardware/tici/agnos.py"
|
AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
|
||||||
MANIFEST="$DIR/system/hardware/tici/agnos.json"
|
MANIFEST="$DIR/system/hardware/tici/agnos.json"
|
||||||
if $AGNOS_PY --verify $MANIFEST; then
|
if $AGNOS_PY --verify $MANIFEST; then
|
||||||
sudo reboot
|
sudo reboot
|
||||||
fi
|
fi
|
||||||
$DIR/common/hardware/tici/updater $AGNOS_PY $MANIFEST
|
$DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
|
|||||||
export QCOM_PRIORITY=12
|
export QCOM_PRIORITY=12
|
||||||
|
|
||||||
if [ -z "$AGNOS_VERSION" ]; then
|
if [ -z "$AGNOS_VERSION" ]; then
|
||||||
export AGNOS_VERSION="18.4"
|
export AGNOS_VERSION="16"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export STAGING_ROOT="/data/safe_staging"
|
export STAGING_ROOT="/data/safe_staging"
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
site_name: openpilot docs
|
||||||
|
repo_url: https://github.com/commaai/openpilot/
|
||||||
|
site_url: https://docs.comma.ai
|
||||||
|
|
||||||
|
exclude_docs: README.md
|
||||||
|
|
||||||
|
strict: true
|
||||||
|
docs_dir: docs
|
||||||
|
site_dir: docs_site/
|
||||||
|
|
||||||
|
hooks:
|
||||||
|
- docs/hooks/glossary.py
|
||||||
|
extra_css:
|
||||||
|
- css/tooltip.css
|
||||||
|
theme:
|
||||||
|
name: readthedocs
|
||||||
|
navigation_depth: 3
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- Getting Started:
|
||||||
|
- What is openpilot?: getting-started/what-is-openpilot.md
|
||||||
|
- How-to:
|
||||||
|
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
||||||
|
- Connect to a comma 3X: how-to/connect-to-comma.md
|
||||||
|
# - Make your first pull request: how-to/make-first-pr.md
|
||||||
|
#- Replay a drive: how-to/replay-a-drive.md
|
||||||
|
- Concepts:
|
||||||
|
- Logs: concepts/logs.md
|
||||||
|
- Safety: concepts/safety.md
|
||||||
|
- Glossary: concepts/glossary.md
|
||||||
|
- Car Porting:
|
||||||
|
- What is a car port?: car-porting/what-is-a-car-port.md
|
||||||
|
- Porting a car brand: car-porting/brand-port.md
|
||||||
|
- Porting a car model: car-porting/model-port.md
|
||||||
|
- Contributing:
|
||||||
|
- Roadmap: contributing/roadmap.md
|
||||||
|
#- Architecture: contributing/architecture.md
|
||||||
|
- Contributing Guide →: https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md
|
||||||
|
- Links:
|
||||||
|
- Blog →: https://blog.comma.ai
|
||||||
|
- Bounties →: https://comma.ai/bounties
|
||||||
|
- GitHub →: https://github.com/commaai
|
||||||
|
- Discord →: https://discord.comma.ai
|
||||||
|
- X →: https://x.com/comma_ai
|
||||||
+1
-1
Submodule msgq_repo updated: 3e3611ee2d...ed2777747d
+1
-1
Submodule opendbc_repo updated: 94858c07a9...9918ec656f
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../third_party
|
||||||
+1
-1
Submodule panda updated: d994e8e800...f5f296c65c
+52
-44
@@ -20,29 +20,24 @@ dependencies = [
|
|||||||
# core
|
# core
|
||||||
"cffi",
|
"cffi",
|
||||||
"scons",
|
"scons",
|
||||||
"pycapnp==2.1.0", # 2.2 introduces a memory leak due to cyclic references
|
"pycapnp",
|
||||||
"Cython",
|
"Cython",
|
||||||
"setuptools",
|
"setuptools",
|
||||||
"numpy >=2.0",
|
"numpy >=2.0",
|
||||||
|
|
||||||
# vendored native dependencies
|
# vendored native dependencies
|
||||||
"bzip2 @ git+https://github.com/commaai/dependencies.git@release-bzip2#subdirectory=bzip2",
|
"bzip2 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=bzip2",
|
||||||
"bootstrap-icons @ git+https://github.com/commaai/dependencies.git@release-bootstrap-icons#subdirectory=bootstrap-icons",
|
"capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto",
|
||||||
"capnproto @ git+https://github.com/commaai/dependencies.git@release-capnproto#subdirectory=capnproto",
|
"eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen",
|
||||||
"catch2 @ git+https://github.com/commaai/dependencies.git@release-catch2#subdirectory=catch2",
|
"ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg",
|
||||||
"acados @ git+https://github.com/commaai/dependencies.git@release-acados#subdirectory=acados",
|
"libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg",
|
||||||
"eigen @ git+https://github.com/commaai/dependencies.git@release-eigen#subdirectory=eigen",
|
"libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv",
|
||||||
"ffmpeg @ git+https://github.com/commaai/dependencies.git@release-ffmpeg#subdirectory=ffmpeg",
|
"openssl3 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=openssl3",
|
||||||
"libjpeg @ git+https://github.com/commaai/dependencies.git@release-libjpeg#subdirectory=libjpeg",
|
"python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev",
|
||||||
"libyuv @ git+https://github.com/commaai/dependencies.git@release-libyuv#subdirectory=libyuv",
|
"zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd",
|
||||||
"zstd @ git+https://github.com/commaai/dependencies.git@release-zstd#subdirectory=zstd",
|
"ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses",
|
||||||
"ncurses @ git+https://github.com/commaai/dependencies.git@release-ncurses#subdirectory=ncurses",
|
"zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq",
|
||||||
"zeromq @ git+https://github.com/commaai/dependencies.git@release-zeromq#subdirectory=zeromq",
|
"git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs",
|
||||||
"libusb @ git+https://github.com/commaai/dependencies.git@release-libusb#subdirectory=libusb",
|
|
||||||
"json11 @ git+https://github.com/commaai/dependencies.git@release-json11#subdirectory=json11",
|
|
||||||
"git-lfs @ git+https://github.com/commaai/dependencies.git@release-git-lfs#subdirectory=git-lfs",
|
|
||||||
"gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@release-gcc-arm-none-eabi#subdirectory=gcc-arm-none-eabi",
|
|
||||||
"xvfb @ git+https://github.com/commaai/dependencies.git@release-xvfb#subdirectory=xvfb",
|
|
||||||
|
|
||||||
# body / webrtcd
|
# body / webrtcd
|
||||||
"av",
|
"av",
|
||||||
@@ -63,28 +58,30 @@ dependencies = [
|
|||||||
"json-rpc",
|
"json-rpc",
|
||||||
"websocket_client",
|
"websocket_client",
|
||||||
|
|
||||||
|
# acados deps
|
||||||
|
"casadi >=3.6.6", # 3.12 fixed in 3.6.6
|
||||||
|
|
||||||
# joystickd
|
# joystickd
|
||||||
"inputs",
|
"inputs",
|
||||||
|
|
||||||
# these should be removed
|
# these should be removed
|
||||||
"psutil",
|
"psutil",
|
||||||
"pycryptodome", # used in panda, body, and a test
|
"pycryptodome", # used in updated/casync, panda, body, and a test
|
||||||
"setproctitle",
|
"setproctitle",
|
||||||
|
|
||||||
# logreader
|
# logreader
|
||||||
"zstandard",
|
"zstandard",
|
||||||
|
|
||||||
# ui
|
# ui
|
||||||
"raylib @ git+https://github.com/commaai/dependencies.git@release-raylib#subdirectory=raylib",
|
"raylib > 5.5.0.3",
|
||||||
"qrcode",
|
"qrcode",
|
||||||
"jeepney",
|
"jeepney",
|
||||||
"pillow",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
docs = [
|
docs = [
|
||||||
"Jinja2",
|
"Jinja2",
|
||||||
"zensical",
|
"mkdocs",
|
||||||
]
|
]
|
||||||
|
|
||||||
testing = [
|
testing = [
|
||||||
@@ -96,6 +93,7 @@ testing = [
|
|||||||
"pytest-subtests",
|
"pytest-subtests",
|
||||||
# https://github.com/pytest-dev/pytest-xdist/pull/1229
|
# https://github.com/pytest-dev/pytest-xdist/pull/1229
|
||||||
"pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da",
|
"pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da",
|
||||||
|
"pytest-asyncio",
|
||||||
"pytest-mock",
|
"pytest-mock",
|
||||||
"ruff",
|
"ruff",
|
||||||
"codespell",
|
"codespell",
|
||||||
@@ -105,11 +103,12 @@ testing = [
|
|||||||
dev = [
|
dev = [
|
||||||
"matplotlib",
|
"matplotlib",
|
||||||
"opencv-python-headless",
|
"opencv-python-headless",
|
||||||
|
"gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi",
|
||||||
]
|
]
|
||||||
|
|
||||||
tools = [
|
tools = [
|
||||||
"imgui @ git+https://github.com/commaai/dependencies.git@release-imgui#subdirectory=imgui",
|
|
||||||
"metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')",
|
"metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')",
|
||||||
|
"dearpygui>=2.1.0; (sys_platform != 'linux' or platform_machine != 'aarch64')", # not vended for linux aarch64
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
@@ -127,17 +126,15 @@ allow-direct-references = true
|
|||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = "6.0"
|
minversion = "6.0"
|
||||||
addopts = "--ignore=openpilot/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ --ignore=selfdrive/modeld -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup"
|
addopts = "--ignore=openpilot/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup"
|
||||||
cpp_files = "test_*"
|
cpp_files = "test_*"
|
||||||
cpp_harness = "selfdrive/test/cpp_harness.py"
|
cpp_harness = "selfdrive/test/cpp_harness.py"
|
||||||
python_files = "test_*.py"
|
python_files = "test_*.py"
|
||||||
|
asyncio_default_fixture_loop_scope = "function"
|
||||||
markers = [
|
markers = [
|
||||||
"slow: tests that take awhile to run and can be skipped with -m 'not slow'",
|
"slow: tests that take awhile to run and can be skipped with -m 'not slow'",
|
||||||
"tici: tests that are only meant to run on the C3/C3X",
|
"tici: tests that are only meant to run on the C3/C3X",
|
||||||
"skip_tici_setup: mark test to skip tici setup fixture",
|
"skip_tici_setup: mark test to skip tici setup fixture"
|
||||||
"nocapture: don't capture test output",
|
|
||||||
"shared_download_cache: share download cache between tests",
|
|
||||||
"xdist_group_class_property: group tests by a property of the class that contains them",
|
|
||||||
]
|
]
|
||||||
testpaths = [
|
testpaths = [
|
||||||
"common",
|
"common",
|
||||||
@@ -153,7 +150,7 @@ quiet-level = 3
|
|||||||
# if you've got a short variable name that's getting flagged, add it here
|
# if you've got a short variable name that's getting flagged, add it here
|
||||||
ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite,ser"
|
ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite,ser"
|
||||||
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
|
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
|
||||||
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, *.pem, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html"
|
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html"
|
||||||
|
|
||||||
# https://docs.astral.sh/ruff/configuration/#using-pyprojecttoml
|
# https://docs.astral.sh/ruff/configuration/#using-pyprojecttoml
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
@@ -178,7 +175,9 @@ lint.ignore = [
|
|||||||
"UP045", "UP007", # these don't play nice with raylib atm
|
"UP045", "UP007", # these don't play nice with raylib atm
|
||||||
]
|
]
|
||||||
line-length = 160
|
line-length = 160
|
||||||
|
target-version ="py311"
|
||||||
exclude = [
|
exclude = [
|
||||||
|
"body",
|
||||||
"cereal",
|
"cereal",
|
||||||
"panda",
|
"panda",
|
||||||
"opendbc",
|
"opendbc",
|
||||||
@@ -187,7 +186,7 @@ exclude = [
|
|||||||
"tinygrad_repo",
|
"tinygrad_repo",
|
||||||
"teleoprtc",
|
"teleoprtc",
|
||||||
"teleoprtc_repo",
|
"teleoprtc_repo",
|
||||||
"third_party/copyparty",
|
"third_party",
|
||||||
"*.ipynb",
|
"*.ipynb",
|
||||||
"generated",
|
"generated",
|
||||||
]
|
]
|
||||||
@@ -197,6 +196,7 @@ lint.flake8-implicit-str-concat.allow-multiline = false
|
|||||||
"selfdrive".msg = "Use openpilot.selfdrive"
|
"selfdrive".msg = "Use openpilot.selfdrive"
|
||||||
"common".msg = "Use openpilot.common"
|
"common".msg = "Use openpilot.common"
|
||||||
"system".msg = "Use openpilot.system"
|
"system".msg = "Use openpilot.system"
|
||||||
|
"third_party".msg = "Use openpilot.third_party"
|
||||||
"tools".msg = "Use openpilot.tools"
|
"tools".msg = "Use openpilot.tools"
|
||||||
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
|
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
|
||||||
"unittest".msg = "Use pytest"
|
"unittest".msg = "Use pytest"
|
||||||
@@ -207,13 +207,13 @@ lint.flake8-implicit-str-concat.allow-multiline = false
|
|||||||
"pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press"
|
"pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press"
|
||||||
"pyray.is_mouse_button_released".msg = "This can miss events. Use Widget._handle_mouse_release"
|
"pyray.is_mouse_button_released".msg = "This can miss events. Use Widget._handle_mouse_release"
|
||||||
"pyray.draw_text".msg = "Use a function (such as rl.draw_font_ex) that takes font as an argument"
|
"pyray.draw_text".msg = "Use a function (such as rl.draw_font_ex) that takes font as an argument"
|
||||||
"pyray.draw_texture".msg = "Use rl.draw_texture_ex for float position support"
|
|
||||||
|
|
||||||
[tool.ruff.format]
|
[tool.ruff.format]
|
||||||
quote-style = "preserve"
|
quote-style = "preserve"
|
||||||
|
|
||||||
[tool.ty.src]
|
[tool.ty.src]
|
||||||
exclude = [
|
exclude = [
|
||||||
|
"cereal/",
|
||||||
"msgq/",
|
"msgq/",
|
||||||
"msgq_repo/",
|
"msgq_repo/",
|
||||||
"opendbc/",
|
"opendbc/",
|
||||||
@@ -229,16 +229,24 @@ exclude = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.ty.rules]
|
[tool.ty.rules]
|
||||||
unresolved-import = "ignore" # Cython-compiled modules (.pyx)
|
# Ignore unresolved imports for Cython-compiled modules (.pyx)
|
||||||
unresolved-attribute = "ignore" # many from capnp and Cython modules
|
unresolved-import = "ignore"
|
||||||
invalid-method-override = "ignore" # signature variance issues
|
# Ignore unresolved attributes - many from capnp and Cython modules
|
||||||
possibly-missing-attribute = "ignore" # too many false positives
|
unresolved-attribute = "ignore"
|
||||||
invalid-assignment = "ignore" # often intentional monkey-patching
|
# Ignore invalid method overrides - signature variance issues
|
||||||
no-matching-overload = "ignore" # numpy/ctypes overload matching issues
|
invalid-method-override = "ignore"
|
||||||
invalid-argument-type = "ignore" # many false positives from raylib, ctypes, numpy
|
# Ignore possibly-missing-attribute - too many false positives
|
||||||
call-non-callable = "ignore" # false positives from dynamic types
|
possibly-missing-attribute = "ignore"
|
||||||
unsupported-operator = "ignore" # false positives from dynamic types
|
# Ignore invalid assignment - often intentional monkey-patching
|
||||||
not-subscriptable = "ignore" # false positives from dynamic types
|
invalid-assignment = "ignore"
|
||||||
|
# Ignore no-matching-overload - numpy/ctypes overload matching issues
|
||||||
[tool.uv]
|
no-matching-overload = "ignore"
|
||||||
python-preference = "only-managed"
|
# Ignore invalid-argument-type - many false positives from raylib, ctypes, numpy
|
||||||
|
invalid-argument-type = "ignore"
|
||||||
|
# Ignore call-non-callable - false positives from dynamic types
|
||||||
|
call-non-callable = "ignore"
|
||||||
|
# Ignore unsupported-operator - false positives from dynamic types
|
||||||
|
unsupported-operator = "ignore"
|
||||||
|
# Ignore not-subscriptable - false positives from dynamic types
|
||||||
|
not-subscriptable = "ignore"
|
||||||
|
# not-iterable errors are now fixed
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user