mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-09 04:54:23 +08:00
Compare commits
13 Commits
feature/sp
...
pyui/sp-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38bb1407c9 | ||
|
|
613e8d27b5 | ||
|
|
3af7841a5d | ||
|
|
5c2c6a9c75 | ||
|
|
acfbfea41d | ||
|
|
1dee05cb1f | ||
|
|
3ff8ccfc23 | ||
|
|
c1f251a34f | ||
|
|
29e76a1e38 | ||
|
|
3384771117 | ||
|
|
a6dfe14487 | ||
|
|
b9c3ef57d1 | ||
|
|
9b5228ec08 |
8
.github/workflows/auto_pr_review.yaml
vendored
8
.github/workflows/auto_pr_review.yaml
vendored
@@ -40,10 +40,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
|
||||
env:
|
||||
PR_LABEL: 'dev'
|
||||
PR_LABEL: 'dev-c3'
|
||||
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
|
||||
steps:
|
||||
- name: Check if PR has dev label
|
||||
- name: Check if PR has dev-c3 label
|
||||
id: check-labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
@@ -62,11 +62,11 @@ jobs:
|
||||
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
||||
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');
|
||||
|
||||
- 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
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
46
.github/workflows/forum-docs.yml
vendored
46
.github/workflows/forum-docs.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: Publish Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Set to true to publish to forum'
|
||||
required: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
DOCS_CATEGORY_ID: 114
|
||||
DOCS_DATA_EXPLORER_QUERY_ID: 2
|
||||
DOCS_TARGET: https://forum.sunnypilot.ai
|
||||
DOCS_API_KEY: ${{ secrets.DISCOURSE_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
|
||||
jobs:
|
||||
# dry_run:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: ruby/setup-ruby@v1
|
||||
# with:
|
||||
# ruby-version: "3.3"
|
||||
# bundler-cache: true
|
||||
# working-directory: ./release/ci
|
||||
# - run: bundle exec ./sync_docs.rb --dry-run
|
||||
# working-directory: ./release/ci
|
||||
|
||||
publish:
|
||||
# if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true')
|
||||
# needs: dry_run
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.3"
|
||||
bundler-cache: true
|
||||
working-directory: ./release/ci
|
||||
- run: bundle exec ./sync_docs.rb
|
||||
working-directory: ./release/ci
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -39,4 +39,4 @@ jobs:
|
||||
git config --global --add safe.directory '*'
|
||||
git lfs pull
|
||||
- name: Push __nightly
|
||||
run: BRANCH=__nightly release/build_stripped.sh
|
||||
run: BRANCH=__nightly release/build_devel.sh
|
||||
|
||||
1
.github/workflows/repo-maintenance.yaml
vendored
1
.github/workflows/repo-maintenance.yaml
vendored
@@ -43,7 +43,6 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: uv lock
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
run: |
|
||||
python3 -m ensurepip --upgrade
|
||||
pip3 install uv
|
||||
|
||||
15
.github/workflows/selfdrive_tests.yaml
vendored
15
.github/workflows/selfdrive_tests.yaml
vendored
@@ -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
|
||||
|
||||
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
|
||||
PYTEST: pytest --continue-on-collection-errors --durations=0 --durations-min=5 -n logical
|
||||
|
||||
jobs:
|
||||
build_release:
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
command: git lfs pull
|
||||
- name: Build devel
|
||||
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
|
||||
- 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
|
||||
@@ -107,6 +107,7 @@ jobs:
|
||||
|
||||
build_mac:
|
||||
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' }}
|
||||
steps:
|
||||
- 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
|
||||
- name: Homebrew cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
if: false # disabling the cache for now because it is breaking macos builds...
|
||||
with:
|
||||
save: false # No need save here if we manually save it later conditionally
|
||||
path: ~/Library/Caches/Homebrew
|
||||
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
@@ -126,8 +125,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: ./tools/mac_setup.sh
|
||||
env:
|
||||
PYTHONWARNINGS: default # package install has DeprecationWarnings
|
||||
HOMEBREW_DISPLAY_INSTALL_TIMES: 1
|
||||
# package install has DeprecationWarnings
|
||||
PYTHONWARNINGS: default
|
||||
- name: Save Homebrew cache
|
||||
uses: actions/cache/save@v4
|
||||
if: github.ref == 'refs/heads/master'
|
||||
@@ -138,7 +137,6 @@ jobs:
|
||||
- name: Getting scons cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
save: false # No need save here if we manually save it later conditionally
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
@@ -192,8 +190,7 @@ jobs:
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }}
|
||||
run: |
|
||||
${{ 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' -qq && \
|
||||
$PYTEST --collect-only -m 'not slow' &> /dev/null && \
|
||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
|
||||
./selfdrive/ui/tests/create_test_translations.sh && \
|
||||
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
|
||||
|
||||
180
.github/workflows/sunnypilot-build-prebuilt.yaml
vendored
180
.github/workflows/sunnypilot-build-prebuilt.yaml
vendored
@@ -8,15 +8,21 @@ env:
|
||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||
|
||||
# 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
|
||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, master-dev ]
|
||||
tags: [ 'release/*' ]
|
||||
branches: [ master, master-dev-c3-new ]
|
||||
tags: [ '*' ]
|
||||
pull_request_target:
|
||||
types: [ labeled ]
|
||||
workflow_dispatch:
|
||||
@@ -28,72 +34,9 @@ on:
|
||||
default: false
|
||||
|
||||
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 common/version.h | grep COMMA_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:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [ prepare_strategy ]
|
||||
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'))
|
||||
}}
|
||||
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'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Wait for Tests
|
||||
@@ -101,26 +44,19 @@ jobs:
|
||||
with:
|
||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
should-wait-for-start: ${{ github.event_name == 'push' && 'true' || 'false' }}
|
||||
|
||||
build:
|
||||
needs: [ validate_tests, prepare_strategy ]
|
||||
needs: [ validate_tests ]
|
||||
concurrency:
|
||||
group: build-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
runs-on: [self-hosted, tici]
|
||||
outputs:
|
||||
new_branch: ${{ needs.prepare_strategy.outputs.new_branch }}
|
||||
version: ${{ needs.prepare_strategy.outputs.version }}
|
||||
extra_version_identifier: ${{ needs.prepare_strategy.outputs.extra_version_identifier }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
if: ${{
|
||||
(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'))
|
||||
}}
|
||||
new_branch: ${{ steps.set-env.outputs.new_branch }}
|
||||
version: ${{ steps.set-env.outputs.version }}
|
||||
extra_version_identifier: ${{ steps.set-env.outputs.extra_version_identifier }}
|
||||
commit_sha: ${{ steps.set-env.outputs.commit_sha }}
|
||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
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.
|
||||
restore-keys: |
|
||||
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 }}
|
||||
|
||||
- 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
|
||||
id: set-env
|
||||
run: |
|
||||
echo "new_branch=${{ needs.prepare_strategy.outputs.new_branch }}" >> $GITHUB_OUTPUT
|
||||
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
|
||||
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
|
||||
# Write to GITHUB_OUTPUT from environment variables
|
||||
echo "new_branch=$NEW_BRANCH" >> $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
|
||||
|
||||
# Set up common environment
|
||||
@@ -241,19 +226,15 @@ jobs:
|
||||
if: always()
|
||||
run: |
|
||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||
|
||||
|
||||
|
||||
publish:
|
||||
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.
|
||||
# This means that if multiple commits come in while we're publishing, they will be queued up and publish one after the other.
|
||||
# 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.
|
||||
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}
|
||||
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 ]
|
||||
group: publish-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
needs: [ build ]
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -285,7 +266,7 @@ jobs:
|
||||
"${{ needs.build.outputs.new_branch }}" \
|
||||
"${{ needs.build.outputs.version }}" \
|
||||
"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 "---- ℹ️ To update the list of branches that auto deploy prebuilts -----"
|
||||
@@ -293,18 +274,11 @@ jobs:
|
||||
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 "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:
|
||||
needs: [ build, publish ]
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ (always() && !cancelled() && !failure()) && needs.publish.result == 'success' && !failure() && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Alpine Linux environment
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: Build dev
|
||||
name: Build dev-c3-new
|
||||
|
||||
env:
|
||||
DEFAULT_SOURCE_BRANCH: "master"
|
||||
DEFAULT_TARGET_BRANCH: "master-dev"
|
||||
PR_LABEL: "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_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
||||
|
||||
@@ -25,7 +25,7 @@ on:
|
||||
target_branch:
|
||||
description: 'Target branch to reset and squash into'
|
||||
required: true
|
||||
default: 'master-dev'
|
||||
default: 'master-dev-c3-new'
|
||||
type: string
|
||||
cancel_in_progress:
|
||||
description: 'Cancel any in-progress runs of this workflow'
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
if: (
|
||||
(github.event_name == 'workflow_dispatch')
|
||||
|| (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 == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||
|| (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:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||
if: (
|
||||
(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 == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||
|| (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:
|
||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||
@@ -118,8 +118,8 @@ jobs:
|
||||
run: |
|
||||
# Use GitHub API to get PRs with specific label, ordered by creation date
|
||||
PR_LIST=$(gh api graphql -f query='
|
||||
query($search_query:String!) {
|
||||
search(query: $search_query, type:ISSUE, first:100) {
|
||||
query($label:String!) {
|
||||
search(query: $label, type:ISSUE, first:100) {
|
||||
nodes {
|
||||
... on PullRequest {
|
||||
number
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -F search_query="repo:${{ github.repository }} is:pr is:open label:${PR_LABEL},${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//\'/}
|
||||
echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,7 +16,6 @@ a.out
|
||||
.cache/
|
||||
|
||||
/docs_site/
|
||||
/docs_sp_site/
|
||||
|
||||
*.mp4
|
||||
*.dylib
|
||||
@@ -51,6 +50,7 @@ cereal/services.h
|
||||
cereal/gen
|
||||
cereal/messaging/bridge
|
||||
selfdrive/mapd/default_speeds_by_region.json
|
||||
system/proclogd/proclogd
|
||||
selfdrive/ui/translations/tmp
|
||||
selfdrive/test/longitudinal_maneuvers/out
|
||||
selfdrive/car/tests/cars_dump
|
||||
|
||||
41
.vscode/launch.json
vendored
41
.vscode/launch.json
vendored
@@ -23,11 +23,6 @@
|
||||
"id": "args",
|
||||
"description": "Arguments to pass to the process",
|
||||
"type": "promptString"
|
||||
},
|
||||
{
|
||||
"id": "replayArg",
|
||||
"type": "promptString",
|
||||
"description": "Enter route or segment to replay."
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
@@ -45,41 +40,7 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/${input:cpp_process}",
|
||||
"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"
|
||||
]
|
||||
"cwd": "${workspaceFolder}",
|
||||
}
|
||||
]
|
||||
}
|
||||
34
Jenkinsfile
vendored
34
Jenkinsfile
vendored
@@ -167,7 +167,7 @@ node {
|
||||
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
||||
|
||||
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
||||
'release-tici', 'testing-closet*', 'hotfix-*']
|
||||
'testing-closet*', 'hotfix-*']
|
||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||
|
||||
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
||||
@@ -178,7 +178,7 @@ node {
|
||||
|
||||
try {
|
||||
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"),
|
||||
])
|
||||
}
|
||||
@@ -186,12 +186,12 @@ node {
|
||||
if (env.BRANCH_NAME == '__nightly') {
|
||||
parallel (
|
||||
'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"),
|
||||
])
|
||||
},
|
||||
'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"),
|
||||
])
|
||||
},
|
||||
@@ -200,30 +200,39 @@ node {
|
||||
|
||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||
parallel (
|
||||
// tici 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("check dirty", "release/check-dirty.sh"),
|
||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||
])
|
||||
},
|
||||
'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("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 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"),
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
|
||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.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': {
|
||||
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
||||
deviceStage("OX03C10", "tici-ox03c10", ["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"),
|
||||
@@ -237,13 +246,17 @@ node {
|
||||
])
|
||||
},
|
||||
'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("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||
])
|
||||
},
|
||||
'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("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("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", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||
// TODO: enable once new AGNOS is available
|
||||
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
||||
|
||||
47
README.md
47
README.md
@@ -22,24 +22,34 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t
|
||||
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
||||
|
||||
## 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-c3-new` 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
|
||||
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.
|
||||
|
||||
* 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.
|
||||
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-c3-new.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.
|
||||
|
||||
* sunnypilot already installed and you installed a version after 0.8.17?
|
||||
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.
|
||||
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-c3-new`
|
||||
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `release-c3`
|
||||
|
||||
| Branch | Installation URL |
|
||||
|:------------:|:--------------------------------:|
|
||||
| `release-c3` | https://release-c3.sunnypilot.ai |
|
||||
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
||||
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
||||
|
||||
### 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 |
|
||||
|:----------------:|:---------------------------------------------:|
|
||||
@@ -49,31 +59,8 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
|
||||
| `release-c3-new` | **Not yet available**. |
|
||||
|
||||
> [!TIP]
|
||||
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'.
|
||||
|
||||
> [!NOTE]
|
||||
> 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.
|
||||
|
||||
|
||||
<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 |
|
||||
|:------------:|:--------------------------------:|
|
||||
| `release-c3` | https://release-c3.sunnypilot.ai |
|
||||
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
||||
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 🎆 Pull Requests
|
||||
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
||||
|
||||
|
||||
20
RELEASES.md
20
RELEASES.md
@@ -1,28 +1,18 @@
|
||||
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)
|
||||
========================
|
||||
* New driving model
|
||||
* New training architecture
|
||||
* Described in our CVPR paper: "Learning to Drive from a World Model"
|
||||
* Longitudinal MPC replaced by E2E planning from World Model in Experimental Mode
|
||||
* Action from lateral MPC as training objective replaced by E2E planning from World Model
|
||||
* Architecture outlined in CVPR paper: "Learning to Drive from a World Model"
|
||||
* Longitudinal MPC replaced by E2E planning from worldmodel in experimental mode
|
||||
* Action from lateral MPC as training objective replaced by E2E planning from worldmodel
|
||||
* Low-speed lead car ground-truth fixes
|
||||
|
||||
* Enable live-learned steering actuation delay
|
||||
* Record driving feedback using LKAS button when MADS is disabled
|
||||
* 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)
|
||||
========================
|
||||
|
||||
@@ -359,6 +359,11 @@ SConscript([
|
||||
'system/ubloxd/SConscript',
|
||||
'system/loggerd/SConscript',
|
||||
])
|
||||
if arch != "Darwin":
|
||||
SConscript([
|
||||
'system/logcatd/SConscript',
|
||||
'system/proclogd/SConscript',
|
||||
])
|
||||
|
||||
if arch == "larch64":
|
||||
SConscript(['system/camerad/SConscript'])
|
||||
|
||||
@@ -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
|
||||
struct LeadData {
|
||||
dRel @0 :Float32;
|
||||
@@ -68,49 +48,6 @@ struct LeadData {
|
||||
|
||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||
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 {
|
||||
@@ -185,13 +122,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
|
||||
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
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 {
|
||||
state @0 :DynamicExperimentalControlState;
|
||||
@@ -203,97 +133,6 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
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 {
|
||||
@@ -333,22 +172,12 @@ struct OnroadEventSP @0xda96579883444c35 {
|
||||
experimentalModeSwitched @14;
|
||||
wrongCarModeAlertOnly @15;
|
||||
pedalPressedAlertOnly @16;
|
||||
laneTurnLeft @17;
|
||||
laneTurnRight @18;
|
||||
speedLimitPreActive @19;
|
||||
speedLimitActive @20;
|
||||
speedLimitChanged @21;
|
||||
speedLimitPending @22;
|
||||
e2eChime @23;
|
||||
}
|
||||
}
|
||||
|
||||
struct CarParamsSP @0x80ae746ee2596b11 {
|
||||
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
||||
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
||||
pcmCruiseSpeed @3 :Bool;
|
||||
intelligentCruiseButtonManagementAvailable @4 :Bool;
|
||||
enableGasInterceptor @5 :Bool;
|
||||
|
||||
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
||||
|
||||
@@ -368,24 +197,10 @@ struct CarControlSP @0xa5cd762cd951a455 {
|
||||
params @1 :List(Param);
|
||||
leadOne @2 :LeadData;
|
||||
leadTwo @3 :LeadData;
|
||||
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
|
||||
|
||||
struct Param {
|
||||
key @0 :Text;
|
||||
type @2 :ParamType;
|
||||
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;
|
||||
value @1 :Text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +247,6 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||
}
|
||||
|
||||
struct CarStateSP @0xb86e6369214c01c8 {
|
||||
speedLimit @0 :Float32;
|
||||
}
|
||||
|
||||
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||
@@ -444,14 +258,7 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||
roadName @5 :Text;
|
||||
}
|
||||
|
||||
struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||
laneTurnDirection @0 :TurnDirection;
|
||||
|
||||
enum TurnDirection {
|
||||
none @0;
|
||||
turnLeft @1;
|
||||
turnRight @2;
|
||||
}
|
||||
struct CustomReserved9 @0xa1680744031fdb2d {
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
|
||||
@@ -585,6 +585,7 @@ struct PandaState @0xa7649e2575e4591e {
|
||||
heartbeatLost @22 :Bool;
|
||||
interruptLoad @25 :Float32;
|
||||
fanPower @28 :UInt8;
|
||||
fanStallCount @34 :UInt8;
|
||||
|
||||
spiErrorCount @33 :UInt16;
|
||||
|
||||
@@ -713,7 +714,6 @@ struct PandaState @0xa7649e2575e4591e {
|
||||
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
||||
safetyParamDEPRECATED @20 :Int16;
|
||||
safetyParam2DEPRECATED @26 :UInt32;
|
||||
fanStallCountDEPRECATED @34 :UInt8;
|
||||
}
|
||||
|
||||
struct PeripheralState {
|
||||
@@ -2631,7 +2631,7 @@ struct Event {
|
||||
backupManagerSP @113 :Custom.BackupManagerSP;
|
||||
carStateSP @114 :Custom.CarStateSP;
|
||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||
customReserved9 @116 :Custom.CustomReserved9;
|
||||
customReserved10 @136 :Custom.CustomReserved10;
|
||||
customReserved11 @137 :Custom.CustomReserved11;
|
||||
customReserved12 @138 :Custom.CustomReserved12;
|
||||
@@ -2684,7 +2684,7 @@ struct Event {
|
||||
lateralPlanDEPRECATED @64 :LateralPlan;
|
||||
navModelDEPRECATED @104 :NavModelData;
|
||||
uiPlanDEPRECATED @106 :UiPlan;
|
||||
liveLocationKalman @72 :LiveLocationKalman;
|
||||
liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
|
||||
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
||||
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ MessageContext message_context;
|
||||
struct SubMaster::SubMessage {
|
||||
std::string name;
|
||||
SubSocket *socket = nullptr;
|
||||
float freq = 0.0f;
|
||||
int freq = 0;
|
||||
bool updated = false, alive = false, valid = false, ignore_alive;
|
||||
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||
void *allocated_msg_reader = nullptr;
|
||||
|
||||
@@ -88,8 +88,6 @@ _services: dict[str, tuple] = {
|
||||
"carControlSP": (True, 100., 10),
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
"modelDataV2SP": (True, 20.),
|
||||
"liveLocationKalman": (True, 20.),
|
||||
|
||||
# debug
|
||||
"uiDebug": (True, 0., 1),
|
||||
@@ -122,12 +120,12 @@ def build_header():
|
||||
h += "#include <map>\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"
|
||||
for k, v in SERVICE_LIST.items():
|
||||
should_log = "true" if v.should_log else "false"
|
||||
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)
|
||||
h += "};\n"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define DEFAULT_MODEL "Firehose (Default)"
|
||||
#define DEFAULT_MODEL "Down To Ride (Default)"
|
||||
|
||||
@@ -73,9 +73,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}},
|
||||
{"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}},
|
||||
{"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}},
|
||||
{"LastUpdateRouteCount", {PERSISTENT, INT, "0"}},
|
||||
{"LastUpdateRouteCount", {PERSISTENT, INT}},
|
||||
{"LastUpdateTime", {PERSISTENT, TIME}},
|
||||
{"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||
{"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT}},
|
||||
{"LiveDelay", {PERSISTENT | BACKUP, BYTES}},
|
||||
{"LiveParameters", {PERSISTENT, JSON}},
|
||||
{"LiveParametersV2", {PERSISTENT, BYTES}},
|
||||
@@ -94,6 +94,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"Offroad_NoFirmware", {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_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
@@ -145,32 +146,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
||||
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
||||
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
|
||||
{"HideVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
|
||||
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"LastGPSPositionLLK", {PERSISTENT, STRING}},
|
||||
{"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
||||
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||
{"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"}},
|
||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"sunnypilot_ui", {PERSISTENT, BOOL, "1"}},
|
||||
|
||||
// MADS params
|
||||
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
@@ -182,7 +168,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
||||
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"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_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||
|
||||
@@ -196,8 +181,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
|
||||
{"SunnylinkDongleId", {PERSISTENT, STRING}},
|
||||
{"SunnylinkdPid", {PERSISTENT, INT}},
|
||||
{"SunnylinkEnabled", {PERSISTENT, BOOL, "1"}},
|
||||
{"SunnylinkTempFault", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0"}},
|
||||
{"SunnylinkEnabled", {PERSISTENT, BOOL}},
|
||||
|
||||
// Backup Manager params
|
||||
{"BackupManager_CreateBackup", {PERSISTENT, BOOL}},
|
||||
@@ -205,22 +189,18 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
|
||||
// sunnypilot car specific params
|
||||
{"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// sunnypilot model params
|
||||
// model panel params
|
||||
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
||||
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
||||
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||
|
||||
// mapd
|
||||
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
||||
{"MapdVersion", {PERSISTENT, STRING}},
|
||||
{"MapdVersion", {PERSISTENT, STRING, ""}},
|
||||
{"MapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT, "0.0"}},
|
||||
{"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||
{"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
@@ -236,26 +216,5 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"OsmStateName", {PERSISTENT, STRING, "All"}},
|
||||
{"OsmStateTitle", {PERSISTENT, STRING}},
|
||||
{"OsmWayTest", {PERSISTENT, 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"}},
|
||||
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING, ""}},
|
||||
};
|
||||
|
||||
@@ -14,7 +14,8 @@ class PIDController:
|
||||
if isinstance(self._k_d, Number):
|
||||
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.speed = 0.0
|
||||
@@ -40,10 +41,6 @@ class PIDController:
|
||||
self.f = 0.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):
|
||||
self.speed = speed
|
||||
self.p = float(error) * self.k_p
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from subprocess import Popen, PIPE, TimeoutExpired
|
||||
|
||||
|
||||
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:
|
||||
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()
|
||||
|
||||
@@ -36,7 +36,6 @@ const double MS_TO_KPH = 3.6;
|
||||
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_FOOT = 3.28084;
|
||||
const double METER_TO_KM = 1. / 1000.0;
|
||||
|
||||
#define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1))
|
||||
|
||||
|
||||
24
docs/CARS.md
24
docs/CARS.md
@@ -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.
|
||||
|
||||
# 337 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> |Video|Setup Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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>|||
|
||||
@@ -73,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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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)|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 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 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<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 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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>|||
|
||||
@@ -99,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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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>|||
|
||||
@@ -129,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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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>||
|
||||
@@ -165,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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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>|||
|
||||
@@ -262,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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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>|||
|
||||
@@ -308,7 +297,6 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 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 RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 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 RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<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 Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Wildlander PHEV 2021|All|openpilot|19 mph|0 mph|[](##)|[](##)|<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 Wildlander PHEV 2021">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 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=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|
||||
@@ -6,7 +6,7 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu
|
||||
|
||||
### Getting Started
|
||||
|
||||
* Set up your [development environment](/tools/)
|
||||
* Setup your [development environment](../tools/)
|
||||
* Join our [Discord](https://discord.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
|
||||
|
||||
[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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
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,
|
||||
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
|
||||
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).
|
||||
|
||||
[^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.**
|
||||
|
||||
@@ -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
|
||||
|
||||
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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
> [!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).
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging.
|
||||
Just run `tools/replay/replay --demo`.
|
||||
|
||||
## Replaying CAN data
|
||||
*Hardware required: jungle and comma 3X*
|
||||
*Hardware required: jungle and comma 3/3X*
|
||||
|
||||
1. Connect your PC to a jungle.
|
||||
2.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Turn the speed blue
|
||||
*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:
|
||||
```bash
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# Safety
|
||||
|
||||
openpilot is an Adaptive Cruise Control (ACC) and Automated Lane Centering (ALC) system.
|
||||
Like other ACC and ALC systems, openpilot is a failsafe passive system and it requires the
|
||||
driver to be alert and to pay attention at all times.
|
||||
|
||||
In order to enforce driver alertness, openpilot includes a driver monitoring feature
|
||||
that alerts the driver when distracted.
|
||||
|
||||
However, even with an attentive driver, we must make further efforts for the system to be
|
||||
safe. We repeat, **driver alertness is necessary, but not sufficient, for openpilot to be
|
||||
used safely** and openpilot is provided with no warranty of fitness for any purpose.
|
||||
|
||||
openpilot is developed in good faith to be compliant with FMVSS requirements and to follow
|
||||
industry standards of safety for Level 2 Driver Assistance Systems. In particular, we observe
|
||||
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/))
|
||||
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.
|
||||
|
||||
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
|
||||
ensuring two main safety requirements.
|
||||
|
||||
1. The driver must always be capable to immediately retake manual control of the vehicle,
|
||||
by stepping on the brake pedal or by pressing the cancel button.
|
||||
2. The vehicle must not alter its trajectory too quickly for the driver to safely
|
||||
react. This means that while the system is engaged, the actuators are constrained
|
||||
to operate within reasonable limits[^1].
|
||||
|
||||
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 [panda/board/safety/](https://github.com/commaai/panda/tree/master/board/safety).
|
||||
|
||||
**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.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:675116a9b50eb713b2266396aff1460eed8263738455b1d49365f48168b4d4f9
|
||||
size 1698
|
||||
@@ -1,13 +0,0 @@
|
||||
# Definitions
|
||||
|
||||
| Branch | Definition | Supported Devices | Description | Stability/Readiness |
|
||||
|:--------------:|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|
|
||||
| `release` | Release branch | Comma 3X | Stable release branch. After testing on `staging`, updates are pushed here and published publicly. | **Ready to Use:** Highly stable, recommended for most users. |
|
||||
| `staging` | Staging branch | Comma 3X | Pre-release testing branch. Community feedback is essential to identify issues before public release. | **Varied Stability:** Generally stable, but intended for testing before public release. |
|
||||
| `dev` | Development branches | Comma 3X | Experimental branch with the latest features and bug fixes brought in manually. Expect bugs and braking changes. | **Experimental:** Least stable, suitable for testers and developers. |
|
||||
| `master` | Primary development branch | Comma 3X | All Pull Requests are merged here for future releases. CI automatically strips, minifies, and pushes changes to `staging`. Running the `master` branch is suitable for development purposes but not recommended for non-development use. | **For Development Use:** Suitable for developers, may be unstable for general use. |
|
||||
| `release-tici` | Release branch | Comma 3 | Stable release branch. After testing on `staging-tici`, updates are pushed here and published publicly. | **Ready to Use:** Highly stable, recommended for most users. |
|
||||
| `staging-tici` | Staging branch | Comma 3 | Pre-release testing branch. Community feedback is essential to identify issues before public release. | **Varied Stability:** Generally stable, but intended for testing before public release. |
|
||||
|
||||
!!! tip
|
||||
Your feedback is invaluable. Testers, even without software development experience, are encourage to run `dev` or `staging` and report issues.
|
||||
@@ -1,17 +0,0 @@
|
||||
# Recommended Branches
|
||||
|
||||
=== "Comma 3X"
|
||||
|
||||
| Branch | Installation URL | Change Logs |
|
||||
|:---------:|----------------------------------- |---------------------------------------------------------------------------------|
|
||||
| `release` | **Coming Soon** | **Coming Soon** |
|
||||
| `staging` | [install.sunnypilot.ai/staging]() | [CHANGELOG](https://github.com/sunnyhaibin/sunnypilot/blob/staging/RELEASES.md) |
|
||||
| `dev` | [install.sunnypilot.ai/dev]() | [CHANGELOG](https://github.com/sunnyhaibin/sunnypilot/blob/dev/RELEASES.md) |
|
||||
|
||||
=== "Comma 3"
|
||||
|
||||
| Branch | Installation URL | Change Logs |
|
||||
|:---------:|--------------------------------------- |---------------------------------------------------------------------------------|
|
||||
| `release-tici` | **Coming Soon** | **Coming Soon** |
|
||||
| `staging-tici` | [install.sunnypilot.ai/staging-tici]() | [CHANGELOG](https://github.com/sunnyhaibin/sunnypilot/blob/staging/RELEASES.md) |
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# How to contribute
|
||||
|
||||
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about open-sourcing and externalization](https://www.sunnypilot.ai/blog/july/a-new-chapter-transparency/). Development activity is coordinated through our [GitHub Issues](https://github.com/sunnypilot/sunnypilot/issues), [GitHub Discussions](https://github.com/sunnypilot/sunnypilot/discussions), and [Discord](https://discord.sunnypilot.ai).
|
||||
|
||||
### Getting Started
|
||||
|
||||
* Setup your [development environment](https://github.com/sunnypilot/sunnypilot/tree/master/tools)
|
||||
* Read about the [development workflow](WORKFLOW.md)
|
||||
* Join our [Discord](https://discord.sunnypilot.ai)
|
||||
* Docs are at [https://docs.sunnypilot.ai](https://docs.sunnypilot.ai) and [https://www.sunnypilot.ai/blog](https://www.sunnypilot.ai/blog)
|
||||
|
||||
## What contributions are we looking for?
|
||||
|
||||
**sunnypilot's priorities are [safety](../SAFETY.md), stability, quality, and features, in that order.** Aligning with comma's ideals, part of sunnypilot's mission is to *solve self-driving cars while delivering shippable intermediaries*, and **all** development is towards that goal.
|
||||
|
||||
### What gets merged?
|
||||
|
||||
The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged.
|
||||
If a PR offers *some* value but will take lots of time to get merged, it will be closed.
|
||||
Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged.
|
||||
|
||||
All of these are examples of good PRs:
|
||||
|
||||
* [typo fix](https://github.com/commaai/openpilot/pull/30678)
|
||||
* [removing unused code](https://github.com/commaai/openpilot/pull/30573)
|
||||
* [simple car model port](https://github.com/commaai/openpilot/pull/30245)
|
||||
* [car brand port](https://github.com/commaai/openpilot/pull/23331)
|
||||
* [UI design changes](https://github.com/sunnypilot/sunnypilot/commit/84f6fce90639135611ec568c4d39a352a300bede)
|
||||
* [new features](https://github.com/sunnypilot/sunnypilot/commit/68e1379003bfdb599921cf9cd5684bfb762fd676)
|
||||
|
||||
### What doesn't get merged?
|
||||
|
||||
* **arbitrary style changes**: code is art, and it's up to the author to make it beautiful
|
||||
* **500+ line PRs**: clean it up, break it up into smaller PRs, or both
|
||||
* **PRs without a clear goal**: every PR must have a singular and clear goal
|
||||
|
||||
### First contribution
|
||||
|
||||
Check out any [good first issue from commaai's openpilot](https://github.com/commaai/openpilot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) to get started.
|
||||
|
||||
### What do I need to contribute?
|
||||
|
||||
A lot of sunnypilot work requires only a PC, and some requires a comma device.
|
||||
Most car-related contributions require access to that car, plus a comma device installed in the car.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull requests should be against the [`master`](https://github.com/sunnypilot/sunnypilot) branch. If you're unsure about a contribution, feel free to open a discussion, issue, or draft PR to discuss the problem you're trying to solve.
|
||||
|
||||
A good pull request has all of the following:
|
||||
|
||||
- a clearly stated purpose
|
||||
- every line changed directly contributes to the stated purpose
|
||||
- verification, i.e. how did you test your PR?
|
||||
- justification
|
||||
|
||||
* if you've optimized something, post benchmarks to prove it's better
|
||||
* if you've improved your car's tuning, post before and after plots
|
||||
|
||||
- passes the CI tests
|
||||
|
||||
## Contributing without Code
|
||||
|
||||
* Report bugs in [GitHub issues](https://github.com/sunnypilot/sunnypilot/issues).
|
||||
* Report driving issues in the `#general` Discord channel.
|
||||
* Consider opting into driver camera uploads to improve the driver monitoring model.
|
||||
* Connect your device to Wi-Fi regularly, so that comma can pull data for training better driving models.
|
||||
* Run the `staging-c3` branch and report issues. This branch is like `master` but it's built just like a release.
|
||||
@@ -1,43 +0,0 @@
|
||||
# sunnypilot development workflow
|
||||
|
||||
Aside from the ML models, most tools used for sunnypilot development are in this repo.
|
||||
|
||||
Most development happens on normal Ubuntu workstations, and not in cars or directly on comma devices. See the [setup guide](https://github.com/sunnypilot/sunnypilot/tree/master/tools) for getting your PC setup for sunnypilot development.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# get the latest stuff
|
||||
git pull
|
||||
git lfs pull
|
||||
git submodule update --init --recursive
|
||||
|
||||
# update dependencies
|
||||
tools/ubuntu_setup.sh
|
||||
|
||||
# build everything
|
||||
scons -j$(nproc)
|
||||
|
||||
# build just the ui with either of these
|
||||
scons -j8 selfdrive/ui/
|
||||
cd selfdrive/ui/ && scons -u -j8
|
||||
|
||||
# test everything
|
||||
pytest
|
||||
|
||||
# test just logging services
|
||||
cd system/loggerd && pytest .
|
||||
|
||||
# run the linter
|
||||
op lint
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Automated Testing
|
||||
|
||||
All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions.
|
||||
|
||||
### Code Style and Linting
|
||||
|
||||
Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`.
|
||||
@@ -1,274 +0,0 @@
|
||||
# Bug Reports
|
||||
|
||||
sunnypilot is an actively maintained project that we constantly strive to improve. With project of this size and complexity,
|
||||
bugs may occur. If you think you have discovered a bug, you can help us by submitting an issue
|
||||
in [comma's public issue tracker][comma's issue tracker],
|
||||
[sunnypilot's public issue tracker][sunnypilot's issue tracker] or on our [Discord][discord], following this guide.
|
||||
|
||||
[comma's issue tracker]: https://github.com/commaai/openpilot/issues
|
||||
[sunnypilot's issue tracker]: https://github.com/sunnypilot/sunnypilot/issues
|
||||
[discord]: https://discord.sunnypilot.ai
|
||||
|
||||
## Before creating an issue
|
||||
|
||||
With more than 2,500 users, issues are created frequently. The maintainers of this project are trying very hard to keep
|
||||
the number of open issues and reports down by fixing bugs as fast as possible. By following this guide, you will know
|
||||
exactly what information we need to help you quickly.
|
||||
|
||||
**But first, please do the following things before creating an issue.**
|
||||
|
||||
### Upgrade to the latest version
|
||||
|
||||
Chances are that the bug you discovered was already fixed in a subsequent version. Thus, before reporting an issue,
|
||||
ensure that you're running the [latest release version](https://github.com/sunnypilot/sunnypilot/releases) of sunnypilot.
|
||||
Please consult our [installation guide](../setup/read-before-installing.md) to learn how to upgrade to the latest version.
|
||||
|
||||
!!! warning "Bug fixes are not backported"
|
||||
Please understand that only bugs that occur in the latest version of sunnypilot will be addressed. Also, to reduce
|
||||
duplicate efforts, fixes cannot be backported to earlier versions.
|
||||
|
||||
### Remove customizations
|
||||
|
||||
If you're using customized features, such as your own tweaks of the features, please remove them from the branch
|
||||
you are testing from before reporting a bug. We can't offer official support for bugs that might hide in your implementations,
|
||||
so make sure to omit any customizations from the version being tested.
|
||||
|
||||
If, after removing the customizations, the bug is gone, the bug is likely caused by your customizations. A good idea is
|
||||
to add them back gradually to narrow down the root cause of the problem If you did a major version upgrade, make sure
|
||||
you adjusted all customizations you have implemented.
|
||||
|
||||
!!! tip
|
||||
If you are an advanced user, you could also utilize `git bisect`
|
||||
to perform a binary search in the history to find a particular regression.
|
||||
|
||||
!!! warning "Customizations mentioned in our documentation"
|
||||
A handful of the features sunnypilot offers can only be implemented with customizations. if you find a bug in any of
|
||||
the customizations that our documentations explicitly mentioned, you are, of course, encouraged to report it.
|
||||
|
||||
**Don't be shy to ask on our [Discord][discord] for help if you run into problems.**
|
||||
|
||||
### Search for solutions
|
||||
|
||||
At this stage, we know that the problem persists in the latest version and is not caused by any of your customizations.
|
||||
However, the problem might result from a small typo or a syntactical error in the source code, e.g., `selfdrive/car/interfaces.py`.
|
||||
|
||||
Now, before you go through the trouble of creating a bug report that is answered and closed right away with a link to
|
||||
the relevant documentation section or another already reported or closed issue or discussion, you can save time for us
|
||||
and yourself by doing some research:
|
||||
|
||||
1. [Search our documentation] and look for the relevant sections that could be related to your problem. If found, make
|
||||
sure that the settings are configured correctly.
|
||||
2. [Search our Discord][discord] to learn if other users are struggling with similar problems and work together with
|
||||
our
|
||||
great community towards a solution. Many problems are solved there.
|
||||
3. [Search comma's openpilot issue tracker][comma's issue tracker], as another user might
|
||||
already have reported the same problem that may exist in
|
||||
stock openpilot, and there might even be a known workaround or fix for it. Thus, no need to create a new issue.
|
||||
4. [Search sunnypilot's issue tracker][sunnypilot's issue tracker], as another user might already have
|
||||
reported the same problem, and there
|
||||
might even be a known workaround or fix for it. Thus, no need to create a new issue.
|
||||
|
||||
[Search our documentation]: ?q=
|
||||
|
||||
**Keep track of all <u>search terms</u> and <u>relevant links</u>, you'll need them in the bug report.**[^1]
|
||||
|
||||
[^1]:
|
||||
We might be using terminology in our documentation different from yours, but we mean the same. When you include the
|
||||
search terms and related links in your bug report, you help us to adjust and improve the documentation.
|
||||
|
||||
---
|
||||
|
||||
At this point, when you still haven't found a solution to your problem, we encourage you to report the issue on our
|
||||
[Discord][discord] because it's now very likely that you stumbled over something we don't know
|
||||
yet. Read the following section
|
||||
to learn how to create a complete and helpful bug report.
|
||||
|
||||
## Issue template
|
||||
|
||||
We have created an issue template to make the bug reporting process as simple as possible, and more efficient for our
|
||||
community and us.
|
||||
|
||||
- [Title]
|
||||
- [Context]<small>optional</small>
|
||||
- [Bug description]
|
||||
- [Related links]
|
||||
- [Reproduction]
|
||||
- [Steps to reproduce]
|
||||
- [Checklist]
|
||||
|
||||
[Title]: #title
|
||||
[Context]: #context
|
||||
[Bug description]: #bug-description
|
||||
[Related links]: #related-links
|
||||
[Reproduction]: #reproduction
|
||||
[Steps to reproduce]: #steps-to-reproduce
|
||||
[Checklist]: #checklist
|
||||
|
||||
### Title
|
||||
|
||||
A good title is short and descriptive. It should be a one-sentence executive summary of the issue, so the impact and
|
||||
severity of the bug you want to report can be inferred from the title.
|
||||
|
||||
| <!-- --> | Example |
|
||||
| -------- |--------------------------------------------------------------------------------------------------------------|
|
||||
| :material-check:{ style="color: #4DB6AC" } __Clear__ | Speed Limit Control (SLC) stuck in `preActive` when engaged |
|
||||
| :material-close:{ style="color: #EF5350" } __Wordy__ | The Speed Limit Control (SLC) remains in the `preActive` state when longitudinal it's supposed to be engaged |
|
||||
| :material-close:{ style="color: #EF5350" } __Unclear__ | SLC does not work |
|
||||
| :material-close:{ style="color: #EF5350" } __Useless__ | Help |
|
||||
|
||||
### Context <small>optional</small> { #context }
|
||||
|
||||
Before describing the bug, you can provide additional context for us to understand what you were trying to achieve.
|
||||
Explain the circumstances in which you're using sunnypilot, and what you _think_ might be relevant. Don't write
|
||||
about the bug here.
|
||||
|
||||
!!! note "__Why this might be helpful__"
|
||||
Some errors only manifest in specific settings, environments or edge cases, for example, when the feature is not available
|
||||
to certain cars.
|
||||
|
||||
### Bug description
|
||||
|
||||
Now, to the bug you want to report. Provide a clear, focused, specific, and concise summary of the bug you encountered.
|
||||
Explain why you think this is a bug that should be reported to sunnypilot, and not to one of its dependencies.[^3]
|
||||
Adhere to the following principles:
|
||||
|
||||
[^3]:
|
||||
Sometimes, users report bugs on our [sunnypilot's issue tracker] or [Discord][discord]
|
||||
that are caused by one of our upstream dependencies, including [comma's openpilot], [comma's panda],
|
||||
or other openpilot forks' dependencies. A good rule of thumb is
|
||||
to reproduce the issue with stock openpilot in the same conditions and
|
||||
check if the problem persists. If it does, the problem is likely not
|
||||
related to sunnypilot and should be reported upstream. When in
|
||||
doubt, use our [Discord][discord] to ask for help.
|
||||
|
||||
[comma's openpilot]: https://github.com/commaai/openpilot
|
||||
[comma's panda]: https://github.com/commaai/panda
|
||||
|
||||
- __Explain the <u>what</u>, not the <u>how</u>__ – don't explain
|
||||
[how to reproduce the bug][Steps to reproduce] here, we're getting there.
|
||||
Focus on articulating the problem and its impact as clearly as possible.
|
||||
|
||||
- __Keep it short and concise__ – if the bug can be precisely explained in one
|
||||
or two sentences, perfect. Don't inflate it – maintainers and future users
|
||||
will be grateful for having to read less.
|
||||
|
||||
- __One bug at a time__ – if you encounter several unrelated bugs, please
|
||||
create separate issues for them. Don't report them in the same issue, as
|
||||
this makes attribution difficult.
|
||||
|
||||
---
|
||||
|
||||
:material-run-fast: __Stretch goal__ – if you found a workaround or a way to fix
|
||||
the bug, you can help other users temporarily mitigate the problem before
|
||||
we maintainers can fix the bug in our code base.
|
||||
|
||||
!!! note "__Why we need this__"
|
||||
In order for us to understand the problem, we need a clear description of it and quantify its impact, which is
|
||||
essential for triage and prioritization.
|
||||
|
||||
### Related links
|
||||
|
||||
Of course, prior to reporting a bug, you have read our documentation and
|
||||
[could not find a working solution][search for solutions]. Please share links
|
||||
to all sections of our documentation that might be relevant to the bug, as it
|
||||
helps us gradually improve it.
|
||||
|
||||
Additionally, since you have searched [comma's issue tracker], [sunnypilot's issue tracker] or [Discord][discord]
|
||||
before reporting an issue, and have possibly found several issues or
|
||||
discussions, include those as well. Every link to an issue or discussion creates
|
||||
a backlink, guiding us maintainers and other users in the future.
|
||||
|
||||
---
|
||||
|
||||
:material-run-fast: __Stretch goal__ – if you also include the search terms you
|
||||
used when [searching for a solution][search for solutions] to your problem, you
|
||||
make it easier for us maintainers to improve the documentation.
|
||||
|
||||
[search for solutions]: #search-for-solutions
|
||||
|
||||
### Reproduction
|
||||
|
||||
A minimal reproduction is at the heart of every well-written bug report, as
|
||||
it allows us maintainers to instantly recreate the necessary conditions to
|
||||
inspect the bug to quickly find its root cause. It's a proven fact that issues
|
||||
with concise and small reproductions can be fixed much faster.
|
||||
|
||||
After you have created the reproduction, take note of your <u>__comma Dongle ID__</u>. It will be used during the bug
|
||||
report.
|
||||
|
||||
!!! note "__Why we need this__"
|
||||
If an issue contains no minimal reproduction or just a link to a repository with thousands of files, the
|
||||
maintainers would need to invest a lot of time into trying to recreate the right conditions to even inspect the
|
||||
bug, let alone fix it.
|
||||
|
||||
!!! warning "Don't share links to repositories"
|
||||
While we know that it is a good practice among developers to include a link
|
||||
to a repository with the bug report, we currently don't support those in our
|
||||
process. The reason is that the reproduction, which is automatically
|
||||
produced by the <u>__route ID__</u> contains all the necessary
|
||||
environment information that is often forgotten to be included.
|
||||
|
||||
Additionally, there are many non-technical users of sunnypilot that
|
||||
have trouble creating repositories.
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
At this point, you provided us with enough information to understand the bug
|
||||
and provided us with a reproduction that we could run and inspect. However, when
|
||||
we check your reproduction, it might not be immediately apparent how we can see
|
||||
the bug in action.
|
||||
|
||||
Thus, please list the specific steps we should follow when running your
|
||||
reproduction to observe the bug. Keep the steps short and concise, and make sure
|
||||
not to leave anything out. Use simple language as you would explain it to a
|
||||
five-year-old, and focus on continuity.
|
||||
|
||||
!!! note "__Why we need this__"
|
||||
We must know how to navigate your reproduction in order
|
||||
to observe the bug, as some bugs only occur at certain viewports or in
|
||||
specific conditions.
|
||||
|
||||
### Uploading logs and preserving routes
|
||||
|
||||
After reproducing the bug, please follow these steps to upload the necessary logs and preserve the routes.
|
||||
|
||||
1. Ensure the route is fully uploaded at [comma Connect]. We cannot look
|
||||
into issues without routes, or at least a comma Dongle ID.
|
||||
|
||||
1. Visit [comma Connect], select the route with the issue reproduced.
|
||||
2. Under the "Files" button, locate "All logs". Click "Upload x files".
|
||||
3. View the upload queue, and confirm that all raw logs are uploaded.
|
||||
|
||||
!!! note
|
||||
Sometimes when the qlogs of the route are still being uploaded, some raw logs may not be available to
|
||||
request for upload. Refresh the page a few times once you have confirmed all qlogs have been uploaded,
|
||||
then try to upload all raw logs again if available.
|
||||
|
||||
2. Share your Dongle ID with sunnypilot on [comma Connect].
|
||||
|
||||
1. Visit [comma Connect], navigate to the gear icon.
|
||||
2. Select "Share by email", and enter `support@sunnypilot.ai`.
|
||||
3. Confirm the sharing by clicking the share icon again.
|
||||
4. Set the device name to your vehicle's year/make/model and your Discord username, so it can be easily identified.
|
||||
|
||||
3. Once all raw logs are uploaded, click "More info" and enable the "Preserved" option to preserve the route.
|
||||
4. Attach the route ID in your issue submission.
|
||||
|
||||
[comma Connect]: https://connect.comma.ai
|
||||
|
||||
### Checklist
|
||||
|
||||
Thanks for following the guide and creating a high-quality and complete bug
|
||||
report – you are almost done. The checklist ensures that you have read this guide
|
||||
and have worked to your best knowledge to provide us with everything we need to
|
||||
know to help you.
|
||||
|
||||
- [ ] I have upgraded to the latest release version of sunnypilot.
|
||||
- [ ] I have removed or disable any customizations and confirmed the bug persists.
|
||||
- [ ] I have searched the documentation, issue trackers, and Discord for similar issues.
|
||||
- [ ] I have created a minimal reproduction and noted my comma Dongle ID.
|
||||
- [ ] I have shared my Dongle ID with sunnypilot at `support@sunnypilot.ai`.
|
||||
- [ ] I have filled out all required sections of the issue template.
|
||||
- [ ] I have followed this guide and ensured all necessary information is included.
|
||||
|
||||
__We'll take it from here.__
|
||||
@@ -1,97 +0,0 @@
|
||||
# Documentation issues
|
||||
|
||||
Our documentation is composed of many pages and includes extensive
|
||||
information on features, configurations, customizations, and much more. If you
|
||||
have found an inconsistency or see room for improvement, please follow this
|
||||
guide to submit an issue on our [issue tracker].
|
||||
|
||||
[issue tracker]: https://github.com/sunnypilot/sunnypilot/issues
|
||||
|
||||
## Issue template
|
||||
|
||||
Reporting a documentation issue is usually less involved than reporting a bug.
|
||||
Please thoroughly read this guide before creating a new documentation issue,
|
||||
and provide the following information as part of the issue:
|
||||
|
||||
- [Title]
|
||||
- [Description]
|
||||
- [Related links]
|
||||
- [Proposed change] <small>optional</small>
|
||||
- [Checklist]
|
||||
|
||||
[Title]: #title
|
||||
[Description]: #description
|
||||
[Related links]: #related-links
|
||||
[Proposed change]: #proposed-change
|
||||
[Checklist]: #checklist
|
||||
|
||||
### Title
|
||||
|
||||
A good title should be a short, one-sentence description of the issue, contain
|
||||
all relevant information and, in particular, keywords to simplify the search in
|
||||
our issue tracker.
|
||||
|
||||
| <!-- --> | Example |
|
||||
| -------- | -------- |
|
||||
| :material-check:{ style="color: #4DB6AC" } __Clear__ | Clarify Speed Limit Control engagement
|
||||
| :material-close:{ style="color: #EF5350" } __Unclear__ | Missing information in the docs
|
||||
| :material-close:{ style="color: #EF5350" } __Useless__ | Help
|
||||
|
||||
### Description
|
||||
|
||||
Provide a clear and concise summary of the inconsistency or issue you
|
||||
encountered in the documentation or the documentation section that needs
|
||||
improvement. Explain why you think the documentation should be adjusted and
|
||||
describe the severity of the issue:
|
||||
|
||||
- __Keep it short and concise__ – if the inconsistency or issue can be
|
||||
precisely explained in one or two sentences, perfect. Maintainers and future
|
||||
users will be grateful for having to read less.
|
||||
|
||||
- __One issue at a time__ – if you encounter several unrelated inconsistencies,
|
||||
please create separate issues for them. Don't report them in the same issue
|
||||
– it makes attribution difficult.
|
||||
|
||||
!!! note "__Why we need this__"
|
||||
Describing the problem clearly and concisely is a prerequisite for improving
|
||||
our documentation – we need to understand what's wrong, so we can fix it.
|
||||
|
||||
### Related links
|
||||
|
||||
After you described the documentation section that needs to be adjusted above,
|
||||
we now ask you to share the link to this specific documentation section and
|
||||
other possibly related sections. Make sure to use anchor links (permanent links)
|
||||
where possible, as it simplifies discovery.
|
||||
|
||||
!!! note "__Why we need this__"
|
||||
Providing the links to the documentation help us understand which sections
|
||||
of our documentation need to be adjusted, extended, or overhauled.
|
||||
|
||||
### Proposed change <small>optional</small> { #proposed-change }
|
||||
|
||||
Now that you have provided us with the description and links to the
|
||||
documentation sections, you can help us, maintainers, and the community by
|
||||
proposing an improvement. You can sketch out rough ideas or write a concrete
|
||||
proposal. This field is optional but very helpful.
|
||||
|
||||
!!! note "__Why we need this__"
|
||||
An improvement proposal can be beneficial for other users who encounter
|
||||
the same issue, as they offer solutions before we maintainers can update
|
||||
the documentation.
|
||||
|
||||
### Checklist
|
||||
|
||||
Thanks for following the guide and providing valuable feedback for our
|
||||
documentation – you are almost done. The checklist ensures that you have read
|
||||
this guide and have worked to your best knowledge to provide us with every piece
|
||||
of information we need to improve it.
|
||||
|
||||
- [ ] I have provided a clear and descriptive title for the documentation issue.
|
||||
- [ ] I have summarized the inconsistency or issue concisely in the description.
|
||||
- [ ] I have included links to the specific documentation section(s) that need
|
||||
adjustments.
|
||||
- [ ] (Optional) I have proposed a change or improvement to the documentation.
|
||||
- [ ] I have followed this guide and ensured all necessary information is included.
|
||||
|
||||
__We'll take it from here.__
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: Auto Lane Change
|
||||
description: Detailed documentation on the Auto Lane Change feature in sunnypilot.
|
||||
---
|
||||
|
||||
# Auto Lane Change
|
||||
|
||||
sunnypilot's Auto Lane Change feature allows the vehicle to automatically change lanes.
|
||||
|
||||
## Key Setting and How to Use
|
||||
|
||||
- **Nudge to Confirm:** This is the default method. To perform a lane change, activate the turn signal in the desired direction and then gently turn the steering wheel (nudges) in the same direction to confirm the maneuver.
|
||||
- **Nudge-less Lane Change:** For a more automated experience, users can enable a "nudge-less" option. With this setting, you only need to activate the turn signal to initiate a lane-change.
|
||||
- **Nudge-less with Delay** This is same as Nudge-less mode, but with a fixed delay. Once the turn-indicator is activated, sunnypilot will wait for the selected delay before initiating the lane change.
|
||||
|
||||
### Additional Related Settings
|
||||
- **Delay with Blind Spot:** This crucial safety feature adds a delay to the lane change if the vehicle's blind spot monitoring (BSM) system detects an object. The lane change will only proceed after the blind spot is clear.
|
||||
|
||||
## Important Considerations
|
||||
|
||||
- **Manual Override:** The driver can regain control at any time by taking control of the steering wheel.
|
||||
- **Driver Monitoring:** sunnypilot utilizes driver monitoring to ensure the driver remains attentive.
|
||||
- **Object Detection:** The system is primarily designed to follow lane lines and may not detect all objects. The driver must always be prepared to take control.
|
||||
- **Lane Markings:** The feature's performance is dependent on clear and visible lane markings.
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Custom ACC Increments
|
||||
description: Detailed documentation on the Custom ACC Increments feature in sunnypilot.
|
||||
---
|
||||
|
||||
# Custom ACC Increments
|
||||
|
||||
sunnypilot offers customization to the set speed increments for Adaptive Cruise Control (ACC) when sunnypilot is controlling the vehicle's longitudinal control. This allows for more precise speed adjustments compared to the default behavior of many vehicles.
|
||||
|
||||
- **Short press**: This controls the speed adjustment when the "RES+" or "SET-" button is pressed for a short period of time.
|
||||
- Allowed values: 1 through 10
|
||||
- **Long press**: This controls the speed adjustment when the "RES+" or "SET-" button is pressed for a longer duration.
|
||||
- Allowed values: 1, 5, 10
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
title: Dynamic Experimental Control
|
||||
description: Detailed documentation on the Dynamic Experimental Control feature in sunnypilot.
|
||||
---
|
||||
|
||||
# Dynamic Experimental Control (DEC)
|
||||
|
||||
Dynamic Experimental Control (DEC) intelligently switches between the standard adaptive cruise control (ACC) and the end-to-end longitudinal control of Experimental Mode based on the driving conditions.
|
||||
|
||||
**Chill Mode** is the standard adaptive cruise control method. It is designed to provide a smooth, predictable driving experience. However, it does not provide advanced driving capabilities like stopping at red lights and stop signs.
|
||||
|
||||
**Experimental Mode** allows the system to control the vehicle's speed and tries to drive like a human, enabling it to slow down for turns, stop at red lights, and stop signs. While capable and experimental, this mode can sometimes be overly cautious, especially on highways.
|
||||
|
||||
***Dynamic Experimental Control*** aims to provide the best of both worlds: the advanced capabilities of Experimental Mode when needed, and the smooth, predictable behavior of the standard system for less complex driving scenarios. This allows for a more natural driving experience by using Experimental Mode in situations where it excels, such as city driving and tight turns, while reverting to the default behavior for highway driving.
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
# **How custom longitudinal tuning works for Hyundai/Kia/Genesis vehicles in sunnypilot.**
|
||||
|
||||
To begin this documentation, I would like to first present the safety guidelines followed to create the tune:
|
||||
|
||||
Our main safety guideline considered is [ISO 15622:2018](https://www.iso.org/obp/ui/en/#iso:std:iso:15622:ed-3:v1:en)
|
||||
This provides the groundwork of safety limits this tune must adhere too, and therefore, must be followed.
|
||||
For example, in our jerk calculations throughout this tune, you will see how maximum jerk is clipped using the equation provided.
|
||||
|
||||
In the tuning you will see a set of equations, the first being jerk, **but what exactly is jerk?**
|
||||
Jerk is calculated by taking current acceleration (in the form of m/s^2), subtracting that by previous acceleration, and
|
||||
dividing that by time. In our tune you will see the following equation:
|
||||
|
||||
planned_accel = CC.actuators.accel
|
||||
current_accel = CS.out.aEgo
|
||||
blended_value = 0.67 * planned_accel + 0.33 * current_accel
|
||||
delta = blended_value - self.state.accel_last_jerk
|
||||
|
||||
self.state.jerk = math.copysign(delta * delta, delta)
|
||||
self.state.accel_last_jerk = blended_value
|
||||
|
||||
Instead of using a hardcoded time, we are focused on making jerk parabolic. First we have our planned acceleration from longitudinal_planner.
|
||||
Then we have our current carstate acceleration. These are then blended together 67:33 = 100% to form our blended value.
|
||||
Following this, we have our delta which subtracts our blended_value from our previous acceleration `self.state.accel_last_jerk`
|
||||
Lastly, we have our finalized jerk calculation, which squares the delta to create a parabolic response while retaining the original sign,
|
||||
which could be positive or negative (e.g., 5.0 or -5.0). This then goes through our minimum and maximum clipping
|
||||
which forces a value between our set min and max, which I discuss later in this readme.
|
||||
|
||||
Moving on, the accel_last_jerk, stores current accel after each iteration and uses that in the calculation as previous accel for
|
||||
our jerk calculations. Now we see the calculation of jerk max and jerk min.
|
||||
|
||||
### Let's dive into how jerk lower limit max is calculated:
|
||||
|
||||
velocity = CS.out.vEgo
|
||||
if velocity < 5.0:
|
||||
decel_jerk_max = self.car_config.jerk_limits[1]
|
||||
elif velocity > 20.0:
|
||||
decel_jerk_max = 2.5
|
||||
else:
|
||||
decel_jerk_max = 3.64284 - 0.05714 * velocity
|
||||
|
||||
This equation above is set by ISO 15622, and dictates that jerk lower limit can only be five when below 5 m/s. In our equation,
|
||||
|
||||
self.car_config.jerk_limits[1]
|
||||
|
||||
Jerk_limits[1] represents a jerk value of 3.3 m/s^3, which is the maximum analyzed lower jerk rate seen on stock SCC CAN.
|
||||
Between 5 m/s and 20 m/s jerk is capped using the calculation:
|
||||
|
||||
decel_jerk_max = 3.64284 - 0.05714 * velocity
|
||||
|
||||
This equation calculates the linear jerk from 6m/s to 19m/s, scaling down from 3.3 to 2.5 m/s^3.
|
||||
This means that if current velocity is say, 15 m/s the final jerk max value would be capped at 2.78 m/s^3.
|
||||
Anything above 20 m/s is capped to a lower jerk max of 2.5 m/s^3. This allows for a smoother jerk range, while complying to ISO standards to a tee.
|
||||
The current jerk Lower Limit you will see in openpilot before this tune, is 5.0 m/s^3; Which as you can see from using the above calculation,
|
||||
the 5.0 m/s^3 technically does not comply with ISO standards at any speed above 5.0 m/s^3.
|
||||
Having our jerk max be clipped to these values not only allows for better consistency with ISO standards,
|
||||
but also enables us to have a much smoother braking experience.
|
||||
|
||||
### Getting into our next topic, I would like to explain how our minimum jerk was chosen.
|
||||
|
||||
Minimum jerk was chosen based off of the following guideline proposed by Handbook of Intellegent Vehicles (2012):
|
||||
`Ride comfort may be sacrificed only under emergency conditions when vehicle and occupant safety consideration may preclude comfort.`
|
||||
|
||||
### What the value of 0.53 m/s^3 of the jerk lower limit was chosen based off of
|
||||
|
||||
[Carlowitz et al. (2024).](https://www.researchgate.net/publication/382274551_User_evaluation_of_comfortable_deceleration_profiles_for_highly_automated_driving_Findings_from_a_test_track_study)
|
||||
This research study identified the average lower jerk used in comfortable driving settings, which is 0.53 m/s^3.
|
||||
This is then inputted to jerk_limits[0] as 0.53 m/s^3 represents the value used in upper jerk absolute minimum.
|
||||
|
||||
min_lower_jerk = self.car_config.jerk_limits[0]
|
||||
|
||||
As shown above, lower jerk minimum of 0.53 is used for our lower_jerk minimum bounds.
|
||||
|
||||
### Why our minimum upper jerk is conditional
|
||||
|
||||
Our minimum upper band jerk is conditional as well and is denoted below:
|
||||
|
||||
min_upper_jerk = self.car_config.jerk_limits[0] if (velocity > 3.611) else 0.60
|
||||
|
||||
This means that for speeds under 3.611 m/s (8.077 mph/ 13 kph) we have a minimum jerk of 0.60. This allows for smooth
|
||||
takeoffs while not causing lag. For all other speeds, we use our normal jerk_limit for minimum, which is 0.53.
|
||||
|
||||
### Next, we have our acceleration limiting
|
||||
|
||||
For acceleration limiting, we use TCS signal brakeLightsDEPRECATED to measure when to enact the standstill delay
|
||||
which stock SCC uses to allow smoother transitions in acceleration.
|
||||
|
||||
### Lastly, we have our accel value calculations for hyundaican.py
|
||||
|
||||
For our accel value calculations we have the following:
|
||||
|
||||
`self.accel_value = np.clip(self.accel_raw, self.state.accel_last - jerk_number, self.state.accel_last + jerk_number)`
|
||||
|
||||
This essentially means that we have our accel_raw, which is acceleration (m/s^2), followed by our clipping variables.
|
||||
jerk_number in this equation represents exactly `0.1`, which is subtracted or added by self.state.accel_last, which is
|
||||
previous calculated accel_value. Furthermore, we have `self.state.accel_last`, which is calculated as the stored accel from
|
||||
the above calculations.
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
title: ICBM (Intelligent Cruise Button Management)
|
||||
description: Detailed documentation on the ICBM feature in sunnypilot.
|
||||
---
|
||||
|
||||
# ICBM (Intelligent Cruise Button Management)
|
||||
|
||||
Intelligent Cruise Button Management (ICBM) is a system designed to manage the vehicle's cruise control set speed by sending cruise control button commands via CAN bus to the car.
|
||||
|
||||
ICBM is particularly useful in vehicles openpilot Longitudinal Control is not available or not desirable to use. By simulating button presses to adjust the stock cruise control set speed, sunnypilot's ICBM can manage the car's speed while keeping native safety features active, such as Forward Collision Warning (FCA) and Automatic Emergency Braking (AEB).
|
||||
|
||||
ICBM also allows the vehicle to use the following features:
|
||||
|
||||
- **[Smart Cruise Control - Vision (SCC-V)]()**
|
||||
- **[Smart Cruise Control - Map (SCC-M)]()**
|
||||
- **[Speed Limit Assist (SLA)]()**
|
||||
- **[Custom ACC Increments]()**
|
||||
@@ -1,17 +0,0 @@
|
||||
# Modular Assistive Driving System (M.A.D.S.)
|
||||
|
||||
Modular Assistive Driving System (MADS) aims to elevate the user's driving experience by modifying the behaviors of
|
||||
driving assist engagements.
|
||||
|
||||
!!! note
|
||||
This feature aligns closely with comma.ai's safety rules.
|
||||
|
||||
## Independent Engagement
|
||||
|
||||
MADS allows users to engage sunnypilot Automatic Lane Centering (ALC) for lateral control and Adaptive Cruise Control
|
||||
(ACC) or Smart Cruise Control (SCC) for longitudinal control independently.
|
||||
|
||||
!!! note "Why This Feature Exists"
|
||||
While newer car models allow for independent engagement of lateral (steering) and longitudinal (speed) control,
|
||||
many older vehicle models and stock openpilot enforce engaging both controls together. MADS introduces this modern
|
||||
convenience to older vehicle models, effectively backporting a feature found in newer cars and providing users more flexibility.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
title: Neural Network Lateral Control (NNLC)
|
||||
description: Detailed documentation on the NNLC feature in sunnypilot.
|
||||
---
|
||||
|
||||
# Neural Network Lateral Control (NNLC)
|
||||
|
||||
sunnypilot's Neural Network Lateral Control (NNLC) is a feature that enhances the system's ability to steer a vehicle. It enhances the standard lateral controller with one based on a neural network trained on the vehicle's torque data, aiming for smoother and more precise steering adjustments.
|
||||
|
||||
## Key Aspects of NNLC
|
||||
|
||||
- **Improved Accuracy:** The neural network is trained using driving data specific to each vehicle, which allows for more accurate control.
|
||||
- **Smoother Turns:** NNLC inputs past curvature data into its driving model to achieve smoother and more precise lateral control, especially noticeable when taking curves on a highway. Users report that it reduces the oversteering and understeering corrections.
|
||||
|
||||
Formerly known as "NNFF" (Neural Network Feedforward), NNLC aims to make the driving experience feel more natural and less "jittery" in turns.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Smart Cruise Control - Map (SCC-M)
|
||||
|
||||
Smart Cruise Control - Map (SCC-M) leverages map data to proactively adjust your vehicle's speed by calculating curvature in the road ahead.
|
||||
|
||||
!!! note
|
||||
This feature is only available when sunnypilot is actively controlling the vehicle's longitudinal control.
|
||||
|
||||
!!! note
|
||||
This feature requires OpenStreetMap data for the current location to be downloaded.
|
||||
@@ -1,6 +0,0 @@
|
||||
# Smart Cruise Control - Vision (SCC-V)
|
||||
|
||||
Smart Cruise Control - Vision (SCC-V) leverages camera vision data to proactively adjust your vehicle's speed by calculating curvature in the road ahead.
|
||||
|
||||
!!! note
|
||||
This feature is only available when sunnypilot is actively controlling the vehicle's longitudinal control.
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: Speed Limit Assist
|
||||
description: Detailed documentation on the Speed Limit Assist feature in sunnypilot.
|
||||
---
|
||||
|
||||
# Speed Limit Assist (SLA)
|
||||
|
||||
Speed Limit Assist (SLA) is a comprehensive framework in sunnypilot that adjusts the vehicle's set cruise speed according to detected speed limits. It reconciles data from multiple sources to determine the current speed limit.
|
||||
|
||||
## Core Functionality
|
||||
|
||||
SLA utilizes various data sources to determine the speed limit for the current road:
|
||||
|
||||
- **OpenStreetMap (OSM):** Utilizes map data to fetch speed limits. sunnypilot has a refactored OSM implementation for lower resource usage and provides weekly updates.
|
||||
- **Car's Stock System:** On some compatible vehicles, it can pull speed limit data directly from the car's native system.
|
||||
|
||||
## Key Features and Customization
|
||||
|
||||
sunnypilot offers extensive customization for its speed control features:
|
||||
|
||||
- **Selectable Speed Limit Source:** Users can define the priority of data sources (e.g., OSM, Navigation, Vision) for determining the speed limit.
|
||||
- **Configurable Offsets:** You can set an offset above the detected speed limit, either as a fixed value (e.g., +5 mph) or a percentage.
|
||||
- **Engagement Modes:**
|
||||
- **Auto:** Automatically adjusts the cruise speed when a new speed limit is detected.
|
||||
- **User Confirm:** Informs the driver of the new speed limit and waits for them to confirm the change before adjusting the speed.
|
||||
- **Alerts and Warnings:** The system provides alerts to inform the driver before a speed adjustment occurs.
|
||||
@@ -1,9 +0,0 @@
|
||||
# To start developing sunnypilot
|
||||
|
||||
sunnypilot is a fork of [commaai's openpilot](https://github.com/commaai/openpilot), developed by [sunnypilot](https://sunnypilot.ai) and by users like you.
|
||||
We welcome both pull requests and issues on [GitHub](http://github.com/sunnypilot/sunnypilot).
|
||||
|
||||
* Join the [community Discord](https://discord.sunnypilot.ai)
|
||||
* Check out [the contributing docs](../community/CONTRIBUTING.md)
|
||||
* Check out the [openpilot tools](https://github.com/sunnypilot/sunnypilot/tree/master/tools)
|
||||
* Read about the [development workflow](../community/WORKFLOW.md)
|
||||
@@ -1,16 +0,0 @@
|
||||
# To start using sunnypilot in a car
|
||||
|
||||
To use sunnypilot in a car, you need four things:
|
||||
|
||||
1. **Supported Device:** a comma 3/3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x).
|
||||
|
||||
2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software. Use the URL `release-c3.sunnypilot.ai` to install the release version.
|
||||
|
||||
3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md).
|
||||
|
||||
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car.
|
||||
|
||||
[comma.ai](https://comma.ai) have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup).
|
||||
|
||||
!!! note
|
||||
It's possible to run sunnypilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play.
|
||||
@@ -1,11 +0,0 @@
|
||||
# What is sunnypilot?
|
||||
|
||||
sunnypilot is a fork of [comma.ai's openpilot](https://github.com/commaai/openpilot), an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 250+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible.
|
||||
|
||||
## How do I use it?
|
||||
|
||||
sunnypilot is designed to be used on the comma 3/3X.
|
||||
|
||||
## How does it work?
|
||||
|
||||
In short, sunnypilot uses the car's existing APIs for the built-in [ADAS](https://en.wikipedia.org/wiki/Advanced_driver-assistance_system) system and simply provides better acceleration, braking, and steering inputs than the stock system.
|
||||
@@ -1,3 +0,0 @@
|
||||
The documentation here is as best-effort sync from the official https://sunnypilot.github.io/ for ease of access and for AI enhancement when users asks on the forum.
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# Prohibited Safety Modifications
|
||||
|
||||
All [official sunnypilot branches](https://github.com/sunnyhaibin/sunnypilot/branches) strictly adhere to [comma.ai's safety policy](https://github.com/commaai/openpilot/blob/master/docs/SAFETY.md). Any changes that go against
|
||||
this policy will result in your fork and your device being banned from both comma.ai and sunnypilot channels.
|
||||
|
||||
The following changes are **VIOLATIONS** of the safety policy and **ARE NOT** supported in any official sunnypilot branches:
|
||||
|
||||
!!! danger "Driver Monitoring"
|
||||
- "Nerfing" or reducing monitoring parameters.
|
||||
|
||||
!!! danger "Panda Safety"
|
||||
- No preventing disengaging of <ins>**longitudinal control**</ins> (positive/negative acceleration) on brake pedal press.
|
||||
- No auto re-engaging of <ins>**longitudinal control**</ins> (positive/negative acceleration) on brake pedal release.
|
||||
- No disengaging on `CRUISE MAIN` in `OFF` state.
|
||||
@@ -1,20 +0,0 @@
|
||||
# 🚨 Read Before Installing
|
||||
|
||||
It is recommended to read the <u>**entire documentation**</u> before proceeding. This will ensure that you fully understand each added feature in sunnypilot. This also ensures that you are choosing the correct settings and branch for your car to have the best driving experience.
|
||||
|
||||
!!! warning
|
||||
By installing this software, you accept all responsibility for anything that might occur while you use it. sunnypilot and all contributors to sunnypilot are not liable.
|
||||
|
||||
**Use at your own risk.**
|
||||
|
||||
## Installation
|
||||
|
||||
Please refer to the [Recommended Branches](../branches/recommended-branches.md) to find your preferred/supported branch. This guide will assume you want to install the latest `release-c3` branch.
|
||||
|
||||
You can install sunnypilot on your comma 3/3X using one of the following methods:
|
||||
|
||||
- ### [URL Method (Directly on Device)](url-method.md)
|
||||
This method allows you to install sunnypilot directly from your device's screen using a provided URL. It's simple and user-friendly, requiring no additional tools or external devices.
|
||||
|
||||
- ### [SSH Method (Command Line)](ssh-method.md)
|
||||
This method is for advanced users who prefer to use SSH to clone the sunnypilot repository and install it manually via the command line. It offeres greater control over the installation process.
|
||||
@@ -1,28 +0,0 @@
|
||||
# SSH Method
|
||||
|
||||
If you are looking to install sunnypilot via SSH, run the following commands in an SSH terminal after connecting to your comma 3/3X:
|
||||
|
||||
1. Navigate to `data` directory
|
||||
```sh
|
||||
cd /data
|
||||
rm -rf openpilot
|
||||
```
|
||||
|
||||
2. Clone sunnypilot
|
||||
|
||||
!!! example ""
|
||||
`staging` branch is used in this step as an example.
|
||||
```sh
|
||||
git clone -b staging --recurse-submodules https://github.com/sunnypilot/sunnypilot.git openpilot
|
||||
```
|
||||
|
||||
3. Git LFS
|
||||
```sh
|
||||
cd openpilot
|
||||
git lfs pull
|
||||
```
|
||||
|
||||
4. Reboot
|
||||
```sh
|
||||
sudo reboot
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
# URL Method
|
||||
|
||||
The URL installation method can be done in two ways, depending on your device & if you already have sunnypilot installed.
|
||||
|
||||
=== "sunnypilot not 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, upon reboot, select `Custom Software` when given the option.
|
||||
3. Input the **Installation URL** per [Recommended Branches](../branches/recommended-branches.md).
|
||||
4. Complete the rest of the installation by following the onscreen instructions.
|
||||
|
||||
|
||||
=== "sunnypilot already installed"
|
||||
|
||||
1. On the comma 3/3X, go to `Settings` → `Software`.
|
||||
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from the sunnypilot repository on GitHub.
|
||||
3. At the `Target Branch` option, press `SELECT` to open the `Target Branch` selector.
|
||||
4. Scroll and select the **Desired Branch** per Recommended Branches.
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="13.1"
|
||||
export AGNOS_VERSION="12.6"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
#!/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
|
||||
|
||||
150
mkdocs-sp.yml
150
mkdocs-sp.yml
@@ -1,150 +0,0 @@
|
||||
site_name: sunnypilot docs
|
||||
repo_name: sunnypilot/sunnypilot
|
||||
repo_url: https://github.com/sunnypilot/sunnypilot/
|
||||
site_description: sunnypilot Documentation
|
||||
site_url: https://docs.sunnypilot.ai
|
||||
edit_uri: blob/new-docs/docs_sp
|
||||
|
||||
exclude_docs: README.md
|
||||
|
||||
strict: true
|
||||
docs_dir: docs_sp
|
||||
site_dir: docs_sp_site/
|
||||
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/brightness-auto
|
||||
name: Switch to light mode
|
||||
- scheme: default
|
||||
media: "(prefers-color-scheme: light)"
|
||||
primary: deep purple
|
||||
accent: teal
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
- scheme: slate
|
||||
media: "(prefers-color-scheme: dark)"
|
||||
primary: deep purple
|
||||
accent: teal
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to system preference
|
||||
font:
|
||||
text: Open Sans
|
||||
code: Fira Code
|
||||
logo: assets/sp_logo.svg
|
||||
favicon: assets/sp_logo.svg
|
||||
features:
|
||||
- content.code.copy
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
|
||||
extra_css:
|
||||
- stylesheets/style.css
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- attr_list
|
||||
- def_list
|
||||
- footnotes
|
||||
- md_in_html
|
||||
- tables
|
||||
- pymdownx.details
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.magiclink:
|
||||
normalize_issue_symbols: true
|
||||
repo_url_shorthand: true
|
||||
user: sunnypilot
|
||||
repo: sunnypilot
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
clickable_checkbox: true
|
||||
- toc:
|
||||
permalink: true
|
||||
|
||||
plugins:
|
||||
- git-authors:
|
||||
show_email_address: false
|
||||
- git-committers:
|
||||
repository: sunnypilot/sunnypilot
|
||||
branch: master
|
||||
enabled: !ENV [CI, false]
|
||||
- git-revision-date-localized:
|
||||
enable_creation_date: true
|
||||
- search
|
||||
- redirects:
|
||||
redirect_maps:
|
||||
'index.md': 'getting-started/what-is-sunnypilot.md'
|
||||
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/sunnypilot/sunnypilot
|
||||
- icon: fontawesome/brands/discord
|
||||
link: https://discord.sunnypilot.ai
|
||||
# analytics:
|
||||
# provider: google
|
||||
# property: !ENV GOOGLE_ANALYTICS_KEY
|
||||
# feedback:
|
||||
# title: Was this page helpful?
|
||||
# ratings:
|
||||
# - icon: material/emoticon-happy-outline
|
||||
# name: This page was helpful
|
||||
# data: 1
|
||||
# note: >-
|
||||
# Thanks for your feedback!
|
||||
# - icon: material/emoticon-sad-outline
|
||||
# name: This page could be improved
|
||||
# data: 0
|
||||
# note: >-
|
||||
# Thanks for your feedback! Help us improve this page by
|
||||
# using our <a href="..." target="_blank" rel="noopener">feedback form</a>.
|
||||
|
||||
nav:
|
||||
- Getting Started:
|
||||
- What is sunnypilot?: getting-started/what-is-sunnypilot.md
|
||||
- Use sunnypilot in a car: getting-started/use-sunnypilot-in-a-car.md
|
||||
- Develop sunnypilot: getting-started/develop-sunnypilot.md
|
||||
- Setup:
|
||||
- 🚨 Read before installing 🚨: setup/read-before-installing.md
|
||||
- Installation:
|
||||
- URL Method: setup/url-method.md
|
||||
- SSH Method: setup/ssh-method.md
|
||||
- Features:
|
||||
- Auto Lane Change: features/auto-lane-change.md
|
||||
- Custom ACC Increments: features/custom-acc-increments.md
|
||||
- Dynamic Experimental Control: features/dynamic-experimental-control.md
|
||||
- Hyundai Longitudinal Tuning: features/hyundai-longitudinal-tuning.md
|
||||
- Intelligent Cruise Button Management: features/icbm.md
|
||||
- Modular Assistive Driving System: features/mads.md
|
||||
- Neutral Network Lateral Control: features/nnlc.md
|
||||
- Smart Cruise Control - Map: features/scc-m.md
|
||||
- Smart Cruise Control - Vision: features/scc-v.md
|
||||
- Speed Limit - Assist: features/speed-limit-assist.md
|
||||
- Community:
|
||||
- Contributing: community/CONTRIBUTING.md
|
||||
- Workflow: community/WORKFLOW.md
|
||||
- Reporting a bug: community/reporting-a-bug.md
|
||||
- Reporting a docs issue: community/reporting-a-docs-issue.md
|
||||
- Discord Community: https://discord.sunnypilot.ai
|
||||
- Safety Information:
|
||||
- Safety: SAFETY.md
|
||||
- Prohibited safety modifications: safety-information/prohibited-safety-modifications.md
|
||||
- References:
|
||||
- Branches:
|
||||
- Recommended Branches: branches/recommended-branches.md
|
||||
- Branch Definitions: branches/definitions.md
|
||||
@@ -21,7 +21,7 @@ nav:
|
||||
- What is openpilot?: getting-started/what-is-openpilot.md
|
||||
- How-to:
|
||||
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
||||
- Connect to a comma 3X: how-to/connect-to-comma.md
|
||||
- Connect to a comma 3/3X: how-to/connect-to-comma.md
|
||||
# - Make your first pull request: how-to/make-first-pr.md
|
||||
#- Replay a drive: how-to/replay-a-drive.md
|
||||
- Concepts:
|
||||
|
||||
Submodule opendbc_repo updated: b8a00bddda...23d37a87ec
2
panda
2
panda
Submodule panda updated: 69ab12ee2a...0e7a3fd8cf
@@ -36,9 +36,6 @@ dependencies = [
|
||||
"pyopenssl < 24.3.0",
|
||||
"pyaudio",
|
||||
|
||||
# ubloxd (TODO: just use struct)
|
||||
"kaitaistruct",
|
||||
|
||||
# panda
|
||||
"libusb1",
|
||||
"spidev; platform_system == 'Linux'",
|
||||
@@ -80,13 +77,6 @@ docs = [
|
||||
"Jinja2",
|
||||
"natsort",
|
||||
"mkdocs",
|
||||
"mkdocs-material",
|
||||
"mkdocs-material-extensions",
|
||||
"mkdocs-git-revision-date-localized-plugin",
|
||||
"mkdocs-git-committers-plugin-2",
|
||||
"mkdocs-git-authors-plugin",
|
||||
"mkdocs-glightbox",
|
||||
"mkdocs-redirects",
|
||||
]
|
||||
|
||||
testing = [
|
||||
@@ -111,9 +101,8 @@ dev = [
|
||||
"av",
|
||||
"azure-identity",
|
||||
"azure-storage-blob",
|
||||
"dbus-next", # TODO: remove once we moved everything to jeepney
|
||||
"dbus-next",
|
||||
"dictdiffer",
|
||||
"jeepney",
|
||||
"matplotlib",
|
||||
"opencv-python-headless",
|
||||
"parameterized >=0.8, <0.9",
|
||||
@@ -131,7 +120,6 @@ dev = [
|
||||
|
||||
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')",
|
||||
"dearpygui>=2.1.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -169,6 +157,7 @@ testpaths = [
|
||||
"system/camerad",
|
||||
"system/hardware",
|
||||
"system/loggerd",
|
||||
"system/proclogd",
|
||||
"system/tests",
|
||||
"system/ubloxd",
|
||||
"system/webrtc",
|
||||
@@ -184,7 +173,7 @@ quiet-level = 3
|
||||
# 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"
|
||||
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]
|
||||
python_version = "3.11"
|
||||
@@ -258,7 +247,6 @@ exclude = [
|
||||
"teleoprtc_repo",
|
||||
"third_party",
|
||||
"*.ipynb",
|
||||
"generated",
|
||||
]
|
||||
lint.flake8-implicit-str-concat.allow-multiline = false
|
||||
|
||||
|
||||
@@ -3,34 +3,33 @@
|
||||
```
|
||||
## release checklist
|
||||
|
||||
### Go to staging
|
||||
- [ ] make a GitHub issue to track release
|
||||
- [ ] create release master branch
|
||||
**Go to `devel-staging`**
|
||||
- [ ] make issue to track release
|
||||
- [ ] 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`
|
||||
- [ ] open a pull request from `devel-staging` to `devel`
|
||||
- [ ] post on Discord, tag `@release crew`
|
||||
|
||||
Updating staging:
|
||||
1. either rebase on master or cherry-pick changes
|
||||
2. run this to update: `BRANCH=devel-staging release/build_devel.sh`
|
||||
3. build new userdata partition from `release3-staging`
|
||||
|
||||
### Go to release
|
||||
- [ ] before going to release, test the following:
|
||||
**Go to `devel`**
|
||||
- [ ] bump version on master: `common/version.h` and `RELEASES.md`
|
||||
- [ ] before merging the pull request, test the following:
|
||||
- [ ] update from previous release -> new release
|
||||
- [ ] update from new release -> previous release
|
||||
- [ ] fresh install with `openpilot-test.comma.ai`
|
||||
- [ ] drive on fresh install
|
||||
- [ ] no submodules or LFS
|
||||
- [ ] check sentry, MTBF, etc.
|
||||
- [ ] stress test passes in production
|
||||
- [ ] stress test in production
|
||||
|
||||
**Go to `release3`**
|
||||
- [ ] publish the blog post
|
||||
- [ ] `git reset --hard origin/release3-staging`
|
||||
- [ ] tag the release: `git tag v0.X.X <commit-hash> && git push origin v0.X.X`
|
||||
- [ ] create GitHub release
|
||||
- [ ] final test install on `openpilot.comma.ai`
|
||||
- [ ] update factory provisioning
|
||||
- [ ] close out milestone and issue
|
||||
- [ ] close out milestone
|
||||
- [ ] post on Discord, X, etc.
|
||||
```
|
||||
|
||||
@@ -17,23 +17,28 @@ rm -rf $TARGET_DIR
|
||||
mkdir -p $TARGET_DIR
|
||||
cd $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
|
||||
|
||||
# tmp branch
|
||||
git checkout --orphan tmp
|
||||
git fetch --depth 1 origin __nightly
|
||||
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
|
||||
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 '{}' \;
|
||||
|
||||
# cleanup before the copy
|
||||
# reset source tree
|
||||
cd $SOURCE_DIR
|
||||
git clean -xdff
|
||||
git submodule foreach --recursive git clean -xdff
|
||||
|
||||
# do the files copy
|
||||
echo "[-] copying files T=$SECONDS"
|
||||
@@ -42,7 +47,6 @@ cp -pR --parents $(./release/release_files.py) $TARGET_DIR/
|
||||
|
||||
# in the directory
|
||||
cd $TARGET_DIR
|
||||
rm -rf .git/modules/
|
||||
rm -f panda/board/obj/panda.bin.signed
|
||||
|
||||
# include source commit hash and build date in commit
|
||||
@@ -81,7 +85,7 @@ fi
|
||||
|
||||
if [ ! -z "$BRANCH" ]; then
|
||||
echo "[-] Pushing to $BRANCH T=$SECONDS"
|
||||
git push -f origin tmp:$BRANCH
|
||||
git push -f origin __nightly:$BRANCH
|
||||
fi
|
||||
|
||||
echo "[-] done T=$SECONDS, ready at $TARGET_DIR"
|
||||
@@ -1,5 +0,0 @@
|
||||
source "https://rubygems.org"
|
||||
gem "faraday"
|
||||
gem "faraday-retry"
|
||||
gem "faraday-multipart"
|
||||
gem "listen"
|
||||
@@ -1,112 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
def self.client
|
||||
@client ||=
|
||||
Faraday.new(url: DOCS_TARGET) do |conn|
|
||||
conn.request :multipart
|
||||
conn.request :url_encoded
|
||||
conn.request :retry,
|
||||
{
|
||||
methods: %i[get post delete put],
|
||||
retry_statuses: [429],
|
||||
max: 3,
|
||||
retry_block: ->(env:, options:, retry_count:, exception:, will_retry_in:) do
|
||||
puts "Rate limited... will retry in #{will_retry_in}s"
|
||||
end,
|
||||
exceptions: [Faraday::TooManyRequestsError]
|
||||
}
|
||||
conn.response :json, content_type: "application/json"
|
||||
conn.response :raise_error
|
||||
conn.adapter Faraday.default_adapter
|
||||
conn.headers["Api-Key"] = DOCS_API_KEY
|
||||
end
|
||||
end
|
||||
|
||||
def self.edit_post(post_id:, raw:, title: nil, category: nil)
|
||||
if dry_run?
|
||||
puts " DRY RUN: skipping PUT /posts/#{post_id}"
|
||||
return
|
||||
end
|
||||
|
||||
params = {
|
||||
post: {
|
||||
raw: raw,
|
||||
edit_reason: "Synced from github.com/discourse/discourse-developer-docs"
|
||||
}
|
||||
}
|
||||
params[:title] = title if title
|
||||
params[:category] = category if category
|
||||
client.put("/posts/#{post_id}", params)
|
||||
end
|
||||
|
||||
def self.create_topic(external_id:, raw:, category:, title:)
|
||||
if dry_run?
|
||||
puts " DRY RUN: skipping POST /posts"
|
||||
return
|
||||
end
|
||||
client.post("/posts", { title: title, raw: raw, external_id: external_id, category: category })
|
||||
end
|
||||
|
||||
def self.trash_topic(topic_id:)
|
||||
if dry_run?
|
||||
puts " DRY RUN: skipping DELETE /t/#{topic_id}.json"
|
||||
return
|
||||
end
|
||||
|
||||
client.delete("/t/#{topic_id}.json")
|
||||
end
|
||||
|
||||
def self.fetch_current_state
|
||||
result =
|
||||
client.post(
|
||||
"/admin/plugins/explorer/queries/#{DATA_EXPLORER_QUERY_ID}/run",
|
||||
{ params: { category_id: CATEGORY_ID.to_s }.to_json }
|
||||
).body
|
||||
|
||||
raise "Data explorer query failed" if result["success"] != true
|
||||
|
||||
if result["columns"] != %w[t_id first_p_id external_id title raw deleted_at is_index_topic]
|
||||
raise "Data explorer query returned unexpected columns: #{result["columns"].inspect}"
|
||||
end
|
||||
|
||||
result["rows"].map do |row|
|
||||
{
|
||||
topic_id: row[0],
|
||||
first_post_id: row[1],
|
||||
external_id: row[2],
|
||||
title: row[3],
|
||||
raw: row[4],
|
||||
deleted_at: row[5],
|
||||
is_index_topic: row[6]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def self.restore_topic(topic_id:)
|
||||
path = "/t/#{topic_id}/recover.json"
|
||||
|
||||
if dry_run?
|
||||
puts " DRY RUN: skipping PUT #{path}"
|
||||
return
|
||||
end
|
||||
|
||||
client.put(path)
|
||||
end
|
||||
|
||||
def self.upload_file(path)
|
||||
if dry_run?
|
||||
puts " DRY RUN: skipping POST /uploads.json"
|
||||
return { "short_url" => "upload://placeholder" }
|
||||
end
|
||||
|
||||
client.post(
|
||||
"/uploads.json",
|
||||
{ type: "composer", synchronous: true, file: Faraday::UploadIO.new(path, "image/png") }
|
||||
).body
|
||||
end
|
||||
|
||||
def self.dry_run?
|
||||
[nil, true].include?(DRY_RUN)
|
||||
end
|
||||
end
|
||||
@@ -1,108 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Asset = Struct.new(:local_path, :local_sha1, :remote_short_url, keyword_init: true)
|
||||
|
||||
class LocalDoc
|
||||
attr_accessor :path,
|
||||
:frontmatter,
|
||||
:content,
|
||||
:topic_id,
|
||||
:first_post_id,
|
||||
:remote_content,
|
||||
:remote_title,
|
||||
:remote_deleted,
|
||||
:assets
|
||||
|
||||
def initialize(**kwargs)
|
||||
kwargs.each { |k, v| send("#{k}=", v) }
|
||||
self.assets ||= []
|
||||
end
|
||||
|
||||
def external_id
|
||||
"DOC-#{frontmatter["id"]}"
|
||||
end
|
||||
|
||||
def section
|
||||
path_segments = path.split("/")
|
||||
path_segments[0] if path_segments.size > 1
|
||||
end
|
||||
|
||||
def content_with_uploads
|
||||
unused_assets = assets.dup
|
||||
|
||||
result =
|
||||
content.gsub(/![^\]]+\]\(([^)]+)\)/) do |match|
|
||||
path = $1
|
||||
next match if !path.start_with?("/")
|
||||
|
||||
resolved = File.expand_path("#{__dir__}/../#{path}")
|
||||
assets_dir = File.expand_path("#{__dir__}/../assets/")
|
||||
raise "Invalid path: #{resolved}" if !resolved.start_with?(assets_dir)
|
||||
|
||||
digest = Digest::SHA1.file(resolved).hexdigest
|
||||
|
||||
asset = assets.find { |a| a.local_sha1 == digest }
|
||||
unused_assets.delete(asset)
|
||||
if !asset
|
||||
puts " Uploading #{path}..."
|
||||
result = API.upload_file(resolved)
|
||||
raise "File upload failed: #{result.inspect}" if !result["short_url"]
|
||||
asset =
|
||||
Asset.new(local_path: path, local_sha1: digest, remote_short_url: result["short_url"])
|
||||
assets.push(asset)
|
||||
end
|
||||
|
||||
short_url = asset.remote_short_url
|
||||
|
||||
match.gsub(path, short_url)
|
||||
end
|
||||
|
||||
unused_assets.each { |asset| assets.delete(asset) }
|
||||
|
||||
result = <<~MD
|
||||
#{result}
|
||||
|
||||
---
|
||||
|
||||
<small>This document is version controlled - suggest changes [on github](https://github.com/sunnypilot/sunnypilot/blob/master/docs_sp/#{path}).</small>
|
||||
MD
|
||||
|
||||
if assets.size == 0
|
||||
result
|
||||
else
|
||||
<<~MD
|
||||
#{result}
|
||||
<!-- START DOCS ASSET MAP
|
||||
#{serialized_assets}
|
||||
END DOCS ASSET MAP -->
|
||||
MD
|
||||
end
|
||||
end
|
||||
|
||||
def serialized_assets
|
||||
JSON.pretty_generate(
|
||||
assets.map do |asset|
|
||||
{
|
||||
local_path: asset.local_path,
|
||||
local_sha1: asset.local_sha1,
|
||||
remote_short_url: asset.remote_short_url
|
||||
}
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
def remote_content=(value)
|
||||
if value.match(/<!-- START DOCS ASSET MAP\n(.+?)\nEND DOCS ASSET MAP -->/m)
|
||||
self.assets = JSON.parse($1).map { |raw_asset| Asset.new(**raw_asset) }
|
||||
end
|
||||
|
||||
value.gsub!(/![^\]]+\]\(([^)]+)\)/) do |match|
|
||||
url = $1
|
||||
found_asset = assets.find { |a| a.remote_short_url == url }
|
||||
match.sub!(path, found_asset.local_path) if found_asset
|
||||
match
|
||||
end
|
||||
|
||||
@remote_content = value
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Util
|
||||
def self.parse_md(raw)
|
||||
if match = raw.match(/\A---\s*\n(.+?)\n---\n?(.*)\z/m)
|
||||
raw_frontmatter, content = match.captures
|
||||
frontmatter = YAML.safe_load(raw_frontmatter)
|
||||
else
|
||||
content = raw
|
||||
frontmatter = {}
|
||||
end
|
||||
|
||||
[frontmatter, content]
|
||||
end
|
||||
end
|
||||
@@ -51,19 +51,26 @@ git fetch origin $DEV_BRANCH || (git checkout -b $DEV_BRANCH && git commit --all
|
||||
|
||||
echo "[-] committing version $VERSION T=$SECONDS"
|
||||
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
|
||||
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
||||
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
||||
SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/common/version.h)
|
||||
SP_VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}')
|
||||
|
||||
# Commit with detailed message
|
||||
git commit -a -m "sunnypilot v$VERSION
|
||||
version: sunnypilot v$SP_VERSION (${EXTRA_VERSION_IDENTIFIER})
|
||||
date: $DATETIME
|
||||
master commit: $GIT_HASH
|
||||
"
|
||||
git branch --set-upstream-to=origin/$DEV_BRANCH
|
||||
# Add built files to git
|
||||
git add -f .
|
||||
if [ "$EXTRA_VERSION_IDENTIFIER" = "-release" ] || [ "$EXTRA_VERSION_IDENTIFIER" = "-staging" ]; then
|
||||
export VERSION=${VERSION%"$EXTRA_VERSION_IDENTIFIER"}
|
||||
git commit --amend -m "sunnypilot v$VERSION"
|
||||
else
|
||||
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
|
||||
|
||||
# Push!
|
||||
|
||||
@@ -14,7 +14,7 @@ def setup_argument_parser():
|
||||
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
|
||||
parser.add_argument('--source-branch', type=str, default='master',
|
||||
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')
|
||||
parser.add_argument('--squash-script-path', type=str, required=True,
|
||||
help='Path to the squash_and_merge.py script')
|
||||
|
||||
@@ -1,762 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "yaml"
|
||||
require "faraday"
|
||||
require "faraday/retry"
|
||||
require "faraday/multipart"
|
||||
require "listen"
|
||||
require "json"
|
||||
require "digest"
|
||||
|
||||
CATEGORY_ID = ENV["DOCS_CATEGORY_ID"].to_i
|
||||
DATA_EXPLORER_QUERY_ID = ENV["DOCS_DATA_EXPLORER_QUERY_ID"].to_i
|
||||
DOCS_TARGET = ENV["DOCS_TARGET"]
|
||||
DOCS_API_KEY = ENV["DOCS_API_KEY"]
|
||||
GEMINI_API_KEY = ENV["GEMINI_API_KEY"]
|
||||
|
||||
VERBOSE = ARGV.include?("-v")
|
||||
WATCH = ARGV.include?("--watch")
|
||||
DRY_RUN = ARGV.include?("--dry-run")
|
||||
|
||||
require_relative "lib/local_doc"
|
||||
require_relative "lib/api"
|
||||
require_relative "lib/util"
|
||||
|
||||
# Gemini API client for title generation
|
||||
class GeminiClient
|
||||
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent"
|
||||
#GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
|
||||
# MAX_REQUESTS_PER_MINUTE = 15
|
||||
MAX_REQUESTS_PER_MINUTE = 9
|
||||
RATE_LIMIT_WINDOW = 60 # seconds
|
||||
|
||||
def initialize(api_key)
|
||||
@api_key = api_key
|
||||
@request_timestamps = []
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
def generate_titles(file_path, content)
|
||||
return nil unless @api_key
|
||||
|
||||
wait_for_rate_limit
|
||||
|
||||
prompt = build_prompt(file_path, content)
|
||||
|
||||
response = Faraday.post(
|
||||
"#{GEMINI_API_URL}?key=#{@api_key}",
|
||||
{ contents: [{ parts: [{ text: prompt }] }] }.to_json,
|
||||
"Content-Type" => "application/json"
|
||||
)
|
||||
|
||||
parse_response(response)
|
||||
rescue => e
|
||||
puts "Error calling Gemini API: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wait_for_rate_limit
|
||||
@mutex.synchronize do
|
||||
now = Time.now
|
||||
|
||||
# Remove timestamps older than the rate limit window
|
||||
@request_timestamps.reject! { |ts| now - ts > RATE_LIMIT_WINDOW }
|
||||
|
||||
# If we've hit the limit, wait until the oldest request expires
|
||||
if @request_timestamps.length >= MAX_REQUESTS_PER_MINUTE
|
||||
oldest_request = @request_timestamps.first
|
||||
sleep_time = RATE_LIMIT_WINDOW - (now - oldest_request) + 0.1 # Add small buffer
|
||||
|
||||
if sleep_time > 0
|
||||
puts "Rate limit reached (#{MAX_REQUESTS_PER_MINUTE}/min). Waiting #{sleep_time.round(1)}s..."
|
||||
sleep(sleep_time)
|
||||
|
||||
# Clean up again after waiting
|
||||
now = Time.now
|
||||
@request_timestamps.reject! { |ts| now - ts > RATE_LIMIT_WINDOW }
|
||||
end
|
||||
end
|
||||
|
||||
# Record this request
|
||||
@request_timestamps << Time.now
|
||||
end
|
||||
end
|
||||
|
||||
def build_prompt(file_path, content)
|
||||
<<~PROMPT
|
||||
You are helping to generate documentation metadata. Given a markdown file path and its content, generate appropriate titles.
|
||||
|
||||
File path: #{file_path}
|
||||
|
||||
Content preview:
|
||||
#{content[0..500]}
|
||||
|
||||
Please analyze the file path and content, then provide:
|
||||
1. A full title (3-8 words, descriptive and professional, MUST be at least 15 characters long)
|
||||
2. A short title (1-3 words, concise, MUST be at least 15 characters long)
|
||||
|
||||
CRITICAL: Both titles MUST be at least 15 characters long. If a title would be shorter, expand it with relevant context.
|
||||
|
||||
Respond ONLY with valid JSON in this exact format:
|
||||
{
|
||||
"title": "Your Full Title Here",
|
||||
"short_title": "Short Title Here"
|
||||
}
|
||||
|
||||
Do not include any other text, explanation, or markdown formatting.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def parse_response(response)
|
||||
body = JSON.parse(response.body)
|
||||
text = body.dig("candidates", 0, "content", "parts", 0, "text")
|
||||
|
||||
return nil unless text
|
||||
|
||||
# Extract JSON from potential markdown code blocks
|
||||
json_text = text.strip.gsub(/^```json\n/, "").gsub(/\n```$/, "").strip
|
||||
|
||||
parsed = JSON.parse(json_text)
|
||||
|
||||
# Validate minimum length
|
||||
if parsed["title"] && parsed["title"].length < 15
|
||||
parsed["title"] = parsed["title"].ljust(15)
|
||||
end
|
||||
|
||||
if parsed["short_title"] && parsed["short_title"].length < 15
|
||||
parsed["short_title"] = parsed["short_title"].ljust(15)
|
||||
end
|
||||
|
||||
parsed
|
||||
rescue => e
|
||||
puts "Error parsing Gemini response: #{e.message}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Convert MkDocs "=== Tabs" sections to Obsidian callouts
|
||||
def convert_mkdocs_tabs_to_callouts(content, debug: false)
|
||||
lines = content.lines
|
||||
result = []
|
||||
i = 0
|
||||
match_count = 0
|
||||
|
||||
while i < lines.length
|
||||
line = lines[i]
|
||||
|
||||
# Detect a MkDocs tab start, e.g., === "sunnypilot not installed"
|
||||
if line =~ /^===\s+"([^"]+)"\s*$/
|
||||
match_count += 1
|
||||
tab_title = $1.strip
|
||||
|
||||
# Collect all indented lines following the tab
|
||||
body_lines = []
|
||||
i += 1
|
||||
while i < lines.length
|
||||
current_line = lines[i]
|
||||
|
||||
# Check if line is indented (4+ spaces or tab)
|
||||
if current_line =~ /^(?: |\t)/
|
||||
body_lines << current_line
|
||||
i += 1
|
||||
# Check if it's a blank line - peek ahead like we do for callouts
|
||||
elsif current_line =~ /^\s*$/
|
||||
peek_index = i + 1
|
||||
has_more_indented = false
|
||||
while peek_index < lines.length
|
||||
if lines[peek_index] =~ /^(?: |\t)/
|
||||
has_more_indented = true
|
||||
break
|
||||
elsif lines[peek_index] =~ /^\s*$/
|
||||
peek_index += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if has_more_indented
|
||||
body_lines << current_line
|
||||
i += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
else
|
||||
# Non-indented, non-blank line (could be next tab!) - stop
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
puts "DEBUG: Tab '#{tab_title}', lines: #{body_lines.length}" if debug
|
||||
|
||||
# Convert the tab section to a callout
|
||||
result << "> [!note] #{tab_title}\n"
|
||||
body_lines.each do |body_line|
|
||||
# Remove the first level of indentation (4 spaces or 1 tab) and add > prefix
|
||||
stripped = body_line.sub(/^(?: |\t)/, '')
|
||||
if stripped.strip.empty?
|
||||
result << ">\n"
|
||||
else
|
||||
result << ">#{stripped}"
|
||||
end
|
||||
end
|
||||
else
|
||||
result << line
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
puts "DEBUG: Total tab matches: #{match_count}" if debug
|
||||
result.join
|
||||
end
|
||||
|
||||
|
||||
# New implementation of MkDocs Material callout to Obsidian converter
|
||||
def convert_mkdocs_to_obsidian_callouts(content, debug: false)
|
||||
# Map of MkDocs callout types to Obsidian equivalents
|
||||
callout_map = {
|
||||
'note' => 'note',
|
||||
'tip' => 'tip',
|
||||
'important' => 'important',
|
||||
'warning' => 'warning',
|
||||
'caution' => 'caution',
|
||||
'info' => 'info',
|
||||
'success' => 'success',
|
||||
'question' => 'question',
|
||||
'failure' => 'failure',
|
||||
'danger' => 'danger',
|
||||
'bug' => 'bug',
|
||||
'example' => 'example',
|
||||
'quote' => 'quote',
|
||||
'abstract' => 'abstract',
|
||||
'summary' => 'summary',
|
||||
'tldr' => 'tldr'
|
||||
}
|
||||
|
||||
lines = content.lines
|
||||
result = []
|
||||
i = 0
|
||||
match_count = 0
|
||||
|
||||
while i < lines.length
|
||||
line = lines[i]
|
||||
|
||||
# Check if this line starts a callout (can be indented or not)
|
||||
# Capture any leading indentation
|
||||
if line =~ /^(\s*)!!!\s+(#{callout_map.keys.map { |k| Regexp.escape(k) }.join('|')})(?:\s+"([^"]*)")?\s*$/
|
||||
leading_indent = $1
|
||||
mkdocs_type = $2
|
||||
custom_title = $3
|
||||
match_count += 1
|
||||
obsidian_type = callout_map[mkdocs_type]
|
||||
|
||||
# Determine the base indentation level (number of spaces/tabs before !!!)
|
||||
base_indent_size = leading_indent.length
|
||||
|
||||
# Collect all lines that have MORE indentation than the base
|
||||
body_lines = []
|
||||
i += 1
|
||||
while i < lines.length
|
||||
current_line = lines[i]
|
||||
|
||||
# Check if line has the required base indentation plus more (for body content)
|
||||
# Body content should be indented relative to the !!! line
|
||||
required_indent = leading_indent + " " # base + 4 more spaces
|
||||
alt_required_indent = leading_indent + "\t" # base + 1 tab
|
||||
|
||||
if current_line.start_with?(required_indent) || current_line.start_with?(alt_required_indent)
|
||||
body_lines << current_line
|
||||
i += 1
|
||||
# Check if line is blank - might be between paragraphs
|
||||
elsif current_line =~ /^\s*$/
|
||||
peek_index = i + 1
|
||||
has_more_indented = false
|
||||
while peek_index < lines.length
|
||||
peek_line = lines[peek_index]
|
||||
if peek_line.start_with?(required_indent) || peek_line.start_with?(alt_required_indent)
|
||||
has_more_indented = true
|
||||
break
|
||||
elsif peek_line =~ /^\s*$/
|
||||
peek_index += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if has_more_indented
|
||||
body_lines << current_line
|
||||
i += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
else
|
||||
# Line doesn't have required indentation - stop
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
puts "DEBUG: Match #{match_count} - indent: #{base_indent_size} spaces, type: #{mkdocs_type}, title: #{custom_title.inspect}, body lines: #{body_lines.length}" if debug
|
||||
|
||||
# Build the converted callout, preserving the base indentation
|
||||
if body_lines.empty?
|
||||
if custom_title && !custom_title.empty?
|
||||
result << "#{leading_indent}> [!#{obsidian_type}] \"#{custom_title}\"\n"
|
||||
else
|
||||
result << "#{leading_indent}> [!#{obsidian_type}]\n"
|
||||
end
|
||||
else
|
||||
if custom_title && !custom_title.empty?
|
||||
result << "#{leading_indent}> [!#{obsidian_type}] \"#{custom_title}\"\n"
|
||||
else
|
||||
result << "#{leading_indent}> [!#{obsidian_type}]\n"
|
||||
end
|
||||
# Add > prefix to each body line, removing the extra level of indentation
|
||||
body_lines.each do |body_line|
|
||||
# Remove the base indent + one level (4 spaces or tab)
|
||||
stripped = body_line.sub(/^#{Regexp.escape(leading_indent)}(?: |\t)/, '')
|
||||
result << "#{leading_indent}>#{stripped}"
|
||||
end
|
||||
end
|
||||
else
|
||||
result << line
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
puts "DEBUG: Total matches found: #{match_count}" if debug
|
||||
|
||||
result.join
|
||||
end
|
||||
|
||||
# Convert MkDocs Material icons to standard emojis
|
||||
def convert_material_icons_to_emojis(content)
|
||||
# Map of common Material icons to emoji equivalents
|
||||
icon_map = {
|
||||
# Check/success icons
|
||||
':material-check:' => '✅',
|
||||
':material-check-circle:' => '✅',
|
||||
':material-check-bold:' => '✅',
|
||||
|
||||
# Close/error icons
|
||||
':material-close:' => '❌',
|
||||
':material-close-circle:' => '❌',
|
||||
':material-alert-circle:' => '⚠️',
|
||||
|
||||
# Info icons
|
||||
':material-information:' => 'ℹ️',
|
||||
':material-information-outline:' => 'ℹ️',
|
||||
':material-help-circle:' => '❓',
|
||||
|
||||
# Arrow icons
|
||||
':material-arrow-right:' => '→',
|
||||
':material-arrow-left:' => '←',
|
||||
':material-arrow-up:' => '↑',
|
||||
':material-arrow-down:' => '↓',
|
||||
|
||||
# Other common icons
|
||||
':material-lightbulb:' => '💡',
|
||||
':material-star:' => '⭐',
|
||||
':material-heart:' => '❤️',
|
||||
':material-fire:' => '🔥',
|
||||
':material-flag:' => '🚩',
|
||||
':material-link:' => '🔗',
|
||||
':material-pencil:' => '✏️',
|
||||
':material-delete:' => '🗑️',
|
||||
':material-calendar:' => '📅',
|
||||
':material-clock:' => '🕐',
|
||||
':material-email:' => '📧',
|
||||
':material-phone:' => '📞',
|
||||
}
|
||||
|
||||
# Replace material icons with emojis, ignoring any style attributes
|
||||
icon_map.each do |material_icon, emoji|
|
||||
# Match the icon with optional style attributes like { style="color: #EF5350" }
|
||||
content.gsub!(/#{Regexp.escape(material_icon)}\{\s*style="[^"]*"\s*\}/, emoji)
|
||||
# Also match without style attributes
|
||||
content.gsub!(material_icon, emoji)
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
# Helper method to generate frontmatter from file path
|
||||
def generate_frontmatter_from_path(path, content = nil, gemini_client = nil)
|
||||
# Remove .md extension and get the base name
|
||||
base_name = File.basename(path, ".md")
|
||||
|
||||
# Generate id from the full path (without extension)
|
||||
# Replace / with -
|
||||
# IMPORTANT: The LocalDoc#external_id method adds "DOC-" prefix (4 chars)
|
||||
# So we need to limit the base ID to 46 chars to stay under the 50 char API limit
|
||||
full_id = path.sub(/\.md$/, "").gsub("/", "-")
|
||||
|
||||
# Maximum length for the base ID (50 char API limit - 4 char "DOC-" prefix)
|
||||
max_base_id_length = 46
|
||||
|
||||
if full_id.length > max_base_id_length
|
||||
# Take first 37 chars and append an 8-char hash for uniqueness (37 + 1 dash + 8 = 46)
|
||||
hash_suffix = Digest::MD5.hexdigest(path)[0..7]
|
||||
id = "#{full_id[0..36]}-#{hash_suffix}"
|
||||
else
|
||||
id = full_id
|
||||
end
|
||||
|
||||
# Try to use Gemini for title generation
|
||||
if gemini_client && content
|
||||
gemini_titles = gemini_client.generate_titles(path, content)
|
||||
|
||||
if gemini_titles
|
||||
return {
|
||||
"id" => id,
|
||||
"title" => gemini_titles["title"],
|
||||
"short_title" => gemini_titles["short_title"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Fallback to original logic if Gemini fails or is not available
|
||||
title = base_name.split(/[-_]/).map(&:capitalize).join(" ")
|
||||
short_title = base_name.split(/[-_]/).map(&:capitalize).join(" ")
|
||||
|
||||
# Ensure minimum length
|
||||
title = title.ljust(15) if title.length < 15
|
||||
short_title = short_title.ljust(15) if short_title.length < 15
|
||||
|
||||
{
|
||||
"id" => id,
|
||||
"title" => title,
|
||||
"short_title" => short_title
|
||||
}
|
||||
end
|
||||
|
||||
# Helper method to generate index.md content for a folder
|
||||
def generate_folder_index(folder_name)
|
||||
# Convert folder name to a nice title (e.g., "my-folder" -> "My Folder")
|
||||
title = folder_name.split(/[-_]/).map(&:capitalize).join(" ")
|
||||
|
||||
"---\ntitle: #{title}\n---\n"
|
||||
end
|
||||
|
||||
# Convert internal markdown links (.md) to Discourse topic links
|
||||
def rewrite_internal_links(content, docs)
|
||||
require "uri"
|
||||
|
||||
content.gsub(/\]\(([^)]+\.md)(#[^)]+)?\)/) do |match|
|
||||
raw_link = $1
|
||||
anchor = $2 || ""
|
||||
# Strip any ./ or ../ from the beginning, but preserve subfolders
|
||||
normalized = raw_link.gsub(%r{^\./}, "").gsub(%r{^\.\./}, "")
|
||||
# Remove trailing .md
|
||||
normalized = normalized.gsub(/\.md$/, "")
|
||||
|
||||
# Try percent-decoding (handles %20 etc)
|
||||
begin
|
||||
normalized_decoded = URI.decode_www_form_component(normalized)
|
||||
rescue
|
||||
normalized_decoded = normalized
|
||||
end
|
||||
|
||||
candidates = []
|
||||
|
||||
# Strategy 1: exact match against doc.path without .md
|
||||
candidates += docs.select { |d| d.path.sub(/\.md$/, "") == normalized_decoded }
|
||||
|
||||
# Strategy 2: ends_with (useful if docs have a different root)
|
||||
if candidates.empty?
|
||||
candidates += docs.select { |d| d.path.end_with?("#{normalized_decoded}.md") }
|
||||
end
|
||||
|
||||
# Strategy 3: match by basename (e.g., linking to index.md or same-named file in subfolder)
|
||||
if candidates.empty?
|
||||
basename = File.basename(normalized_decoded)
|
||||
candidates += docs.select { |d| File.basename(d.path, ".md") == basename }
|
||||
end
|
||||
|
||||
# Strategy 4: index.md handling — if link pointed to a folder/index.md, allow folder match
|
||||
if candidates.empty? && normalized_decoded.end_with?("/index")
|
||||
folder = normalized_decoded.sub(/\/index$/, "")
|
||||
candidates += docs.select { |d| File.dirname(d.path) == folder && File.basename(d.path, ".md") == "index" }
|
||||
end
|
||||
|
||||
# Pick the best candidate (prefer exact match)
|
||||
target_doc =
|
||||
if candidates.any?
|
||||
# prefer exact equality if present
|
||||
exact = candidates.find { |d| d.path.sub(/\.md$/, "") == normalized_decoded }
|
||||
exact || candidates.first
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
if target_doc && target_doc.topic_id
|
||||
# Return a Discourse link preserving the anchor
|
||||
"](/t/-/#{target_doc.topic_id}?silent=true#{anchor})"
|
||||
else
|
||||
if VERBOSE
|
||||
puts "⚠️ rewrite_internal_links: unresolved '#{raw_link}' -> normalized='#{normalized_decoded}'"
|
||||
# show up to 10 possible docs to help debugging
|
||||
sample = docs.first(10).map(&:path).join(", ")
|
||||
puts " sample docs: #{sample}"
|
||||
end
|
||||
# Return original match unchanged so the link doesn't become invalid text
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize Gemini client if API key is available
|
||||
gemini_client = GEMINI_API_KEY ? GeminiClient.new(GEMINI_API_KEY) : nil
|
||||
|
||||
if gemini_client
|
||||
puts "✓ Gemini API configured for title generation"
|
||||
else
|
||||
puts "⚠ GEMINI_API_KEY not set - using fallback title generation"
|
||||
end
|
||||
|
||||
docs = []
|
||||
|
||||
puts "Reading local docs..."
|
||||
BASE = "#{__dir__}/../../docs_sp/"
|
||||
|
||||
# Generate index.md for each folder that doesn't have one
|
||||
folders_needing_index = Set.new
|
||||
Dir.glob("**/", base: BASE).each do |folder|
|
||||
next if folder == "./" || folder.empty?
|
||||
|
||||
folder_path = folder.chomp("/")
|
||||
index_path = File.join(BASE, folder_path, "index.md")
|
||||
|
||||
unless File.exist?(index_path)
|
||||
folders_needing_index.add(folder_path)
|
||||
|
||||
# Get the folder name (last component of the path)
|
||||
folder_name = File.basename(folder_path)
|
||||
|
||||
# Generate the index.md content
|
||||
index_content = generate_folder_index(folder_name)
|
||||
|
||||
# Write the index.md file
|
||||
File.write(index_path, index_content)
|
||||
puts "Generated index.md for folder: #{folder_path}" if VERBOSE
|
||||
end
|
||||
end
|
||||
|
||||
puts "Generated #{folders_needing_index.size} index.md files" if folders_needing_index.any?
|
||||
|
||||
Dir
|
||||
.glob("**/*.md", base: BASE)
|
||||
.each do |path|
|
||||
next if path.end_with?("index.md")
|
||||
next if path.include?("SAFETY")
|
||||
|
||||
content = File.read(File.join(BASE, path))
|
||||
|
||||
frontmatter, content = Util.parse_md(content)
|
||||
|
||||
# Convert MkDocs Material callouts to Obsidian format
|
||||
content = convert_mkdocs_tabs_to_callouts(content)
|
||||
content = convert_mkdocs_to_obsidian_callouts(content)
|
||||
content = convert_material_icons_to_emojis(content)
|
||||
|
||||
# Generate missing frontmatter fields dynamically
|
||||
generated = generate_frontmatter_from_path(path, content, gemini_client)
|
||||
|
||||
# Apply the generated values, ensuring ID is limited to 50 chars
|
||||
frontmatter["id"] = generated["id"]
|
||||
frontmatter["title"] ||= generated["title"]
|
||||
frontmatter["short_title"] ||= generated["short_title"]
|
||||
|
||||
puts "Generated frontmatter for '#{path}': id='#{frontmatter["id"]}', title='#{frontmatter["title"]}'" if VERBOSE
|
||||
|
||||
docs.push(LocalDoc.new(frontmatter:, path:, content:))
|
||||
end
|
||||
|
||||
puts "Rewriting internal links..."
|
||||
docs.each do |doc|
|
||||
doc.content = rewrite_internal_links(doc.content, docs)
|
||||
end
|
||||
|
||||
puts "Validating local docs..."
|
||||
docs
|
||||
.group_by { |doc| doc.external_id }
|
||||
.each do |id, docs|
|
||||
if docs.size > 1
|
||||
puts "- duplicate external_id '#{id}' found in:"
|
||||
docs.each { |doc| puts "- #{doc.path}" }
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
exit 0 if !DOCS_API_KEY
|
||||
|
||||
puts "Fetching remote info via data-explorer..."
|
||||
remote_topics = API.fetch_current_state
|
||||
|
||||
puts "Mapping to existing topics..."
|
||||
map_to_remote =
|
||||
lambda do
|
||||
docs.each do |doc|
|
||||
puts "- checking '#{doc.external_id}'..." if VERBOSE
|
||||
if topic_info = remote_topics.find { |t| t[:external_id] == doc.external_id }
|
||||
doc.topic_id = topic_info[:topic_id]
|
||||
doc.first_post_id = topic_info[:first_post_id]
|
||||
doc.remote_title = topic_info[:title]
|
||||
doc.remote_content = topic_info[:raw]
|
||||
doc.remote_deleted = topic_info[:deleted_at]
|
||||
puts " found topic_id: #{doc.topic_id}" if VERBOSE
|
||||
else
|
||||
puts " not found" if VERBOSE
|
||||
end
|
||||
end
|
||||
end
|
||||
map_to_remote.call
|
||||
|
||||
puts "Deleting topics if necessary..."
|
||||
|
||||
cat_desc_topic = remote_topics.find { |t| t[:is_index_topic] }
|
||||
if cat_desc_topic.nil?
|
||||
puts "Docs category is missing an index topic"
|
||||
exit 1
|
||||
end
|
||||
cat_desc_topic_id = cat_desc_topic[:topic_id]
|
||||
|
||||
remote_topics
|
||||
.reject { |remote_doc| remote_doc[:deleted_at] }
|
||||
.reject { |remote_doc| docs.any? { |doc| doc.topic_id == remote_doc[:topic_id] } }
|
||||
.reject { |remote_doc| remote_doc[:topic_id] == cat_desc_topic_id }
|
||||
.each do |remote_doc|
|
||||
id = remote_doc[:topic_id]
|
||||
puts "- deleting topic #{id}..."
|
||||
API.trash_topic(topic_id: id)
|
||||
end
|
||||
|
||||
puts "Restoring topics if necessary..."
|
||||
docs
|
||||
.filter(&:remote_deleted)
|
||||
.each do |doc|
|
||||
puts "- restoring '#{doc.external_id}'..."
|
||||
API.restore_topic(topic_id: doc.topic_id)
|
||||
end
|
||||
|
||||
puts "Creating missing topics..."
|
||||
created_any = false
|
||||
docs.each do |doc|
|
||||
next if doc.topic_id
|
||||
|
||||
created_any = true
|
||||
puts "- creating '#{doc.external_id} with title '#{doc.frontmatter["title"]}'..."
|
||||
converted_content = convert_mkdocs_to_obsidian_callouts(doc.content_with_uploads)
|
||||
API.create_topic(
|
||||
external_id: doc.external_id,
|
||||
raw: converted_content,
|
||||
category: CATEGORY_ID,
|
||||
title: doc.frontmatter["title"]
|
||||
)
|
||||
rescue Faraday::UnprocessableEntityError => e
|
||||
puts " 422 error: #{e.response[:body]}"
|
||||
raise e
|
||||
end
|
||||
|
||||
if created_any
|
||||
puts "Re-fetching remote info..."
|
||||
remote_topics = API.fetch_current_state
|
||||
map_to_remote.call
|
||||
end
|
||||
|
||||
puts "Updating content..."
|
||||
docs.each do |doc|
|
||||
if doc.topic_id.nil?
|
||||
next if DRY_RUN
|
||||
raise "Topic ID not found for '#{doc.external_id}'. Something went wrong with creating it?"
|
||||
end
|
||||
|
||||
# Convert callouts in the content before comparison and upload
|
||||
converted_content = convert_mkdocs_to_obsidian_callouts(doc.content_with_uploads)
|
||||
|
||||
if converted_content.strip == doc.remote_content.strip &&
|
||||
doc.frontmatter["title"] == doc.remote_title
|
||||
puts "- no changes required for '#{doc.external_id}' (topic_id: #{doc.topic_id})" if VERBOSE
|
||||
next
|
||||
end
|
||||
|
||||
puts "- updating '#{doc.external_id}' (topic_id: #{doc.topic_id})... new title: '#{doc.frontmatter["title"]}'"
|
||||
API.edit_post(
|
||||
post_id: doc.first_post_id,
|
||||
raw: converted_content,
|
||||
title: doc.frontmatter["title"],
|
||||
category: CATEGORY_ID
|
||||
)
|
||||
rescue Faraday::UnprocessableEntityError => e
|
||||
puts " 422 error: #{e.response[:body]}"
|
||||
raise e
|
||||
end
|
||||
|
||||
puts "Building index..."
|
||||
_, index_content = Util.parse_md(File.read("#{BASE}index.md"))
|
||||
index_content += "\n\n"
|
||||
docs
|
||||
.group_by { |doc| doc.section }
|
||||
.each do |section, section_docs|
|
||||
if section
|
||||
section_frontmatter, _ = Util.parse_md(File.read("#{BASE}#{section}/index.md"))
|
||||
index_content += "## #{section_frontmatter["title"]}\n\n"
|
||||
end
|
||||
|
||||
section_docs.each do |doc|
|
||||
index_content +=
|
||||
"- #{doc.frontmatter["short_title"]}: [#{doc.frontmatter["title"]}](/t/-/#{doc.topic_id}?silent=true)\n"
|
||||
end
|
||||
index_content += "\n"
|
||||
end
|
||||
|
||||
index_post_info = remote_topics.find { |t| t[:topic_id] == cat_desc_topic_id }
|
||||
|
||||
if index_post_info[:raw].strip == index_content.strip
|
||||
puts "- no changes required for index"
|
||||
else
|
||||
puts "- updating index..."
|
||||
API.edit_post(post_id: index_post_info[:first_post_id], raw: index_content)
|
||||
end
|
||||
|
||||
if WATCH
|
||||
puts "Watching for changes to files..."
|
||||
|
||||
Listen
|
||||
.to("#{__dir__}/docs") do |modified, added, removed|
|
||||
if added.size > 0 || removed.size > 0
|
||||
puts "Files added/removed. Restarting sync..."
|
||||
exec("ruby", "#{__dir__}/sync_docs", *ARGV)
|
||||
end
|
||||
|
||||
modified.each do |path|
|
||||
relative = path.sub(BASE, "")
|
||||
doc = docs.find { |d| d.path == relative }
|
||||
raise "Modified file not recognized: #{relative}" if doc.nil?
|
||||
|
||||
print "- updating '#{doc.external_id}' (topic_id: #{doc.topic_id})..."
|
||||
new_frontmatter, new_content = Util.parse_md(File.read(path))
|
||||
if %w[id short_title].any? { |key| doc.frontmatter[key] != new_frontmatter[key] }
|
||||
puts "Frontmatter changed. Restarting sync..."
|
||||
exec("ruby", "#{__dir__}/sync_docs", *ARGV)
|
||||
end
|
||||
doc.content, doc.frontmatter = new_content, new_frontmatter
|
||||
|
||||
# Convert callouts before uploading
|
||||
converted_content = convert_mkdocs_to_obsidian_callouts(doc.content_with_uploads)
|
||||
|
||||
API.edit_post(
|
||||
post_id: doc.first_post_id,
|
||||
raw: converted_content,
|
||||
title: doc.frontmatter["title"]
|
||||
)
|
||||
puts " done"
|
||||
end
|
||||
end
|
||||
.start
|
||||
|
||||
sleep
|
||||
else
|
||||
puts "Done."
|
||||
end
|
||||
Binary file not shown.
Binary file not shown.
@@ -71,7 +71,7 @@ class Car:
|
||||
|
||||
def __init__(self, CI=None, RI=None) -> None:
|
||||
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.can_rcv_cum_timeout_counter = 0
|
||||
@@ -124,7 +124,7 @@ class Car:
|
||||
|
||||
self.CP.alternativeExperience = 0
|
||||
# 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)
|
||||
|
||||
# Dynamic Experimental Control
|
||||
@@ -179,7 +179,7 @@ class Car:
|
||||
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
|
||||
|
||||
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.experimental_mode = self.params.get_bool("ExperimentalMode")
|
||||
@@ -216,7 +216,6 @@ class Car:
|
||||
if can_rcv_valid and REPLAY:
|
||||
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)
|
||||
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
|
||||
# Use CarState w/ buttons from the step selfdrived enables on
|
||||
|
||||
@@ -30,8 +30,8 @@ CRUISE_INTERVAL_SIGN = {
|
||||
|
||||
|
||||
class VCruiseHelper(VCruiseHelperSP):
|
||||
def __init__(self, CP, CP_SP):
|
||||
VCruiseHelperSP.__init__(self, CP, CP_SP)
|
||||
def __init__(self, CP):
|
||||
VCruiseHelperSP.__init__(self)
|
||||
self.CP = CP
|
||||
self.v_cruise_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):
|
||||
self.v_cruise_kph_last = self.v_cruise_kph
|
||||
|
||||
self.get_minimum_set_speed(is_metric)
|
||||
|
||||
if CS.cruiseState.available:
|
||||
_enabled = self.update_enabled_state(CS, enabled)
|
||||
if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled):
|
||||
if not self.CP.pcmCruise:
|
||||
# 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_speed_limit_assist_v_cruise_non_pcm()
|
||||
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
|
||||
self.v_cruise_cluster_kph = self.v_cruise_kph
|
||||
self.update_button_timers(CS, enabled)
|
||||
else:
|
||||
@@ -105,12 +101,6 @@ class VCruiseHelper(VCruiseHelperSP):
|
||||
if not self.button_change_states[button_type]["enabled"]:
|
||||
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)
|
||||
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
|
||||
@@ -121,7 +111,7 @@ class VCruiseHelper(VCruiseHelperSP):
|
||||
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 = 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):
|
||||
# increment timer for buttons still pressed
|
||||
|
||||
@@ -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.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.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
|
||||
struct_dataclass.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(
|
||||
**remove_deprecated(struct_dict.get('intelligentCruiseButtonManagement', {}))
|
||||
)
|
||||
|
||||
return struct_dataclass
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import os
|
||||
import math
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import Phase, given, settings
|
||||
from parameterized import parameterized
|
||||
|
||||
from cereal import car, custom
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
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.values import PLATFORMS
|
||||
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
|
||||
|
||||
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'))
|
||||
|
||||
|
||||
@@ -29,10 +37,43 @@ class TestCarInterfaces:
|
||||
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
||||
@given(data=st.data())
|
||||
def test_car_interfaces(self, car_name, data):
|
||||
car_interface = get_fuzzy_car_interface(car_name, data.draw)
|
||||
car_params = car_interface.CP.as_reader()
|
||||
car_params_sp = car_interface.CP_SP
|
||||
CarInterface = interfaces[car_name]
|
||||
|
||||
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)
|
||||
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_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)
|
||||
@@ -60,7 +101,7 @@ class TestCarInterfaces:
|
||||
# Test controller initialization
|
||||
# TODO: wait until card refactor is merged to run controller a few times,
|
||||
# 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:
|
||||
LatControlAngle(car_params, car_params_sp, car_interface)
|
||||
elif car_params.lateralTuning.which() == 'pid':
|
||||
|
||||
@@ -5,7 +5,7 @@ import numpy as np
|
||||
from parameterized import parameterized_class
|
||||
from cereal import log
|
||||
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.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'
|
||||
|
||||
|
||||
# TODO: test pcmCruise and pcmCruiseSpeed
|
||||
@parameterized_class(('pcm_cruise', 'pcm_cruise_speed'), [(False, True)])
|
||||
# TODO: test pcmCruise
|
||||
@parameterized_class(('pcm_cruise',), [(False,)])
|
||||
class TestVCruiseHelper:
|
||||
def setup_method(self):
|
||||
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.CP_SP)
|
||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
||||
self.reset_cruise_speed_state()
|
||||
|
||||
def reset_cruise_speed_state(self):
|
||||
|
||||
@@ -58,7 +58,7 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
self.pose_calibrator = PoseCalibrator()
|
||||
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.LaC: LatControl
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||
@@ -95,8 +95,6 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered,
|
||||
torque_params.frictionCoefficientFiltered)
|
||||
|
||||
self.LaC.extension.update_limits()
|
||||
|
||||
self.LaC.extension.update_model_v2(self.sm['modelV2'])
|
||||
|
||||
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
|
||||
@@ -116,8 +114,7 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
|
||||
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||
(not standstill or self.CP.steerAtStandstill)
|
||||
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and \
|
||||
(self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
|
||||
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
|
||||
|
||||
actuators = CC.actuators
|
||||
actuators.longControlState = self.LoC.long_control_state
|
||||
@@ -169,7 +166,7 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
CC.orientationNED = self.calibrated_pose.orientation.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.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
from cereal import log, custom
|
||||
from cereal import log
|
||||
from openpilot.common.constants import CV
|
||||
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.lane_turn_desire import LaneTurnController
|
||||
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
TurnDirection = custom.ModelDataV2SP.TurnDirection
|
||||
|
||||
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
|
||||
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:
|
||||
def __init__(self):
|
||||
@@ -49,25 +41,13 @@ class DesireHelper:
|
||||
self.prev_one_blinker = False
|
||||
self.desire = log.Desire.none
|
||||
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):
|
||||
self.alc.update_params()
|
||||
self.lane_turn_controller.update_params()
|
||||
v_ego = carstate.vEgo
|
||||
one_blinker = carstate.leftBlinker != carstate.rightBlinker
|
||||
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:
|
||||
self.lane_change_state = LaneChangeState.off
|
||||
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:
|
||||
self.lane_change_state = LaneChangeState.preLaneChange
|
||||
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
|
||||
elif self.lane_change_state == LaneChangeState.preLaneChange:
|
||||
# Update lane change direction
|
||||
self.lane_change_direction = self.get_lane_change_direction(carstate)
|
||||
# Set lane change direction
|
||||
self.lane_change_direction = LaneChangeDirection.left if \
|
||||
carstate.leftBlinker else LaneChangeDirection.right
|
||||
|
||||
torque_applied = carstate.steeringPressed and \
|
||||
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
|
||||
@@ -127,10 +106,7 @@ class DesireHelper:
|
||||
|
||||
self.prev_one_blinker = one_blinker
|
||||
|
||||
if self.lane_turn_direction != TurnDirection.none:
|
||||
self.desire = TURN_DESIRES[self.lane_turn_direction]
|
||||
else:
|
||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||
|
||||
# Send keep pulse once per second during LaneChangeStart.preLaneChange
|
||||
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
|
||||
|
||||
@@ -3,6 +3,7 @@ import numpy as np
|
||||
|
||||
from cereal import log
|
||||
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.selfdrive.controls.lib.latcontrol import LatControl
|
||||
from openpilot.common.pid import PIDController
|
||||
@@ -28,30 +29,19 @@ class LatControlTorque(LatControl):
|
||||
def __init__(self, CP, CP_SP, CI):
|
||||
super().__init__(CP, CP_SP, CI)
|
||||
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,
|
||||
k_f=self.torque_params.kf)
|
||||
self.update_limits()
|
||||
k_f=self.torque_params.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
|
||||
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
|
||||
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):
|
||||
self.torque_params.latAccelFactor = latAccelFactor
|
||||
self.torque_params.latAccelOffset = latAccelOffset
|
||||
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):
|
||||
# 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()
|
||||
if not active:
|
||||
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)
|
||||
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))
|
||||
|
||||
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
|
||||
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
||||
|
||||
@@ -69,33 +61,33 @@ class LatControlTorque(LatControl):
|
||||
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
|
||||
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
|
||||
gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation
|
||||
|
||||
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
|
||||
pid_log.error = float(setpoint - measurement)
|
||||
ff = gravity_adjusted_lateral_accel
|
||||
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
|
||||
ff -= self.torque_params.latAccelOffset
|
||||
torque_from_setpoint = self.torque_from_lateral_accel(LatControlInputs(setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
|
||||
gravity_adjusted=False)
|
||||
torque_from_measurement = self.torque_from_lateral_accel(LatControlInputs(measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
|
||||
gravity_adjusted=False)
|
||||
pid_log.error = float(torque_from_setpoint - torque_from_measurement)
|
||||
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)
|
||||
|
||||
# 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
|
||||
output_lataccel = self.pid.update(pid_log.error,
|
||||
output_torque = self.pid.update(pid_log.error,
|
||||
feedforward=ff,
|
||||
speed=CS.vEgo,
|
||||
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.p = float(self.pid.p)
|
||||
pid_log.i = float(self.pid.i)
|
||||
pid_log.d = float(self.pid.d)
|
||||
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.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))
|
||||
|
||||
@@ -10,11 +10,8 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
|
||||
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):
|
||||
# Gas Interceptor
|
||||
cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor
|
||||
|
||||
stopping_condition = should_stop
|
||||
starting_condition = (not should_stop 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
|
||||
|
||||
class LongControl:
|
||||
def __init__(self, CP, CP_SP):
|
||||
def __init__(self, CP):
|
||||
self.CP = CP
|
||||
self.CP_SP = CP_SP
|
||||
self.long_control_state = LongCtrlState.off
|
||||
self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
|
||||
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
|
||||
@@ -65,7 +61,7 @@ class LongControl:
|
||||
self.pid.neg_limit = accel_limits[0]
|
||||
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,
|
||||
CS.cruiseState.standstill)
|
||||
if self.long_control_state == LongCtrlState.off:
|
||||
|
||||
@@ -93,12 +93,12 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
return x, v, a, j, throttle_prob
|
||||
|
||||
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:
|
||||
self.mpc.mode = mode
|
||||
self.mpc.mode = self.mode
|
||||
LongitudinalPlannerSP.update(self, sm)
|
||||
if dec_mpc_mode := self.get_mpc_mode():
|
||||
mode = dec_mpc_mode
|
||||
self.mode = dec_mpc_mode
|
||||
if not self.mlsim:
|
||||
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
|
||||
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)]
|
||||
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)
|
||||
@@ -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])
|
||||
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:
|
||||
v_cruise = 0.0
|
||||
|
||||
@@ -176,7 +173,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
|
||||
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
|
||||
self.output_should_stop = output_should_stop_mpc
|
||||
else:
|
||||
|
||||
@@ -5,7 +5,6 @@ from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
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 openpilot.selfdrive.car.helpers import convert_to_capnp
|
||||
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:
|
||||
|
||||
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque),
|
||||
(NISSAN.NISSAN_LEAF, LatControlAngle), (GM.CHEVROLET_BOLT_EUV, LatControlTorque)])
|
||||
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (NISSAN.NISSAN_LEAF, LatControlAngle)])
|
||||
def test_saturation(self, car_name, controller):
|
||||
CarInterface = interfaces[car_name]
|
||||
CP = CarInterface.get_non_essential_params(car_name)
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from cereal import car
|
||||
from openpilot.common.gps import get_gps_location_service
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import Priority, config_realtime_process
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
@@ -17,18 +16,14 @@ def main():
|
||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("plannerd got CarParams: %s", CP.brand)
|
||||
|
||||
gps_location_service = get_gps_location_service(params)
|
||||
|
||||
ldw = LaneDepartureWarning()
|
||||
longitudinal_planner = LongitudinalPlanner(CP)
|
||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
||||
'liveMapDataSP', 'carStateSP', gps_location_service],
|
||||
poll='carState')
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
|
||||
poll='modelV2')
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
longitudinal_planner.sla.update_car_state(sm['carState'])
|
||||
if sm.updated['modelV2']:
|
||||
longitudinal_planner.update(sm)
|
||||
longitudinal_planner.publish(sm, pm)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -8,52 +8,49 @@ class TestLongControlStateTransition:
|
||||
|
||||
def test_stay_stopped(self):
|
||||
CP = car.CarParams.new_message()
|
||||
CP_SP = custom.CarParamsSP.new_message()
|
||||
active = True
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
assert next_state == LongCtrlState.pid
|
||||
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)
|
||||
assert next_state == LongCtrlState.off
|
||||
|
||||
def test_engage():
|
||||
CP = car.CarParams.new_message()
|
||||
CP_SP = custom.CarParamsSP.new_message()
|
||||
active = True
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
assert next_state == LongCtrlState.pid
|
||||
|
||||
def test_starting():
|
||||
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
|
||||
CP_SP = custom.CarParamsSP.new_message()
|
||||
active = True
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
@@ -11,7 +11,6 @@ from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
|
||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||
from openpilot.sunnypilot.selfdrive.locationd.torqued_ext import TorqueEstimatorExt
|
||||
|
||||
HISTORY = 5 # secs
|
||||
POINTS_PER_BUCKET = 1500
|
||||
@@ -51,10 +50,9 @@ class TorqueBuckets(PointBuckets):
|
||||
break
|
||||
|
||||
|
||||
class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
|
||||
class TorqueEstimator(ParameterEstimator):
|
||||
def __init__(self, CP, decimated=False, track_all_points=False):
|
||||
ParameterEstimator.__init__(self)
|
||||
TorqueEstimatorExt.__init__(self, CP)
|
||||
super().__init__()
|
||||
self.CP = CP
|
||||
self.hist_len = int(HISTORY / DT_MDL)
|
||||
self.lag = 0.0
|
||||
@@ -84,8 +82,6 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
|
||||
|
||||
self.calibrator = PoseCalibrator()
|
||||
|
||||
TorqueEstimatorExt.initialize_custom_params(self, decimated)
|
||||
|
||||
self.reset()
|
||||
|
||||
initial_params = {
|
||||
@@ -264,8 +260,6 @@ def main(demo=False):
|
||||
t = sm.logMonoTime[which] * 1e-9
|
||||
estimator.handle_log(t, which, sm[which])
|
||||
|
||||
TorqueEstimatorExt.update_use_params(estimator)
|
||||
|
||||
# 4Hz driven by livePose
|
||||
if sm.frame % 5 == 0:
|
||||
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
|
||||
|
||||
@@ -13,9 +13,12 @@ class ModelConstants:
|
||||
META_T_IDXS = [2., 4., 6., 8., 10.]
|
||||
|
||||
# model inputs constants
|
||||
N_FRAMES = 2
|
||||
MODEL_RUN_FREQ = 20
|
||||
MODEL_CONTEXT_FREQ = 5 # "model_trained_fps"
|
||||
MODEL_FREQ = 20
|
||||
HISTORY_FREQ = 5
|
||||
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
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import capnp
|
||||
import numpy as np
|
||||
from cereal import log
|
||||
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')
|
||||
|
||||
@@ -96,8 +95,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
# action
|
||||
modelV2.action = action
|
||||
|
||||
# times at X_IDXS of edges and lines
|
||||
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
|
||||
# times at X_IDXS of edges and lines aren't used
|
||||
LINE_T_IDXS: list[float] = []
|
||||
|
||||
# lane lines
|
||||
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()
|
||||
|
||||
# confidence
|
||||
if vipc_frame_id % (2*ModelConstants.MODEL_RUN_FREQ) == 0:
|
||||
if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0:
|
||||
# any disengage prob
|
||||
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
|
||||
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]
|
||||
|
||||
@@ -80,64 +80,6 @@ class FrameMeta:
|
||||
if vipc is not None:
|
||||
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):
|
||||
frames: dict[str, DrivingModelFrame]
|
||||
inputs: dict[str, np.ndarray]
|
||||
@@ -160,15 +102,22 @@ class ModelState(ModelStateBase):
|
||||
self.policy_output_slices = policy_metadata['output_slices']
|
||||
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.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
|
||||
self.numpy_inputs = {k: np.zeros(self.policy_input_shapes[k], dtype=np.float32) for k in self.policy_input_shapes}
|
||||
self.full_input_queues = InputQueues(ModelConstants.MODEL_CONTEXT_FREQ, ModelConstants.MODEL_RUN_FREQ, ModelConstants.N_FRAMES)
|
||||
for k in ['desire_pulse', 'features_buffer']:
|
||||
self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape})
|
||||
self.full_input_queues.reset()
|
||||
self.numpy_inputs = {
|
||||
'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
|
||||
'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32),
|
||||
'lateral_control_params': np.zeros((1, ModelConstants.LATERAL_CONTROL_PARAMS_LEN), dtype=np.float32),
|
||||
'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
|
||||
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],
|
||||
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
|
||||
inputs['desire_pulse'][0] = 0
|
||||
new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0)
|
||||
self.prev_desire[:] = inputs['desire_pulse']
|
||||
inputs['desire'][0] = 0
|
||||
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
|
||||
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}
|
||||
|
||||
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()
|
||||
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})
|
||||
for k in ['desire_pulse', 'features_buffer']:
|
||||
self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k]
|
||||
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
|
||||
self.full_features_buffer[0,:-1] = self.full_features_buffer[0,1:]
|
||||
self.full_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :]
|
||||
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
|
||||
|
||||
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))
|
||||
|
||||
# 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}
|
||||
if SEND_RAW_PRED:
|
||||
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})")
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
params = Params()
|
||||
|
||||
# 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
|
||||
last_vipc_frame_id = 0
|
||||
run_count = 0
|
||||
@@ -340,6 +299,7 @@ def main(demo=False):
|
||||
if sm.frame % 60 == 0:
|
||||
model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay)
|
||||
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']:
|
||||
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
||||
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}
|
||||
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] = {
|
||||
'desire_pulse': vec_desire,
|
||||
'desire': vec_desire,
|
||||
'traffic_convention': traffic_convention,
|
||||
'lateral_control_params': lateral_control_params,
|
||||
}
|
||||
|
||||
mt1 = time.perf_counter()
|
||||
@@ -383,7 +344,6 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
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)
|
||||
prev_action = action
|
||||
@@ -398,7 +358,6 @@ def main(demo=False):
|
||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||
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.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
@@ -406,7 +365,6 @@ def main(demo=False):
|
||||
pm.send('modelV2', modelv2_send)
|
||||
pm.send('drivingModelData', drivingdata_send)
|
||||
pm.send('cameraOdometry', posenet_send)
|
||||
pm.send('modelDataV2SP', mdv2sp_send)
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -22,10 +22,9 @@ class Parser:
|
||||
self.ignore_missing = ignore_missing
|
||||
|
||||
def check_missing(self, outs, name):
|
||||
missing = name not in outs
|
||||
if missing and not self.ignore_missing:
|
||||
if name not in outs and not self.ignore_missing:
|
||||
raise ValueError(f"Missing output {name}")
|
||||
return missing
|
||||
return name not in outs
|
||||
|
||||
def parse_categorical_crossentropy(self, name, outs, out_shape=None):
|
||||
if self.check_missing(outs, name):
|
||||
@@ -85,13 +84,6 @@ class Parser:
|
||||
outs[name] = pred_mu_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]:
|
||||
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,))
|
||||
@@ -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_binary_crossentropy('meta', 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)
|
||||
lead_in_N, lead_out_N = (ModelConstants.LEAD_MHP_N, ModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0)
|
||||
lead_out_shape = (ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH) if lead_mhp else \
|
||||
(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH)
|
||||
self.parse_mdn('lead', outs, in_N=lead_in_N, out_N=lead_out_N, out_shape=lead_out_shape)
|
||||
if outs['lead'].shape[1] == 2 * ModelConstants.LEAD_MHP_SELECTION *ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH:
|
||||
self.parse_mdn('lead', outs, in_N=0, out_N=0,
|
||||
out_shape=(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
|
||||
else:
|
||||
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
|
||||
|
||||
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)
|
||||
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=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
|
||||
if outs['plan'].shape[1] == 2 * ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH:
|
||||
self.parse_mdn('plan', outs, in_N=0, out_N=0,
|
||||
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,))
|
||||
return outs
|
||||
|
||||
|
||||
@@ -84,10 +84,12 @@ Panda *connect(std::string serial="", uint32_t index=0) {
|
||||
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) {
|
||||
LOGW("panda %s is not supported (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
|
||||
if (is_deprecated_panda) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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.setInterruptLoad(health.interrupt_load_pkt);
|
||||
ps.setFanPower(health.fan_power);
|
||||
ps.setFanStallCount(health.fan_stall_count);
|
||||
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
|
||||
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
||||
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
void pandad_main_thread(std::vector<std::string> serials);
|
||||
|
||||
// deprecated devices
|
||||
static const std::vector<cereal::PandaState::PandaType> SUPPORTED_PANDA_TYPES = {
|
||||
cereal::PandaState::PandaType::RED_PANDA,
|
||||
cereal::PandaState::PandaType::TRES,
|
||||
cereal::PandaState::PandaType::CUATRO,
|
||||
static const std::vector<cereal::PandaState::PandaType> DEPRECATED_PANDA_TYPES = {
|
||||
cereal::PandaState::PandaType::WHITE_PANDA,
|
||||
cereal::PandaState::PandaType::GREY_PANDA,
|
||||
cereal::PandaState::PandaType::BLACK_PANDA,
|
||||
cereal::PandaState::PandaType::PEDAL,
|
||||
cereal::PandaState::PandaType::UNO,
|
||||
cereal::PandaState::PandaType::RED_PANDA_V2
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -29,12 +29,6 @@ def flash_panda(panda_serial: str) -> Panda:
|
||||
HARDWARE.recover_internal_panda()
|
||||
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)
|
||||
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()
|
||||
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:
|
||||
cloudlog.info("Panda firmware out of date, update required")
|
||||
panda.flash()
|
||||
@@ -67,14 +67,6 @@ def flash_panda(panda_serial: str) -> 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:
|
||||
# signal pandad to close the relay and exit
|
||||
def signal_handler(signum, frame):
|
||||
@@ -99,6 +91,11 @@ def main() -> None:
|
||||
cloudlog.event("pandad.flash_and_connect", count=count)
|
||||
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
|
||||
if no_internal_panda_count > 0:
|
||||
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))
|
||||
|
||||
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
|
||||
health = panda.health()
|
||||
if health["heartbeat_lost"]:
|
||||
|
||||
BIN
selfdrive/pandad/tests/bootstub.panda.bin
Executable file
BIN
selfdrive/pandad/tests/bootstub.panda.bin
Executable file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user