Compare commits

..

13 Commits

Author SHA1 Message Date
nayan 38bb1407c9 remove backup from param 2025-08-23 20:41:43 -04:00
nayan 613e8d27b5 refactor 2025-08-23 20:40:23 -04:00
nayan 3af7841a5d lovely params 2025-08-23 20:11:27 -04:00
nayan 5c2c6a9c75 Merge remote-tracking branch 'origin/master' into pyui/sp-toggles
# Conflicts:
#	common/params_keys.h
#	system/manager/manager.py
2025-08-23 20:05:29 -04:00
Nayan acfbfea41d Merge branch 'master-new' into pyui/sp-toggles 2025-07-25 22:30:58 -04:00
Nayan 1dee05cb1f add backup to sunnypilot_ui param
Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-24 16:38:49 -04:00
nayan 3ff8ccfc23 lint.. LINT.!! LIIINNNTTTT...!!!! 2025-07-22 11:31:09 -04:00
nayan c1f251a34f better, bigger toggles 2025-07-22 11:21:58 -04:00
Nayan 29e76a1e38 Merge branch 'master-new' into pyui/sp-toggles 2025-07-22 08:51:35 -04:00
nayan 3384771117 update names to sp 2025-07-21 19:47:57 -04:00
nayan a6dfe14487 remove unused imports 2025-07-21 15:46:27 -04:00
nayan b9c3ef57d1 add sunnypilot_ui param 2025-07-21 14:43:01 -04:00
nayan 9b5228ec08 add left-aligned sp toggle style 2025-07-21 13:51:57 -04:00
422 changed files with 9144 additions and 25299 deletions
-1
View File
@@ -3,4 +3,3 @@ REGIST
PullRequest PullRequest
cancelled cancelled
FOF FOF
NoO
+4 -4
View File
@@ -40,10 +40,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize')) if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
env: env:
PR_LABEL: 'dev' PR_LABEL: 'dev-c3'
TRUST_FORK_PR_LABEL: 'trust-fork-pr' TRUST_FORK_PR_LABEL: 'trust-fork-pr'
steps: steps:
- name: Check if PR has dev label - name: Check if PR has dev-c3 label
id: check-labels id: check-labels
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
@@ -62,11 +62,11 @@ jobs:
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`); console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`); console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
core.setOutput('has-dev', hasDevC3Label ? 'true' : 'false'); core.setOutput('has-dev-c3', hasDevC3Label ? 'true' : 'false');
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false'); core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
- name: Remove trust-fork-pr label if present - name: Remove trust-fork-pr label if present
if: steps.check-labels.outputs.has-dev == 'true' && steps.check-labels.outputs.has-trust == 'true' if: steps.check-labels.outputs.has-dev-c3 == 'true' && steps.check-labels.outputs.has-trust == 'true'
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -1,105 +0,0 @@
name: 'Post to Discourse'
description: 'Posts a message to a Discourse topic (existing or new)'
inputs:
discourse-url:
description: 'Discourse instance URL (e.g., https://discourse.example.com)'
required: true
api-key:
description: 'Discourse API key'
required: true
api-username:
description: 'Discourse API username'
required: true
topic-id:
description: 'Discourse topic ID to post to (use this OR category-id + title)'
required: false
category-id:
description: 'Category ID for new topic (required if topic-id not provided)'
required: false
title:
description: 'Title for new topic (required if topic-id not provided)'
required: false
message:
description: 'Message content (markdown supported)'
required: true
outputs:
post-number:
description: 'The post number in the topic'
value: ${{ steps.post.outputs.post_number }}
post-url:
description: 'Direct URL to the post'
value: ${{ steps.post.outputs.post_url }}
topic-id:
description: 'The topic ID (useful when creating a new topic)'
value: ${{ steps.post.outputs.topic_id }}
runs:
using: "composite"
steps:
- name: Post to Discourse
id: post
shell: bash
run: |
# Validate inputs
if [ -z "${{ inputs.topic-id }}" ] && ([ -z "${{ inputs.category-id }}" ] || [ -z "${{ inputs.title }}" ]); then
echo "❌ Error: Must provide either topic-id OR both category-id and title"
exit 1
fi
if [ -n "${{ inputs.topic-id }}" ] && ([ -n "${{ inputs.category-id }}" ] || [ -n "${{ inputs.title }}" ]); then
echo "⚠️ Warning: Both topic-id and category-id/title provided. Will post to existing topic."
fi
# Determine if creating new topic or posting to existing
if [ -n "${{ inputs.topic-id }}" ]; then
echo "📝 Posting to existing topic ID: ${{ inputs.topic-id }}"
# Create JSON payload for posting to existing topic
PAYLOAD=$(jq -n \
--arg content '${{ inputs.message }}' \
--arg topic_id "${{ inputs.topic-id }}" \
'{topic_id: $topic_id, raw: $content}')
else
echo "✨ Creating new topic: ${{ inputs.title }}"
# Create JSON payload for new topic
PAYLOAD=$(jq -n \
--arg content '${{ inputs.message }}' \
--arg title "${{ inputs.title }}" \
--arg category "${{ inputs.category-id }}" \
'{title: $title, category: ($category | tonumber), raw: $content}')
fi
# Post to Discourse
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "${{ inputs.discourse-url }}/posts.json" \
-H "Content-Type: application/json" \
-H "Api-Key: ${{ inputs.api-key }}" \
-H "Api-Username: ${{ inputs.api-username }}" \
-d "$PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Successfully posted to Discourse!"
POST_NUMBER=$(echo "$BODY" | jq -r '.post_number // "unknown"')
TOPIC_ID=$(echo "$BODY" | jq -r '.topic_id // "${{ inputs.topic-id }}"')
POST_URL="${{ inputs.discourse-url }}/t/${TOPIC_ID}/${POST_NUMBER}"
echo "post_number=${POST_NUMBER}" >> $GITHUB_OUTPUT
echo "post_url=${POST_URL}" >> $GITHUB_OUTPUT
echo "topic_id=${TOPIC_ID}" >> $GITHUB_OUTPUT
echo "Topic ID: ${TOPIC_ID}"
echo "Post number: ${POST_NUMBER}"
echo "URL: ${POST_URL}"
else
echo "❌ Failed to post to Discourse"
echo "HTTP Code: ${HTTP_CODE}"
echo "Response: ${BODY}"
exit 1
fi
+1 -1
View File
@@ -39,4 +39,4 @@ jobs:
git config --global --add safe.directory '*' git config --global --add safe.directory '*'
git lfs pull git lfs pull
- name: Push __nightly - name: Push __nightly
run: BRANCH=__nightly release/build_stripped.sh run: BRANCH=__nightly release/build_devel.sh
-1
View File
@@ -43,7 +43,6 @@ jobs:
with: with:
submodules: true submodules: true
- name: uv lock - name: uv lock
if: github.repository == 'commaai/openpilot'
run: | run: |
python3 -m ensurepip --upgrade python3 -m ensurepip --upgrade
pip3 install uv pip3 install uv
+6 -9
View File
@@ -27,7 +27,7 @@ env:
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical PYTEST: pytest --continue-on-collection-errors --durations=0 --durations-min=5 -n logical
jobs: jobs:
build_release: build_release:
@@ -52,7 +52,7 @@ jobs:
command: git lfs pull command: git lfs pull
- name: Build devel - name: Build devel
timeout-minutes: 1 timeout-minutes: 1
run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
- uses: ./.github/workflows/setup-with-retry - uses: ./.github/workflows/setup-with-retry
- name: Build openpilot and run checks - name: Build openpilot and run checks
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
@@ -107,6 +107,7 @@ jobs:
build_mac: build_mac:
name: build macOS name: build macOS
if: false # temp disable since homebrew install is getting stuck
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -115,9 +116,7 @@ jobs:
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
- name: Homebrew cache - name: Homebrew cache
uses: ./.github/workflows/auto-cache uses: ./.github/workflows/auto-cache
if: false # disabling the cache for now because it is breaking macos builds...
with: with:
save: false # No need save here if we manually save it later conditionally
path: ~/Library/Caches/Homebrew path: ~/Library/Caches/Homebrew
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
restore-keys: | restore-keys: |
@@ -126,8 +125,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: ./tools/mac_setup.sh run: ./tools/mac_setup.sh
env: env:
PYTHONWARNINGS: default # package install has DeprecationWarnings # package install has DeprecationWarnings
HOMEBREW_DISPLAY_INSTALL_TIMES: 1 PYTHONWARNINGS: default
- name: Save Homebrew cache - name: Save Homebrew cache
uses: actions/cache/save@v4 uses: actions/cache/save@v4
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
@@ -138,7 +137,6 @@ jobs:
- name: Getting scons cache - name: Getting scons cache
uses: ./.github/workflows/auto-cache uses: ./.github/workflows/auto-cache
with: with:
save: false # No need save here if we manually save it later conditionally
path: /tmp/scons_cache path: /tmp/scons_cache
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
restore-keys: | restore-keys: |
@@ -192,8 +190,7 @@ jobs:
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }} timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }}
run: | run: |
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
# Pre-compile Python bytecode so each pytest worker doesn't need to $PYTEST --collect-only -m 'not slow' &> /dev/null && \
$PYTEST --collect-only -m 'not slow' -qq && \
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \ MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
./selfdrive/ui/tests/create_test_translations.sh && \ ./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
+102 -143
View File
@@ -8,15 +8,21 @@ env:
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot" PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
# Branch configurations # Branch configurations
STAGING_SOURCE_BRANCH: 'master' STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master' }} # vars are set on repo settings.
DEV_C3_SOURCE_BRANCH: ${{ vars.DEV_C3_SOURCE_BRANCH || 'master-dev-c3-new' }} # vars are set on repo settings.
# Target branch configurations
STAGING_TARGET_BRANCH: ${{ vars.STAGING_TARGET_BRANCH || 'staging-c3-new' }} # vars are set on repo settings.
DEV_TARGET_BRANCH: ${{ vars.DEV_TARGET_BRANCH || 'dev-c3-new' }} # vars are set on repo settings.
RELEASE_TARGET_BRANCH: ${{ vars.RELEASE_TARGET_BRANCH || 'release-c3-new' }} # vars are set on repo settings.
# Runtime configuration # Runtime configuration
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}" SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
on: on:
push: push:
branches: [ master, master-dev ] branches: [ master, master-dev-c3-new ]
tags: [ 'release/*' ] tags: [ '*' ]
pull_request_target: pull_request_target:
types: [ labeled ] types: [ labeled ]
workflow_dispatch: workflow_dispatch:
@@ -28,72 +34,9 @@ on:
default: false default: false
jobs: jobs:
prepare_strategy:
runs-on: ubuntu-24.04
if: (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
outputs:
environment: ${{ steps.strategy.outputs.environment }}
new_branch: ${{ steps.strategy.outputs.new_branch }}
extra_version_identifier: ${{ steps.strategy.outputs.extra_version_identifier }}
version: ${{ steps.strategy.outputs.version }}
cancel_publish_in_progress: ${{ steps.strategy.outputs.cancel_publish_in_progress }}
publish_concurrency_group: ${{ steps.strategy.outputs.publish_concurrency_group }}
is_stable_branch: ${{ steps.strategy.outputs.is_stable_branch }}
build: ${{ steps.strategy.outputs.build }}
steps:
- uses: actions/checkout@v4
- name: Extract deploy strategy
id: strategy
run: |
echo '::group::Strategy Extraction'
BRANCH="${{ github.head_ref || github.ref_name }}"
echo "Current branch: $BRANCH"
STRATEGY_JSON='${{ vars.DEPLOY_STRATEGY }}'
CONFIG=$(echo "$STRATEGY_JSON" | jq -r --arg branch "$BRANCH" '
.configs[] | select(.branch == $branch)
')
BUILD="$(date '+%Y.%m.%d')-${{ github.run_number }}"
if [[ -z "$CONFIG" || "$CONFIG" == "null" ]]; then
echo "No exact strategy match found. Falling back to feature/fork logic."
IS_FORK="${{ github.event.pull_request.head.repo.fork && 'true' || 'false' }}"
FORK_SUFFIX=$( [[ "$IS_FORK" == "true" ]] && echo "-fork" || echo "" )
NEW_BRANCH="${BRANCH}${FORK_SUFFIX}-prebuilt"
echo "new_branch=$NEW_BRANCH" >> $GITHUB_OUTPUT
echo "version=$BUILD" >> $GITHUB_OUTPUT
echo "cancel_publish_in_progress=true" >> $GITHUB_OUTPUT
echo "publish_concurrency_group=publish-${BRANCH}" >> $GITHUB_OUTPUT
echo "environment=feature-branch" >> $GITHUB_OUTPUT
echo "extra_version_identifier=feature-branch" >> $GITHUB_OUTPUT
else
echo "Matched config: $CONFIG"
environment=$(echo "$CONFIG" | jq -r '.environment')
echo "environment=$environment" >> $GITHUB_OUTPUT
echo "new_branch=$(echo "$CONFIG" | jq -r '.target_branch')" >> $GITHUB_OUTPUT
cancel="$(echo "$CONFIG" | jq -r '.cancel_publish_in_progress')";
echo "cancel_publish_in_progress=$( [ "$cancel" = "null" ] && echo "true" || echo $cancel)" >> $GITHUB_OUTPUT
echo "publish_concurrency_group=publish-${BRANCH}$( [ "$cancel" = "null" ] || [ "$cancel" = "true" ] || echo "${{ github.sha }}" )" >> $GITHUB_OUTPUT
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
fi
echo "build=$BUILD" >> $GITHUB_OUTPUT
cat $GITHUB_OUTPUT
validate_tests: validate_tests:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: [ prepare_strategy ] if: ((github.event_name == 'workflow_dispatch' && inputs.wait_for_tests) || contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
if: ${{
((github.event_name == 'workflow_dispatch' && inputs.wait_for_tests) ||
(github.event_name == 'push' && needs.prepare_strategy.outputs.is_stable_branch == 'true') ||
contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
}}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Wait for Tests - name: Wait for Tests
@@ -101,26 +44,19 @@ jobs:
with: with:
workflow: selfdrive_tests.yaml # The workflow file to monitor workflow: selfdrive_tests.yaml # The workflow file to monitor
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
should-wait-for-start: ${{ github.event_name == 'push' && 'true' || 'false' }}
build: build:
needs: [ validate_tests, prepare_strategy ] needs: [ validate_tests ]
concurrency: concurrency:
group: build-${{ github.head_ref || github.ref_name }} group: build-${{ github.head_ref || github.ref_name }}
cancel-in-progress: false cancel-in-progress: false
runs-on: [self-hosted, tici] runs-on: [self-hosted, tici]
outputs: outputs:
new_branch: ${{ needs.prepare_strategy.outputs.new_branch }} new_branch: ${{ steps.set-env.outputs.new_branch }}
version: ${{ needs.prepare_strategy.outputs.version }} version: ${{ steps.set-env.outputs.version }}
extra_version_identifier: ${{ needs.prepare_strategy.outputs.extra_version_identifier }} extra_version_identifier: ${{ steps.set-env.outputs.extra_version_identifier }}
commit_sha: ${{ github.sha }} commit_sha: ${{ steps.set-env.outputs.commit_sha }}
if: ${{ if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
(always() && !cancelled() && !failure()) &&
needs.prepare_strategy.result == 'success' &&
(needs.validate_tests.result == 'success' || needs.validate_tests.result == 'skipped') &&
(!contains(github.event_name, 'pull_request') ||
(github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
}}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -138,15 +74,64 @@ jobs:
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden. # for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
restore-keys: | restore-keys: |
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }} scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_SOURCE_BRANCH }} scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_C3_SOURCE_BRANCH }}
scons-${{ runner.os }}-${{ runner.arch }} scons-${{ runner.os }}-${{ runner.arch }}
- name: Set Feature Branch Prebuilt Configuration
id: set_feature_configuration
if: (
!(env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH) &&
!(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH) &&
!(startsWith(github.ref, 'refs/tags/'))
)
run: |
echo "NEW_BRANCH=${{ env.SOURCE_BRANCH }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-prebuilt" >> $GITHUB_ENV
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
- name: Set dev-c3-new prebuilt Configuration
id: set_dev_configuration
if: (
steps.set_feature_configuration.outcome == 'skipped' &&
env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH
)
run: |
echo "NEW_BRANCH=${{ env.DEV_TARGET_BRANCH }}" >> $GITHUB_ENV
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
echo "EXTRA_VERSION_IDENTIFIER=${{ github.run_number }}" >> $GITHUB_ENV
- name: Set staging-c3-new prebuilt Configuration
id: set_staging_configuration
if: (
steps.set_feature_configuration.outcome == 'skipped' &&
!contains(github.event_name, 'pull_request') &&
steps.set_dev_configuration.outcome == 'skipped' &&
(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH)
)
run: |
echo "NEW_BRANCH=${{ env.STAGING_TARGET_BRANCH }}" >> $GITHUB_ENV
echo "EXTRA_VERSION_IDENTIFIER=staging" >> $GITHUB_ENV
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-staging" >> $GITHUB_ENV
- name: Set release-c3-new prebuilt Configuration
id: set_tag_configuration
if: (
steps.set_feature_configuration.outcome == 'skipped' &&
!contains(github.event_name, 'pull_request') &&
steps.set_staging_configuration.outcome == 'skipped' &&
startsWith(github.ref, 'refs/tags/')
)
run: |
echo "NEW_BRANCH=${{ env.RELEASE_TARGET_BRANCH }}" >> $GITHUB_ENV
echo "EXTRA_VERSION_IDENTIFIER=release" >> $GITHUB_ENV
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-release" >> $GITHUB_ENV
- name: Set environment variables - name: Set environment variables
id: set-env id: set-env
run: | run: |
echo "new_branch=${{ needs.prepare_strategy.outputs.new_branch }}" >> $GITHUB_OUTPUT # Write to GITHUB_OUTPUT from environment variables
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT echo "new_branch=$NEW_BRANCH" >> $GITHUB_OUTPUT
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT [[ ! -z "$EXTRA_VERSION_IDENTIFIER" ]] && echo "extra_version_identifier=$EXTRA_VERSION_IDENTIFIER" >> $GITHUB_OUTPUT
[[ ! -z "$VERSION" ]] && echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
# Set up common environment # Set up common environment
@@ -241,19 +226,15 @@ jobs:
if: always() if: always()
run: | run: |
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
publish: publish:
concurrency: concurrency:
# We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name. group: publish-${{ github.head_ref || github.ref_name }}
# This means that if multiple commits come in while we're publishing, they will be queued up and publish one after the other. cancel-in-progress: true
# Otherwise, if a job is waiting to be published due to environment wait time, it would be canceled by a new commit and restart the wait time. if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }} needs: [ build ]
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
if: ${{ (always() && !cancelled() && !failure()) && needs.build.result == 'success' && needs.prepare_strategy.result == 'success' && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
needs: [ build, prepare_strategy ]
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
environment: ${{ needs.prepare_strategy.outputs.environment }} environment: ${{ (contains(fromJSON(vars.AUTO_DEPLOY_PREBUILT_BRANCHES), github.head_ref || github.ref_name) || contains(github.event.pull_request.labels.*.name, 'prebuilt')) && 'auto-deploy' || 'feature-branch' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -285,7 +266,7 @@ jobs:
"${{ needs.build.outputs.new_branch }}" \ "${{ needs.build.outputs.new_branch }}" \
"${{ needs.build.outputs.version }}" \ "${{ needs.build.outputs.version }}" \
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \ "https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
"${{ needs.build.outputs.extra_version_identifier }}" "-${{ needs.build.outputs.extra_version_identifier }}"
echo "" echo ""
echo "---- ️ To update the list of branches that auto deploy prebuilts -----" echo "---- ️ To update the list of branches that auto deploy prebuilts -----"
@@ -293,60 +274,38 @@ jobs:
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES" echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}" echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
echo "3. Update as needed (JSON array with no spaces)" echo "3. Update as needed (JSON array with no spaces)"
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
run: |
TAG="${{ needs.prepare_strategy.outputs.environment }}/${{ needs.prepare_strategy.outputs.version }}/${{ needs.prepare_strategy.outputs.build }}"
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.build.outputs.build }}."
git push -f origin ${TAG}
notify: notify:
needs: needs: [ build, publish ]
- prepare_strategy
- build
- publish
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
if: ${{ (always() && !cancelled() && !failure()) if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
&& needs.publish.result == 'success'
&& (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
&& (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Alpine Linux environment
- name: Prepare notification message uses: jirutka/setup-alpine@v1.2.0
id: message
run: |
TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}'
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
export branch_name="${{ env.SOURCE_BRANCH }}"
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
export commit_sha="${{ github.sha }}"
export commit_short_sha="${{ github.sha }}"
export commit_short_sha="${commit_short_sha:0:7}"
export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}"
export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}"
MESSAGE=$(cat << 'EOF' | envsubst
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
EOF
)
{
echo 'content<<EOFMARKER'
echo "$MESSAGE"
echo 'EOFMARKER'
} >> $GITHUB_OUTPUT
shell: bash
- name: Post to Discourse
uses: ./.github/workflows/post-to-discourse
with: with:
discourse-url: ${{ vars.DISCOURSE_URL }} packages: 'jq gettext curl'
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: "system" - name: Send Discord Notification
topic-id: ${{ fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name].topic_id }} env:
message: ${{ steps.message.outputs.content }} DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), env.SOURCE_BRANCH) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }}
run: |
TEMPLATE='${{ vars.DISCORD_GENERAL_UPDATE_NOTICE }}'
export EXTRA_VERSION_IDENTIFIER="${{ needs.build.outputs.extra_version_identifier }}"
export VERSION="${{ needs.build.outputs.version }}"
export branch_name=${{ env.SOURCE_BRANCH }}
export new_branch=${{ needs.build.outputs.new_branch }}
export extra_version_identifier=${{ needs.build.outputs.extra_version_identifier || github.run_number}}
echo ${TEMPLATE} | envsubst | jq -c '.' | tee payload.json
curl -X POST -H "Content-Type: application/json" -d @payload.json $DISCORD_WEBHOOK
echo ""
echo "---- ️ To update the list of branches that notify to dev-feedback -----"
echo ""
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/DEV_FEEDBACK_NOTIFICATION_BRANCHES"
echo "2. Current value: ${{ vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES }}"
echo "3. Update as needed (JSON array with no spaces)"
shell: alpine.sh {0}
manage-pr-labels: manage-pr-labels:
name: Remove prebuilt label name: Remove prebuilt label
@@ -1,8 +1,9 @@
name: Build dev name: Build dev-c3-new
env: env:
DEFAULT_SOURCE_BRANCH: "master" DEFAULT_SOURCE_BRANCH: "master"
DEFAULT_TARGET_BRANCH: "master-dev" DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
PR_LABEL: "dev-c3"
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs' LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git' LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
@@ -24,7 +25,7 @@ on:
target_branch: target_branch:
description: 'Target branch to reset and squash into' description: 'Target branch to reset and squash into'
required: true required: true
default: 'master-dev' default: 'master-dev-c3-new'
type: string type: string
cancel_in_progress: cancel_in_progress:
description: 'Cancel any in-progress runs of this workflow' description: 'Cancel any in-progress runs of this workflow'
@@ -42,7 +43,7 @@ jobs:
if: ( if: (
(github.event_name == 'workflow_dispatch') (github.event_name == 'workflow_dispatch')
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) || (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL)))) || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
) )
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -54,7 +55,7 @@ jobs:
uses: ./.github/workflows/wait-for-action # Path to where you place the action uses: ./.github/workflows/wait-for-action # Path to where you place the action
if: ( if: (
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL)))) || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
) )
with: with:
workflow: selfdrive_tests.yaml # The workflow file to monitor workflow: selfdrive_tests.yaml # The workflow file to monitor
@@ -117,8 +118,8 @@ jobs:
run: | run: |
# Use GitHub API to get PRs with specific label, ordered by creation date # Use GitHub API to get PRs with specific label, ordered by creation date
PR_LIST=$(gh api graphql -f query=' PR_LIST=$(gh api graphql -f query='
query($search_query:String!) { query($label:String!) {
search(query: $search_query, type:ISSUE, first:40) { search(query: $label, type:ISSUE, first:100) {
nodes { nodes {
... on PullRequest { ... on PullRequest {
number number
@@ -148,7 +149,7 @@ jobs:
} }
} }
} }
}' -F search_query="repo:${{ github.repository }} is:pr is:open label:${{ vars.PREBUILT_PR_LABEL }},${{ vars.PREBUILT_PR_LABEL }}-c3 draft:false sort:created-asc") }' -F label="is:pr is:open label:${PR_LABEL} draft:false sort:created-asc")
PR_LIST=${PR_LIST//\'/} PR_LIST=${PR_LIST//\'/}
echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT
-78
View File
@@ -1,78 +0,0 @@
name: Debug Discourse Posting
on:
push:
jobs:
test-discourse-post:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Post test message to Discourse
uses: ./.github/workflows/post-to-discourse
with:
discourse-url: ${{ vars.DISCOURSE_URL }}
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
message: |
## 🧪 Test Post from GitHub Actions
**This is a test post to verify Discourse integration**
- **Workflow**: ${{ github.workflow }}
- **Run Number**: #${{ github.run_number }}
- **Branch**: `${{ github.ref_name }}`
- **Commit**: ${{ github.sha }}
- **Actor**: @${{ github.actor }}
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
---
### Fake Build Info (for testing)
- **Version**: 0.9.8-test
- **Build**: #42
- **Branch**: release-test
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
*This is an automated test message. Drive safe! 🚗💨*
- name: Create topic on Discourse
uses: ./.github/workflows/post-to-discourse
with:
discourse-url: ${{ vars.DISCOURSE_URL }}
api-key: ${{ secrets.DISCOURSE_API_KEY }}
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
#topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
category-id: 4
title: "This is a test of a new topic instead of a reply"
message: |
## 🧪 Test Post from GitHub Actions
**This is a test post to verify Discourse integration**
- **Workflow**: ${{ github.workflow }}
- **Run Number**: #${{ github.run_number }}
- **Branch**: `${{ github.ref_name }}`
- **Commit**: ${{ github.sha }}
- **Actor**: @${{ github.actor }}
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
---
### Fake Build Info (for testing)
- **Version**: 0.9.8-test
- **Build**: #42
- **Branch**: release-test
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
*This is an automated test message. Drive safe! 🚗💨*
- name: Display results
if: always()
run: |
echo "::notice::Discourse post test completed"
echo "Check your Discourse topic to verify the post appeared correctly"
+1
View File
@@ -50,6 +50,7 @@ cereal/services.h
cereal/gen cereal/gen
cereal/messaging/bridge cereal/messaging/bridge
selfdrive/mapd/default_speeds_by_region.json selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd
selfdrive/ui/translations/tmp selfdrive/ui/translations/tmp
selfdrive/test/longitudinal_maneuvers/out selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump selfdrive/car/tests/cars_dump
+1 -40
View File
@@ -23,11 +23,6 @@
"id": "args", "id": "args",
"description": "Arguments to pass to the process", "description": "Arguments to pass to the process",
"type": "promptString" "type": "promptString"
},
{
"id": "replayArg",
"type": "promptString",
"description": "Enter route or segment to replay."
} }
], ],
"configurations": [ "configurations": [
@@ -45,41 +40,7 @@
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/${input:cpp_process}", "program": "${workspaceFolder}/${input:cpp_process}",
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}",
},
{
"name": "Attach LLDB to Replay drive",
"type": "lldb",
"request": "attach",
"pid": "${command:pickMyProcess}",
"initCommands": [
"script import time; time.sleep(3)"
]
},
{
"name": "Replay drive",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/opendbc/safety/tests/safety_replay/replay_drive.py",
"args": [
"${input:replayArg}"
],
"console": "integratedTerminal",
"justMyCode": false,
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"subProcess": true,
"stopOnEntry": false
}
],
"compounds": [
{
"name": "Replay drive + Safety LLDB",
"configurations": [
"Replay drive",
"Attach LLDB to Replay drive"
]
} }
] ]
} }
-1080
View File
File diff suppressed because it is too large Load Diff
Vendored
+24 -10
View File
@@ -167,7 +167,7 @@ node {
env.GIT_COMMIT = checkout(scm).GIT_COMMIT env.GIT_COMMIT = checkout(scm).GIT_COMMIT
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging', def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
'release-tici', 'testing-closet*', 'hotfix-*'] '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_')) {
@@ -178,7 +178,7 @@ node {
try { try {
if (env.BRANCH_NAME == 'devel-staging') { if (env.BRANCH_NAME == 'devel-staging') {
deviceStage("build release3-staging", "tizi-needs-can", [], [ deviceStage("build release3-staging", "tici-needs-can", [], [
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"), step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
]) ])
} }
@@ -186,12 +186,12 @@ node {
if (env.BRANCH_NAME == '__nightly') { if (env.BRANCH_NAME == '__nightly') {
parallel ( parallel (
'nightly': { 'nightly': {
deviceStage("build nightly", "tizi-needs-can", [], [ deviceStage("build nightly", "tici-needs-can", [], [
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"), step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
]) ])
}, },
'nightly-dev': { 'nightly-dev': {
deviceStage("build nightly-dev", "tizi-needs-can", [], [ deviceStage("build nightly-dev", "tici-needs-can", [], [
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"), step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
]) ])
}, },
@@ -200,30 +200,39 @@ node {
if (!env.BRANCH_NAME.matches(excludeRegex)) { if (!env.BRANCH_NAME.matches(excludeRegex)) {
parallel ( parallel (
// tici tests
'onroad tests': { 'onroad tests': {
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [ deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
step("build openpilot", "cd system/manager && ./build.py"), step("build openpilot", "cd system/manager && ./build.py"),
step("check dirty", "release/check-dirty.sh"), step("check dirty", "release/check-dirty.sh"),
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]), step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
]) ])
}, },
'HW + Unit Tests': { 'HW + Unit Tests': {
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [ deviceStage("tici-hardware", "tici-common", ["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", [diffPaths: ["panda", "selfdrive/pandad/"]]), step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
step("test power draw", "pytest -s system/hardware/tici/tests/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 pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
step("test manager", "pytest system/manager/test/test_manager.py"), step("test manager", "pytest system/manager/test/test_manager.py"),
]) ])
}, },
'loopback': { 'loopback': {
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [ deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
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"),
]) ])
}, },
'camerad AR0231': {
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
])
},
'camerad OX03C10': { 'camerad OX03C10': {
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"), step("build", "cd system/manager && ./build.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
step("test exposure", "pytest system/camerad/test/test_exposure.py"), step("test exposure", "pytest system/camerad/test/test_exposure.py"),
@@ -237,13 +246,17 @@ node {
]) ])
}, },
'sensord': { 'sensord': {
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [ deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
])
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"), step("build", "cd system/manager && ./build.py"),
step("test sensord", "pytest system/sensord/tests/test_sensord.py"), step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
]) ])
}, },
'replay': { 'replay': {
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [ deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]), step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]), step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
]) ])
@@ -253,6 +266,7 @@ node {
step("build openpilot", "cd system/manager && ./build.py"), step("build openpilot", "cd system/manager && ./build.py"),
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"), step("test pandad loopback", "SINGLE_PANDA=1 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 pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"), step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
// TODO: enable once new AGNOS is available // TODO: enable once new AGNOS is available
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"), // step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
+27 -38
View File
@@ -3,9 +3,11 @@
## 🌞 What is sunnypilot? ## 🌞 What is sunnypilot?
[sunnypilot](https://github.com/sunnyhaibin/sunnypilot) is a fork of comma.ai's openpilot, an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 300+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible. [sunnypilot](https://github.com/sunnyhaibin/sunnypilot) is a fork of comma.ai's openpilot, an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 300+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible.
## 💭 Join our Community Forum ## 💭 Join our Discord
Join the official sunnypilot community forum to stay up to date with all the latest features and be a part of shaping the future of sunnypilot! Join the official sunnypilot Discord server to stay up to date with all the latest features and be a part of shaping the future of sunnypilot!
* https://community.sunnypilot.ai/ * https://discord.gg/sunnypilot
![](https://dcbadge.vercel.app/api/server/wRW3meAgtx?style=flat) ![Discord Shield](https://discordapp.com/api/guilds/880416502577266699/widget.png?style=shield)
## Documentation ## Documentation
https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot
@@ -14,54 +16,25 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t
* A supported device to run this software * A supported device to run this software
* a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x) * a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x)
* This software * This software
* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot. * One of [the 300+ supported cars](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car * A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup). Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
## Installation ## Installation
Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch. Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `release-c3` branch.
### If you want to use our newest branches (our rewrite)
> [!TIP]
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
* sunnypilot not installed or you installed a version before 0.8.17? * sunnypilot not installed or you installed a version before 0.8.17?
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed. 1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option. 2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```. 3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```release-c3.sunnypilot.ai```.
4. Complete the rest of the installation following the onscreen instructions. 4. Complete the rest of the installation following the onscreen instructions.
* sunnypilot already installed and you installed a version after 0.8.17? * sunnypilot already installed and you installed a version after 0.8.17?
1. On the comma three/3X, go to `Settings` ▶️ `Software`. 1. On the comma three, go to `Settings` ▶️ `Software`.
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot. 2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector. 3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging` 4. Scroll to select the desired branch per Recommended Branches (see below). Example: `release-c3`
### Recommended Branches
| Branch | Installation URL |
|:---------------:|:---------------------------------------------:|
| `release` | `https://release.sunnypilot.ai` |
| `staging` | `https://staging.sunnypilot.ai` |
| `dev` | `https://dev.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
> [!TIP]
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'.
> [!NOTE]
> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel.
<details>
<summary>Older legacy branches</summary>
### If you want to use our older legacy branches (*not recommended*)
> [**IMPORTANT**]
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
> You can still restore the latest sunnylink backup made on the old branches.
| Branch | Installation URL | | Branch | Installation URL |
|:------------:|:--------------------------------:| |:------------:|:--------------------------------:|
@@ -69,9 +42,25 @@ Please refer to [Recommended Branches](#recommended-branches) to find your prefe
| `staging-c3` | https://staging-c3.sunnypilot.ai | | `staging-c3` | https://staging-c3.sunnypilot.ai |
| `dev-c3` | https://dev-c3.sunnypilot.ai | | `dev-c3` | https://dev-c3.sunnypilot.ai |
</details> ### If you want to use our newest branches (our rewrite)
> [!TIP]
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
> [!IMPORTANT]
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
> You can still restore the latest sunnylink backup made on the old branches.
| Branch | Installation URL |
|:----------------:|:---------------------------------------------:|
| `staging-c3-new` | `https://staging-c3-new.sunnypilot.ai` |
| `dev-c3-new` | `https://dev-c3-new.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
| `release-c3-new` | **Not yet available**. |
> [!TIP]
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
## 🎆 Pull Requests ## 🎆 Pull Requests
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged. We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
+5 -15
View File
@@ -1,28 +1,18 @@
Version 0.10.1 (2025-09-08) Version 0.10.1 (2025-09-08)
======================== ========================
* New driving model
* World Model: removed global localization inputs
* World Model: 2x the number of parameters
* World Model: trained on 4x the number of segments
* Driving Vision Model: trained on 4x the number of segments
* Honda City 2023 support thanks to vanillagorillaa and drFritz!
* Honda N-Box 2018 support thanks to miettal!
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
Version 0.10.0 (2025-08-05) Version 0.10.0 (2025-08-05)
======================== ========================
* New driving model * New driving model
* New training architecture * New training architecture
* Described in our CVPR paper: "Learning to Drive from a World Model" * Architecture outlined in CVPR paper: "Learning to Drive from a World Model"
* Longitudinal MPC replaced by E2E planning from World Model in Experimental Mode * Longitudinal MPC replaced by E2E planning from worldmodel in experimental mode
* Action from lateral MPC as training objective replaced by E2E planning from World Model * Action from lateral MPC as training objective replaced by E2E planning from worldmodel
* Low-speed lead car ground-truth fixes * Low-speed lead car ground-truth fixes
* Enable live-learned steering actuation delay * Enable live-learned steering actuation delay
* Record driving feedback using LKAS button when MADS is disabled
* Opt-in audio recording for dashcam video * Opt-in audio recording for dashcam video
* Acura MDX 2025 support thanks to vanillagorillaa and MVL!
* Honda Accord 2023-25 support thanks to vanillagorillaa and MVL!
* Honda CR-V 2023-25 support thanks to vanillagorillaa and MVL!
* Honda Pilot 2023-25 support thanks to vanillagorillaa and MVL!
Version 0.9.9 (2025-05-23) Version 0.9.9 (2025-05-23)
======================== ========================
+5
View File
@@ -359,6 +359,11 @@ SConscript([
'system/ubloxd/SConscript', 'system/ubloxd/SConscript',
'system/loggerd/SConscript', 'system/loggerd/SConscript',
]) ])
if arch != "Darwin":
SConscript([
'system/logcatd/SConscript',
'system/proclogd/SConscript',
])
if arch == "larch64": if arch == "larch64":
SConscript(['system/camerad/SConscript']) SConscript(['system/camerad/SConscript'])
+2 -195
View File
@@ -25,26 +25,6 @@ struct ModularAssistiveDrivingSystem {
} }
} }
struct IntelligentCruiseButtonManagement {
state @0 :IntelligentCruiseButtonManagementState;
sendButton @1 :SendButtonState;
vTarget @2 :Float32;
enum IntelligentCruiseButtonManagementState {
inactive @0; # No button press or default state
preActive @1; # Pre-active state before transitioning to increasing or decreasing
increasing @2; # Increasing speed
decreasing @3; # Decreasing speed
holding @4; # Holding steady speed
}
enum SendButtonState {
none @0;
increase @1;
decrease @2;
}
}
# Same struct as Log.RadarState.LeadData # Same struct as Log.RadarState.LeadData
struct LeadData { struct LeadData {
dRel @0 :Float32; dRel @0 :Float32;
@@ -68,49 +48,6 @@ struct LeadData {
struct SelfdriveStateSP @0x81c2f05a394cf4af { struct SelfdriveStateSP @0x81c2f05a394cf4af {
mads @0 :ModularAssistiveDrivingSystem; mads @0 :ModularAssistiveDrivingSystem;
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
enum AudibleAlert {
none @0;
engage @1;
disengage @2;
refuse @3;
warningSoft @4;
warningImmediate @5;
prompt @6;
promptRepeat @7;
promptDistracted @8;
# unused, these are reserved for upstream events so we don't collide
reserved9 @9;
reserved10 @10;
reserved11 @11;
reserved12 @12;
reserved13 @13;
reserved14 @14;
reserved15 @15;
reserved16 @16;
reserved17 @17;
reserved18 @18;
reserved19 @19;
reserved20 @20;
reserved21 @21;
reserved22 @22;
reserved23 @23;
reserved24 @24;
reserved25 @25;
reserved26 @26;
reserved27 @27;
reserved28 @28;
reserved29 @29;
reserved30 @30;
promptSingleLow @31;
promptSingleHigh @32;
}
} }
struct ModelManagerSP @0xaedffd8f31e7b55d { struct ModelManagerSP @0xaedffd8f31e7b55d {
@@ -185,13 +122,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 { struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
dec @0 :DynamicExperimentalControl; dec @0 :DynamicExperimentalControl;
longitudinalPlanSource @1 :LongitudinalPlanSource;
smartCruiseControl @2 :SmartCruiseControl;
speedLimit @3 :SpeedLimit;
vTarget @4 :Float32;
aTarget @5 :Float32;
events @6 :List(OnroadEventSP.Event);
e2eAlerts @7 :E2eAlerts;
struct DynamicExperimentalControl { struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState; state @0 :DynamicExperimentalControlState;
@@ -203,97 +133,6 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
blended @1; blended @1;
} }
} }
struct SmartCruiseControl {
vision @0 :Vision;
map @1 :Map;
struct Vision {
state @0 :VisionState;
vTarget @1 :Float32;
aTarget @2 :Float32;
currentLateralAccel @3 :Float32;
maxPredictedLateralAccel @4 :Float32;
enabled @5 :Bool;
active @6 :Bool;
}
struct Map {
state @0 :MapState;
vTarget @1 :Float32;
aTarget @2 :Float32;
enabled @3 :Bool;
active @4 :Bool;
}
enum VisionState {
disabled @0; # System disabled or inactive.
enabled @1; # No predicted substantial turn on vision range.
entering @2; # A substantial turn is predicted ahead, adapting speed to turn comfort levels.
turning @3; # Actively turning. Managing acceleration to provide a roll on turn feeling.
leaving @4; # Road ahead straightens. Start to allow positive acceleration.
overriding @5; # System overriding with manual control.
}
enum MapState {
disabled @0; # System disabled or inactive.
enabled @1; # No predicted substantial turn on map range.
turning @2; # Actively turning. Managing acceleration to provide a roll on turn feeling.
overriding @3; # System overriding with manual control.
}
}
struct SpeedLimit {
resolver @0 :Resolver;
assist @1 :Assist;
struct Resolver {
speedLimit @0 :Float32;
distToSpeedLimit @1 :Float32;
source @2 :Source;
speedLimitOffset @3 :Float32;
speedLimitLast @4 :Float32;
speedLimitFinal @5 :Float32;
speedLimitFinalLast @6 :Float32;
speedLimitValid @7 :Bool;
speedLimitLastValid @8 :Bool;
}
struct Assist {
state @0 :AssistState;
enabled @1 :Bool;
active @2 :Bool;
vTarget @3 :Float32;
aTarget @4 :Float32;
}
enum Source {
none @0;
car @1;
map @2;
}
enum AssistState {
disabled @0;
inactive @1; # No speed limit set or not enabled by parameter.
preActive @2;
pending @3; # Awaiting new speed limit.
adapting @4; # Reducing speed to match new speed limit.
active @5; # Cruising at speed limit.
}
}
enum LongitudinalPlanSource {
cruise @0;
sccVision @1;
sccMap @2;
speedLimitAssist @3;
}
struct E2eAlerts {
greenLightAlert @0 :Bool;
leadDepartAlert @1 :Bool;
}
} }
struct OnroadEventSP @0xda96579883444c35 { struct OnroadEventSP @0xda96579883444c35 {
@@ -333,22 +172,12 @@ struct OnroadEventSP @0xda96579883444c35 {
experimentalModeSwitched @14; experimentalModeSwitched @14;
wrongCarModeAlertOnly @15; wrongCarModeAlertOnly @15;
pedalPressedAlertOnly @16; pedalPressedAlertOnly @16;
laneTurnLeft @17;
laneTurnRight @18;
speedLimitPreActive @19;
speedLimitActive @20;
speedLimitChanged @21;
speedLimitPending @22;
e2eChime @23;
} }
} }
struct CarParamsSP @0x80ae746ee2596b11 { struct CarParamsSP @0x80ae746ee2596b11 {
flags @0 :UInt32; # flags for car specific quirks in sunnypilot flags @0 :UInt32; # flags for car specific quirks in sunnypilot
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
pcmCruiseSpeed @3 :Bool;
intelligentCruiseButtonManagementAvailable @4 :Bool;
enableGasInterceptor @5 :Bool;
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl; neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
@@ -368,24 +197,10 @@ struct CarControlSP @0xa5cd762cd951a455 {
params @1 :List(Param); params @1 :List(Param);
leadOne @2 :LeadData; leadOne @2 :LeadData;
leadTwo @3 :LeadData; leadTwo @3 :LeadData;
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
struct Param { struct Param {
key @0 :Text; key @0 :Text;
type @2 :ParamType; value @1 :Text;
value @3 :Data;
valueDEPRECATED @1 :Text; # The data type change may cause issues with backwards compatibility.
}
enum ParamType {
string @0;
bool @1;
int @2;
float @3;
time @4;
json @5;
bytes @6;
} }
} }
@@ -432,7 +247,6 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
} }
struct CarStateSP @0xb86e6369214c01c8 { struct CarStateSP @0xb86e6369214c01c8 {
speedLimit @0 :Float32;
} }
struct LiveMapDataSP @0xf416ec09499d9d19 { struct LiveMapDataSP @0xf416ec09499d9d19 {
@@ -444,14 +258,7 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
roadName @5 :Text; roadName @5 :Text;
} }
struct ModelDataV2SP @0xa1680744031fdb2d { struct CustomReserved9 @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
enum TurnDirection {
none @0;
turnLeft @1;
turnRight @2;
}
} }
struct CustomReserved10 @0xcb9fd56c7057593a { struct CustomReserved10 @0xcb9fd56c7057593a {
+3 -3
View File
@@ -585,6 +585,7 @@ struct PandaState @0xa7649e2575e4591e {
heartbeatLost @22 :Bool; heartbeatLost @22 :Bool;
interruptLoad @25 :Float32; interruptLoad @25 :Float32;
fanPower @28 :UInt8; fanPower @28 :UInt8;
fanStallCount @34 :UInt8;
spiErrorCount @33 :UInt16; spiErrorCount @33 :UInt16;
@@ -713,7 +714,6 @@ struct PandaState @0xa7649e2575e4591e {
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED; usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
safetyParamDEPRECATED @20 :Int16; safetyParamDEPRECATED @20 :Int16;
safetyParam2DEPRECATED @26 :UInt32; safetyParam2DEPRECATED @26 :UInt32;
fanStallCountDEPRECATED @34 :UInt8;
} }
struct PeripheralState { struct PeripheralState {
@@ -2631,7 +2631,7 @@ struct Event {
backupManagerSP @113 :Custom.BackupManagerSP; backupManagerSP @113 :Custom.BackupManagerSP;
carStateSP @114 :Custom.CarStateSP; carStateSP @114 :Custom.CarStateSP;
liveMapDataSP @115 :Custom.LiveMapDataSP; liveMapDataSP @115 :Custom.LiveMapDataSP;
modelDataV2SP @116 :Custom.ModelDataV2SP; customReserved9 @116 :Custom.CustomReserved9;
customReserved10 @136 :Custom.CustomReserved10; customReserved10 @136 :Custom.CustomReserved10;
customReserved11 @137 :Custom.CustomReserved11; customReserved11 @137 :Custom.CustomReserved11;
customReserved12 @138 :Custom.CustomReserved12; customReserved12 @138 :Custom.CustomReserved12;
@@ -2684,7 +2684,7 @@ struct Event {
lateralPlanDEPRECATED @64 :LateralPlan; lateralPlanDEPRECATED @64 :LateralPlan;
navModelDEPRECATED @104 :NavModelData; navModelDEPRECATED @104 :NavModelData;
uiPlanDEPRECATED @106 :UiPlan; uiPlanDEPRECATED @106 :UiPlan;
liveLocationKalman @72 :LiveLocationKalman; liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED); liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED); onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
} }
+1 -1
View File
@@ -33,7 +33,7 @@ MessageContext message_context;
struct SubMaster::SubMessage { struct SubMaster::SubMessage {
std::string name; std::string name;
SubSocket *socket = nullptr; SubSocket *socket = nullptr;
float freq = 0.0f; int freq = 0;
bool updated = false, alive = false, valid = false, ignore_alive; bool updated = false, alive = false, valid = false, ignore_alive;
uint64_t rcv_time = 0, rcv_frame = 0; uint64_t rcv_time = 0, rcv_frame = 0;
void *allocated_msg_reader = nullptr; void *allocated_msg_reader = nullptr;
+2 -4
View File
@@ -88,8 +88,6 @@ _services: dict[str, tuple] = {
"carControlSP": (True, 100., 10), "carControlSP": (True, 100., 10),
"carStateSP": (True, 100., 10), "carStateSP": (True, 100., 10),
"liveMapDataSP": (True, 1., 1), "liveMapDataSP": (True, 1., 1),
"modelDataV2SP": (True, 20.),
"liveLocationKalman": (True, 20.),
# debug # debug
"uiDebug": (True, 0., 1), "uiDebug": (True, 0., 1),
@@ -122,12 +120,12 @@ def build_header():
h += "#include <map>\n" h += "#include <map>\n"
h += "#include <string>\n" h += "#include <string>\n"
h += "struct service { std::string name; bool should_log; float frequency; int decimation; };\n" h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n"
h += "static std::map<std::string, service> services = {\n" h += "static std::map<std::string, service> services = {\n"
for k, v in SERVICE_LIST.items(): for k, v in SERVICE_LIST.items():
should_log = "true" if v.should_log else "false" should_log = "true" if v.should_log else "false"
decimation = -1 if v.decimation is None else v.decimation decimation = -1 if v.decimation is None else v.decimation
h += ' { "%s", {"%s", %s, %f, %d}},\n' % \ h += ' { "%s", {"%s", %s, %d, %d}},\n' % \
(k, k, should_log, v.frequency, decimation) (k, k, should_log, v.frequency, decimation)
h += "};\n" h += "};\n"
+1 -1
View File
@@ -1 +1 @@
#define DEFAULT_MODEL "TCPv3 + gWMv9 (Default)" #define DEFAULT_MODEL "Down To Ride (Default)"
+8 -51
View File
@@ -73,9 +73,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}}, {"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}},
{"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}}, {"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}},
{"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}}, {"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}},
{"LastUpdateRouteCount", {PERSISTENT, INT, "0"}}, {"LastUpdateRouteCount", {PERSISTENT, INT}},
{"LastUpdateTime", {PERSISTENT, TIME}}, {"LastUpdateTime", {PERSISTENT, TIME}},
{"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT, "0.0"}}, {"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT}},
{"LiveDelay", {PERSISTENT | BACKUP, BYTES}}, {"LiveDelay", {PERSISTENT | BACKUP, BYTES}},
{"LiveParameters", {PERSISTENT, JSON}}, {"LiveParameters", {PERSISTENT, JSON}},
{"LiveParametersV2", {PERSISTENT, BYTES}}, {"LiveParametersV2", {PERSISTENT, BYTES}},
@@ -94,6 +94,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_StorageMissing", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
@@ -145,33 +146,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}}, {"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}}, {"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}}, {"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}}, {"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}}, {"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
{"HideVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}}, {"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"IsReleaseSpBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"LastGPSPositionLLK", {PERSISTENT, STRING}},
{"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}}, {"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}}, {"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}}, {"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
{"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"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}}, {"sunnypilot_ui", {PERSISTENT, BOOL, "1"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params // MADS params
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}}, {"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
@@ -183,7 +168,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}}, {"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}}, {"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}}, {"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}}, {"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}}, {"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
@@ -197,8 +181,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SunnylinkCache_Users", {PERSISTENT, STRING}}, {"SunnylinkCache_Users", {PERSISTENT, STRING}},
{"SunnylinkDongleId", {PERSISTENT, STRING}}, {"SunnylinkDongleId", {PERSISTENT, STRING}},
{"SunnylinkdPid", {PERSISTENT, INT}}, {"SunnylinkdPid", {PERSISTENT, INT}},
{"SunnylinkEnabled", {PERSISTENT, BOOL, "1"}}, {"SunnylinkEnabled", {PERSISTENT, BOOL}},
{"SunnylinkTempFault", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0"}},
// Backup Manager params // Backup Manager params
{"BackupManager_CreateBackup", {PERSISTENT, BOOL}}, {"BackupManager_CreateBackup", {PERSISTENT, BOOL}},
@@ -206,23 +189,18 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// sunnypilot car specific params // sunnypilot car specific params
{"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}}, {"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}},
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
// sunnypilot model params // model panel params
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}}, {"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}}, {"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}}, {"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
// mapd // mapd
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}}, {"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
{"MapdVersion", {PERSISTENT, STRING}}, {"MapdVersion", {PERSISTENT, STRING, ""}},
{"MapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT, "0.0"}}, {"MapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT, "0.0"}},
{"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}},
@@ -238,26 +216,5 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OsmStateName", {PERSISTENT, STRING, "All"}}, {"OsmStateName", {PERSISTENT, STRING, "All"}},
{"OsmStateTitle", {PERSISTENT, STRING}}, {"OsmStateTitle", {PERSISTENT, STRING}},
{"OsmWayTest", {PERSISTENT, STRING}}, {"OsmWayTest", {PERSISTENT, STRING}},
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}}, {"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING, ""}},
{"RoadNameToggle", {PERSISTENT, STRING}},
// Speed Limit
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
{"SpeedLimitOffsetType", {PERSISTENT | BACKUP, INT, "0"}},
{"SpeedLimitPolicy", {PERSISTENT | BACKUP, INT, "3"}},
{"SpeedLimitValueOffset", {PERSISTENT | BACKUP, INT, "0"}},
// Smart Cruise Control
{"MapTargetVelocities", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
{"SmartCruiseControlMap", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SmartCruiseControlVision", {PERSISTENT | BACKUP, BOOL, "0"}},
// Torque lateral control custom params
{"CustomTorqueParams", {PERSISTENT | BACKUP , BOOL}},
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
}; };
+2 -5
View File
@@ -14,7 +14,8 @@ class PIDController:
if isinstance(self._k_d, Number): if isinstance(self._k_d, Number):
self._k_d = [[0], [self._k_d]] self._k_d = [[0], [self._k_d]]
self.set_limits(pos_limit, neg_limit) self.pos_limit = pos_limit
self.neg_limit = neg_limit
self.i_rate = 1.0 / rate self.i_rate = 1.0 / rate
self.speed = 0.0 self.speed = 0.0
@@ -40,10 +41,6 @@ class PIDController:
self.f = 0.0 self.f = 0.0
self.control = 0 self.control = 0
def set_limits(self, pos_limit, neg_limit):
self.pos_limit = pos_limit
self.neg_limit = neg_limit
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False): def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
self.speed = speed self.speed = speed
self.p = float(error) * self.k_p self.p = float(error) * self.k_p
-15
View File
@@ -1,6 +1,4 @@
import subprocess import subprocess
from contextlib import contextmanager
from subprocess import Popen, PIPE, TimeoutExpired
def run_cmd(cmd: list[str], cwd=None, env=None) -> str: def run_cmd(cmd: list[str], cwd=None, env=None) -> str:
@@ -13,16 +11,3 @@ def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> st
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return default return default
@contextmanager
def managed_proc(cmd: list[str], env: dict[str, str]):
proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE)
try:
yield proc
finally:
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except TimeoutExpired:
proc.kill()
+1 -3
View File
@@ -15,8 +15,6 @@
#include "common/version.h" #include "common/version.h"
#include "system/hardware/hw.h" #include "system/hardware/hw.h"
#include "sunnypilot/common/version.h"
class SwaglogState { class SwaglogState {
public: public:
SwaglogState() { SwaglogState() {
@@ -58,7 +56,7 @@ public:
if (char* daemon_name = getenv("MANAGER_DAEMON")) { if (char* daemon_name = getenv("MANAGER_DAEMON")) {
ctx_j["daemon"] = daemon_name; ctx_j["daemon"] = daemon_name;
} }
ctx_j["version"] = SUNNYPILOT_VERSION; ctx_j["version"] = COMMA_VERSION;
ctx_j["dirty"] = !getenv("CLEAN"); ctx_j["dirty"] = !getenv("CLEAN");
ctx_j["device"] = Hardware::get_name(); ctx_j["device"] = Hardware::get_name();
} }
+1 -1
View File
@@ -6,7 +6,7 @@ from openpilot.common.markdown import parse_markdown
class TestMarkdown: class TestMarkdown:
def test_all_release_notes(self): def test_all_release_notes(self):
with open(os.path.join(BASEDIR, "CHANGELOG.md")) as f: with open(os.path.join(BASEDIR, "RELEASES.md")) as f:
release_notes = f.read().split("\n\n") release_notes = f.read().split("\n\n")
assert len(release_notes) > 10 assert len(release_notes) > 10
+1 -3
View File
@@ -9,8 +9,6 @@
#include "system/hardware/hw.h" #include "system/hardware/hw.h"
#include "third_party/json11/json11.hpp" #include "third_party/json11/json11.hpp"
#include "sunnypilot/common/version.h"
std::string daemon_name = "testy"; std::string daemon_name = "testy";
std::string dongle_id = "test_dongle_id"; std::string dongle_id = "test_dongle_id";
int LINE_NO = 0; int LINE_NO = 0;
@@ -55,7 +53,7 @@ void recv_log(int thread_cnt, int thread_msg_cnt) {
REQUIRE(ctx["dongle_id"].string_value() == dongle_id); REQUIRE(ctx["dongle_id"].string_value() == dongle_id);
REQUIRE(ctx["dirty"].bool_value() == true); REQUIRE(ctx["dirty"].bool_value() == true);
REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION); REQUIRE(ctx["version"].string_value() == COMMA_VERSION);
std::string device = Hardware::get_name(); std::string device = Hardware::get_name();
REQUIRE(ctx["device"].string_value() == device); REQUIRE(ctx["device"].string_value() == device);
-1
View File
@@ -36,7 +36,6 @@ const double MS_TO_KPH = 3.6;
const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE; const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE;
const double METER_TO_MILE = KM_TO_MILE / 1000.0; const double METER_TO_MILE = KM_TO_MILE / 1000.0;
const double METER_TO_FOOT = 3.28084; const double METER_TO_FOOT = 3.28084;
const double METER_TO_KM = 1. / 1000.0;
#define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1)) #define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1))
+18 -32
View File
@@ -4,13 +4,12 @@
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.
# 339 Supported Cars # 325 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>&nbsp;|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>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
@@ -21,10 +20,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2017">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2018-21">Buy Here</a></sub></details>|||
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Malibu Non-ACC 2016-23">Buy Here</a></sub></details>|||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Silverado 1500 2020-21">Buy Here</a></sub></details>|||
|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Trailblazer 2021-22">Buy Here</a></sub></details>||| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Trailblazer 2021-22">Buy Here</a></sub></details>|||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>||| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>|||
@@ -76,25 +72,19 @@ A supported vehicle is one that just works when you install a comma device. All
|Genesis|GV80 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV80 2023">Buy Here</a></sub></details>||| |Genesis|GV80 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>|| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>||| |Honda|Accord 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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</sup>](#footnotes)|0 mph|2 mph[<sup>5</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>5</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>||| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025">Buy Here</a></sub></details>||| |Honda|Civic Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector + Honda Clarity Proxy Board<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://shop.retropilot.org/product/honda-clarity-proxy-board-kit">Buy Here</a></sub></details>|||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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</sup>](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
@@ -102,12 +92,9 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|HR-V 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|Insight 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|Honda|Inspire 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|Honda|N-Box 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|Honda|Odyssey 2021-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-25">Buy Here</a></sub></details>|||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>||| |Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>||| |Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>||| |Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>||| |Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
@@ -132,7 +119,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>||| |Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||| |Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2020">Buy Here</a></sub></details>||| |Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2020">Buy Here</a></sub></details>|||
|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022-23">Buy Here</a></sub></details>||| |Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>||
@@ -168,7 +155,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|EV6 (without HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>||| |Kia|EV6 (without HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte 2019-21">Buy Here</a></sub></details>||| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte 2019-21">Buy Here</a></sub></details>|||
|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte 2022-23">Buy Here</a></sub></details>||| |Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte 2022-23">Buy Here</a></sub></details>|||
|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2019">Buy Here</a></sub></details>|||
|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K5 2021-24">Buy Here</a></sub></details>||| |Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K5 2021-24">Buy Here</a></sub></details>|||
|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K5 Hybrid 2020-22">Buy Here</a></sub></details>|||
|Kia|K8 Hybrid (with HDA II) 2023[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia K8 Hybrid (with HDA II) 2023">Buy Here</a></sub></details>|||
@@ -239,20 +225,20 @@ A supported vehicle is one that just works when you install a comma device. All
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<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>|
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>||| |SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>||| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>||| |Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2017-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|Forester 2017-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|Forester 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>||| |Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2015-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|Legacy 2015-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|Legacy 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2015-17|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2015-17|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|Fabia 2022-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>17</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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Š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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)||| |Š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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Škoda|Karoq 2019-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>||| |Škoda|Karoq 2019-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
@@ -265,7 +251,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>||| |Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>||| |Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>||| |Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>||| |Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW4) 2024[<sup>10</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024">Buy Here</a></sub></details>|||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Avalon 2016">Buy Here</a></sub></details>|||
+2 -2
View File
@@ -6,7 +6,7 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu
### Getting Started ### Getting Started
* Set up your [development environment](/tools/) * Setup your [development environment](../tools/)
* Join our [Discord](https://discord.comma.ai) * Join our [Discord](https://discord.comma.ai)
* Docs are at https://docs.comma.ai and https://blog.comma.ai * Docs are at https://docs.comma.ai and https://blog.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's lot of bounties that don't require a comma 3X or a car. There's lot of bounties that don't require a comma 3/3X or a car.
## Pull Requests ## Pull Requests
-30
View File
@@ -1,30 +0,0 @@
# Debugging Panda Safety with Replay Drive + LLDB
## 1. Start the debugger in VS Code
* Select **Replay drive + Safety LLDB**.
* Enter the route or segment when prompted.
[<img src="https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604">](https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604)
## 2. Attach LLDB
* When prompted, pick the running **`replay_drive` process**.
* ⚠️ Attach quickly, or `replay_drive` will start consuming messages.
> [!TIP]
> Add a Python breakpoint at the start of `replay_drive.py` to pause execution and give yourself time to attach LLDB.
## 3. Set breakpoints in VS Code
Breakpoints can be set directly in `modes/xxx.h` (or any C file).
No extra LLDB commands are required — just place breakpoints in the editor.
## 4. Resume execution
Once attached, you can step through both Python (on the replay) and C safety code as CAN logs are replayed.
> [!NOTE]
> * Use short routes for quicker iteration.
> * Pause `replay_drive` early to avoid wasting log messages.
## Video
View a demo of this workflow on the PR that added it: https://github.com/commaai/openpilot/pull/36055#issue-3352911578
+4 -14
View File
@@ -16,7 +16,7 @@ industry standards of safety for Level 2 Driver Assistance Systems. In particula
ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf) ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf)
released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/)) released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/))
on parts of openpilot that are safety relevant. We also perform software-in-the-loop, on parts of openpilot that are safety relevant. We also perform software-in-the-loop,
hardware-in-the-loop, and in-vehicle tests before each software release. hardware-in-the-loop and in-vehicle tests before each software release.
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
ensuring two main safety requirements. ensuring two main safety requirements.
@@ -29,18 +29,8 @@ ensuring two main safety requirements.
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety). For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation. **Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
not fully meeting the above requirements.
--- [^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
### Forks of openpilot
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
* If your fork modifies any of the code in `opendbc/safety/`:
* your fork cannot use the openpilot trademark
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
Failure to comply with these standards will get you and your users banned from comma.ai servers.
**comma.ai strongly discourages the use of openpilot forks with safety code either missing or not fully meeting the above requirements.**
+4 -4
View File
@@ -1,11 +1,11 @@
# connect to a comma 3X # connect to a comma 3/3X
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). A comma 3/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 both the comma three and 3X, the serial console is accessible from the main OBD-C port. On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
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. Connect the comma 3/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.
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. 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.
@@ -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 on the comma 3X. > The default port for ADB is 5555 on the comma 3/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).
+1 -1
View File
@@ -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 3X* *Hardware required: jungle and comma 3/3X*
1. Connect your PC to a jungle. 1. Connect your PC to a jungle.
2. 2.
+3 -3
View File
@@ -1,11 +1,11 @@
# Turn the speed blue # Turn the speed blue
*A getting started guide for openpilot development* *A getting started guide for openpilot development*
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 setup on your computer and make some changes to openpilot's UI.
And if you have a comma 3X, we'll deploy the change to your device for testing. And if you have a comma 3/3X, we'll deploy the change to your device for testing.
## 1. Set up your development environment ## 1. Setup your development environment
Run this to clone openpilot and install all the dependencies: Run this to clone openpilot and install all the dependencies:
```bash ```bash
+1 -1
View File
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="13.1" export AGNOS_VERSION="12.6"
fi fi
export STAGING_ROOT="/data/safe_staging" export STAGING_ROOT="/data/safe_staging"
-17
View File
@@ -1,20 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# On any failure, run the fallback launcher
trap 'exec ./launch_chffrplus.sh' ERR
C3_LAUNCH_SH="./sunnypilot/system/hardware/c3/launch_chffrplus.sh"
MODEL="$(tr -d '\0' < "/sys/firmware/devicetree/base/model")"
export MODEL
if [ "$MODEL" = "comma tici" ]; then
# Force a failure if the launcher doesn't exist
[ -x "$C3_LAUNCH_SH" ] || false
# If it exists, run it
exec "$C3_LAUNCH_SH"
fi
exec ./launch_chffrplus.sh exec ./launch_chffrplus.sh
+1 -1
View File
@@ -21,7 +21,7 @@ nav:
- What is openpilot?: getting-started/what-is-openpilot.md - What is openpilot?: getting-started/what-is-openpilot.md
- How-to: - How-to:
- Turn the speed blue: how-to/turn-the-speed-blue.md - Turn the speed blue: how-to/turn-the-speed-blue.md
- Connect to a comma 3X: how-to/connect-to-comma.md - Connect to a comma 3/3X: how-to/connect-to-comma.md
# - Make your first pull request: how-to/make-first-pr.md # - Make your first pull request: how-to/make-first-pr.md
#- Replay a drive: how-to/replay-a-drive.md #- Replay a drive: how-to/replay-a-drive.md
- Concepts: - Concepts:
+1 -1
Submodule panda updated: 69ab12ee2a...0e7a3fd8cf
+3 -8
View File
@@ -36,9 +36,6 @@ dependencies = [
"pyopenssl < 24.3.0", "pyopenssl < 24.3.0",
"pyaudio", "pyaudio",
# ubloxd (TODO: just use struct)
"kaitaistruct",
# panda # panda
"libusb1", "libusb1",
"spidev; platform_system == 'Linux'", "spidev; platform_system == 'Linux'",
@@ -104,9 +101,8 @@ dev = [
"av", "av",
"azure-identity", "azure-identity",
"azure-storage-blob", "azure-storage-blob",
"dbus-next", # TODO: remove once we moved everything to jeepney "dbus-next",
"dictdiffer", "dictdiffer",
"jeepney",
"matplotlib", "matplotlib",
"opencv-python-headless", "opencv-python-headless",
"parameterized >=0.8, <0.9", "parameterized >=0.8, <0.9",
@@ -124,7 +120,6 @@ dev = [
tools = [ tools = [
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')", "metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')",
"dearpygui>=2.1.0",
] ]
[project.urls] [project.urls]
@@ -162,6 +157,7 @@ testpaths = [
"system/camerad", "system/camerad",
"system/hardware", "system/hardware",
"system/loggerd", "system/loggerd",
"system/proclogd",
"system/tests", "system/tests",
"system/ubloxd", "system/ubloxd",
"system/webrtc", "system/webrtc",
@@ -177,7 +173,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" 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"
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/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*" skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*"
[tool.mypy] [tool.mypy]
python_version = "3.11" python_version = "3.11"
@@ -251,7 +247,6 @@ exclude = [
"teleoprtc_repo", "teleoprtc_repo",
"third_party", "third_party",
"*.ipynb", "*.ipynb",
"generated",
] ]
lint.flake8-implicit-str-concat.allow-multiline = false lint.flake8-implicit-str-concat.allow-multiline = false
+12 -13
View File
@@ -3,34 +3,33 @@
``` ```
## release checklist ## release checklist
### Go to staging **Go to `devel-staging`**
- [ ] make a GitHub issue to track release - [ ] make issue to track release
- [ ] create release master branch
- [ ] update RELEASES.md - [ ] update RELEASES.md
- [ ] bump version on master: `common/version.h` and `RELEASES.md` - [ ] trigger new nightly build: https://github.com/commaai/openpilot/actions/workflows/release.yaml
- [ ] update `devel-staging`: `git reset --hard origin/__nightly`
- [ ] build new userdata partition from `release3-staging` - [ ] build new userdata partition from `release3-staging`
- [ ] open a pull request from `devel-staging` to `devel`
- [ ] post on Discord, tag `@release crew` - [ ] post on Discord, tag `@release crew`
Updating staging: **Go to `devel`**
1. either rebase on master or cherry-pick changes - [ ] bump version on master: `common/version.h` and `RELEASES.md`
2. run this to update: `BRANCH=devel-staging release/build_devel.sh` - [ ] before merging the pull request, test the following:
3. build new userdata partition from `release3-staging`
### Go to release
- [ ] before going to release, test the following:
- [ ] update from previous release -> new release - [ ] update from previous release -> new release
- [ ] update from new release -> previous release - [ ] update from new release -> previous release
- [ ] fresh install with `openpilot-test.comma.ai` - [ ] fresh install with `openpilot-test.comma.ai`
- [ ] drive on fresh install - [ ] drive on fresh install
- [ ] no submodules or LFS - [ ] no submodules or LFS
- [ ] check sentry, MTBF, etc. - [ ] check sentry, MTBF, etc.
- [ ] stress test passes in production - [ ] stress test in production
**Go to `release3`**
- [ ] publish the blog post - [ ] publish the blog post
- [ ] `git reset --hard origin/release3-staging` - [ ] `git reset --hard origin/release3-staging`
- [ ] tag the release: `git tag v0.X.X <commit-hash> && git push origin v0.X.X` - [ ] tag the release: `git tag v0.X.X <commit-hash> && git push origin v0.X.X`
- [ ] create GitHub release - [ ] create GitHub release
- [ ] final test install on `openpilot.comma.ai` - [ ] final test install on `openpilot.comma.ai`
- [ ] update factory provisioning - [ ] update factory provisioning
- [ ] close out milestone and issue - [ ] close out milestone
- [ ] post on Discord, X, etc. - [ ] post on Discord, X, etc.
``` ```
@@ -17,23 +17,28 @@ rm -rf $TARGET_DIR
mkdir -p $TARGET_DIR mkdir -p $TARGET_DIR
cd $TARGET_DIR cd $TARGET_DIR
cp -r $SOURCE_DIR/.git $TARGET_DIR cp -r $SOURCE_DIR/.git $TARGET_DIR
pre-commit uninstall || true
echo "[-] setting up stripped branch sync T=$SECONDS" echo "[-] bringing __nightly and devel in sync T=$SECONDS"
cd $TARGET_DIR cd $TARGET_DIR
# tmp branch git fetch --depth 1 origin __nightly
git checkout --orphan tmp git fetch --depth 1 origin devel
git checkout -f --track origin/__nightly
git reset --hard __nightly
git checkout __nightly
git reset --hard origin/devel
git clean -xdff
git lfs uninstall
# remove everything except .git # remove everything except .git
echo "[-] erasing old sunnypilot T=$SECONDS" echo "[-] erasing old sunnypilot T=$SECONDS"
git submodule deinit -f --all
git rm -rf --cached .
find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \;
# cleanup before the copy # reset source tree
cd $SOURCE_DIR cd $SOURCE_DIR
git clean -xdff git clean -xdff
git submodule foreach --recursive git clean -xdff
# do the files copy # do the files copy
echo "[-] copying files T=$SECONDS" echo "[-] copying files T=$SECONDS"
@@ -42,14 +47,13 @@ cp -pR --parents $(./release/release_files.py) $TARGET_DIR/
# in the directory # in the directory
cd $TARGET_DIR cd $TARGET_DIR
rm -rf .git/modules/
rm -f panda/board/obj/panda.bin.signed rm -f panda/board/obj/panda.bin.signed
# include source commit hash and build date in commit # include source commit hash and build date in commit
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD) GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD)
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
VERSION=$(cat $SOURCE_DIR/sunnypilot/common/version.h | awk -F\" '{print $2}') VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}')
echo -n "$GIT_HASH" > git_src_commit echo -n "$GIT_HASH" > git_src_commit
echo -n "$GIT_COMMIT_DATE" > git_src_commit_date echo -n "$GIT_COMMIT_DATE" > git_src_commit_date
@@ -81,7 +85,7 @@ fi
if [ ! -z "$BRANCH" ]; then if [ ! -z "$BRANCH" ]; then
echo "[-] Pushing to $BRANCH T=$SECONDS" echo "[-] Pushing to $BRANCH T=$SECONDS"
git push -f origin tmp:$BRANCH git push -f origin __nightly:$BRANCH
fi fi
echo "[-] done T=$SECONDS, ready at $TARGET_DIR" echo "[-] done T=$SECONDS, ready at $TARGET_DIR"
+1 -1
View File
@@ -39,7 +39,7 @@ cd $BUILD_DIR
rm -f panda/board/obj/panda.bin.signed rm -f panda/board/obj/panda.bin.signed
rm -f panda/board/obj/panda_h7.bin.signed rm -f panda/board/obj/panda_h7.bin.signed
VERSION=$(cat sunnypilot/common/version.h | awk -F[\"-] '{print $2}') VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}')
echo "[-] committing version $VERSION T=$SECONDS" echo "[-] committing version $VERSION T=$SECONDS"
git add -f . git add -f .
git commit -a -m "openpilot v$VERSION release" git commit -a -m "openpilot v$VERSION release"
+16 -9
View File
@@ -30,7 +30,7 @@ if [ -z "$GIT_ORIGIN" ]; then
fi fi
# "Tagging" # "Tagging"
echo "#define SUNNYPILOT_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/sunnypilot/common/version.h echo "#define COMMA_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/common/version.h
## set git identity ## set git identity
#source $DIR/identity.sh #source $DIR/identity.sh
@@ -51,19 +51,26 @@ git fetch origin $DEV_BRANCH || (git checkout -b $DEV_BRANCH && git commit --all
echo "[-] committing version $VERSION T=$SECONDS" echo "[-] committing version $VERSION T=$SECONDS"
git add -f . git add -f .
git commit -a -m "sunnypilot v$VERSION release"
git branch --set-upstream-to=origin/$DEV_BRANCH
# include source commit hash and build date in commit # include source commit hash and build date in commit
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/sunnypilot/common/version.h) SP_VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}')
# Commit with detailed message # Add built files to git
git commit -a -m "sunnypilot v$VERSION git add -f .
version: sunnypilot v$SP_VERSION (${EXTRA_VERSION_IDENTIFIER}) if [ "$EXTRA_VERSION_IDENTIFIER" = "-release" ] || [ "$EXTRA_VERSION_IDENTIFIER" = "-staging" ]; then
date: $DATETIME export VERSION=${VERSION%"$EXTRA_VERSION_IDENTIFIER"}
master commit: $GIT_HASH git commit --amend -m "sunnypilot v$VERSION"
" else
git branch --set-upstream-to=origin/$DEV_BRANCH git commit --amend -m "sunnypilot v$VERSION
version: sunnypilot v$SP_VERSION release
date: $DATETIME
master commit: $GIT_HASH
"
fi
git branch -m $DEV_BRANCH git branch -m $DEV_BRANCH
# Push! # Push!
+1 -1
View File
@@ -14,7 +14,7 @@ def setup_argument_parser():
parser.add_argument('--pr-data', type=str, help='PR data in JSON format') parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
parser.add_argument('--source-branch', type=str, default='master', parser.add_argument('--source-branch', type=str, default='master',
help='Source branch for merging') help='Source branch for merging')
parser.add_argument('--target-branch', type=str, default='master-dev-test', parser.add_argument('--target-branch', type=str, default='master-dev-c3-new-test',
help='Target branch for merging') help='Target branch for merging')
parser.add_argument('--squash-script-path', type=str, required=True, parser.add_argument('--squash-script-path', type=str, required=True,
help='Path to the squash_and_merge.py script') help='Path to the squash_and_merge.py script')
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dbfa5858c0a672411ffdc691efdecb06d01ae458cc1df409bcf3fdeaa4756f72
size 34638
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:db9671bb03e01f119bba1eb6cc0507e0f039ac4e5b7f9f839a87071c52e86e56
size 44416
+4 -6
View File
@@ -71,7 +71,7 @@ class Car:
def __init__(self, CI=None, RI=None) -> None: def __init__(self, CI=None, RI=None) -> None:
self.can_sock = messaging.sub_sock('can', timeout=20) self.can_sock = messaging.sub_sock('can', timeout=20)
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'] + ['carControlSP', 'longitudinalPlanSP']) self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'] + ['carControlSP'])
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'] + ['carParamsSP', 'carStateSP']) self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'] + ['carParamsSP', 'carStateSP'])
self.can_rcv_cum_timeout_counter = 0 self.can_rcv_cum_timeout_counter = 0
@@ -88,7 +88,6 @@ class Car:
self.can_callbacks = can_comm_callbacks(self.can_sock, self.pm.sock['sendcan']) self.can_callbacks = can_comm_callbacks(self.can_sock, self.pm.sock['sendcan'])
is_release = self.params.get_bool("IsReleaseBranch") is_release = self.params.get_bool("IsReleaseBranch")
is_release_sp = self.params.get_bool("IsReleaseSpBranch")
if CI is None: if CI is None:
# wait for one pandaState and one CAN packet # wait for one pandaState and one CAN packet
@@ -111,7 +110,7 @@ class Car:
init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params) init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params)
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params, self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params,
fixed_fingerprint, init_params_list_sp, is_release_sp) fixed_fingerprint, init_params_list_sp)
sunnypilot_interfaces.setup_interfaces(self.CI, self.params) sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
self.CP = self.CI.CP self.CP = self.CI.CP
@@ -125,7 +124,7 @@ class Car:
self.CP.alternativeExperience = 0 self.CP.alternativeExperience = 0
# mads # mads
set_alternative_experience(self.CP, self.CP_SP, self.params) set_alternative_experience(self.CP, self.params)
set_car_specific_params(self.CP, self.CP_SP, self.params) set_car_specific_params(self.CP, self.CP_SP, self.params)
# Dynamic Experimental Control # Dynamic Experimental Control
@@ -180,7 +179,7 @@ class Car:
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes) self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
self.mock_carstate = MockCarState() self.mock_carstate = MockCarState()
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP) self.v_cruise_helper = VCruiseHelper(self.CP)
self.is_metric = self.params.get_bool("IsMetric") self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") self.experimental_mode = self.params.get_bool("ExperimentalMode")
@@ -217,7 +216,6 @@ class Car:
if can_rcv_valid and REPLAY: if can_rcv_valid and REPLAY:
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
self.v_cruise_helper.update_speed_limit_assist(self.is_metric, self.sm['longitudinalPlanSP'])
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric) self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
if self.sm['carControl'].enabled and not self.CC_prev.enabled: if self.sm['carControl'].enabled and not self.CC_prev.enabled:
# Use CarState w/ buttons from the step selfdrived enables on # Use CarState w/ buttons from the step selfdrived enables on
+5 -15
View File
@@ -30,8 +30,8 @@ CRUISE_INTERVAL_SIGN = {
class VCruiseHelper(VCruiseHelperSP): class VCruiseHelper(VCruiseHelperSP):
def __init__(self, CP, CP_SP): def __init__(self, CP):
VCruiseHelperSP.__init__(self, CP, CP_SP) VCruiseHelperSP.__init__(self)
self.CP = CP self.CP = CP
self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET
@@ -46,14 +46,10 @@ class VCruiseHelper(VCruiseHelperSP):
def update_v_cruise(self, CS, enabled, is_metric): def update_v_cruise(self, CS, enabled, is_metric):
self.v_cruise_kph_last = self.v_cruise_kph self.v_cruise_kph_last = self.v_cruise_kph
self.get_minimum_set_speed(is_metric)
if CS.cruiseState.available: if CS.cruiseState.available:
_enabled = self.update_enabled_state(CS, enabled) if not self.CP.pcmCruise:
if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled):
# if stock cruise is completely disabled, then we can use our own set speed logic # if stock cruise is completely disabled, then we can use our own set speed logic
self._update_v_cruise_non_pcm(CS, _enabled, is_metric) self._update_v_cruise_non_pcm(CS, enabled, is_metric)
self.update_speed_limit_assist_v_cruise_non_pcm()
self.v_cruise_cluster_kph = self.v_cruise_kph self.v_cruise_cluster_kph = self.v_cruise_kph
self.update_button_timers(CS, enabled) self.update_button_timers(CS, enabled)
else: else:
@@ -105,12 +101,6 @@ class VCruiseHelper(VCruiseHelperSP):
if not self.button_change_states[button_type]["enabled"]: if not self.button_change_states[button_type]["enabled"]:
return return
# Speed Limit Assist for Non PCM long cars.
# True: Disallow set speed changes when user confirmed the target set speed during preActive state
# False: Allow set speed changes as SLA is not requesting user confirmation
if self.update_speed_limit_assist_pre_active_confirmed(button_type):
return
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta) long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
@@ -121,7 +111,7 @@ class VCruiseHelper(VCruiseHelperSP):
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH) self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), self.v_cruise_min, V_CRUISE_MAX) self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
def update_button_timers(self, CS, enabled): def update_button_timers(self, CS, enabled):
# increment timer for buttons still pressed # increment timer for buttons still pressed
+1 -4
View File
@@ -57,11 +57,8 @@ def convert_carControlSP(struct: capnp.lib.capnp._DynamicStructReader) -> struct
struct_dataclass = structs.CarControlSP(**remove_deprecated({k: v for k, v in struct_dict.items() if not isinstance(k, dict)})) struct_dataclass = structs.CarControlSP(**remove_deprecated({k: v for k, v in struct_dict.items() if not isinstance(k, dict)}))
struct_dataclass.mads = structs.ModularAssistiveDrivingSystem(**remove_deprecated(struct_dict.get('mads', {}))) struct_dataclass.mads = structs.ModularAssistiveDrivingSystem(**remove_deprecated(struct_dict.get('mads', {})))
# struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])] struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {}))) struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {}))) struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
struct_dataclass.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(
**remove_deprecated(struct_dict.get('intelligentCruiseButtonManagement', {}))
)
return struct_dataclass return struct_dataclass
+46 -5
View File
@@ -1,12 +1,15 @@
import os import os
import math
import hypothesis.strategies as st import hypothesis.strategies as st
from hypothesis import Phase, given, settings from hypothesis import Phase, given, settings
from parameterized import parameterized from parameterized import parameterized
from cereal import car, custom from cereal import car, custom
from opendbc.car import DT_CTRL from opendbc.car import DT_CTRL
from opendbc.car.car_helpers import interfaces
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface_args
from opendbc.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
from opendbc.car.mock.values import CAR as MOCK from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.values import PLATFORMS from opendbc.car.values import PLATFORMS
from openpilot.selfdrive.car.helpers import convert_carControlSP from openpilot.selfdrive.car.helpers import convert_carControlSP
@@ -18,6 +21,11 @@ from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
ALL_ECUS = {ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}
ALL_ECUS |= {ecu for config in FW_QUERY_CONFIGS.values() for ecu in config.extra_ecus}
ALL_REQUESTS = {tuple(r.request) for config in FW_QUERY_CONFIGS.values() for r in config.requests}
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60')) MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60'))
@@ -29,10 +37,43 @@ class TestCarInterfaces:
phases=(Phase.reuse, Phase.generate, Phase.shrink)) phases=(Phase.reuse, Phase.generate, Phase.shrink))
@given(data=st.data()) @given(data=st.data())
def test_car_interfaces(self, car_name, data): def test_car_interfaces(self, car_name, data):
car_interface = get_fuzzy_car_interface(car_name, data.draw) CarInterface = interfaces[car_name]
car_params = car_interface.CP.as_reader()
car_params_sp = car_interface.CP_SP args = get_fuzzy_car_interface_args(data.draw)
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
alpha_long=args['alpha_long'], is_release=False, docs=False)
car_params_sp = CarInterface.get_params_sp(car_params, car_name, args['fingerprints'], args['car_fw'],
alpha_long=args['alpha_long'], docs=False)
car_params = car_params.as_reader()
car_interface = CarInterface(car_params, car_params_sp)
sunnypilot_interfaces.setup_interfaces(car_interface) sunnypilot_interfaces.setup_interfaces(car_interface)
assert car_params
assert car_params_sp
assert car_interface
assert car_params.mass > 1
assert car_params.wheelbase > 0
# centerToFront is center of gravity to front wheels, assert a reasonable range
assert car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7
assert car_params.maxLateralAccel > 0
# Longitudinal sanity checks
assert len(car_params.longitudinalTuning.kpV) == len(car_params.longitudinalTuning.kpBP)
assert len(car_params.longitudinalTuning.kiV) == len(car_params.longitudinalTuning.kiBP)
# Lateral sanity checks
if car_params.steerControlType != CarParams.SteerControlType.angle:
tune = car_params.lateralTuning
if tune.which() == 'pid':
if car_name != MOCK.MOCK:
assert not math.isnan(tune.pid.kf) and tune.pid.kf > 0
assert len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)
assert len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)
elif tune.which() == 'torque':
assert not math.isnan(tune.torque.kf) and tune.torque.kf > 0
assert not math.isnan(tune.torque.friction) and tune.torque.friction > 0
cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True) cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True)
cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True) cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)
@@ -60,7 +101,7 @@ class TestCarInterfaces:
# Test controller initialization # Test controller initialization
# TODO: wait until card refactor is merged to run controller a few times, # TODO: wait until card refactor is merged to run controller a few times,
# hypothesis also slows down significantly with just one more message draw # hypothesis also slows down significantly with just one more message draw
LongControl(car_params, car_params_sp) LongControl(car_params)
if car_params.steerControlType == CarParams.SteerControlType.angle: if car_params.steerControlType == CarParams.SteerControlType.angle:
LatControlAngle(car_params, car_params_sp, car_interface) LatControlAngle(car_params, car_params_sp, car_interface)
elif car_params.lateralTuning.which() == 'pid': elif car_params.lateralTuning.which() == 'pid':
+4 -5
View File
@@ -5,7 +5,7 @@ import numpy as np
from parameterized import parameterized_class from parameterized import parameterized_class
from cereal import log from cereal import log
from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
from cereal import car, custom from cereal import car
from openpilot.common.constants import CV from openpilot.common.constants import CV
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
@@ -44,13 +44,12 @@ class TestCruiseSpeed:
assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s' assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s'
# TODO: test pcmCruise and pcmCruiseSpeed # TODO: test pcmCruise
@parameterized_class(('pcm_cruise', 'pcm_cruise_speed'), [(False, True)]) @parameterized_class(('pcm_cruise',), [(False,)])
class TestVCruiseHelper: class TestVCruiseHelper:
def setup_method(self): def setup_method(self):
self.CP = car.CarParams(pcmCruise=self.pcm_cruise) self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
self.CP_SP = custom.CarParamsSP(pcmCruiseSpeed=self.pcm_cruise_speed) self.v_cruise_helper = VCruiseHelper(self.CP)
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
self.reset_cruise_speed_state() self.reset_cruise_speed_state()
def reset_cruise_speed_state(self): def reset_cruise_speed_state(self):
+1 -1
View File
@@ -151,7 +151,7 @@ class TestCarModelBase(unittest.TestCase):
cls.CarInterface = interfaces[cls.platform] cls.CarInterface = interfaces[cls.platform]
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False) cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False)
cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False) cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, docs=False)
assert cls.CP assert cls.CP
assert cls.CP_SP assert cls.CP_SP
assert cls.CP.carFingerprint == cls.platform assert cls.CP.carFingerprint == cls.platform
+5 -10
View File
@@ -58,7 +58,7 @@ class Controls(ControlsExt, ModelStateBase):
self.pose_calibrator = PoseCalibrator() self.pose_calibrator = PoseCalibrator()
self.calibrated_pose: Pose | None = None self.calibrated_pose: Pose | None = None
self.LoC = LongControl(self.CP, self.CP_SP) self.LoC = LongControl(self.CP)
self.VM = VehicleModel(self.CP) self.VM = VehicleModel(self.CP)
self.LaC: LatControl self.LaC: LatControl
if self.CP.steerControlType == car.CarParams.SteerControlType.angle: if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
@@ -95,10 +95,9 @@ class Controls(ControlsExt, ModelStateBase):
self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered, self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered,
torque_params.frictionCoefficientFiltered) torque_params.frictionCoefficientFiltered)
self.LaC.extension.update_limits()
self.LaC.extension.update_model_v2(self.sm['modelV2']) self.LaC.extension.update_model_v2(self.sm['modelV2'])
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
self.LaC.extension.update_lateral_lag(self.lat_delay) self.LaC.extension.update_lateral_lag(self.lat_delay)
long_plan = self.sm['longitudinalPlan'] long_plan = self.sm['longitudinalPlan']
@@ -115,8 +114,7 @@ class Controls(ControlsExt, ModelStateBase):
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
(not standstill or self.CP.steerAtStandstill) (not standstill or self.CP.steerAtStandstill)
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and \ CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
(self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
actuators = CC.actuators actuators = CC.actuators
actuators.longControlState = self.LoC.long_control_state actuators.longControlState = self.LoC.long_control_state
@@ -132,7 +130,7 @@ class Controls(ControlsExt, ModelStateBase):
self.LoC.reset() self.LoC.reset()
# accel PID loop # accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, self.CP_SP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS) pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits)) actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits))
# Steering PID loop and lateral MPC # Steering PID loop and lateral MPC
@@ -168,7 +166,7 @@ class Controls(ControlsExt, ModelStateBase):
CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist() CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist()
CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist() CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist()
CC.cruiseControl.override = CC.enabled and not CC.longActive and (self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed) CC.cruiseControl.override = CC.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise) CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise)
CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop
@@ -233,9 +231,6 @@ class Controls(ControlsExt, ModelStateBase):
while not evt.is_set(): while not evt.is_set():
self.get_params_sp() self.get_params_sp()
if self.CP.lateralTuning.which() == 'torque':
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
time.sleep(0.1) time.sleep(0.1)
def run(self): def run(self):
+5 -29
View File
@@ -1,12 +1,10 @@
from cereal import log, custom from cereal import log
from openpilot.common.constants import CV from openpilot.common.constants import CV
from openpilot.common.realtime import DT_MDL from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
LaneChangeState = log.LaneChangeState LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection LaneChangeDirection = log.LaneChangeDirection
TurnDirection = custom.ModelDataV2SP.TurnDirection
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
LANE_CHANGE_TIME_MAX = 10. LANE_CHANGE_TIME_MAX = 10.
@@ -32,12 +30,6 @@ DESIRES = {
}, },
} }
TURN_DESIRES = {
TurnDirection.none: log.Desire.none,
TurnDirection.turnLeft: log.Desire.turnLeft,
TurnDirection.turnRight: log.Desire.turnRight,
}
class DesireHelper: class DesireHelper:
def __init__(self): def __init__(self):
@@ -49,25 +41,13 @@ class DesireHelper:
self.prev_one_blinker = False self.prev_one_blinker = False
self.desire = log.Desire.none self.desire = log.Desire.none
self.alc = AutoLaneChangeController(self) self.alc = AutoLaneChangeController(self)
self.lane_turn_controller = LaneTurnController(self)
self.lane_turn_direction = TurnDirection.none
@staticmethod
def get_lane_change_direction(CS):
return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right
def update(self, carstate, lateral_active, lane_change_prob): def update(self, carstate, lateral_active, lane_change_prob):
self.alc.update_params() self.alc.update_params()
self.lane_turn_controller.update_params()
v_ego = carstate.vEgo v_ego = carstate.vEgo
one_blinker = carstate.leftBlinker != carstate.rightBlinker one_blinker = carstate.leftBlinker != carstate.rightBlinker
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN
# Lane turn controller update
self.lane_turn_controller.update_lane_turn(blindspot_left=carstate.leftBlindspot, blindspot_right=carstate.rightBlindspot,
left_blinker=carstate.leftBlinker, right_blinker=carstate.rightBlinker, v_ego=v_ego)
self.lane_turn_direction = self.lane_turn_controller.get_turn_direction()
if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF: if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF:
self.lane_change_state = LaneChangeState.off self.lane_change_state = LaneChangeState.off
self.lane_change_direction = LaneChangeDirection.none self.lane_change_direction = LaneChangeDirection.none
@@ -76,13 +56,12 @@ class DesireHelper:
if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed: if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
self.lane_change_state = LaneChangeState.preLaneChange self.lane_change_state = LaneChangeState.preLaneChange
self.lane_change_ll_prob = 1.0 self.lane_change_ll_prob = 1.0
# Initialize lane change direction to prevent UI alert flicker
self.lane_change_direction = self.get_lane_change_direction(carstate)
# LaneChangeState.preLaneChange # LaneChangeState.preLaneChange
elif self.lane_change_state == LaneChangeState.preLaneChange: elif self.lane_change_state == LaneChangeState.preLaneChange:
# Update lane change direction # Set lane change direction
self.lane_change_direction = self.get_lane_change_direction(carstate) self.lane_change_direction = LaneChangeDirection.left if \
carstate.leftBlinker else LaneChangeDirection.right
torque_applied = carstate.steeringPressed and \ torque_applied = carstate.steeringPressed and \
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or ((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
@@ -127,10 +106,7 @@ class DesireHelper:
self.prev_one_blinker = one_blinker self.prev_one_blinker = one_blinker
if self.lane_turn_direction != TurnDirection.none: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
self.desire = TURN_DESIRES[self.lane_turn_direction]
else:
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
# Send keep pulse once per second during LaneChangeStart.preLaneChange # Send keep pulse once per second during LaneChangeStart.preLaneChange
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting): if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
+22 -30
View File
@@ -3,6 +3,7 @@ import numpy as np
from cereal import log from cereal import log
from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction
from opendbc.car.interfaces import LatControlInputs
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.common.pid import PIDController from openpilot.common.pid import PIDController
@@ -28,30 +29,19 @@ class LatControlTorque(LatControl):
def __init__(self, CP, CP_SP, CI): def __init__(self, CP, CP_SP, CI):
super().__init__(CP, CP_SP, CI) super().__init__(CP, CP_SP, CI)
self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_params = CP.lateralTuning.torque.as_builder()
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki, self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
k_f=self.torque_params.kf) k_f=self.torque_params.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.update_limits() self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
self.extension = LatControlTorqueExt(self, CP, CP_SP, CI) self.extension = LatControlTorqueExt(self, CP, CP_SP)
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
self.torque_params.latAccelFactor = latAccelFactor self.torque_params.latAccelFactor = latAccelFactor
self.torque_params.latAccelOffset = latAccelOffset self.torque_params.latAccelOffset = latAccelOffset
self.torque_params.friction = friction self.torque_params.friction = friction
self.update_limits()
def update_limits(self):
self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited): def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
# Override torque params from extension
if self.extension.update_override_torque_params(self.torque_params):
self.update_limits()
pid_log = log.ControlsState.LateralTorqueState.new_message() pid_log = log.ControlsState.LateralTorqueState.new_message()
if not active: if not active:
output_torque = 0.0 output_torque = 0.0
@@ -60,8 +50,10 @@ class LatControlTorque(LatControl):
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
desired_lateral_accel = desired_curvature * CS.vEgo ** 2 desired_lateral_accel = desired_curvature * CS.vEgo ** 2
# desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
# desired_lateral_jerk = desired_curvature_rate * CS.vEgo ** 2
actual_lateral_accel = actual_curvature * CS.vEgo ** 2 actual_lateral_accel = actual_curvature * CS.vEgo ** 2
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
@@ -69,33 +61,33 @@ class LatControlTorque(LatControl):
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
measurement = actual_lateral_accel + low_speed_factor * actual_curvature measurement = actual_lateral_accel + low_speed_factor * actual_curvature
gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation
torque_from_setpoint = self.torque_from_lateral_accel(LatControlInputs(setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly gravity_adjusted=False)
pid_log.error = float(setpoint - measurement) torque_from_measurement = self.torque_from_lateral_accel(LatControlInputs(measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
ff = gravity_adjusted_lateral_accel gravity_adjusted=False)
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll pid_log.error = float(torque_from_setpoint - torque_from_measurement)
ff -= self.torque_params.latAccelOffset ff = self.torque_from_lateral_accel(LatControlInputs(gravity_adjusted_lateral_accel, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
gravity_adjusted=True)
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
# Lateral acceleration torque controller extension updates
# Overrides stock ff and pid_log.error
ff, pid_log = self.extension.update(CS, VM, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_lataccel = self.pid.update(pid_log.error, output_torque = self.pid.update(pid_log.error,
feedforward=ff, feedforward=ff,
speed=CS.vEgo, speed=CS.vEgo,
freeze_integrator=freeze_integrator) freeze_integrator=freeze_integrator)
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
# Lateral acceleration torque controller extension updates
# Overrides pid_log.error and output_torque
pid_log, output_torque = self.extension.update(CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature, steer_limited_by_safety, output_torque)
pid_log.active = True pid_log.active = True
pid_log.p = float(self.pid.p) pid_log.p = float(self.pid.p)
pid_log.i = float(self.pid.i) pid_log.i = float(self.pid.i)
pid_log.d = float(self.pid.d) pid_log.d = float(self.pid.d)
pid_log.f = float(self.pid.f) pid_log.f = float(self.pid.f)
pid_log.output = float(-output_torque) # TODO: log lat accel? pid_log.output = float(-output_torque)
pid_log.actualLateralAccel = float(actual_lateral_accel) pid_log.actualLateralAccel = float(actual_lateral_accel)
pid_log.desiredLateralAccel = float(desired_lateral_accel) pid_log.desiredLateralAccel = float(desired_lateral_accel)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited)) pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
+3 -7
View File
@@ -10,11 +10,8 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
LongCtrlState = car.CarControl.Actuators.LongControlState LongCtrlState = car.CarControl.Actuators.LongControlState
def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego, def long_control_state_trans(CP, active, long_control_state, v_ego,
should_stop, brake_pressed, cruise_standstill): should_stop, brake_pressed, cruise_standstill):
# Gas Interceptor
cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor
stopping_condition = should_stop stopping_condition = should_stop
starting_condition = (not should_stop and starting_condition = (not should_stop and
not cruise_standstill and not cruise_standstill and
@@ -48,9 +45,8 @@ def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego,
return long_control_state return long_control_state
class LongControl: class LongControl:
def __init__(self, CP, CP_SP): def __init__(self, CP):
self.CP = CP self.CP = CP
self.CP_SP = CP_SP
self.long_control_state = LongCtrlState.off self.long_control_state = LongCtrlState.off
self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV), self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV), (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
@@ -65,7 +61,7 @@ class LongControl:
self.pid.neg_limit = accel_limits[0] self.pid.neg_limit = accel_limits[0]
self.pid.pos_limit = accel_limits[1] self.pid.pos_limit = accel_limits[1]
self.long_control_state = long_control_state_trans(self.CP, self.CP_SP, active, self.long_control_state, CS.vEgo, self.long_control_state = long_control_state_trans(self.CP, active, self.long_control_state, CS.vEgo,
should_stop, CS.brakePressed, should_stop, CS.brakePressed,
CS.cruiseState.standstill) CS.cruiseState.standstill)
if self.long_control_state == LongCtrlState.off: if self.long_control_state == LongCtrlState.off:
+7 -10
View File
@@ -51,12 +51,12 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
class LongitudinalPlanner(LongitudinalPlannerSP): class LongitudinalPlanner(LongitudinalPlannerSP):
def __init__(self, CP, CP_SP, init_v=0.0, init_a=0.0, dt=DT_MDL): def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP self.CP = CP
self.mpc = LongitudinalMpc(dt=dt) self.mpc = LongitudinalMpc(dt=dt)
# TODO remove mpc modes when TR released # TODO remove mpc modes when TR released
self.mpc.mode = 'acc' self.mpc.mode = 'acc'
LongitudinalPlannerSP.__init__(self, self.CP, CP_SP, self.mpc) LongitudinalPlannerSP.__init__(self, self.CP, self.mpc)
self.fcw = False self.fcw = False
self.dt = dt self.dt = dt
self.allow_throttle = True self.allow_throttle = True
@@ -93,12 +93,12 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
return x, v, a, j, throttle_prob return x, v, a, j, throttle_prob
def update(self, sm): def update(self, sm):
mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
if not self.mlsim: if not self.mlsim:
self.mpc.mode = mode self.mpc.mode = self.mode
LongitudinalPlannerSP.update(self, sm) LongitudinalPlannerSP.update(self, sm)
if dec_mpc_mode := self.get_mpc_mode(): if dec_mpc_mode := self.get_mpc_mode():
mode = dec_mpc_mode self.mode = dec_mpc_mode
if not self.mlsim: if not self.mlsim:
self.mpc.mode = dec_mpc_mode self.mpc.mode = dec_mpc_mode
@@ -123,7 +123,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
# No change cost when user is controlling the speed, or when standstill # No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill) prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if mode == 'acc': if self.mode == 'acc':
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
@@ -146,9 +146,6 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast]) clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp) accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
# Get new v_cruise and a_desired from Smart Cruise Control and Speed Limit Assist
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
if force_slow_decel: if force_slow_decel:
v_cruise = 0.0 v_cruise = 0.0
@@ -176,7 +173,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop output_should_stop_e2e = sm['modelV2'].action.shouldStop
if mode == 'acc' or not self.mlsim: if self.mode == 'acc' or not self.mlsim:
output_a_target = output_a_target_mpc output_a_target = output_a_target_mpc
self.output_should_stop = output_should_stop_mpc self.output_should_stop = output_should_stop_mpc
else: else:
@@ -5,7 +5,6 @@ from opendbc.car.car_helpers import interfaces
from opendbc.car.honda.values import CAR as HONDA from opendbc.car.honda.values import CAR as HONDA
from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.toyota.values import CAR as TOYOTA
from opendbc.car.nissan.values import CAR as NISSAN from opendbc.car.nissan.values import CAR as NISSAN
from opendbc.car.gm.values import CAR as GM
from opendbc.car.vehicle_model import VehicleModel from opendbc.car.vehicle_model import VehicleModel
from openpilot.selfdrive.car.helpers import convert_to_capnp from openpilot.selfdrive.car.helpers import convert_to_capnp
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
@@ -18,8 +17,7 @@ from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfac
class TestLatControl: class TestLatControl:
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), @parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (NISSAN.NISSAN_LEAF, LatControlAngle)])
(NISSAN.NISSAN_LEAF, LatControlAngle), (GM.CHEVROLET_BOLT_EUV, LatControlTorque)])
def test_saturation(self, car_name, controller): def test_saturation(self, car_name, controller):
CarInterface = interfaces[car_name] CarInterface = interfaces[car_name]
CP = CarInterface.get_non_essential_params(car_name) CP = CarInterface.get_non_essential_params(car_name)
+4 -13
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from cereal import car, custom from cereal import car
from openpilot.common.gps import get_gps_location_service
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import Priority, config_realtime_process from openpilot.common.realtime import Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
@@ -17,22 +16,14 @@ def main():
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("plannerd got CarParams: %s", CP.brand) cloudlog.info("plannerd got CarParams: %s", CP.brand)
cloudlog.info("plannerd is waiting for CarParamsSP")
CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP)
cloudlog.info("plannerd got CarParamsSP")
gps_location_service = get_gps_location_service(params)
ldw = LaneDepartureWarning() ldw = LaneDepartureWarning()
longitudinal_planner = LongitudinalPlanner(CP, CP_SP) longitudinal_planner = LongitudinalPlanner(CP)
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP']) pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState', sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
'liveMapDataSP', 'carStateSP', gps_location_service], poll='modelV2')
poll='carState')
while True: while True:
sm.update() sm.update()
longitudinal_planner.sla.update_car_state(sm['carState'])
if sm.updated['modelV2']: if sm.updated['modelV2']:
longitudinal_planner.update(sm) longitudinal_planner.update(sm)
longitudinal_planner.publish(sm, pm) longitudinal_planner.publish(sm, pm)
+12 -15
View File
@@ -1,4 +1,4 @@
from cereal import car, custom from cereal import car
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
@@ -8,52 +8,49 @@ class TestLongControlStateTransition:
def test_stay_stopped(self): def test_stay_stopped(self):
CP = car.CarParams.new_message() CP = car.CarParams.new_message()
CP_SP = custom.CarParamsSP.new_message()
active = True active = True
current_state = LongCtrlState.stopping current_state = LongCtrlState.stopping
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=True, brake_pressed=False, cruise_standstill=False) should_stop=True, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=True, cruise_standstill=False) should_stop=False, brake_pressed=True, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=True) should_stop=False, brake_pressed=False, cruise_standstill=True)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0, next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid assert next_state == LongCtrlState.pid
active = False active = False
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0, next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.off assert next_state == LongCtrlState.off
def test_engage(): def test_engage():
CP = car.CarParams.new_message() CP = car.CarParams.new_message()
CP_SP = custom.CarParamsSP.new_message()
active = True active = True
current_state = LongCtrlState.off current_state = LongCtrlState.off
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=True, brake_pressed=False, cruise_standstill=False) should_stop=True, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=True, cruise_standstill=False) should_stop=False, brake_pressed=True, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=True) should_stop=False, brake_pressed=False, cruise_standstill=True)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid assert next_state == LongCtrlState.pid
def test_starting(): def test_starting():
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5) CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
CP_SP = custom.CarParamsSP.new_message()
active = True active = True
current_state = LongCtrlState.starting current_state = LongCtrlState.starting
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1, next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.starting assert next_state == LongCtrlState.starting
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0, next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid assert next_state == LongCtrlState.pid
@@ -1,70 +0,0 @@
import numpy as np
from cereal import car, messaging
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
from opendbc.car import structs
from opendbc.car.lateral import get_friction, FRICTION_THRESHOLD
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS
np.random.seed(0)
LA_ERR_STD = 1.0
INPUT_NOISE_STD = 0.08
V_EGO = 30.0
WARMUP_BUCKET_POINTS = (1.5*MIN_BUCKET_POINTS).astype(int)
STRAIGHT_ROAD_LA_BOUNDS = (0.02, 0.03)
ROLL_BIAS_DEG = 2.0
ROLL_COMPENSATION_BIAS = ACCELERATION_DUE_TO_GRAVITY*float(np.sin(np.deg2rad(ROLL_BIAS_DEG)))
TORQUE_TUNE = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=0.0, friction=0.2)
TORQUE_TUNE_BIASED = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=-ROLL_COMPENSATION_BIAS, friction=0.2)
def generate_inputs(torque_tune, la_err_std, input_noise_std=None):
rng = np.random.default_rng(0)
steer_torques = np.concat([rng.uniform(bnd[0], bnd[1], pts) for bnd, pts in zip(STEER_BUCKET_BOUNDS, WARMUP_BUCKET_POINTS, strict=True)])
la_errs = rng.normal(scale=la_err_std, size=steer_torques.size)
frictions = np.array([get_friction(la_err, 0.0, FRICTION_THRESHOLD, torque_tune) for la_err in la_errs])
lat_accels = torque_tune.latAccelFactor*steer_torques + torque_tune.latAccelOffset + frictions
if input_noise_std is not None:
steer_torques += rng.normal(scale=input_noise_std, size=steer_torques.size)
lat_accels += rng.normal(scale=input_noise_std, size=steer_torques.size)
return steer_torques, lat_accels
def get_warmed_up_estimator(steer_torques, lat_accels):
est = TorqueEstimator(car.CarParams())
for steer_torque, lat_accel in zip(steer_torques, lat_accels, strict=True):
est.filtered_points.add_point(steer_torque, lat_accel)
return est
def simulate_straight_road_msgs(est):
carControl = messaging.new_message('carControl').carControl
carOutput = messaging.new_message('carOutput').carOutput
carState = messaging.new_message('carState').carState
livePose = messaging.new_message('livePose').livePose
carControl.latActive = True
carState.vEgo = V_EGO
carState.steeringPressed = False
ts = DT_MDL*np.arange(2*POINTS_PER_BUCKET)
steer_torques = np.concat((np.linspace(-0.03, -0.02, POINTS_PER_BUCKET), np.linspace(0.02, 0.03, POINTS_PER_BUCKET)))
lat_accels = TORQUE_TUNE.latAccelFactor * steer_torques
for t, steer_torque, lat_accel in zip(ts, steer_torques, lat_accels, strict=True):
carOutput.actuatorsOutput.torque = float(-steer_torque)
livePose.orientationNED.x = float(np.deg2rad(ROLL_BIAS_DEG))
livePose.angularVelocityDevice.z = float(lat_accel / V_EGO)
for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)):
est.handle_log(t, which, msg)
def test_estimated_offset():
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
msg = est.get_msg()
# TODO add lataccelfactor and friction check when we have more accurate estimates
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
def test_straight_road_roll_bias():
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
simulate_straight_road_msgs(est)
msg = est.get_msg()
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
+2 -8
View File
@@ -11,7 +11,6 @@ from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.selfdrive.locationd.torqued_ext import TorqueEstimatorExt
HISTORY = 5 # secs HISTORY = 5 # secs
POINTS_PER_BUCKET = 1500 POINTS_PER_BUCKET = 1500
@@ -51,10 +50,9 @@ class TorqueBuckets(PointBuckets):
break break
class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt): class TorqueEstimator(ParameterEstimator):
def __init__(self, CP, decimated=False, track_all_points=False): def __init__(self, CP, decimated=False, track_all_points=False):
ParameterEstimator.__init__(self) super().__init__()
TorqueEstimatorExt.__init__(self, CP)
self.CP = CP self.CP = CP
self.hist_len = int(HISTORY / DT_MDL) self.hist_len = int(HISTORY / DT_MDL)
self.lag = 0.0 self.lag = 0.0
@@ -84,8 +82,6 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
self.calibrator = PoseCalibrator() self.calibrator = PoseCalibrator()
TorqueEstimatorExt.initialize_custom_params(self, decimated)
self.reset() self.reset()
initial_params = { initial_params = {
@@ -264,8 +260,6 @@ def main(demo=False):
t = sm.logMonoTime[which] * 1e-9 t = sm.logMonoTime[which] * 1e-9
estimator.handle_log(t, which, sm[which]) estimator.handle_log(t, which, sm[which])
TorqueEstimatorExt.update_use_params(estimator)
# 4Hz driven by livePose # 4Hz driven by livePose
if sm.frame % 5 == 0: if sm.frame % 5 == 0:
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks())) pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
+6 -3
View File
@@ -13,9 +13,12 @@ class ModelConstants:
META_T_IDXS = [2., 4., 6., 8., 10.] META_T_IDXS = [2., 4., 6., 8., 10.]
# model inputs constants # model inputs constants
N_FRAMES = 2 MODEL_FREQ = 20
MODEL_RUN_FREQ = 20 HISTORY_FREQ = 5
MODEL_CONTEXT_FREQ = 5 # "model_trained_fps" HISTORY_LEN_SECONDS = 5
TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ
FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS
INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS
FEATURE_LEN = 512 FEATURE_LEN = 512
+3 -4
View File
@@ -3,7 +3,6 @@ import capnp
import numpy as np import numpy as np
from cereal import log from cereal import log
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -96,8 +95,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
# action # action
modelV2.action = action modelV2.action = action
# times at X_IDXS of edges and lines # times at X_IDXS of edges and lines aren't used
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data) LINE_T_IDXS: list[float] = []
# lane lines # lane lines
modelV2.init('laneLines', 4) modelV2.init('laneLines', 4)
@@ -150,7 +149,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
meta.hardBrakePredicted = hard_brake_predicted.item() meta.hardBrakePredicted = hard_brake_predicted.item()
# confidence # confidence
if vipc_frame_id % (2*ModelConstants.MODEL_RUN_FREQ) == 0: if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0:
# any disengage prob # any disengage prob
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE] brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE] gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]
+35 -77
View File
@@ -80,64 +80,6 @@ class FrameMeta:
if vipc is not None: if vipc is not None:
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
class InputQueues:
def __init__ (self, model_fps, env_fps, n_frames_input):
assert env_fps % model_fps == 0
assert env_fps >= model_fps
self.model_fps = model_fps
self.env_fps = env_fps
self.n_frames_input = n_frames_input
self.dtypes = {}
self.shapes = {}
self.q = {}
def update_dtypes_and_shapes(self, input_dtypes, input_shapes) -> None:
self.dtypes.update(input_dtypes)
if self.env_fps == self.model_fps:
self.shapes.update(input_shapes)
else:
for k in input_shapes:
shape = list(input_shapes[k])
if 'img' in k:
n_channels = shape[1] // self.n_frames_input
shape[1] = (self.env_fps // self.model_fps + (self.n_frames_input - 1)) * n_channels
else:
shape[1] = (self.env_fps // self.model_fps) * shape[1]
self.shapes[k] = tuple(shape)
def reset(self) -> None:
self.q = {k: np.zeros(self.shapes[k], dtype=self.dtypes[k]) for k in self.dtypes.keys()}
def enqueue(self, inputs:dict[str, np.ndarray]) -> None:
for k in inputs.keys():
if inputs[k].dtype != self.dtypes[k]:
raise ValueError(f'supplied input <{k}({inputs[k].dtype})> has wrong dtype, expected {self.dtypes[k]}')
input_shape = list(self.shapes[k])
input_shape[1] = -1
single_input = inputs[k].reshape(tuple(input_shape))
sz = single_input.shape[1]
self.q[k][:,:-sz] = self.q[k][:,sz:]
self.q[k][:,-sz:] = single_input
def get(self, *names) -> dict[str, np.ndarray]:
if self.env_fps == self.model_fps:
return {k: self.q[k] for k in names}
else:
out = {}
for k in names:
shape = self.shapes[k]
if 'img' in k:
n_channels = shape[1] // (self.env_fps // self.model_fps + (self.n_frames_input - 1))
out[k] = np.concatenate([self.q[k][:, s:s+n_channels] for s in np.linspace(0, shape[1] - n_channels, self.n_frames_input, dtype=int)], axis=1)
elif 'pulse' in k:
# any pulse within interval counts
out[k] = self.q[k].reshape((shape[0], shape[1] * self.model_fps // self.env_fps, self.env_fps // self.model_fps, -1)).max(axis=2)
else:
idxs = np.arange(-1, -shape[1], -self.env_fps // self.model_fps)[::-1]
out[k] = self.q[k][:, idxs]
return out
class ModelState(ModelStateBase): class ModelState(ModelStateBase):
frames: dict[str, DrivingModelFrame] frames: dict[str, DrivingModelFrame]
inputs: dict[str, np.ndarray] inputs: dict[str, np.ndarray]
@@ -160,15 +102,22 @@ class ModelState(ModelStateBase):
self.policy_output_slices = policy_metadata['output_slices'] self.policy_output_slices = policy_metadata['output_slices']
policy_output_size = policy_metadata['output_shapes']['outputs'][1] policy_output_size = policy_metadata['output_shapes']['outputs'][1]
self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.frames = {name: DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) for name in self.vision_input_names}
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
self.full_features_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
self.full_desire = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32)
self.full_prev_desired_curv = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32)
self.temporal_idxs = slice(-1-(ModelConstants.TEMPORAL_SKIP*(ModelConstants.INPUT_HISTORY_BUFFER_LEN-1)), None, ModelConstants.TEMPORAL_SKIP)
# policy inputs # policy inputs
self.numpy_inputs = {k: np.zeros(self.policy_input_shapes[k], dtype=np.float32) for k in self.policy_input_shapes} self.numpy_inputs = {
self.full_input_queues = InputQueues(ModelConstants.MODEL_CONTEXT_FREQ, ModelConstants.MODEL_RUN_FREQ, ModelConstants.N_FRAMES) 'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
for k in ['desire_pulse', 'features_buffer']: 'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32),
self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) 'lateral_control_params': np.zeros((1, ModelConstants.LATERAL_CONTROL_PARAMS_LEN), dtype=np.float32),
self.full_input_queues.reset() 'prev_desired_curv': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32),
'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
}
# img buffers are managed in openCL transform code # img buffers are managed in openCL transform code
self.vision_inputs: dict[str, Tensor] = {} self.vision_inputs: dict[str, Tensor] = {}
@@ -190,10 +139,16 @@ class ModelState(ModelStateBase):
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None: inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge # Model decides when action is completed, so desire input is just a pulse triggered on rising edge
inputs['desire_pulse'][0] = 0 inputs['desire'][0] = 0
new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.prev_desire[:] = inputs['desire_pulse'] self.prev_desire[:] = inputs['desire']
self.full_desire[0,:-1] = self.full_desire[0,1:]
self.full_desire[0,-1] = new_desire
self.numpy_inputs['desire'][:] = self.full_desire.reshape((1,ModelConstants.INPUT_HISTORY_BUFFER_LEN,ModelConstants.TEMPORAL_SKIP,-1)).max(axis=2)
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
self.numpy_inputs['lateral_control_params'][:] = inputs['lateral_control_params']
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names}
if TICI and not USBGPU: if TICI and not USBGPU:
@@ -212,14 +167,18 @@ class ModelState(ModelStateBase):
self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy()
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices))
self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) self.full_features_buffer[0,:-1] = self.full_features_buffer[0,1:]
for k in ['desire_pulse', 'features_buffer']: self.full_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :]
self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k] self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy()
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices))
# TODO model only uses last value now
self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:]
self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :]
self.numpy_inputs['prev_desired_curv'][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs]
combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict}
if SEND_RAW_PRED: if SEND_RAW_PRED:
combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()])
@@ -266,14 +225,14 @@ def main(demo=False):
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})") cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
# messaging # messaging
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"]) pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
publish_state = PublishState() publish_state = PublishState()
params = Params() params = Params()
# setup filter to track dropped frames # setup filter to track dropped frames
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_RUN_FREQ) frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ)
frame_id = 0 frame_id = 0
last_vipc_frame_id = 0 last_vipc_frame_id = 0
run_count = 0 run_count = 0
@@ -340,6 +299,7 @@ def main(demo=False):
if sm.frame % 60 == 0: if sm.frame % 60 == 0:
model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay) model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay)
lat_delay = model.lat_delay + LAT_SMOOTH_SECONDS lat_delay = model.lat_delay + LAT_SMOOTH_SECONDS
lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
@@ -370,8 +330,9 @@ def main(demo=False):
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names} bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names} transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
inputs:dict[str, np.ndarray] = { inputs:dict[str, np.ndarray] = {
'desire_pulse': vec_desire, 'desire': vec_desire,
'traffic_convention': traffic_convention, 'traffic_convention': traffic_convention,
'lateral_control_params': lateral_control_params,
} }
mt1 = time.perf_counter() mt1 = time.perf_counter()
@@ -383,7 +344,6 @@ def main(demo=False):
modelv2_send = messaging.new_message('modelV2') modelv2_send = messaging.new_message('modelV2')
drivingdata_send = messaging.new_message('drivingModelData') drivingdata_send = messaging.new_message('drivingModelData')
posenet_send = messaging.new_message('cameraOdometry') posenet_send = messaging.new_message('cameraOdometry')
mdv2sp_send = messaging.new_message('modelDataV2SP')
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego) action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
prev_action = action prev_action = action
@@ -398,7 +358,6 @@ def main(demo=False):
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
@@ -406,7 +365,6 @@ def main(demo=False):
pm.send('modelV2', modelv2_send) pm.send('modelV2', modelv2_send)
pm.send('drivingModelData', drivingdata_send) pm.send('drivingModelData', drivingdata_send)
pm.send('cameraOdometry', posenet_send) pm.send('cameraOdometry', posenet_send)
pm.send('modelDataV2SP', mdv2sp_send)
last_vipc_frame_id = meta_main.frame_id last_vipc_frame_id = meta_main.frame_id
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:76e7beaa7822219b019a4352ab111d0898abf72bd4b0d9520bd8dc68174e8c31 oid sha256:1af87c38492444521632a0e75839b5684ee46bf255b3474773784bffb9fe4f57
size 13926460 size 15583374
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8f16d548ea4eb5d01518a9e90d4527cd97c31a84bcaf6f695dead8f0015fecc4 oid sha256:c824f68646a3b94f117f01c70dc8316fb466e05fbd42ccdba440b8a8dc86914b
size 46271942 size 46265993
+16 -18
View File
@@ -22,10 +22,9 @@ class Parser:
self.ignore_missing = ignore_missing self.ignore_missing = ignore_missing
def check_missing(self, outs, name): def check_missing(self, outs, name):
missing = name not in outs if name not in outs and not self.ignore_missing:
if missing and not self.ignore_missing:
raise ValueError(f"Missing output {name}") raise ValueError(f"Missing output {name}")
return missing return name not in outs
def parse_categorical_crossentropy(self, name, outs, out_shape=None): def parse_categorical_crossentropy(self, name, outs, out_shape=None):
if self.check_missing(outs, name): if self.check_missing(outs, name):
@@ -85,13 +84,6 @@ class Parser:
outs[name] = pred_mu_final.reshape(final_shape) outs[name] = pred_mu_final.reshape(final_shape)
outs[name + '_stds'] = pred_std_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape)
def is_mhp(self, outs, name, shape):
if self.check_missing(outs, name):
return False
if outs[name].shape[1] == 2 * shape:
return False
return True
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
@@ -102,17 +94,23 @@ class Parser:
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH))
self.parse_binary_crossentropy('meta', outs) self.parse_binary_crossentropy('meta', outs)
self.parse_binary_crossentropy('lead_prob', outs) self.parse_binary_crossentropy('lead_prob', outs)
lead_mhp = self.is_mhp(outs, 'lead', ModelConstants.LEAD_MHP_SELECTION * ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH) if outs['lead'].shape[1] == 2 * ModelConstants.LEAD_MHP_SELECTION *ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH:
lead_in_N, lead_out_N = (ModelConstants.LEAD_MHP_N, ModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0) self.parse_mdn('lead', outs, in_N=0, out_N=0,
lead_out_shape = (ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH) if lead_mhp else \ out_shape=(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH) else:
self.parse_mdn('lead', outs, in_N=lead_in_N, out_N=lead_out_N, out_shape=lead_out_shape) self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
return outs return outs
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH) if outs['plan'].shape[1] == 2 * ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH:
plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) self.parse_mdn('plan', outs, in_N=0, out_N=0,
self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
else:
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
if 'desired_curvature' in outs:
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
return outs return outs
+6 -3
View File
@@ -84,10 +84,12 @@ Panda *connect(std::string serial="", uint32_t index=0) {
panda->set_can_fd_auto(i, true); panda->set_can_fd_auto(i, true);
} }
bool is_supported_panda = std::find(SUPPORTED_PANDA_TYPES.begin(), SUPPORTED_PANDA_TYPES.end(), panda->hw_type) != SUPPORTED_PANDA_TYPES.end(); bool is_deprecated_panda = std::find(DEPRECATED_PANDA_TYPES.begin(),
DEPRECATED_PANDA_TYPES.end(),
panda->hw_type) != DEPRECATED_PANDA_TYPES.end();
if (!is_supported_panda) { if (is_deprecated_panda) {
LOGW("panda %s is not supported (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type)); LOGW("panda %s is deprecated (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
return panda.release(); return panda.release();
} }
@@ -173,6 +175,7 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt)); ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
ps.setInterruptLoad(health.interrupt_load_pkt); ps.setInterruptLoad(health.interrupt_load_pkt);
ps.setFanPower(health.fan_power); ps.setFanPower(health.fan_power);
ps.setFanStallCount(health.fan_stall_count);
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt)); ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
ps.setSpiErrorCount(health.spi_error_count_pkt); ps.setSpiErrorCount(health.spi_error_count_pkt);
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f); ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
+7 -4
View File
@@ -9,10 +9,13 @@
void pandad_main_thread(std::vector<std::string> serials); void pandad_main_thread(std::vector<std::string> serials);
// deprecated devices // deprecated devices
static const std::vector<cereal::PandaState::PandaType> SUPPORTED_PANDA_TYPES = { static const std::vector<cereal::PandaState::PandaType> DEPRECATED_PANDA_TYPES = {
cereal::PandaState::PandaType::RED_PANDA, cereal::PandaState::PandaType::WHITE_PANDA,
cereal::PandaState::PandaType::TRES, cereal::PandaState::PandaType::GREY_PANDA,
cereal::PandaState::PandaType::CUATRO, cereal::PandaState::PandaType::BLACK_PANDA,
cereal::PandaState::PandaType::PEDAL,
cereal::PandaState::PandaType::UNO,
cereal::PandaState::PandaType::RED_PANDA_V2
}; };
+11 -20
View File
@@ -29,12 +29,6 @@ def flash_panda(panda_serial: str) -> Panda:
HARDWARE.recover_internal_panda() HARDWARE.recover_internal_panda()
raise raise
# skip flashing if the detected panda is not supported
supported_panda = check_panda_support(panda)
if not supported_panda:
cloudlog.warning(f"Panda {panda_serial} is not supported (hw_type: {panda.get_type()}), skipping flash...")
return panda
fw_signature = get_expected_signature(panda) fw_signature = get_expected_signature(panda)
internal_panda = panda.is_internal() internal_panda = panda.is_internal()
@@ -42,6 +36,12 @@ def flash_panda(panda_serial: str) -> Panda:
panda_signature = b"" if panda.bootstub else panda.get_signature() panda_signature = b"" if panda.bootstub else panda.get_signature()
cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}") cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}")
# skip flashing if the detected device is deprecated from upstream
hw_type = panda.get_type()
if hw_type in Panda.DEPRECATED_DEVICES:
cloudlog.warning(f"Panda {panda_serial} is deprecated (hw_type: {hw_type}), skipping flash...")
return panda
if panda.bootstub or panda_signature != fw_signature: if panda.bootstub or panda_signature != fw_signature:
cloudlog.info("Panda firmware out of date, update required") cloudlog.info("Panda firmware out of date, update required")
panda.flash() panda.flash()
@@ -67,14 +67,6 @@ def flash_panda(panda_serial: str) -> Panda:
return panda return panda
def check_panda_support(panda) -> bool:
hw_type = panda.get_type()
if hw_type in Panda.SUPPORTED_DEVICES:
return True
return False
def main() -> None: def main() -> None:
# signal pandad to close the relay and exit # signal pandad to close the relay and exit
def signal_handler(signum, frame): def signal_handler(signum, frame):
@@ -99,6 +91,11 @@ def main() -> None:
cloudlog.event("pandad.flash_and_connect", count=count) cloudlog.event("pandad.flash_and_connect", count=count)
params.remove("PandaSignatures") params.remove("PandaSignatures")
# TODO: remove this in the next AGNOS
# wait until USB is up before counting
if time.monotonic() < 35.:
no_internal_panda_count = 0
# Handle missing internal panda # Handle missing internal panda
if no_internal_panda_count > 0: if no_internal_panda_count > 0:
if no_internal_panda_count == 3: if no_internal_panda_count == 3:
@@ -148,12 +145,6 @@ def main() -> None:
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas)) params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
for panda in pandas: for panda in pandas:
# skip health check if the detected panda is not supported
supported_panda = check_panda_support(panda)
if not supported_panda:
cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...")
continue
# check health for lost heartbeat # check health for lost heartbeat
health = panda.health() health = panda.health()
if health["heartbeat_lost"]: if health["heartbeat_lost"]:
Binary file not shown.
+5 -1
View File
@@ -22,6 +22,8 @@ class TestPandad:
if len(Panda.list()) == 0: if len(Panda.list()) == 0:
self._run_test(60) self._run_test(60)
self.spi = HARDWARE.get_device_type() != 'tici'
def teardown_method(self): def teardown_method(self):
managed_processes['pandad'].stop() managed_processes['pandad'].stop()
@@ -104,9 +106,11 @@ class TestPandad:
# - 0.2s pandad -> pandad # - 0.2s pandad -> pandad
# - plus some buffer # - plus some buffer
print("startup times", ts, sum(ts) / len(ts)) print("startup times", ts, sum(ts) / len(ts))
assert 0.1 < (sum(ts)/len(ts)) < 0.7 assert 0.1 < (sum(ts)/len(ts)) < (0.7 if self.spi else 5.0)
def test_protocol_version_check(self): def test_protocol_version_check(self):
if not self.spi:
pytest.skip("SPI test")
# flash old fw # flash old fw
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin") fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
self._flash_bootstub_and_test(fn, expect_mismatch=True) self._flash_bootstub_and_test(fn, expect_mismatch=True)
@@ -6,6 +6,7 @@ import random
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal.services import SERVICE_LIST from cereal.services import SERVICE_LIST
from openpilot.system.hardware import HARDWARE
from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.test.helpers import with_processes
from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages
@@ -15,6 +16,8 @@ JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ
class TestBoarddSpi: class TestBoarddSpi:
@classmethod @classmethod
def setup_class(cls): def setup_class(cls):
if HARDWARE.get_device_type() == 'tici':
pytest.skip("only for spi pandas")
os.environ['STARTED'] = '1' os.environ['STARTED'] = '1'
os.environ['SPI_ERR_PROB'] = '0.001' os.environ['SPI_ERR_PROB'] = '0.001'
if not JUNGLE_SPAM: if not JUNGLE_SPAM:
+4 -5
View File
@@ -29,6 +29,10 @@
"text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.", "text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.",
"severity": 1 "severity": 1
}, },
"Offroad_StorageMissing": {
"text": "NVMe drive not mounted.",
"severity": 1
},
"Offroad_CarUnrecognized": { "Offroad_CarUnrecognized": {
"text": "sunnypilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.", "text": "sunnypilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.",
"severity": 0 "severity": 0
@@ -45,10 +49,5 @@
"text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.", "text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.",
"severity": 1, "severity": 1,
"_comment": "Set extra field to lateral or longitudinal." "_comment": "Set extra field to lateral or longitudinal."
},
"Offroad_TiciSupport": {
"text": "<b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three.",
"severity": 1,
"_comment": "Set extra field to the current branch name."
} }
} }
+1 -1
View File
@@ -83,7 +83,7 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S
f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}", f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
"", "",
AlertStatus.userPrompt, AlertSize.small, AlertStatus.userPrompt, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4) Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4)
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
+13 -35
View File
@@ -21,13 +21,12 @@ from openpilot.selfdrive.selfdrived.helpers import ExcessiveActuationCheck
from openpilot.selfdrive.selfdrived.state import StateMachine from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
from openpilot.system.hardware import HARDWARE
from openpilot.system.version import get_build_metadata from openpilot.system.version import get_build_metadata
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
from openpilot.sunnypilot import get_sanitize_int_param
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
REPLAY = "REPLAY" in os.environ REPLAY = "REPLAY" in os.environ
@@ -44,7 +43,6 @@ LaneChangeDirection = log.LaneChangeDirection
EventName = log.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel SafetyModel = car.CarParams.SafetyModel
TurnDirection = custom.ModelDataV2SP.TurnDirection
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
@@ -88,7 +86,7 @@ class SelfdriveD(CruiseHelper):
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches # TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
self.car_state_sock = messaging.sub_sock('carState', timeout=20) self.car_state_sock = messaging.sub_sock('carState', timeout=20)
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP'] ignore = self.sensor_packets + self.gps_packets + ['alertDebug']
if SIMULATION: if SIMULATION:
ignore += ['driverCameraState', 'managerState'] ignore += ['driverCameraState', 'managerState']
if REPLAY: if REPLAY:
@@ -97,8 +95,7 @@ class SelfdriveD(CruiseHelper):
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay', 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback', 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback'] + \
'modelDataV2SP', 'longitudinalPlanSP'] + \
self.camera_packets + self.sensor_packets + self.gps_packets, self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore, ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL)) ignore_valid=ignore, frequency=int(1/DT_CTRL))
@@ -132,17 +129,18 @@ class SelfdriveD(CruiseHelper):
self.logged_comm_issue = None self.logged_comm_issue = None
self.not_running_prev = None self.not_running_prev = None
self.experimental_mode = False self.experimental_mode = False
self.personality = get_sanitize_int_param( self.personality = self.params.get("LongitudinalPersonality", return_default=True)
"LongitudinalPersonality",
min(log.LongitudinalPersonality.schema.enumerants.values()),
max(log.LongitudinalPersonality.schema.enumerants.values()),
self.params
)
self.recalibrating_seen = False self.recalibrating_seen = False
self.state_machine = StateMachine() self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None) self.rk = Ratekeeper(100, print_delay_threshold=None)
self.ignored_processes = {'mapd', } # some comma three with NVMe experience NVMe dropouts mid-drive that
# cause loggerd to crash on write, so ignore it only on that platform
self.ignored_processes = set()
nvme_expected = os.path.exists('/dev/nvme0n1') or (not os.path.isfile("/persist/comma/living-in-the-moment"))
if HARDWARE.get_device_type() == 'tici' and nvme_expected:
self.ignored_processes = {'loggerd', }
self.ignored_processes.update({'mapd'})
# Determine startup event # Determine startup event
is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote
@@ -164,9 +162,8 @@ class SelfdriveD(CruiseHelper):
self.events_sp_prev = [] self.events_sp_prev = []
self.mads = ModularAssistiveDrivingSystem(self) self.mads = ModularAssistiveDrivingSystem(self)
self.icbm = IntelligentCruiseButtonManagement(self.CP, self.CP_SP)
self.car_events_sp = CarSpecificEventsSP(self.CP, self.CP_SP) self.car_events_sp = CarSpecificEventsSP(self.CP, self.params)
CruiseHelper.__init__(self, self.CP) CruiseHelper.__init__(self, self.CP)
@@ -212,7 +209,6 @@ class SelfdriveD(CruiseHelper):
if not self.CP.notCar: if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events) self.events.add_from_msg(self.sm['driverMonitoringState'].events)
self.events_sp.add_from_msg(self.sm['longitudinalPlanSP'].events)
# Add car events, ignore if CAN isn't valid # Add car events, ignore if CAN isn't valid
if CS.canValid: if CS.canValid:
@@ -269,10 +265,7 @@ class SelfdriveD(CruiseHelper):
if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture: if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture:
self.events.add(EventName.ldw) self.events.add(EventName.ldw)
# ****************************************************************************************** # Check for excessive actuation
# NOTE: To fork maintainers.
# Disabling or nerfing safety features will get you and your users banned from our servers.
# We recommend that you do not change these numbers from the defaults.
if self.sm.updated['liveCalibration']: if self.sm.updated['liveCalibration']:
self.pose_calibrator.feed_live_calib(self.sm['liveCalibration']) self.pose_calibrator.feed_live_calib(self.sm['liveCalibration'])
if self.sm.updated['livePose']: if self.sm.updated['livePose']:
@@ -287,7 +280,6 @@ class SelfdriveD(CruiseHelper):
if self.excessive_actuation: if self.excessive_actuation:
self.events.add(EventName.excessiveActuation) self.events.add(EventName.excessiveActuation)
# ******************************************************************************************
# Handle lane change # Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange: if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
@@ -304,13 +296,6 @@ class SelfdriveD(CruiseHelper):
LaneChangeState.laneChangeFinishing): LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange) self.events.add(EventName.laneChange)
# Handle lane turn
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
if lane_turn_direction == TurnDirection.turnLeft:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
elif lane_turn_direction == TurnDirection.turnRight:
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
for i, pandaState in enumerate(self.sm['pandaStates']): for i, pandaState in enumerate(self.sm['pandaStates']):
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput # All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
if i < len(self.CP.safetyConfigs): if i < len(self.CP.safetyConfigs):
@@ -452,8 +437,6 @@ class SelfdriveD(CruiseHelper):
self.events.add(EventName.personalityChanged) self.events.add(EventName.personalityChanged)
self.experimental_mode_switched = False self.experimental_mode_switched = False
self.icbm.run(CS, self.sm['carControl'], self.sm['longitudinalPlanSP'], self.is_metric)
def data_sample(self): def data_sample(self):
_car_state = messaging.recv_one(self.car_state_sock) _car_state = messaging.recv_one(self.car_state_sock)
CS = _car_state.carState if _car_state else self.CS_prev CS = _car_state.carState if _car_state else self.CS_prev
@@ -558,11 +541,6 @@ class SelfdriveD(CruiseHelper):
mads.active = self.mads.active mads.active = self.mads.active
mads.available = self.mads.enabled_toggle mads.available = self.mads.enabled_toggle
icbm = ss_sp.intelligentCruiseButtonManagement
icbm.state = self.icbm.state
icbm.sendButton = self.icbm.cruise_button
icbm.vTarget = self.icbm.v_target
self.pm.send('selfdriveStateSP', ss_sp_msg) self.pm.send('selfdriveStateSP', ss_sp_msg)
# onroadEventsSP - logged every second or on change # onroadEventsSP - logged every second or on change
+2 -10
View File
@@ -51,9 +51,7 @@ class Plant:
from opendbc.car.honda.values import CAR from opendbc.car.honda.values import CAR
from opendbc.car.honda.interface import CarInterface from opendbc.car.honda.interface import CarInterface
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.HONDA_CIVIC), init_v=self.speed)
CP_SP = CarInterface.get_non_essential_params_sp(CP, CAR.HONDA_CIVIC)
self.planner = LongitudinalPlanner(CP, CP_SP, init_v=self.speed)
@property @property
def current_time(self): def current_time(self):
@@ -69,9 +67,6 @@ class Plant:
lp = messaging.new_message('liveParameters') lp = messaging.new_message('liveParameters')
car_control = messaging.new_message('carControl') car_control = messaging.new_message('carControl')
model = messaging.new_message('modelV2') model = messaging.new_message('modelV2')
car_state_sp = messaging.new_message('carStateSP')
live_map_data_sp = messaging.new_message('liveMapDataSP')
gps_data = messaging.new_message('gpsLocation')
a_lead = (v_lead - self.v_lead_prev)/self.ts a_lead = (v_lead - self.v_lead_prev)/self.ts
self.v_lead_prev = v_lead self.v_lead_prev = v_lead
@@ -138,10 +133,7 @@ class Plant:
'controlsState': control.controlsState, 'controlsState': control.controlsState,
'selfdriveState': ss.selfdriveState, 'selfdriveState': ss.selfdriveState,
'liveParameters': lp.liveParameters, 'liveParameters': lp.liveParameters,
'modelV2': model.modelV2, 'modelV2': model.modelV2}
'carStateSP': car_state_sp.carStateSP,
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
'gpsLocation': gps_data.gpsLocation}
self.planner.update(sm) self.planner.update(sm)
self.acceleration = self.planner.output_a_target self.acceleration = self.planner.output_a_target
self.speed = self.speed + self.acceleration * self.ts self.speed = self.speed + self.acceleration * self.ts
+2 -4
View File
@@ -28,8 +28,7 @@ MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr # 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
# 4. it must return a list of operations to be applied to the logreader (replace, add, delete) # 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
# 5. all migration functions must be independent of each other # 5. all migration functions must be independent of each other
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False, def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False):
live_location_kalman: bool = True):
migrations = [ migrations = [
migrate_sensorEvents, migrate_sensorEvents,
migrate_carParams, migrate_carParams,
@@ -38,6 +37,7 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo
migrate_carOutput, migrate_carOutput,
migrate_controlsState, migrate_controlsState,
migrate_carState, migrate_carState,
migrate_liveLocationKalman,
migrate_liveTracks, migrate_liveTracks,
migrate_driverAssistance, migrate_driverAssistance,
migrate_drivingModelData, migrate_drivingModelData,
@@ -51,8 +51,6 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo
migrations.extend([migrate_pandaStates, migrate_peripheralState]) migrations.extend([migrate_pandaStates, migrate_peripheralState])
if camera_states: if camera_states:
migrations.append(migrate_cameraStates) migrations.append(migrate_cameraStates)
if live_location_kalman:
migrations.append(migrate_liveLocationKalman)
return migrate(lr, migrations) return migrate(lr, migrations)
@@ -496,7 +496,7 @@ CONFIGS = [
pubs=[ pubs=[
"cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState" "cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState"
], ],
subs=["liveLocationKalman", "livePose"], subs=["livePose"],
ignore=["logMonoTime"], ignore=["logMonoTime"],
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"), should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
@@ -595,7 +595,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") ->
if len(live_calibration) > 0: if len(live_calibration) > 0:
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes() custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
if len(live_parameters) > 0: if len(live_parameters) > 0:
custom_params["LiveParametersV2"] = live_parameters[msg_index].as_builder().to_bytes() custom_params["LiveParameters"] = live_parameters[msg_index].as_builder().to_bytes()
if len(live_torque_parameters) > 0: if len(live_torque_parameters) > 0:
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes() custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
+1 -1
View File
@@ -1 +1 @@
afcab1abb62b9d5678342956cced4712f44e909e 543bd2347fa35f8300478a3893fdd0a03a7c1fe6
@@ -63,7 +63,7 @@ segments = [
] ]
# dashcamOnly makes don't need to be tested until a full port is done # dashcamOnly makes don't need to be tested until a full port is done
excluded_interfaces = ["mock", "body", "psa"] excluded_interfaces = ["mock", "body"]
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
+24 -11
View File
@@ -18,6 +18,7 @@ from openpilot.common.timeout import Timeout
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.selfdrived.events import EVENTS, ET from openpilot.selfdrive.selfdrived.events import EVENTS, ET
from openpilot.selfdrive.test.helpers import set_params_enabled, release_only from openpilot.selfdrive.test.helpers import set_params_enabled, release_only
from openpilot.system.hardware import HARDWARE
from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import Paths
from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.logreader import LogReader
from openpilot.tools.lib.log_time_series import msgs_to_time_series from openpilot.tools.lib.log_time_series import msgs_to_time_series
@@ -32,7 +33,7 @@ CPU usage budget
TEST_DURATION = 25 TEST_DURATION = 25
LOG_OFFSET = 8 LOG_OFFSET = 8
MAX_TOTAL_CPU = 300. # total for all 8 cores MAX_TOTAL_CPU = 280. # total for all 8 cores
PROCS = { PROCS = {
# Baseline CPU usage by process # Baseline CPU usage by process
"selfdrive.controls.controlsd": 16.0, "selfdrive.controls.controlsd": 16.0,
@@ -56,20 +57,30 @@ PROCS = {
"selfdrive.ui.soundd": 3.0, "selfdrive.ui.soundd": 3.0,
"selfdrive.ui.feedback.feedbackd": 1.0, "selfdrive.ui.feedback.feedbackd": 1.0,
"selfdrive.monitoring.dmonitoringd": 4.0, "selfdrive.monitoring.dmonitoringd": 4.0,
"system.proclogd": 3.0, "./proclogd": 2.0,
"system.logmessaged": 1.0, "system.logmessaged": 1.0,
"system.tombstoned": 0, "system.tombstoned": 0,
"system.journald": 1.0, "./logcatd": 1.0,
"system.micd": 5.0, "system.micd": 5.0,
"system.timed": 0, "system.timed": 0,
"selfdrive.pandad.pandad": 0, "selfdrive.pandad.pandad": 0,
"system.statsd": 1.0, "system.statsd": 1.0,
"system.loggerd.uploader": 15.0, "system.loggerd.uploader": 15.0,
"system.loggerd.deleter": 1.0, "system.loggerd.deleter": 1.0,
"./pandad": 19.0,
"system.qcomgpsd.qcomgpsd": 1.0,
} }
PROCS.update({
"tici": {
"./pandad": 5.0,
"./ubloxd": 1.0,
"system.ubloxd.pigeond": 6.0,
},
"tizi": {
"./pandad": 19.0,
"system.qcomgpsd.qcomgpsd": 1.0,
}
}.get(HARDWARE.get_device_type(), {}))
TIMINGS = { TIMINGS = {
# rtols: max/min, rsd # rtols: max/min, rsd
"can": [2.5, 0.35], "can": [2.5, 0.35],
@@ -322,18 +333,20 @@ class TestOnroad:
assert np.all(eof_sof_diff > 0) assert np.all(eof_sof_diff > 0)
assert np.all(eof_sof_diff < 50*1e6) assert np.all(eof_sof_diff < 50*1e6)
first_fid = {min(self.ts[c]['frameId']) for c in cams} first_fid = {c: min(self.ts[c]['frameId']) for c in cams}
assert len(first_fid) == 1, "Cameras don't start on same frame ID"
if cam.endswith('CameraState'): if cam.endswith('CameraState'):
# camerad guarantees that all cams start on frame ID 0 # camerad guarantees that all cams start on frame ID 0
# (note loggerd also needs to start up fast enough to catch it) # (note loggerd also needs to start up fast enough to catch it)
assert next(iter(first_fid)) < 100, "Cameras start on frame ID too high" assert set(first_fid.values()) == {0, }, "Cameras don't start on frame ID 0"
else:
# encoder guarantees all cams start on the same frame ID
assert len(set(first_fid.values())) == 1, "Cameras don't start on same frame ID"
# we don't do a full segment rotation, so these might not match exactly # we don't do a full segment rotation, so these might not match exactly
last_fid = {max(self.ts[c]['frameId']) for c in cams} last_fid = {c: max(self.ts[c]['frameId']) for c in cams}
assert max(last_fid) - min(last_fid) < 10 assert max(last_fid.values()) - min(last_fid.values()) < 10
start, end = min(first_fid), min(last_fid) start, end = min(first_fid.values()), min(last_fid.values())
for i in range(end-start): for i in range(end-start):
ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams} ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams}
diff = (max(ts.values()) - min(ts.values())) diff = (max(ts.values()) - min(ts.values()))
+1 -2
View File
@@ -22,9 +22,8 @@ def main():
sm.update() sm.update()
should_send_bookmark = False should_send_bookmark = False
# TODO: https://github.com/commaai/openpilot/issues/36015
# only allow the LKAS button to record feedback when MADS is disabled # only allow the LKAS button to record feedback when MADS is disabled
if False and sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available: if sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available:
for be in sm['carState'].buttonEvents: for be in sm['carState'].buttonEvents:
if be.type == ButtonType.lkas: if be.type == ButtonType.lkas:
if be.pressed: if be.pressed:
+13 -33
View File
@@ -24,13 +24,11 @@ const std::string BRANCH_STR = get_str(BRANCH "?
#define GIT_SSH_URL "git@github.com:commaai/openpilot.git" #define GIT_SSH_URL "git@github.com:commaai/openpilot.git"
#define CONTINUE_PATH "/data/continue.sh" #define CONTINUE_PATH "/data/continue.sh"
const std::string INSTALL_PATH = "/data/openpilot"; const std::string CACHE_PATH = "/data/openpilot.cache";
const std::string VALID_CACHE_PATH = "/data/.openpilot_cache";
#define INSTALL_PATH "/data/openpilot"
#define TMP_INSTALL_PATH "/data/tmppilot" #define TMP_INSTALL_PATH "/data/tmppilot"
const int FONT_SIZE = 120;
extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start"); extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start");
extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end");
extern const uint8_t inter_ttf[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_start"); extern const uint8_t inter_ttf[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_start");
@@ -43,16 +41,6 @@ void run(const char* cmd) {
assert(err == 0); assert(err == 0);
} }
void finishInstall() {
BeginDrawing();
ClearBackground(BLACK);
const char *m = "Finishing install...";
int text_width = MeasureText(m, FONT_SIZE);
DrawTextEx(font, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE);
EndDrawing();
util::sleep_for(60 * 1000);
}
void renderProgress(int progress) { void renderProgress(int progress) {
BeginDrawing(); BeginDrawing();
ClearBackground(BLACK); ClearBackground(BLACK);
@@ -74,11 +62,11 @@ int doInstall() {
} }
// cleanup previous install attempts // cleanup previous install attempts
run("rm -rf " TMP_INSTALL_PATH); run("rm -rf " TMP_INSTALL_PATH " " INSTALL_PATH);
// do the install // do the install
if (util::file_exists(INSTALL_PATH) && util::file_exists(VALID_CACHE_PATH)) { if (util::file_exists(CACHE_PATH)) {
return cachedFetch(INSTALL_PATH); return cachedFetch(CACHE_PATH);
} else { } else {
return freshClone(); return freshClone();
} }
@@ -147,9 +135,7 @@ void cloneFinished(int exitCode) {
run("git submodule update --init"); run("git submodule update --init");
// move into place // move into place
run(("rm -f " + VALID_CACHE_PATH).c_str()); run("mv " TMP_INSTALL_PATH " " INSTALL_PATH);
run(("rm -rf " + INSTALL_PATH).c_str());
run(util::string_format("mv %s %s", TMP_INSTALL_PATH, INSTALL_PATH.c_str()).c_str());
#ifdef INTERNAL #ifdef INTERNAL
run("mkdir -p /data/params/d/"); run("mkdir -p /data/params/d/");
@@ -167,9 +153,9 @@ void cloneFinished(int exitCode) {
param << value; param << value;
param.close(); param.close();
} }
run(("cd " + INSTALL_PATH + " && " run("cd " INSTALL_PATH " && "
"git remote set-url origin --push " GIT_SSH_URL " && " "git remote set-url origin --push " GIT_SSH_URL " && "
"git config --replace-all remote.origin.fetch \"+refs/heads/*:refs/remotes/origin/*\"").c_str()); "git config --replace-all remote.origin.fetch \"+refs/heads/*:refs/remotes/origin/*\"");
#endif #endif
// write continue.sh // write continue.sh
@@ -185,22 +171,16 @@ void cloneFinished(int exitCode) {
run("mv /data/continue.sh.new " CONTINUE_PATH); run("mv /data/continue.sh.new " CONTINUE_PATH);
// wait for the installed software's UI to take over // wait for the installed software's UI to take over
finishInstall(); util::sleep_for(60 * 1000);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
InitWindow(2160, 1080, "Installer"); InitWindow(2160, 1080, "Installer");
font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, FONT_SIZE, NULL, 0); font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, 120, NULL, 0);
SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR);
renderProgress(0);
if (util::file_exists(CONTINUE_PATH)) { int result = doInstall();
finishInstall(); cloneFinished(result);
} else {
renderProgress(0);
int result = doInstall();
cloneFinished(result);
}
CloseWindow(); CloseWindow();
UnloadFont(font); UnloadFont(font);
return 0; return 0;
+3 -9
View File
@@ -63,20 +63,14 @@ class MainLayout(Widget):
# Don't hide sidebar from interactive timeout # Don't hide sidebar from interactive timeout
if self._current_mode != MainState.ONROAD: if self._current_mode != MainState.ONROAD:
self._sidebar.set_visible(False) self._sidebar.set_visible(False)
self._set_current_layout(MainState.ONROAD) self._current_mode = MainState.ONROAD
else: else:
self._set_current_layout(MainState.HOME) self._current_mode = MainState.HOME
self._sidebar.set_visible(True) self._sidebar.set_visible(True)
def _set_current_layout(self, layout: MainState):
if layout != self._current_mode:
self._layouts[self._current_mode].hide_event()
self._current_mode = layout
self._layouts[self._current_mode].show_event()
def open_settings(self, panel_type: PanelType): def open_settings(self, panel_type: PanelType):
self._layouts[MainState.SETTINGS].set_current_panel(panel_type) self._layouts[MainState.SETTINGS].set_current_panel(panel_type)
self._set_current_layout(MainState.SETTINGS) self._current_mode = MainState.SETTINGS
self._sidebar.set_visible(False) self._sidebar.set_visible(False)
def _on_settings_clicked(self): def _on_settings_clicked(self):
+17
View File
@@ -0,0 +1,17 @@
import pyray as rl
from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import WifiManagerUI
class NetworkLayout(Widget):
def __init__(self):
super().__init__()
self.wifi_manager = WifiManagerWrapper()
self.wifi_ui = WifiManagerUI(self.wifi_manager)
def _render(self, rect: rl.Rectangle):
self.wifi_ui.render(rect)
def shutdown(self):
self.wifi_manager.shutdown()
@@ -4,6 +4,9 @@ from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.list_view import toggle_item from openpilot.system.ui.widgets.list_view import toggle_item
from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.scroller import Scroller
if Params().get_bool("sunnypilot_ui"):
from openpilot.system.ui.sunnypilot.lib.list_view import toggle_item_sp as toggle_item
# Description constants # Description constants
DESCRIPTIONS = { DESCRIPTIONS = {
'enable_adb': ( 'enable_adb': (
+3 -3
View File
@@ -2,7 +2,7 @@ import pyray as rl
import time import time
import threading import threading
from openpilot.common.api import api_get from openpilot.common.api import Api, api_get
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.ui_state import ui_state
@@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.wrap_text import wrap_text
from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.lib.api_helpers import get_token
TITLE = "Firehose Mode" TITLE = "Firehose Mode"
DESCRIPTION = ( DESCRIPTION = (
@@ -163,7 +163,7 @@ class FirehoseLayout(Widget):
dongle_id = self.params.get("DongleId") dongle_id = self.params.get("DongleId")
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
return return
identity_token = get_token(dongle_id) identity_token = Api(dongle_id).get_token()
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token)
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
+6 -16
View File
@@ -2,6 +2,7 @@ import pyray as rl
from dataclasses import dataclass from dataclasses import dataclass
from enum import IntEnum from enum import IntEnum
from collections.abc import Callable from collections.abc import Callable
from openpilot.selfdrive.ui.layouts.network import NetworkLayout
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
@@ -9,9 +10,7 @@ from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.wifi_manager import WifiManager
from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import WifiManagerUI
# Settings close button # Settings close button
SETTINGS_CLOSE_TEXT = "×" SETTINGS_CLOSE_TEXT = "×"
@@ -44,7 +43,7 @@ class PanelType(IntEnum):
@dataclass @dataclass
class PanelInfo: class PanelInfo:
name: str name: str
instance: Widget instance: object
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
@@ -54,12 +53,9 @@ class SettingsLayout(Widget):
self._current_panel = PanelType.DEVICE self._current_panel = PanelType.DEVICE
# Panel configuration # Panel configuration
wifi_manager = WifiManager()
wifi_manager.set_active(False)
self._panels = { self._panels = {
PanelType.DEVICE: PanelInfo("Device", DeviceLayout()), PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
PanelType.NETWORK: PanelInfo("Network", WifiManagerUI(wifi_manager)), PanelType.NETWORK: PanelInfo("Network", NetworkLayout()),
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()), PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()), PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()), PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
@@ -153,14 +149,8 @@ class SettingsLayout(Widget):
def set_current_panel(self, panel_type: PanelType): def set_current_panel(self, panel_type: PanelType):
if panel_type != self._current_panel: if panel_type != self._current_panel:
self._panels[self._current_panel].instance.hide_event()
self._current_panel = panel_type self._current_panel = panel_type
self._panels[self._current_panel].instance.show_event()
def show_event(self): def close_settings(self):
super().show_event() if self._close_callback:
self._panels[self._current_panel].instance.show_event() self._close_callback()
def hide_event(self):
super().hide_event()
self._panels[self._current_panel].instance.hide_event()
+13
View File
@@ -3,6 +3,9 @@ from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item
from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.scroller import Scroller
if Params().get_bool("sunnypilot_ui"):
from openpilot.system.ui.sunnypilot.lib.list_view import toggle_item_sp as toggle_item
# Description constants # Description constants
DESCRIPTIONS = { DESCRIPTIONS = {
"OpenpilotEnabledToggle": ( "OpenpilotEnabledToggle": (
@@ -23,6 +26,10 @@ DESCRIPTIONS = {
'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", 'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
"IsMetric": "Display speed in km/h instead of mph.", "IsMetric": "Display speed in km/h instead of mph.",
"RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.", "RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.",
"RecordAudioFeedback": (
"Press the LKAS button to record audio feedback about openpilot. When this toggle is disabled, the button acts as a bookmark button. " +
"The event will be highlighted in comma connect and the segment will be preserved on your device's storage."
),
} }
@@ -81,6 +88,12 @@ class TogglesLayout(Widget):
self._params.get_bool("RecordAudio"), self._params.get_bool("RecordAudio"),
icon="microphone.png", icon="microphone.png",
), ),
toggle_item(
"Record Audio Feedback with LKAS button",
DESCRIPTIONS["RecordAudioFeedback"],
self._params.get_bool("RecordAudioFeedback"),
icon="microphone.png",
),
toggle_item( toggle_item(
"Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="metric.png" "Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="metric.png"
), ),
-14
View File
@@ -1,14 +0,0 @@
import time
from functools import lru_cache
from openpilot.common.api import Api
TOKEN_EXPIRY_HOURS = 2
@lru_cache(maxsize=1)
def _get_token(dongle_id: str, t: int):
return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS)
def get_token(dongle_id: str):
return _get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60)))
+2 -3
View File
@@ -3,11 +3,10 @@ import os
import threading import threading
import time import time
from openpilot.common.api import api_get from openpilot.common.api import Api, api_get
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
from openpilot.selfdrive.ui.lib.api_helpers import get_token
class PrimeType(IntEnum): class PrimeType(IntEnum):
@@ -50,7 +49,7 @@ class PrimeState:
return return
try: try:
identity_token = get_token(dongle_id) identity_token = Api(dongle_id).get_token()
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token) response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
+19 -11
View File
@@ -187,9 +187,9 @@ class ModelRenderer(Widget):
self._path.raw_points, 0.9, self._path_offset_z, max_idx, allow_invert=False self._path.raw_points, 0.9, self._path_offset_z, max_idx, allow_invert=False
) )
self._update_experimental_gradient() self._update_experimental_gradient(self._rect.height)
def _update_experimental_gradient(self): def _update_experimental_gradient(self, height):
"""Pre-calculate experimental mode gradient colors""" """Pre-calculate experimental mode gradient colors"""
if not self._experimental_mode: if not self._experimental_mode:
return return
@@ -201,21 +201,22 @@ class ModelRenderer(Widget):
i = 0 i = 0
while i < max_len: while i < max_len:
# Some points (screen space) are out of frame (rect space) track_idx = max_len - i - 1 # flip idx to start from bottom right
track_y = self._path.projected_points[i][1] track_y = self._path.projected_points[track_idx][1]
if track_y < self._rect.y or track_y > (self._rect.y + self._rect.height): if track_y < 0 or track_y > height:
i += 1 i += 1
continue continue
# Calculate color based on acceleration (0 is bottom, 1 is top) # Calculate color based on acceleration
lin_grad_point = 1 - (track_y - self._rect.y) / self._rect.height lin_grad_point = (height - track_y) / height
# speed up: 120, slow down: 0 # speed up: 120, slow down: 0
path_hue = np.clip(60 + self._acceleration_x[i] * 35, 0, 120) path_hue = max(min(60 + self._acceleration_x[i] * 35, 120), 0)
path_hue = int(path_hue * 100 + 0.5) / 100
saturation = min(abs(self._acceleration_x[i] * 1.5), 1) saturation = min(abs(self._acceleration_x[i] * 1.5), 1)
lightness = np.interp(saturation, [0.0, 1.0], [0.95, 0.62]) lightness = self._map_val(saturation, 0.0, 1.0, 0.95, 0.62)
alpha = np.interp(lin_grad_point, [0.75 / 2.0, 0.75], [0.4, 0.0]) alpha = self._map_val(lin_grad_point, 0.75 / 2.0, 0.75, 0.4, 0.0)
# Use HSL to RGB conversion # Use HSL to RGB conversion
color = self._hsla_to_color(path_hue / 360.0, saturation, lightness, alpha) color = self._hsla_to_color(path_hue / 360.0, saturation, lightness, alpha)
@@ -279,7 +280,7 @@ class ModelRenderer(Widget):
if self._experimental_mode: if self._experimental_mode:
# Draw with acceleration coloring # Draw with acceleration coloring
if len(self._exp_gradient['colors']) > 1: if len(self._exp_gradient['colors']) > 2:
draw_polygon(self._rect, self._path.projected_points, gradient=self._exp_gradient) draw_polygon(self._rect, self._path.projected_points, gradient=self._exp_gradient)
else: else:
draw_polygon(self._rect, self._path.projected_points, rl.Color(255, 255, 255, 30)) draw_polygon(self._rect, self._path.projected_points, rl.Color(255, 255, 255, 30))
@@ -408,6 +409,13 @@ class ModelRenderer(Widget):
return np.vstack((left_screen.T, right_screen[:, ::-1].T)).astype(np.float32) return np.vstack((left_screen.T, right_screen[:, ::-1].T)).astype(np.float32)
@staticmethod
def _map_val(x, x0, x1, y0, y1):
x = np.clip(x, x0, x1)
ra = x1 - x0
rb = y1 - y0
return (x - x0) * rb / ra + y0 if ra != 0 else y0
@staticmethod @staticmethod
def _hsla_to_color(h, s, l, a): def _hsla_to_color(h, s, l, a):
rgb = colorsys.hls_to_rgb(h, l, s) rgb = colorsys.hls_to_rgb(h, l, s)
+4 -4
View File
@@ -32,11 +32,11 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
experimentalLongitudinalToggle = new ParamControl( experimentalLongitudinalToggle = new ParamControl(
"AlphaLongitudinalEnabled", "AlphaLongitudinalEnabled",
tr("sunnypilot Longitudinal Control (Alpha)"), tr("openpilot Longitudinal Control (Alpha)"),
QString("<b>%1</b><br><br>%2") QString("<b>%1</b><br><br>%2")
.arg(tr("WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
.arg(tr("On this car, sunnypilot defaults to the car's built-in ACC instead of sunnypilot's longitudinal control. " .arg(tr("On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. "
"Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha.")), "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")),
"" ""
); );
experimentalLongitudinalToggle->setConfirmation(true, false); experimentalLongitudinalToggle->setConfirmation(true, false);
+1 -1
View File
@@ -12,7 +12,7 @@ public:
explicit DeveloperPanel(SettingsWindow *parent); explicit DeveloperPanel(SettingsWindow *parent);
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
protected: private:
Params params; Params params;
ParamControl* adbToggle; ParamControl* adbToggle;
ParamControl* joystickToggle; ParamControl* joystickToggle;
+15 -1
View File
@@ -33,6 +33,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/icons/experimental_white.svg", "../assets/icons/experimental_white.svg",
false, false,
}, },
{
"DynamicExperimentalControl",
tr("Enable Dynamic Experimental Control"),
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
"../assets/offroad/icon_blank.png",
false,
},
{ {
"DisengageOnAccelerator", "DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"), tr("Disengage on Accelerator Pedal"),
@@ -68,6 +75,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/icons/microphone.png", "../assets/icons/microphone.png",
true, true,
}, },
{
"RecordAudioFeedback",
tr("Record Audio Feedback with LKAS button"),
tr("Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device's storage.\n\nNote that this feature is only compatible with select cars."),
"../assets/icons/microphone.png",
false,
},
{ {
"IsMetric", "IsMetric",
tr("Use Metric System"), tr("Use Metric System"),
@@ -188,7 +202,7 @@ void TogglesPanel::updateToggles() {
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
QString long_desc = unavailable + " " + \ QString long_desc = unavailable + " " + \
tr("sunnypilot longitudinal control may come in a future update."); tr("openpilot longitudinal control may come in a future update.");
if (CP.getAlphaLongitudinalAvailable()) { if (CP.getAlphaLongitudinalAvailable()) {
if (is_release) { if (is_release) {
long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches.");

Some files were not shown because too many files have changed in this diff Show More