Compare commits

..

7 Commits

Author SHA1 Message Date
discountchubbs d9b2023f36 license 2025-08-11 10:42:15 -07:00
James Vecellio-Grant 56f3b795df Merge branch 'master' into move-to-steering 2025-08-11 10:37:09 -07:00
discountchubbs 7fd30dabcd Live delay in steering panel instead 2025-08-11 10:35:06 -07:00
discountchubbs 65f33a10fe dynamic max 2025-08-11 09:18:24 -07:00
James Vecellio-Grant ce6ea8f84b Merge branch 'master' into models-stuff 2025-08-10 07:50:31 -07:00
James Vecellio-Grant 1e641ba96c Merge branch 'master' into models-stuff 2025-08-09 22:04:57 -07:00
discountchubbs 990b3d4998 Handle decoding directly in ui bc why not 2025-08-09 19:00:25 -07:00
140 changed files with 1944 additions and 3565 deletions
@@ -127,7 +127,7 @@ jobs:
retry_failed_models:
needs: [setup, get_and_build]
runs-on: ubuntu-latest
if: ${{ needs.setup.result != 'failure' && !cancelled() }}
if: ${{ needs.setup.result != 'failure' && (failure() && !cancelled()) }}
outputs:
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
steps:
+1 -1
View File
@@ -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
View File
@@ -50,7 +50,6 @@ jobs:
- name: bump submodules
run: |
git config --global --add safe.directory '*'
git config submodule.tinygrad.update none
git submodule update --remote
git add .
- name: update car docs
+3 -4
View File
@@ -27,7 +27,7 @@ env:
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
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
@@ -190,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 && \
+74 -100
View File
@@ -9,6 +9,12 @@ env:
# Branch configurations
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 }}"
@@ -16,7 +22,7 @@ env:
on:
push:
branches: [ master, master-dev-c3-new ]
tags: [ 'release/*' ]
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:
@@ -141,12 +77,61 @@ jobs:
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
Vendored
+1 -1
View File
@@ -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_')) {
+5 -12
View File
@@ -1,22 +1,15 @@
Version 0.10.1 (2025-09-08)
========================
* Record driving feedback using LKAS button
* Honda City 2023 support thanks to drFritz!
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)
========================
+1 -1
View File
@@ -1 +1 @@
#define DEFAULT_MODEL "Steam Powered (Default)"
#define DEFAULT_MODEL "Space Lab 2 (Default)"
+4 -5
View File
@@ -73,9 +73,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}},
{"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}},
@@ -147,7 +147,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
@@ -199,7 +198,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// 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}},
@@ -215,5 +214,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}},
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING, ""}},
};
+19 -13
View File
@@ -14,8 +14,10 @@ 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_unwind_rate = 0.3 / rate
self.i_rate = 1.0 / rate
self.speed = 0.0
@@ -33,6 +35,10 @@ class PIDController:
def k_d(self):
return np.interp(self.speed, self._k_d[0], self._k_d[1])
@property
def error_integral(self):
return self.i/self.k_i
def reset(self):
self.p = 0.0
self.i = 0.0
@@ -40,25 +46,25 @@ 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):
def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False):
self.speed = speed
self.p = float(error) * self.k_p
self.f = feedforward * self.k_f
self.d = error_rate * self.k_d
if not freeze_integrator:
i = self.i + error * self.k_i * self.i_rate
if override:
self.i -= self.i_unwind_rate * float(np.sign(self.i))
else:
if not freeze_integrator:
self.i = self.i + error * self.k_i * self.i_rate
# Don't allow windup if already clipping
test_control = self.p + i + self.d + self.f
i_upperbound = self.i if test_control > self.pos_limit else self.pos_limit
i_lowerbound = self.i if test_control < self.neg_limit else self.neg_limit
self.i = np.clip(i, i_lowerbound, i_upperbound)
# Clip i to prevent exceeding control limits
control_no_i = self.p + self.d + self.f
control_no_i = np.clip(control_no_i, self.neg_limit, self.pos_limit)
self.i = np.clip(self.i, self.neg_limit - control_no_i, self.pos_limit - control_no_i)
control = self.p + self.i + self.d + self.f
self.control = np.clip(control, self.neg_limit, self.pos_limit)
return self.control
-15
View File
@@ -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()
+1 -1
View File
@@ -1 +1 @@
#define COMMA_VERSION "0.10.1"
#define COMMA_VERSION "0.10.0"
+4 -17
View File
@@ -4,13 +4,12 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 334 Supported Cars
# 321 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
@@ -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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV80 2023">Buy Here</a></sub></details>|||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>5</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector + Honda Clarity Proxy Board<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://shop.retropilot.org/product/honda-clarity-proxy-board-kit">Buy Here</a></sub></details>|||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
@@ -102,7 +95,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
@@ -113,7 +105,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai 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 Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra GT 2017-20">Buy Here</a></sub></details>|||
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai 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 Elantra Non-SCC 2022">Buy Here</a></sub></details>|||
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J 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 Genesis 2015-16">Buy Here</a></sub></details>|||
|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai i30 2017-19">Buy Here</a></sub></details>|||
|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai 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=Hyundai Ioniq 5 (Southeast Asia and Europe only) 2022-24">Buy Here</a></sub></details>|||
@@ -127,13 +118,11 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2020">Buy Here</a></sub></details>|||
|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022-23">Buy Here</a></sub></details>|||
|Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2018-21">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2022-23">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Kona Electric Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric Non-SCC 2019">Buy Here</a></sub></details>|||
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I 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 Hybrid 2020">Buy Here</a></sub></details>|||
|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai 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 Non-SCC 2019">Buy Here</a></sub></details>|||
|Hyundai|Nexo 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Nexo 2021">Buy Here</a></sub></details>|||
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Santa Cruz 2022-24[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N 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 Santa Cruz 2022-24">Buy Here</a></sub></details>|||
@@ -157,7 +146,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Carnival 2022-24[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|Kia|Carnival (China only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai 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=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai 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 Ceed 2019-21">Buy Here</a></sub></details>|||
|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I 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 Ceed Plug-in Hybrid Non-SCC 2022">Buy Here</a></sub></details>|||
|Kia|EV6 (Southeast Asia only) 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P 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 (Southeast Asia only) 2022-24">Buy Here</a></sub></details>|||
|Kia|EV6 (with HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P 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 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|Kia|EV6 (without HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
@@ -305,7 +293,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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota 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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota 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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota 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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota 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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=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>||
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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 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 -1
View File
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="12.8"
export AGNOS_VERSION="12.6"
fi
export STAGING_ROOT="/data/safe_staging"
-17
View File
@@ -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
+1 -1
Submodule panda updated: f10ddc6a89...0e7a3fd8cf
+12 -13
View File
@@ -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"
+15 -8
View File
@@ -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!
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a69514fe68dd1b4c73f28771640dcba10fc40989d1c7e771cb48bfd830fef206
size 5713
+1 -3
View File
@@ -107,10 +107,8 @@ class Car:
cached_params = _cached_params
fixed_fingerprint = (self.params.get("CarPlatformBundle") or {}).get("platform", None)
init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params)
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params,
fixed_fingerprint, init_params_list_sp)
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params, fixed_fingerprint)
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
self.CP = self.CI.CP
+3 -1
View File
@@ -439,7 +439,9 @@ class TestCarModelBase(unittest.TestCase):
if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.brake > 0.05:
brake_pressed = False
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
# TODO: remove this exception before closing commaai/opendbc#1100
if not (self.CP.brand == "honda" and self.CP.flags & HondaFlags.BOSCH):
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
checks['steeringDisengage'] += CS.steeringDisengage != self.safety.get_steering_disengage_prev()
if self.CP.pcmCruise:
+4 -6
View File
@@ -51,7 +51,7 @@ class Controls(ControlsExt, ModelStateBase):
poll='selfdriveState')
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + self.pm_services_ext)
self.steer_limited_by_safety = False
self.steer_limited_by_controls = False
self.curvature = 0.0
self.desired_curvature = 0.0
@@ -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)
@@ -142,7 +140,7 @@ class Controls(ControlsExt, ModelStateBase):
actuators.curvature = self.desired_curvature
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited_by_safety, self.desired_curvature,
self.steer_limited_by_controls, self.desired_curvature,
self.calibrated_pose, curvature_limited) # TODO what if not available
actuators.torque = float(steer)
actuators.steeringAngleDeg = float(steeringAngleDeg)
@@ -189,10 +187,10 @@ class Controls(ControlsExt, ModelStateBase):
if self.sm['selfdriveState'].active:
CO = self.sm['carOutput']
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.steer_limited_by_safety = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
self.steer_limited_by_controls = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
STEER_ANGLE_SATURATION_THRESHOLD
else:
self.steer_limited_by_safety = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2
self.steer_limited_by_controls = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2
# TODO: both controlsState and carControl valids should be set by
# sm.all_checks(), but this creates a circular dependency
+3 -3
View File
@@ -15,15 +15,15 @@ class LatControl(ABC):
self.steer_max = 1.0
@abstractmethod
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
pass
def reset(self):
self.sat_count = 0.
def _check_saturation(self, saturated, CS, steer_limited_by_safety, curvature_limited):
def _check_saturation(self, saturated, CS, steer_limited_by_controls, curvature_limited):
# Saturated only if control output is not being limited by car torque/angle rate limits
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_safety and not CS.steeringPressed:
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_controls and not CS.steeringPressed:
self.sat_count += self.sat_count_rate
else:
self.sat_count -= self.sat_count_rate
+4 -5
View File
@@ -3,7 +3,6 @@ import math
from cereal import log
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
# TODO This is speed dependent
STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees
@@ -11,9 +10,9 @@ class LatControlAngle(LatControl):
def __init__(self, CP, CP_SP, CI):
super().__init__(CP, CP_SP, CI)
self.sat_check_min_speed = 5.
self.use_steer_limited_by_safety = CP.brand == "tesla"
self.use_steer_limited_by_controls = CP.brand == "tesla"
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
angle_log = log.ControlsState.LateralAngleState.new_message()
if not active:
@@ -24,9 +23,9 @@ class LatControlAngle(LatControl):
angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
angle_steers_des += params.angleOffsetDeg
if self.use_steer_limited_by_safety:
if self.use_steer_limited_by_controls:
# these cars' carcontrollers calculate max lateral accel and jerk, so we can rely on carOutput for saturation
angle_control_saturated = steer_limited_by_safety
angle_control_saturated = steer_limited_by_controls
else:
# for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota)
# or relying on EPS (Ford Q3), carOutput does not capture maxing out torque # TODO: this can be improved
+13 -13
View File
@@ -13,7 +13,11 @@ class LatControlPID(LatControl):
k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.get_steer_feedforward = CI.get_steer_feedforward_function()
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
def reset(self):
super().reset()
self.pid.reset()
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
pid_log = log.ControlsState.LateralPIDState.new_message()
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
pid_log.steeringRateDeg = float(CS.steeringRateDeg)
@@ -25,24 +29,20 @@ class LatControlPID(LatControl):
pid_log.steeringAngleDesiredDeg = angle_steers_des
pid_log.angleError = error
if not active:
output_torque = 0.0
output_steer = 0.0
pid_log.active = False
self.pid.reset()
else:
# offset does not contribute to resistive torque
ff = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_torque = self.pid.update(error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
steer_feedforward = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
output_steer = self.pid.update(error, override=CS.steeringPressed,
feedforward=steer_feedforward, speed=CS.vEgo)
pid_log.active = True
pid_log.p = float(self.pid.p)
pid_log.i = float(self.pid.i)
pid_log.f = float(self.pid.f)
pid_log.output = float(output_torque)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
pid_log.output = float(output_steer)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited_by_controls, curvature_limited))
return output_torque, angle_steers_des, pid_log
return output_steer, angle_steers_des, pid_log
+22 -26
View File
@@ -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,26 +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):
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
pid_log = log.ControlsState.LateralTorqueState.new_message()
if not active:
output_torque = 0.0
@@ -67,34 +61,36 @@ 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
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)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_lataccel = self.pid.update(pid_log.error,
# 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_controls or CS.steeringPressed or CS.vEgo < 5
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))
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_controls, curvature_limited))
# TODO left is positive in this convention
return -output_torque, 0.0, pid_log
@@ -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)
@@ -173,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)
+11
View File
@@ -107,12 +107,15 @@ class ModelState(ModelStateBase):
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 = {
'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),
}
@@ -145,6 +148,7 @@ class ModelState(ModelStateBase):
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:
@@ -170,6 +174,11 @@ class ModelState(ModelStateBase):
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()])
@@ -290,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))]
@@ -322,6 +332,7 @@ def main(demo=False):
inputs:dict[str, np.ndarray] = {
'desire': vec_desire,
'traffic_convention': traffic_convention,
'lateral_control_params': lateral_control_params,
}
mt1 = time.perf_counter()
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:04b763fb71efe57a8a4c4168a8043ecd58939015026ded0dc755ded6905ac251
size 12343523
oid sha256:22aec22a10ce09384d4a4af2a0bbff08d54af7e0c888503508f356fae4ff0e29
size 15583374
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e66bb8d53eced3786ed71a59b55ffc6810944cb217f0518621cc76303260a1ef
size 46271991
oid sha256:c824f68646a3b94f117f01c70dc8316fb466e05fbd42ccdba440b8a8dc86914b
size 46265993
+10 -18
View File
@@ -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,17 @@ 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)
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))
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 'lat_planner_solution' in outs:
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_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
+1 -1
View File
@@ -93,7 +93,7 @@ def main() -> None:
# TODO: remove this in the next AGNOS
# wait until USB is up before counting
if time.monotonic() < 60.:
if time.monotonic() < 35.:
no_internal_panda_count = 0
# Handle missing internal panda
+1 -5
View File
@@ -265,10 +265,7 @@ class SelfdriveD(CruiseHelper):
if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture:
self.events.add(EventName.ldw)
# ******************************************************************************************
# NOTE: To fork maintainers.
# Disabling or nerfing safety features will get you and your users banned from our servers.
# We recommend that you do not change these numbers from the defaults.
# Check for excessive actuation
if self.sm.updated['liveCalibration']:
self.pose_calibrator.feed_live_calib(self.sm['liveCalibration'])
if self.sm.updated['livePose']:
@@ -283,7 +280,6 @@ class SelfdriveD(CruiseHelper):
if self.excessive_actuation:
self.events.add(EventName.excessiveActuation)
# ******************************************************************************************
# Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
+1 -1
View File
@@ -1 +1 @@
6d3219bca9f66a229b38a5382d301a92b0147edb
62b3e8590fe85f87494517b3177a0ccaad1e17e5
@@ -63,7 +63,7 @@ segments = [
]
# dashcamOnly makes don't need to be tested until a full port is done
excluded_interfaces = ["mock", "body", "psa"]
excluded_interfaces = ["mock", "body"]
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
+8 -6
View File
@@ -333,18 +333,20 @@ class TestOnroad:
assert np.all(eof_sof_diff > 0)
assert np.all(eof_sof_diff < 50*1e6)
first_fid = {min(self.ts[c]['frameId']) for c in cams}
assert len(first_fid) == 1, "Cameras don't start on same frame ID"
first_fid = {c: min(self.ts[c]['frameId']) for c in cams}
if cam.endswith('CameraState'):
# camerad guarantees that all cams start on frame ID 0
# (note loggerd also needs to start up fast enough to catch it)
assert next(iter(first_fid)) < 100, "Cameras start on frame ID too high"
assert set(first_fid.values()) == {0, }, "Cameras don't start on frame ID 0"
else:
# encoder guarantees all cams start on the same frame ID
assert len(set(first_fid.values())) == 1, "Cameras don't start on same frame ID"
# we don't do a full segment rotation, so these might not match exactly
last_fid = {max(self.ts[c]['frameId']) for c in cams}
assert max(last_fid) - min(last_fid) < 10
last_fid = {c: max(self.ts[c]['frameId']) for c in cams}
assert max(last_fid.values()) - min(last_fid.values()) < 10
start, end = min(first_fid), min(last_fid)
start, end = min(first_fid.values()), min(last_fid.values())
for i in range(end-start):
ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams}
diff = (max(ts.values()) - min(ts.values()))
+1 -2
View File
@@ -22,9 +22,8 @@ def main():
sm.update()
should_send_bookmark = False
# TODO: https://github.com/commaai/openpilot/issues/36015
# only allow the LKAS button to record feedback when MADS is disabled
if False and sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available:
if sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available:
for be in sm['carState'].buttonEvents:
if be.type == ButtonType.lkas:
if be.pressed:
+13 -33
View File
@@ -24,13 +24,11 @@ const std::string BRANCH_STR = get_str(BRANCH "?
#define GIT_SSH_URL "git@github.com:commaai/openpilot.git"
#define CONTINUE_PATH "/data/continue.sh"
const std::string INSTALL_PATH = "/data/openpilot";
const std::string VALID_CACHE_PATH = "/data/.openpilot_cache";
const std::string CACHE_PATH = "/data/openpilot.cache";
#define INSTALL_PATH "/data/openpilot"
#define TMP_INSTALL_PATH "/data/tmppilot"
const int FONT_SIZE = 120;
extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start");
extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end");
extern const uint8_t inter_ttf[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_start");
@@ -43,16 +41,6 @@ void run(const char* cmd) {
assert(err == 0);
}
void finishInstall() {
BeginDrawing();
ClearBackground(BLACK);
const char *m = "Finishing install...";
int text_width = MeasureText(m, FONT_SIZE);
DrawTextEx(font, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE);
EndDrawing();
util::sleep_for(60 * 1000);
}
void renderProgress(int progress) {
BeginDrawing();
ClearBackground(BLACK);
@@ -74,11 +62,11 @@ int doInstall() {
}
// cleanup previous install attempts
run("rm -rf " TMP_INSTALL_PATH);
run("rm -rf " TMP_INSTALL_PATH " " INSTALL_PATH);
// do the install
if (util::file_exists(INSTALL_PATH) && util::file_exists(VALID_CACHE_PATH)) {
return cachedFetch(INSTALL_PATH);
if (util::file_exists(CACHE_PATH)) {
return cachedFetch(CACHE_PATH);
} else {
return freshClone();
}
@@ -147,9 +135,7 @@ void cloneFinished(int exitCode) {
run("git submodule update --init");
// move into place
run(("rm -f " + VALID_CACHE_PATH).c_str());
run(("rm -rf " + INSTALL_PATH).c_str());
run(util::string_format("mv %s %s", TMP_INSTALL_PATH, INSTALL_PATH.c_str()).c_str());
run("mv " TMP_INSTALL_PATH " " INSTALL_PATH);
#ifdef INTERNAL
run("mkdir -p /data/params/d/");
@@ -167,9 +153,9 @@ void cloneFinished(int exitCode) {
param << value;
param.close();
}
run(("cd " + INSTALL_PATH + " && "
run("cd " INSTALL_PATH " && "
"git remote set-url origin --push " GIT_SSH_URL " && "
"git config --replace-all remote.origin.fetch \"+refs/heads/*:refs/remotes/origin/*\"").c_str());
"git config --replace-all remote.origin.fetch \"+refs/heads/*:refs/remotes/origin/*\"");
#endif
// write continue.sh
@@ -185,22 +171,16 @@ void cloneFinished(int exitCode) {
run("mv /data/continue.sh.new " CONTINUE_PATH);
// wait for the installed software's UI to take over
finishInstall();
util::sleep_for(60 * 1000);
}
int main(int argc, char *argv[]) {
InitWindow(2160, 1080, "Installer");
font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, FONT_SIZE, NULL, 0);
font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, 120, NULL, 0);
SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR);
if (util::file_exists(CONTINUE_PATH)) {
finishInstall();
} else {
renderProgress(0);
int result = doInstall();
cloneFinished(result);
}
renderProgress(0);
int result = doInstall();
cloneFinished(result);
CloseWindow();
UnloadFont(font);
return 0;
+1 -1
View File
@@ -28,7 +28,7 @@ PANEL_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_PRESSED = rl.Color(59, 59, 59, 255)
TEXT_NORMAL = rl.Color(128, 128, 128, 255)
TEXT_SELECTED = rl.WHITE
TEXT_SELECTED = rl.Color(255, 255, 255, 255)
class PanelType(IntEnum):
+10
View File
@@ -23,6 +23,10 @@ DESCRIPTIONS = {
'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
"IsMetric": "Display speed in km/h instead of mph.",
"RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.",
"RecordAudioFeedback": (
"Press the LKAS button to record audio feedback about openpilot. When this toggle is disabled, the button acts as a bookmark button. " +
"The event will be highlighted in comma connect and the segment will be preserved on your device's storage."
),
}
@@ -81,6 +85,12 @@ class TogglesLayout(Widget):
self._params.get_bool("RecordAudio"),
icon="microphone.png",
),
toggle_item(
"Record Audio Feedback with LKAS button",
DESCRIPTIONS["RecordAudioFeedback"],
self._params.get_bool("RecordAudioFeedback"),
icon="microphone.png",
),
toggle_item(
"Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="metric.png"
),
+3 -3
View File
@@ -24,18 +24,18 @@ NetworkType = log.DeviceState.NetworkType
# Color scheme
class Colors:
SIDEBAR_BG = rl.Color(57, 57, 57, 255)
WHITE = rl.WHITE
WHITE = rl.Color(255, 255, 255, 255)
WHITE_DIM = rl.Color(255, 255, 255, 85)
GRAY = rl.Color(84, 84, 84, 255)
# Status colors
GOOD = rl.WHITE
GOOD = rl.Color(255, 255, 255, 255)
WARNING = rl.Color(218, 202, 37, 255)
DANGER = rl.Color(201, 34, 49, 255)
# UI elements
METRIC_BORDER = rl.Color(255, 255, 255, 85)
BUTTON_NORMAL = rl.WHITE
BUTTON_NORMAL = rl.Color(255, 255, 255, 255)
BUTTON_PRESSED = rl.Color(255, 255, 255, 166)
+1 -12
View File
@@ -2,15 +2,12 @@ from enum import IntEnum
import os
import threading
import time
from functools import lru_cache
from openpilot.common.api import Api, api_get
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
TOKEN_EXPIRY_HOURS = 2
class PrimeType(IntEnum):
UNKNOWN = -2,
@@ -23,12 +20,6 @@ class PrimeType(IntEnum):
PURPLE = 5,
@lru_cache(maxsize=1)
def get_token(dongle_id: str, t: int):
print('getting token')
return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS)
class PrimeState:
FETCH_INTERVAL = 5.0 # seconds between API calls
API_TIMEOUT = 10.0 # seconds for API requests
@@ -58,15 +49,13 @@ class PrimeState:
return
try:
identity_token = get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60)))
identity_token = Api(dongle_id).get_token()
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
if response.status_code == 200:
data = response.json()
is_paired = data.get("is_paired", False)
prime_type = data.get("prime_type", 0)
self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
elif response.status_code == 401:
get_token.cache_clear()
except Exception as e:
cloudlog.error(f"Failed to fetch prime status: {e}")
+2 -2
View File
@@ -34,7 +34,7 @@ class FontSizes:
@dataclass(frozen=True)
class Colors:
white: rl.Color = rl.WHITE
white: rl.Color = rl.Color(255, 255, 255, 255)
disengaged: rl.Color = rl.Color(145, 155, 149, 255)
override: rl.Color = rl.Color(145, 155, 149, 255) # Added
engaged: rl.Color = rl.Color(128, 216, 166, 255)
@@ -47,7 +47,7 @@ class Colors:
white_translucent: rl.Color = rl.Color(255, 255, 255, 200)
border_translucent: rl.Color = rl.Color(255, 255, 255, 75)
header_gradient_start: rl.Color = rl.Color(0, 0, 0, 114)
header_gradient_end: rl.Color = rl.BLANK
header_gradient_end: rl.Color = rl.Color(0, 0, 0, 0)
UI_CONFIG = UIConfig()
+19 -11
View File
@@ -187,9 +187,9 @@ class ModelRenderer(Widget):
self._path.raw_points, 0.9, self._path_offset_z, max_idx, allow_invert=False
)
self._update_experimental_gradient()
self._update_experimental_gradient(self._rect.height)
def _update_experimental_gradient(self):
def _update_experimental_gradient(self, height):
"""Pre-calculate experimental mode gradient colors"""
if not self._experimental_mode:
return
@@ -201,21 +201,22 @@ class ModelRenderer(Widget):
i = 0
while i < max_len:
# Some points (screen space) are out of frame (rect space)
track_y = self._path.projected_points[i][1]
if track_y < self._rect.y or track_y > (self._rect.y + self._rect.height):
track_idx = max_len - i - 1 # flip idx to start from bottom right
track_y = self._path.projected_points[track_idx][1]
if track_y < 0 or track_y > height:
i += 1
continue
# Calculate color based on acceleration (0 is bottom, 1 is top)
lin_grad_point = 1 - (track_y - self._rect.y) / self._rect.height
# Calculate color based on acceleration
lin_grad_point = (height - track_y) / height
# speed up: 120, slow down: 0
path_hue = np.clip(60 + self._acceleration_x[i] * 35, 0, 120)
path_hue = max(min(60 + self._acceleration_x[i] * 35, 120), 0)
path_hue = int(path_hue * 100 + 0.5) / 100
saturation = min(abs(self._acceleration_x[i] * 1.5), 1)
lightness = np.interp(saturation, [0.0, 1.0], [0.95, 0.62])
alpha = np.interp(lin_grad_point, [0.75 / 2.0, 0.75], [0.4, 0.0])
lightness = self._map_val(saturation, 0.0, 1.0, 0.95, 0.62)
alpha = self._map_val(lin_grad_point, 0.75 / 2.0, 0.75, 0.4, 0.0)
# Use HSL to RGB conversion
color = self._hsla_to_color(path_hue / 360.0, saturation, lightness, alpha)
@@ -279,7 +280,7 @@ class ModelRenderer(Widget):
if self._experimental_mode:
# Draw with acceleration coloring
if len(self._exp_gradient['colors']) > 1:
if len(self._exp_gradient['colors']) > 2:
draw_polygon(self._rect, self._path.projected_points, gradient=self._exp_gradient)
else:
draw_polygon(self._rect, self._path.projected_points, rl.Color(255, 255, 255, 30))
@@ -408,6 +409,13 @@ class ModelRenderer(Widget):
return np.vstack((left_screen.T, right_screen[:, ::-1].T)).astype(np.float32)
@staticmethod
def _map_val(x, x0, x1, y0, y1):
x = np.clip(x, x0, x1)
ra = x1 - x0
rb = y1 - y0
return (x - x0) * rb / ra + y0 if ra != 0 else y0
@staticmethod
def _hsla_to_color(h, s, l, a):
rgb = colorsys.hls_to_rgb(h, l, s)
+7
View File
@@ -75,6 +75,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/icons/microphone.png",
true,
},
{
"RecordAudioFeedback",
tr("Record Audio Feedback with LKAS button"),
tr("Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device's storage.\n\nNote that this feature is only compatible with select cars."),
"../assets/icons/microphone.png",
false,
},
{
"IsMetric",
tr("Use Metric System"),
+1 -7
View File
@@ -29,7 +29,6 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(
flag_img = loadPixmap("../assets/images/button_flag.png", home_btn.size());
settings_img = loadPixmap("../assets/images/button_settings.png", settings_btn.size(), Qt::IgnoreAspectRatio);
mic_img = loadPixmap("../assets/icons/microphone.png", QSize(30, 30));
link_img = loadPixmap("../assets/icons/link.png", QSize(60, 60));
connect(this, &Sidebar::valueChanged, [=] { update(); });
@@ -155,12 +154,7 @@ void Sidebar::drawSidebar(QPainter &p) {
p.setFont(InterFont(35));
p.setPen(QColor(0xff, 0xff, 0xff));
const QRect r = QRect(58, 247, width() - 100, 50);
if (net_type == "Hotspot") {
p.drawPixmap(r.x(), r.y() + (r.height() - link_img.height()) / 2, link_img);
} else {
p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter, net_type);
}
p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter, net_type);
#ifndef SUNNYPILOT
// metrics
+1 -1
View File
@@ -43,7 +43,7 @@ protected:
void drawMetric(QPainter &p, const QPair<QString, QString> &label, QColor c, int y);
virtual void drawSidebar(QPainter &p);
QPixmap home_img, flag_img, settings_img, mic_img, link_img;
QPixmap home_img, flag_img, settings_img, mic_img;
bool onroad, recording_audio, flag_pressed, settings_pressed, mic_indicator_pressed;
const QMap<cereal::DeviceState::NetworkType, QString> network_type = {
{cereal::DeviceState::NetworkType::NONE, tr("--")},
+1 -1
View File
@@ -4,7 +4,6 @@ widgets_src = [
"sunnypilot/qt/widgets/controls.cc",
"sunnypilot/qt/widgets/drive_stats.cc",
"sunnypilot/qt/widgets/expandable_row.cc",
"sunnypilot/qt/widgets/external_storage.cc",
"sunnypilot/qt/widgets/prime.cc",
"sunnypilot/qt/widgets/scrollview.cc",
"sunnypilot/qt/network/networking.cc",
@@ -46,6 +45,7 @@ qt_src = [
lateral_panel_qt_src = [
"sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/lagd_toggle_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
@@ -5,14 +5,9 @@
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/external_storage.h"
DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) {
#ifndef __APPLE__
addItem(new ExternalStorageControl());
#endif
// Advanced Controls Toggle
showAdvancedControls = new ParamControlSP("ShowAdvancedControls", tr("Show Advanced Controls"), tr("Toggle visibility of advanced sunnypilot controls.\nThis only toggles the visibility of the controls; it does not toggle the actual control enabled/disabled state."), "");
addItem(showAdvancedControls);
@@ -52,17 +47,17 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare
addItem(errorLogBtn);
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
is_release = params.getBool("IsReleaseBranch");
is_tested = params.getBool("IsTestedBranch");
is_development = params.getBool("IsDevelopmentBranch");
}
void DeveloperPanelSP::updateToggles(bool offroad) {
bool is_release = params.getBool("IsReleaseBranch");
bool is_tested = params.getBool("IsTestedBranch");
bool is_development = params.getBool("IsDevelopmentBranch");
bool disable_updates = params.getBool("DisableUpdates");
prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
prebuiltToggle->setEnabled(disable_updates);
params.putBool("QuickBootToggle", QFile::exists("/data/openpilot/prebuilt"));
prebuiltToggle->refresh();
@@ -71,7 +66,6 @@ void DeveloperPanelSP::updateToggles(bool offroad) {
"it immediately removes the prebuilt file so compilation of locally edited cpp files can be made. "
"<br><br><b>To edit C++ files locally on device, you MUST first turn off this toggle so the changes can recompile.</b>")
: tr("Quickboot mode requires updates to be disabled.<br>Enable 'Disable Updates' in the Software panel first."));
prebuiltToggle->showDescription();
enableGithubRunner->setVisible(!is_release);
errorLogBtn->setVisible(!is_release);
@@ -80,6 +74,7 @@ void DeveloperPanelSP::updateToggles(bool offroad) {
void DeveloperPanelSP::showEvent(QShowEvent *event) {
DeveloperPanel::showEvent(event);
AbstractControlSP::UpdateAllAdvancedControls();
updateToggles(!uiState()->scene.started);
AbstractControlSP::UpdateAllAdvancedControls();
prebuiltToggle->showDescription();
}
@@ -21,9 +21,6 @@ private:
ParamControlSP *prebuiltToggle;
Params params;
ParamControlSP *showAdvancedControls;
bool is_development;
bool is_release;
bool is_tested;
private slots:
void updateToggles(bool offroad);
@@ -211,7 +211,7 @@ void DevicePanelSP::updateState() {
toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus));
QString timeoutValue = QString::fromStdString(params.get("InteractivityTimeout"));
if (timeoutValue == "0" || timeoutValue.isEmpty()) {
if (timeoutValue == "0") {
interactivityTimeout->setLabel("Default");
} else {
interactivityTimeout->setLabel(timeoutValue + "s");
@@ -0,0 +1,98 @@
/**
* 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.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lagd_toggle_settings.h"
LagdToggleSettings::LagdToggleSettings(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
// Back button
PanelBackButton *back = new PanelBackButton();
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
// lagd toggle
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
lagd_toggle_control->showDescription();
main_layout->addWidget(lagd_toggle_control);
int liveDelayMaxInt = 30;
std::string liveDelayBytes = params.get("LiveDelay");
if (!liveDelayBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
liveDelayBytes.size() / sizeof(capnp::word)));
auto event = msg.getRoot<cereal::Event>();
if (event.hasLiveDelay()) {
auto liveDelay = event.getLiveDelay();
float lateralDelay = liveDelay.getLateralDelay();
liveDelayMaxInt = static_cast<int>(lateralDelay * 100.0f) + 20;
}
}
// optioncontrol for lagd 'off' state
delay_control = new OptionControlSP("LagdToggleDelay", tr("Adjust Software Delay"),
tr("The default software delay value is 0.2"),
"", {5, liveDelayMaxInt}, 1, false, nullptr, true, true);
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
});
connect(lagd_toggle_control, &ParamControlSP::toggleFlipped, [=](bool state) {
delay_control->setVisible(!state);
});
delay_control->showDescription();
main_layout->addWidget(delay_control);
}
void LagdToggleSettings::refresh() {
// Update toggle descriptions
QString desc = tr("Enable this for the car to learn and adapt its steering response time. "
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
bool lagdEnabled = params.getBool("LagdToggle");
if (lagdEnabled) {
std::string liveDelayBytes = params.get("LiveDelay");
if (!liveDelayBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
liveDelayBytes.size() / sizeof(capnp::word)));
auto event = msg.getRoot<cereal::Event>();
if (event.hasLiveDelay()) {
auto liveDelay = event.getLiveDelay();
float lateralDelay = liveDelay.getLateralDelay();
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 2));
}
}
} else {
std::string carParamsBytes = params.get("CarParamsPersistent");
if (!carParamsBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(carParamsBytes.data()),
carParamsBytes.size() / sizeof(capnp::word)));
auto carParams = msg.getRoot<cereal::CarParams>();
float steerDelay = carParams.getSteerActuatorDelay();
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
float totalLag = steerDelay + softwareDelay;
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
"<b>%1</b> %2 s + <b>%3</b> %4 s = <b>%5</b> %6 s</span>")
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
}
}
lagd_toggle_control->setDescription(desc);
lagd_toggle_control->showDescription();
delay_control->setVisible(!params.getBool("LagdToggle"));
if (delay_control->isVisible()) {
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
delay_control->showDescription();
}
}
@@ -0,0 +1,26 @@
/**
* 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.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class LagdToggleSettings : public QWidget {
Q_OBJECT
public:
explicit LagdToggleSettings(QWidget *parent = nullptr);
void refresh();
signals:
void backPress();
private:
Params params;
ParamControlSP *lagd_toggle_control = nullptr;
OptionControlSP *delay_control = nullptr;
};
@@ -46,20 +46,41 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
list->addItem(horizontal_line());
list->addItem(vertical_space());
// Lane Change Settings
laneChangeSettingsButton = new PushButtonSP(tr("Customize Lane Change"));
// Inline lagd toggle and lane change buttons
QHBoxLayout *inlineBtnLayout = new QHBoxLayout();
inlineBtnLayout->setSpacing(15);
lagdSettingsButton = new PushButtonSP(tr("Customize Live Delay"), 740);
lagdSettingsButton->setObjectName("lagd_btn");
connect(lagdSettingsButton, &QPushButton::clicked, [=]() {
lagdToggleSettings->refresh();
main_layout->setCurrentWidget(lagdToggleSettings);
});
inlineBtnLayout->addWidget(lagdSettingsButton);
lagdToggleSettings = new LagdToggleSettings(this);
connect(lagdToggleSettings, &LagdToggleSettings::backPress, [=]() {
main_layout->setCurrentWidget(sunnypilotScreen);
});
laneChangeSettingsButton = new PushButtonSP(tr("Customize Lane Change"), 740);
laneChangeSettingsButton->setObjectName("lane_change_btn");
connect(laneChangeSettingsButton, &QPushButton::clicked, [=]() {
sunnypilotScroller->setLastScrollPosition();
main_layout->setCurrentWidget(laneChangeWidget);
});
inlineBtnLayout->addWidget(laneChangeSettingsButton);
laneChangeWidget = new LaneChangeSettings(this);
connect(laneChangeWidget, &LaneChangeSettings::backPress, [=]() {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
list->addItem(laneChangeSettingsButton);
QWidget *inlineBtnWidget = new QWidget();
inlineBtnWidget->setLayout(inlineBtnLayout);
list->addItem(inlineBtnWidget);
list->addItem(vertical_space(0));
list->addItem(horizontal_line());
@@ -94,11 +115,16 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
};
QObject::connect(uiState(), &UIState::offroadTransition, this, &LateralPanel::updateToggles);
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, [=]() {
updateToggles(offroad);
});
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen);
main_layout->addWidget(madsWidget);
main_layout->addWidget(lagdToggleSettings);
main_layout->addWidget(laneChangeWidget);
setStyleSheet(R"(
@@ -151,6 +177,7 @@ void LateralPanel::updateToggles(bool _offroad) {
madsSettingsButton->setEnabled(madsToggle->isToggled());
blinkerPauseLateralSettings->refresh();
lagdToggleSettings->refresh();
offroad = _offroad;
}
@@ -14,6 +14,7 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lagd_toggle_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
@@ -41,6 +42,8 @@ private:
ParamControl *madsToggle;
PushButtonSP *madsSettingsButton;
MadsSettings *madsWidget = nullptr;
PushButtonSP *lagdSettingsButton = nullptr;
LagdToggleSettings *lagdToggleSettings = nullptr;
PushButtonSP *laneChangeSettingsButton;
LaneChangeSettings *laneChangeWidget = nullptr;
NeuralNetworkLateralControl *nnlcToggle = nullptr;
@@ -103,40 +103,6 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
list->addItem(policyFrame);
list->addItem(horizontal_line());
// LiveDelay toggle
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
lagd_toggle_control->showDescription();
list->addItem(lagd_toggle_control);
// Software delay control
int liveDelayMaxInt = 30;
std::string liveDelayBytes = params.get("LiveDelay");
if (!liveDelayBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
liveDelayBytes.size() / sizeof(capnp::word)));
auto event = msg.getRoot<cereal::Event>();
if (event.hasLiveDelay()) {
auto liveDelay = event.getLiveDelay();
float lateralDelay = liveDelay.getLateralDelay();
liveDelayMaxInt = static_cast<int>(lateralDelay * 100.0f) + 20;
}
}
delay_control = new OptionControlSP("LagdToggleDelay", tr("Adjust Software Delay"),
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
"\nThe default software delay value is 0.2"),
"", {5, liveDelayMaxInt}, 1, false, nullptr, true, true);
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
});
connect(lagd_toggle_control, &ParamControlSP::toggleFlipped, [=](bool state) {
delay_control->setVisible(!state);
});
delay_control->showDescription();
list->addItem(delay_control);
}
QProgressBar* ModelsPanel::createProgressBar(QWidget *parent) {
@@ -378,49 +344,6 @@ void ModelsPanel::updateLabels() {
currentModelLblBtn->setEnabled(!is_onroad && !isDownloading());
currentModelLblBtn->setValue(GetActiveModelInternalName());
// Update lagdToggle description with current value
QString desc = tr("Enable this for the car to learn and adapt its steering response time. "
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
bool lagdEnabled = params.getBool("LagdToggle");
if (lagdEnabled) {
std::string liveDelayBytes = params.get("LiveDelay");
if (!liveDelayBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
liveDelayBytes.size() / sizeof(capnp::word)));
auto event = msg.getRoot<cereal::Event>();
if (event.hasLiveDelay()) {
auto liveDelay = event.getLiveDelay();
float lateralDelay = liveDelay.getLateralDelay();
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
}
}
} else {
std::string carParamsBytes = params.get("CarParamsPersistent");
if (!carParamsBytes.empty()) {
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
reinterpret_cast<const capnp::word*>(carParamsBytes.data()),
carParamsBytes.size() / sizeof(capnp::word)));
auto carParams = msg.getRoot<cereal::CarParams>();
float steerDelay = carParams.getSteerActuatorDelay();
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
float totalLag = steerDelay + softwareDelay;
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
"<b>%1</b> %2 s + <b>%3</b> %4 s = <b>%5</b> %6 s</span>")
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
}
}
lagd_toggle_control->setDescription(desc);
delay_control->setVisible(!params.getBool("LagdToggle"));
if (delay_control->isVisible()) {
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
}
clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
}
@@ -473,8 +396,4 @@ double ModelsPanel::calculateCacheSize() {
}
void ModelsPanel::showEvent(QShowEvent *event) {
lagd_toggle_control->showDescription();
if (delay_control->isVisible()) {
delay_control->showDescription();
}
}
@@ -67,8 +67,6 @@ private:
bool is_onroad = false;
ButtonControlSP *currentModelLblBtn;
ParamControlSP *lagd_toggle_control;
OptionControlSP *delay_control;
QProgressBar *supercomboProgressBar;
QFrame *supercomboFrame;
QProgressBar *navigationProgressBar;
@@ -34,27 +34,6 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
vlayout->setContentsMargins(50, 20, 50, 20);
auto *list = new ListWidget(this, false);
QVBoxLayout *titleLayout = new QVBoxLayout;
QLabel *title = new QLabel(tr("🚀 sunnylink 🚀"));
title->setStyleSheet("font-size: 90px; font-weight: 500; font-family: 'Noto Color Emoji';");
titleLayout->addWidget(title, 0, Qt::AlignCenter);
QLabel *sunnylinkDesc = new QLabel("<div align='center'><font color='green'>"+
tr("For secure backup, restore, and remote configuration")+ "</font></div>");
QLabel *sponsorMsg = new QLabel("<div align='center'><font color='orange'>"+
tr("Sponsorship isn't required for basic backup/restore") + "<br>" +
tr("Click the sponsor button for more details")+ "</font></div>");
sunnylinkDesc->setStyleSheet("font-size: 40px; font-weight: 100; font-family: 'Noto';");
sponsorMsg->setStyleSheet("font-size: 35px; font-weight: 100; font-family: 'Noto';");
titleLayout->addWidget(sunnylinkDesc, 0, Qt::AlignCenter);
titleLayout->addWidget(sponsorMsg, 0, Qt::AlignCenter);
list->addItem(titleLayout);
QString sunnylinkEnabledBtnDesc = tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.");
sunnylinkEnabledBtn = new ParamControl(
"SunnylinkEnabled",
@@ -87,14 +66,6 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
});
list->addItem(horizontal_line());
QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)");
sunnylinkUploaderEnabledBtn = new ParamControlSP(
"EnableSunnylinkUploader",
tr("[Don't use] Enable sunnylink uploader"),
sunnylinkUploaderDesc,
"", nullptr, true);
list->addItem(sunnylinkUploaderEnabledBtn);
connect(sunnylinkEnabledBtn, &ParamControl::showDescriptionEvent, [=]() {
// resets the description to the default one for the Easter egg
sunnylinkEnabledBtn->setDescription(sunnylinkEnabledBtnDesc);
@@ -290,8 +261,6 @@ void SunnylinkPanel::updatePanel() {
pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled);
pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired"));
sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier == SponsorTier::Guardian && is_sunnylink_enabled);
if (!is_sunnylink_enabled) {
sunnylinkEnabledBtn->setValue("");
sponsorBtn->setValue("");
@@ -49,7 +49,6 @@ private:
QString sunnylinkBtnDescription;
PushButtonSP *restoreSettings;
PushButtonSP *backupSettings;
ParamControlSP * sunnylinkUploaderEnabledBtn;
void stopSunnylink() const;
void startSunnylink() const;
@@ -1,170 +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.
*/
#include "selfdrive/ui/sunnypilot/qt/widgets/external_storage.h"
#include <QProcess>
#include <QCoreApplication>
#include <QShowEvent>
#include <QTimer>
#include <QtConcurrent>
#include "common/params.h"
#include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/sunnypilot/ui.h"
ExternalStorageControl::ExternalStorageControl() :
ButtonControl(tr("External Storage"), "", tr("Extend your comma device's storage by inserting a USB drive into the aux port.")) {
QObject::connect(this, &ButtonControl::clicked, [=]() {
if (text() == tr("CHECK") || text() == tr("MOUNT")) {
mountStorage();
} else if (text() == tr("UNMOUNT")) {
unmountStorage();
} else if (text() == tr("FORMAT")) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to format this drive? This will erase all data."), tr("Format"), this)) {
formatStorage();
}
}
});
QObject::connect(uiState(), &UIState::offroadTransition, this, &ExternalStorageControl::updateState);
updateState(!uiState()->scene.started);
refresh();
}
void ExternalStorageControl::updateState(bool offroad) {
setEnabled(offroad);
}
void ExternalStorageControl::debouncedRefresh() {
if (refreshPending) return;
refreshPending = true;
QTimer::singleShot(250, this, [=]() {
refreshPending = false;
refresh();
});
}
void ExternalStorageControl::refresh() {
QtConcurrent::run([=]() {
auto run = [](const QString &cmd) {
QProcess p;
p.start("sh", QStringList() << "-c" << cmd);
p.waitForFinished();
return p.exitCode() == 0;
};
bool isMounted = run("findmnt -n /mnt/external_realdata");
bool hasDrive = run("lsblk -f /dev/sdg");
bool hasFs = run("lsblk -f /dev/sdg1 | grep -q ext4");
bool hasLabel = run("sudo blkid /dev/sdg1 | grep -q 'LABEL=\"openpilot\"'");
QString info;
if (isMounted && hasLabel) {
QProcess df;
df.start("sh", QStringList() << "-c" << "df -h /mnt/external_realdata | awk 'NR==2 {print $3 \"/\" $2}'");
df.waitForFinished();
info = df.readAllStandardOutput().trimmed();
}
QMetaObject::invokeMethod(this, [=]() {
if (formatting) {
setValue(tr("formatting"));
setText(tr("FORMAT"));
setEnabled(false);
} else {
if (!hasDrive) {
setValue(tr("insert drive"));
setText(tr("CHECK"));
} else if (!hasFs || !hasLabel) {
setValue(tr("needs format"));
setText(tr("FORMAT"));
} else if (isMounted) {
setValue(info);
setText(tr("UNMOUNT"));
} else {
setValue("drive detected");
setText(tr("MOUNT"));
}
updateState(!uiState()->scene.started);
}
}, Qt::QueuedConnection);
});
}
void ExternalStorageControl::mountStorage() {
setValue(tr("mounting"));
setEnabled(false);
QtConcurrent::run([=]() {
QProcess process;
process.start("sh", QStringList() << "-c" <<
"sudo mount -o remount,rw / && "
"sudo mkdir -p /mnt/external_realdata && "
"grep -q '/dev/sdg1 /mnt/external_realdata' /etc/fstab || "
"echo '/dev/sdg1 /mnt/external_realdata ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab && "
"sudo systemctl daemon-reexec && "
"sudo mount /mnt/external_realdata && "
"sudo chown -R comma:comma /mnt/external_realdata && "
"sudo chmod -R 775 /mnt/external_realdata && "
"sudo mount -o remount,ro /");
process.waitForFinished();
QMetaObject::invokeMethod(this, [=]() {
debouncedRefresh();
}, Qt::QueuedConnection);
});
}
void ExternalStorageControl::unmountStorage() {
setValue(tr("unmounting"));
setEnabled(false);
QtConcurrent::run([=]() {
QProcess process;
process.start("sh", QStringList() << "-c" << "sudo umount /mnt/external_realdata");
process.waitForFinished();
QMetaObject::invokeMethod(this, [=]() {
debouncedRefresh();
}, Qt::QueuedConnection);
});
}
void ExternalStorageControl::formatStorage() {
unmountStorage();
formatting = true;
setValue(tr("formatting"));
setEnabled(false);
QProcess *process = new QProcess(this);
connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, [=](int exitCode, QProcess::ExitStatus status) {
process->deleteLater();
formatting = false;
if (exitCode == 0 && status == QProcess::NormalExit) {
mountStorage();
} else {
setValue(tr("needs format"));
updateState(!uiState()->scene.started);
}
});
process->start("sh", QStringList() << "-c" <<
"sudo wipefs -a /dev/sdg && "
"sudo parted -s /dev/sdg mklabel gpt mkpart primary ext4 0% 100% && "
"sudo mkfs.ext4 -F -L openpilot /dev/sdg1"
);
}
void ExternalStorageControl::showEvent(QShowEvent *event) {
ButtonControl::showEvent(event);
QTimer::singleShot(100, this, &ExternalStorageControl::debouncedRefresh);
}
@@ -1,34 +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.
*/
#pragma once
#include "system/hardware/hw.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#define ButtonControl ButtonControlSP
class ExternalStorageControl : public ButtonControl {
Q_OBJECT
public:
ExternalStorageControl();
protected:
void showEvent(QShowEvent *event) override;
private:
Params params;
bool refreshPending = false;
bool formatting = false;
void updateState(bool offroad);
void refresh();
void debouncedRefresh();
void mountStorage();
void unmountStorage();
void formatStorage();
};
-1
View File
@@ -5,7 +5,6 @@ from openpilot.common.params import Params
from openpilot.system.manager.process_config import managed_processes
@pytest.mark.skip("tmp disabled")
class TestFeedbackd:
def setup_method(self):
self.pm = messaging.PubMaster(['carState', 'rawAudioData'])
+14 -56
View File
@@ -972,6 +972,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1016,38 +1020,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished">إلغاء</translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2095,30 +2067,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2264,6 +2212,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+14 -56
View File
@@ -964,6 +964,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1008,38 +1012,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished">Abbrechen</translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2077,30 +2049,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2246,6 +2194,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+15 -57
View File
@@ -462,7 +462,7 @@ La calibración del retraso de la dirección está completa.</translation>
</message>
<message>
<source>Select a language</source>
<translation type="unfinished">Seleccione el idioma</translation>
<translation type="unfinished"></translation>
</message>
<message>
<source>Wake-Up Behavior</source>
@@ -968,6 +968,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1012,38 +1016,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished">Cancelar</translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2079,30 +2051,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2248,6 +2196,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation>Graba y almacena el audio del micrófono mientras conduces. El audio se incluirá en el video de la cámara del tablero en comma connect.</translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+14 -56
View File
@@ -964,6 +964,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1008,38 +1012,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished">Annuler</translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2075,30 +2047,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2244,6 +2192,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+14 -56
View File
@@ -966,6 +966,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1010,38 +1014,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2074,30 +2046,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2243,6 +2191,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation> comma connect </translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
File diff suppressed because it is too large Load Diff
+14 -56
View File
@@ -968,6 +968,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1012,38 +1016,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished">Cancelar</translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2079,30 +2051,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2248,6 +2196,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation>Grave e armazene o áudio do microfone enquanto estiver dirigindo. O áudio será incluído ao vídeo dashcam no comma connect.</translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+14 -56
View File
@@ -962,6 +962,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1006,38 +1010,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2070,30 +2042,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2239,6 +2187,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+14 -56
View File
@@ -962,6 +962,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1006,38 +1010,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2069,30 +2041,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2238,6 +2186,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+14 -56
View File
@@ -966,6 +966,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1010,38 +1014,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2074,30 +2046,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2243,6 +2191,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation> comma connect </translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+14 -56
View File
@@ -966,6 +966,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. The Current value is updated automatically when the vehicle is Onroad.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation type="unfinished"></translation>
@@ -1010,38 +1014,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Refresh Model List</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REFRESH</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Fetching Latest Models</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Live Steer Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Actuator Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Software Delay:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total Delay:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiOptionDialog</name>
@@ -2074,30 +2046,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>
@@ -2243,6 +2191,16 @@ Warning: You are on a metered connection!</source>
<source>Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.</source>
<translation> comma connect </translation>
</message>
<message>
<source>Record Audio Feedback with LKAS button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press the LKAS button to record and share driving feedback with the openpilot team. When this toggle is disabled, the button acts as a bookmark button. The event will be highlighted in comma connect and the segment will be preserved on your device&apos;s storage.
Note that this feature is only compatible with select cars.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation type="unfinished"></translation>
+3 -3
View File
@@ -37,7 +37,7 @@ class ExperimentalModeButton(Widget):
self.params.put_bool("ExperimentalMode", self.experimental_mode)
def _render(self, rect):
rl.draw_rectangle_rounded(rect, 0.08, 20, rl.WHITE)
rl.draw_rectangle_rounded(rect, 0.08, 20, rl.Color(255, 255, 255, 255))
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
self._draw_gradient_background(rect)
@@ -53,7 +53,7 @@ class ExperimentalModeButton(Widget):
text_x = rect.x + self.horizontal_padding
text_y = rect.y + rect.height / 2 - 45 // 2 # Center vertically
rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.BLACK)
rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.Color(0, 0, 0, 255))
# Draw icon (right aligned)
icon_x = rect.x + rect.width - self.horizontal_padding - self.img_width
@@ -63,4 +63,4 @@ class ExperimentalModeButton(Widget):
# Draw current mode icon
current_icon = self.experimental_pixmap if self.experimental_mode else self.chill_pixmap
source_rect = rl.Rectangle(0, 0, current_icon.width, current_icon.height)
rl.draw_texture_pro(current_icon, source_rect, icon_rect, rl.Vector2(0, 0), 0, rl.WHITE)
rl.draw_texture_pro(current_icon, source_rect, icon_rect, rl.Vector2(0, 0), 0, rl.Color(255, 255, 255, 255))
+1 -1
View File
@@ -36,7 +36,7 @@ class PrimeWidget(Widget):
font = gui_app.font(FontWeight.LIGHT)
wrapped_text = "\n".join(wrap_text(font, "Become a comma prime member at connect.comma.ai", 56, int(w)))
text_size = measure_text_cached(font, wrapped_text, 56)
rl.draw_text_ex(font, wrapped_text, rl.Vector2(x, desc_y), 56, 0, rl.WHITE)
rl.draw_text_ex(font, wrapped_text, rl.Vector2(x, desc_y), 56, 0, rl.Color(255, 255, 255, 255))
# Features section
features_y = desc_y + text_size.y + 50
@@ -34,7 +34,7 @@ class OsmMapData(BaseMapData):
return float(self.mem_params.get("MapSpeedLimit") or 0.0)
def get_current_road_name(self) -> str:
return str(self.mem_params.get("RoadName") or "")
return str(self.mem_params.get("RoadName"))
def get_next_speed_limit_and_distance(self) -> tuple[float, float]:
next_speed_limit_section_str = self.mem_params.get("NextMapSpeedLimit")
+1 -1
View File
@@ -90,7 +90,7 @@ class MapdInstallManager:
logging.error("Failed to download file after all retries")
def get_installed_version(self) -> str:
return str(self._params.get("MapdVersion") or "")
return str(self._params.get("MapdVersion"))
def wait_for_internet_connection(self, return_on_failure: bool = False) -> bool:
max_retries = 10
-27
View File
@@ -28,30 +28,3 @@ for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transfor
cython_libs = envCython["LIBS"] + libs
commonmodel_lib = lenv.Library('commonmodel', common_src)
lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks)
tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]
# Get model metadata
for model_name in ['supercombo', 'driving_vision', 'driving_policy']:
fn = File(f"models/{model_name}").abspath
if File(f"models/{model_name}.onnx").exists():
script_files = [File(Dir("#sunnypilot/modeld_v2").File("get_model_metadata.py").abspath)]
cmd = f'python3 {Dir("#sunnypilot/modeld_v2").abspath}/get_model_metadata.py {fn}.onnx'
lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd)
def tg_compile(flags, model_name):
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
fn = File(f"models/{model_name}").abspath
return lenv.Command(
fn + "_tinygrad.pkl",
[fn + ".onnx"] + tinygrad_files,
f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl'
)
# Compile small models
for model_name in ['supercombo', 'driving_vision', 'driving_policy']:
if File(f"models/{model_name}.onnx").exists():
flags = {
'larch64': 'DEV=QCOM',
'Darwin': 'DEV=CPU IMAGE=0',
}.get(arch, 'DEV=LLVM IMAGE=0')
tg_compile(flags, model_name)
-29
View File
@@ -82,32 +82,3 @@ class Meta:
BRAKE_PRESS = slice(32, 55, 4)
LEFT_BLINKER = slice(33, 55, 4)
RIGHT_BLINKER = slice(34, 55, 4)
class MetaTombRaider:
ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 41, 8)
BRAKE_DISENGAGE = slice(2, 41, 8)
STEER_OVERRIDE = slice(3, 41, 8)
HARD_BRAKE_3 = slice(4, 41, 8)
HARD_BRAKE_4 = slice(5, 41, 8)
HARD_BRAKE_5 = slice(6, 41, 8)
GAS_PRESS = slice(7, 41, 8)
BRAKE_PRESS = slice(8, 41, 8)
# next 0, 2, 4, 6, 8, 10 seconds
LEFT_BLINKER = slice(41, 53, 2)
RIGHT_BLINKER = slice(42, 53, 2)
class MetaSimPose:
ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 36, 7)
BRAKE_DISENGAGE = slice(2, 36, 7)
STEER_OVERRIDE = slice(3, 36, 7)
HARD_BRAKE_3 = slice(4, 36, 7)
HARD_BRAKE_4 = slice(5, 36, 7)
HARD_BRAKE_5 = slice(6, 36, 7)
GAS_PRESS = slice(7, 36, 7)
# next 0, 2, 4, 6, 8, 10 seconds
LEFT_BLINKER = slice(36, 48, 2)
RIGHT_BLINKER = slice(37, 48, 2)
+6 -32
View File
@@ -4,7 +4,6 @@ import numpy as np
from cereal import log
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants, Plan
from openpilot.selfdrive.controls.lib.drive_helpers import get_curvature_from_plan
from openpilot.sunnypilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_lag_adjusted_curvature, MIN_SPEED
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -13,16 +12,8 @@ ConfidenceClass = log.ModelDataV2.ConfidenceClass
def get_curvature_from_output(output, vego, lat_action_t, mlsim):
if not mlsim:
if 'lat_planner_solution' in output:
x, y, yaw, yawRate = [output['lat_planner_solution'][0, :, i].tolist() for i in range(4)]
x_sol = np.column_stack([x, y, yaw, yawRate])
v_ego = max(MIN_SPEED, vego)
psis = x_sol[0:CONTROL_N, 2].tolist()
curvatures = (x_sol[0:CONTROL_N, 3] / v_ego).tolist()
desired_curvature = get_lag_adjusted_curvature(lat_action_t, v_ego, psis, curvatures)
else:
desired_curvature = float(output.get('desired_curvature')[0, 0])
return desired_curvature
if desired_curv := output.get('desired_curvature'): # If the model outputs the desired curvature, use that directly
return float(desired_curv[0, 0])
plan_output = output['plan'][0]
return float(get_curvature_from_plan(plan_output[:, Plan.T_FROM_CURRENT_EULER][:, 2], plan_output[:, Plan.ORIENTATION_RATE][:, 2],
@@ -127,31 +118,14 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
# action (includes lateral planning now)
modelV2.action = action
# times at X_IDXS according to model plan
PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N
PLAN_T_IDXS[0] = 0.0
plan_x = net_output_data['plan'][0, :, Plan.POSITION][:, 0].tolist()
for xidx in range(1, ModelConstants.IDX_N):
tidx = 0
# increment tidx until we find an element that's further away than the current xidx
while tidx < ModelConstants.IDX_N - 1 and plan_x[tidx + 1] < ModelConstants.X_IDXS[xidx]:
tidx += 1
if tidx == ModelConstants.IDX_N - 1:
# if the Plan doesn't extend far enough, set plan_t to the max value (10s), then break
PLAN_T_IDXS[xidx] = ModelConstants.T_IDXS[ModelConstants.IDX_N - 1]
break
# interpolate to find `t` for the current xidx
current_x_val = plan_x[tidx]
next_x_val = plan_x[tidx + 1]
p = (ModelConstants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) if abs(
next_x_val - current_x_val) > 1e-9 else float('nan')
PLAN_T_IDXS[xidx] = p * ModelConstants.T_IDXS[tidx + 1] + (1 - p) * ModelConstants.T_IDXS[tidx]
# times at X_IDXS of edges and lines aren't used
LINE_T_IDXS: list[float] = []
# lane lines
modelV2.init('laneLines', 4)
for i in range(4):
lane_line = modelV2.laneLines[i]
fill_xyzt(lane_line, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1])
fill_xyzt(lane_line, LINE_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1])
modelV2.laneLineStds = net_output_data['lane_lines_stds'][0,:,0,0].tolist()
modelV2.laneLineProbs = net_output_data['lane_lines_prob'][0,1::2].tolist()
@@ -161,7 +135,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
modelV2.init('roadEdges', 2)
for i in range(2):
road_edge = modelV2.roadEdges[i]
fill_xyzt(road_edge, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1])
fill_xyzt(road_edge, LINE_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1])
modelV2.roadEdgeStds = net_output_data['road_edges_stds'][0,:,0,0].tolist()
# leads
@@ -1,310 +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.
"""
import argparse
import os
import pickle
import sys
from typing import Any
def parse_metadata_file(metadata_path: str, type_key: str = None) -> dict:
if not os.path.exists(metadata_path):
print(f"Error: File not found: {metadata_path}")
sys.exit(1)
with open(metadata_path, 'rb') as f:
metadata = pickle.load(f)
result = {
"metadata_path": metadata_path,
"model_checkpoint": metadata.get("model_checkpoint"),
**{k: metadata[k] for k in ("input_shapes", "output_shapes", "output_slices") if k in metadata}
}
if type_key:
result[type_key] = True
return result
def add_to_model_metadata(new_data: dict, key: str) -> None:
file_path = os.path.abspath(__file__)
with open(file_path) as f:
lines = f.readlines()
# Find insertion point for new entry
for i in range(len(lines)-1, -1, -1):
if lines[i].strip() == '}' and any('MODEL_METADATA' in l for l in lines[:i]):
insert_idx = i
break
else:
print("Could not find where to insert new entry in MODEL_METADATA.")
sys.exit(1)
def format_val(val: Any, indent: int = 2) -> str:
if isinstance(val, dict):
items = [f'"{k}": {format_val(v, indent+2)}' for k, v in val.items()]
return '{\n' + ',\n'.join(' '*(indent+2) + item for item in items) + '\n' + ' '*indent + '}'
if isinstance(val, str):
return f'"{val}"'
if isinstance(val, slice):
if val.step is None:
return f'slice({val.start}, {val.stop})'
return f'slice({val.start}, {val.stop}, {val.step})'
if isinstance(val, (tuple, list)):
open_s, close_s = ('(', ')') if isinstance(val, tuple) else ('[', ']')
return open_s + ', '.join(format_val(x, 0) for x in val) + close_s
return repr(val)
entry_str = f' "{key}": {format_val(new_data, 2)},\n'
lines.insert(insert_idx, entry_str)
with open(file_path, 'w') as f:
f.writelines(lines)
print(f"Added entry to MODEL_METADATA with key: {key}")
MODEL_METADATA = {
"supercombo_wd40": {
"metadata_path": "/Users/james/Downloads/model-WD40 (April 09, 2024)-558/supercombo_wd40_metadata.pkl",
"model_checkpoint": None,
"non20hz": True,
"input_shapes": {
"input_imgs": (1, 12, 128, 256),
"big_input_imgs": (1, 12, 128, 256),
"desire": (1, 100, 8),
"traffic_convention": (1, 2),
"lateral_control_params": (1, 2),
"prev_desired_curv": (1, 100, 1),
"features_buffer": (1, 99, 512),
},
"output_shapes": {"outputs": (1, 6504)},
"output_slices": {
"plan": slice(0, 4955),
"lane_lines": slice(4955, 5483),
"lane_lines_prob": slice(5483, 5491),
"road_edges": slice(5491, 5755),
"lead": slice(5755, 5857),
"lead_prob": slice(5857, 5860),
"desire_state": slice(5860, 5868),
"meta": slice(5868, 5916),
"desire_pred": slice(5916, 5948),
"pose": slice(5948, 5960),
"wide_from_device_euler": slice(5960, 5966),
"sim_pose": slice(5966, 5978),
"road_transform": slice(5978, 5990),
"desired_curvature": slice(5990, 5992),
"hidden_state": slice(5992, None),
},
},
"supercombo_farmville": {
"metadata_path": "/Users/james/Downloads/model-Farmville (November 07, 2023)-557/supercombo_farmville_metadata.pkl",
"model_checkpoint": None,
"non20hz": True,
"input_shapes": {
"input_imgs": (1, 12, 128, 256),
"big_input_imgs": (1, 12, 128, 256),
"desire": (1, 100, 8),
"traffic_convention": (1, 2),
"lat_planner_state": (1, 4),
"nav_features": (1, 256),
"nav_instructions": (1, 150),
"features_buffer": (1, 99, 512),
},
"output_shapes": {"outputs": (1, 6768)},
"output_slices": {
"plan": slice(0, 4955),
"lane_lines": slice(4955, 5483),
"lane_lines_prob": slice(5483, 5491),
"road_edges": slice(5491, 5755),
"lead": slice(5755, 5857),
"lead_prob": slice(5857, 5860),
"desire_state": slice(5860, 5868),
"meta": slice(5868, 5916),
"desire_pred": slice(5916, 5948),
"pose": slice(5948, 5960),
"wide_from_device_euler": slice(5960, 5966),
"sim_pose": slice(5966, 5978),
"road_transform": slice(5978, 5990),
"lat_planner_solution": slice(5990, 6254),
"hidden_state": slice(6254, -2),
"pad": slice(-2, None),
},
},
"driving_policy_steam_powered": {
"metadata_path": "selfdrive/modeld/models/driving_policy_metadata.pkl",
"model_checkpoint": "a8f96b93-bde2-4e28-a732-4df21ebba968/400",
"split": True,
"input_shapes": {
"desire": (1, 25, 8),
"traffic_convention": (1, 2),
"features_buffer": (1, 25, 512),
},
"output_shapes": {"outputs": (1, 1000)},
"output_slices": {
"plan": slice(0, 990),
"desire_state": slice(990, 998),
"pad": slice(-2, None),
},
},
"supercombo_op": {
"metadata_path": "/Users/james/Downloads/model-Optimus Prime (September 21, 2023)-559/supercombo_op_metadata.pkl",
"model_checkpoint": None,
"non20hz": True,
"input_shapes": {
"input_imgs": (1, 12, 128, 256),
"big_input_imgs": (1, 12, 128, 256),
"desire": (1, 100, 8),
"traffic_convention": (1, 2),
"nav_features": (1, 256),
"nav_instructions": (1, 150),
"features_buffer": (1, 99, 512),
},
"output_shapes": {"outputs": (1, 6504)},
"output_slices": {
"plan": slice(0, 4955),
"lane_lines": slice(4955, 5483),
"lane_lines_prob": slice(5483, 5491),
"road_edges": slice(5491, 5755),
"lead": slice(5755, 5857),
"lead_prob": slice(5857, 5860),
"desire_state": slice(5860, 5868),
"meta": slice(5868, 5916),
"desire_pred": slice(5916, 5948),
"pose": slice(5948, 5960),
"wide_from_device_euler": slice(5960, 5966),
"sim_pose": slice(5966, 5978),
"road_transform": slice(5978, 5990),
"hidden_state": slice(5990, -2),
"pad": slice(-2, None),
},
},
"supercombo_nd": {
"metadata_path": "/Users/james/Downloads/model-Notre Dame (July 01, 2024)-568/supercombo_nd_metadata.pkl",
"model_checkpoint": None,
"non20hz": True,
"input_shapes": {
"input_imgs": (1, 12, 128, 256),
"big_input_imgs": (1, 12, 128, 256),
"desire": (1, 100, 8),
"traffic_convention": (1, 2),
"lateral_control_params": (1, 2),
"prev_desired_curv": (1, 100, 1),
"features_buffer": (1, 99, 512),
},
"output_shapes": {"outputs": (1, 6512)},
"output_slices": {
"plan": slice(0, 4955),
"lane_lines": slice(4955, 5483),
"lane_lines_prob": slice(5483, 5491),
"road_edges": slice(5491, 5755),
"lead": slice(5755, 5857),
"lead_prob": slice(5857, 5860),
"desire_state": slice(5860, 5868),
"meta": slice(5868, 5921),
"desire_pred": slice(5921, 5953),
"pose": slice(5953, 5965),
"wide_from_device_euler": slice(5965, 5971),
"sim_pose": slice(5971, 5983),
"road_transform": slice(5983, 5995),
"desired_curvature": slice(5995, 5997),
"hidden_state": slice(5997, -3),
"pad": slice(-3, None),
},
},
"supercombo_npr": {
"metadata_path": "/Users/james/Downloads/supercombo_npr_metadata.pkl",
"model_checkpoint": None,
"input_shapes": {
"input_imgs": (1, 12, 128, 256),
"big_input_imgs": (1, 12, 128, 256),
"desire": (1, 25, 8),
"traffic_convention": (1, 2),
"features_buffer": (1, 24, 512)
},
"output_shapes": {
"outputs": (1, 6500)
},
"output_slices": {
"plan": slice(0, 4955),
"lane_lines": slice(4955, 5483),
"lane_lines_prob": slice(5483, 5491),
"road_edges": slice(5491, 5755),
"lead": slice(5755, 5857),
"lead_prob": slice(5857, 5860),
"desire_state": slice(5860, 5868),
"meta": slice(5868, 5923),
"desire_pred": slice(5923, 5955),
"pose": slice(5955, 5967),
"wide_from_device_euler": slice(5967, 5973),
"road_transform": slice(5973, 5985),
"hidden_state": slice(5985, -3),
"pad": slice(-3, None)
},
"20hz": True
},
"driving_policy_renamed_desire": {
"metadata_path": "/Users/james/Downloads/model-ugh (August 27, 2025)-575/driving_policy_ugh_metadata.pkl",
"model_checkpoint": "a8f96b93-bde2-4e28-a732-4df21ebba968/400",
"split": True,
"input_shapes": {
"desire_pulse": (1, 25, 8),
"traffic_convention": (1, 2),
"features_buffer": (1, 25, 512)
},
"output_shapes": {
"outputs": (1, 1000)
},
"output_slices": {
"plan": slice(0, 990),
"desire_state": slice(990, 998),
"pad": slice(-2, None)
}
},
"supercombo_nts": { # released in January of this year, so its not 20hz, but it is modern logic..
"metadata_path": "/Users/james/Downloads/supercombo_nts_metadata.pkl",
"model_checkpoint": None,
"non20hz": True,
"input_shapes": {
"input_imgs": (1, 12, 128, 256),
"big_input_imgs": (1, 12, 128, 256),
"desire": (1, 100, 8),
"traffic_convention": (1, 2),
"lateral_control_params": (1, 2),
"prev_desired_curv": (1, 100, 1),
"features_buffer": (1, 99, 512)
},
"output_shapes": {
"outputs": (1, 6512)
},
"output_slices": {
"plan": slice(0, 4955),
"lane_lines": slice(4955, 5483),
"lane_lines_prob": slice(5483, 5491),
"road_edges": slice(5491, 5755),
"lead": slice(5755, 5857),
"lead_prob": slice(5857, 5860),
"desire_state": slice(5860, 5868),
"meta": slice(5868, 5923),
"desire_pred": slice(5923, 5955),
"pose": slice(5955, 5967),
"wide_from_device_euler": slice(5967, 5973),
"sim_pose": slice(5973, 5985),
"road_transform": slice(5985, 5997),
"desired_curvature": slice(5997, 5999),
"hidden_state": slice(5999, -1),
"pad": slice(-1, None)
}
},
}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Add model metadata from .pkl file to lookup dictionary")
parser.add_argument("metadata_file", help="Path to *_metadata.pkl file to parse")
parser.add_argument("--type", dest="type_key", default=None,
help="Type key to set (e.g., non20hz, split, 20hz)")
args = parser.parse_args()
basename = os.path.basename(args.metadata_file)
dict_key = basename.replace("_metadata.pkl", "")
metadata = parse_metadata_file(args.metadata_file, args.type_key)
add_to_model_metadata(metadata, dict_key)
+86 -120
View File
@@ -27,7 +27,7 @@ from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.models.helpers import get_active_bundle
from openpilot.sunnypilot.models.runners.helpers import get_model_runner
PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad"
PROCESS_NAME = "selfdrive.modeld.modeld"
class FrameMeta:
@@ -40,76 +40,11 @@ class FrameMeta:
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
class InputQueues:
def __init__(self, input_shapes: dict, input_dtypes: dict):
self.input_shapes = input_shapes
self.input_dtypes = input_dtypes
self.buffers: dict[str, np.ndarray | None] = {}
self.indices: dict[str, np.ndarray | None] = {}
for key, shape in input_shapes.items():
self._setup_buffer_for_key(key, shape, input_dtypes[key])
def _setup_buffer_for_key(self, key, shape, dtype):
# Temporal input: shape is [batch, history, features]
if len(shape) == 3 and shape[1] > 1:
buffer_history_len = max(100, shape[1] * 4 if shape[1] < 100 else shape[1])
self.buffers[key] = np.zeros((1, buffer_history_len, shape[2]), dtype=dtype)
features_buffer_shape = self.input_shapes.get('features_buffer')
if shape[1] in (24, 25) and features_buffer_shape and features_buffer_shape[1] == 24:
step = int(-buffer_history_len / shape[1])
self.indices[key] = np.arange(step, step * (shape[1] + 1), step)[::-1]
elif shape[1] == 25:
skip = buffer_history_len // shape[1]
self.indices[key] = np.arange(buffer_history_len)[-1 - (skip * (shape[1] - 1))::skip]
elif shape[1] == buffer_history_len:
self.indices[key] = np.arange(buffer_history_len)
else:
self.indices[key] = None
def update_dtypes_and_shapes(self, input_dtypes: dict, input_shapes: dict) -> None:
self.input_dtypes.update(input_dtypes)
self.input_shapes.update(input_shapes)
for key in input_dtypes:
if key in self.buffers and self.buffers[key] is not None:
shape = input_shapes[key]
self._setup_buffer_for_key(key, shape, input_dtypes[key])
def enqueue(self, inputs: dict[str, np.ndarray]) -> None:
for key, new_val in inputs.items():
if key not in self.buffers or self.buffers[key] is None:
continue
if new_val.dtype != self.input_dtypes[key]:
raise ValueError(f'Input {key} has wrong dtype {new_val.dtype}, expected {self.input_dtypes[key]}')
buf = self.buffers[key]
if buf is not None:
if buf.shape[1] == new_val.shape[0]:
buf[0, -new_val.shape[0]:] = new_val
buf[0, :-new_val.shape[0]] = buf[0, new_val.shape[0]:]
else:
buf[0, :-1] = buf[0, 1:]
buf[0, -1] = new_val
def get(self, *names) -> dict[str, np.ndarray]:
result: dict[str, np.ndarray] = {}
for key in names:
buf = self.buffers.get(key, None)
if buf is not None:
out_shape = self.input_shapes.get(key)
# Roll buffer and assign based on desire.shape[1] value
if out_shape is not None and key.startswith('desire') and buf.shape[1] > out_shape[1]:
skip = buf.shape[1] // out_shape[1]
result[key] = buf.reshape((out_shape[0], out_shape[1], skip, -1)).max(axis=2)
elif self.indices[key] is not None and buf.shape[1] > 1:
result[key] = buf[0, self.indices[key]]
elif out_shape is not None and buf.shape[1] >= out_shape[1]:
result[key] = buf[0, -out_shape[1]:]
return result
class ModelState(ModelStateBase):
frames: dict[str, DrivingModelFrame]
inputs: dict[str, np.ndarray]
prev_desire: np.ndarray # for tracking the rising edge of the pulse
temporal_idxs: slice | np.ndarray
def __init__(self, context: CLContext):
ModelStateBase.__init__(self)
@@ -121,47 +56,64 @@ class ModelState(ModelStateBase):
raise
model_bundle = get_active_bundle()
self.generation = model_bundle.generation if model_bundle else None
overrides = {override.key: override.value for override in model_bundle.overrides} if model_bundle else {}
self.generation = model_bundle.generation if model_bundle is not None else None
overrides = {override.key: override.value for override in model_bundle.overrides}
self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".0"))
self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0"))
self.MIN_LAT_CONTROL_SPEED = 0.3
buffer_length = 4 if self.model_runner.is_20hz else 2
buffer_length = 5 if self.model_runner.is_20hz else 2
self.frames = {name: DrivingModelFrame(context, buffer_length) for name in self.model_runner.vision_input_names}
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
input_dtypes = dict.fromkeys(self.model_runner.input_shapes, np.float32)
self.numpy_inputs = {k: np.zeros(shape, dtype=input_dtypes[k]) for k, shape in self.model_runner.input_shapes.items() if k not in self.frames}
# img buffers are managed in openCL transform code
self.numpy_inputs = {}
temporal_inputs = {k: v for k, v in self.model_runner.input_shapes.items() if len(v) == 3 and v[1] > 1}
self.input_queues = InputQueues(temporal_inputs, dict.fromkeys(temporal_inputs, np.float32))
self.prev_desire = np.zeros(self.numpy_inputs[self.desire_key].shape[2], dtype=np.float32)
for key, shape in self.model_runner.input_shapes.items():
if key not in self.frames: # Managed by opencl
self.numpy_inputs[key] = np.zeros(shape, dtype=np.float32)
if self.model_runner.is_20hz_3d: # split models
self.full_features_buffer = np.zeros((1, self.constants.FULL_HISTORY_BUFFER_LEN, self.constants.FEATURE_LEN), dtype=np.float32)
self.full_desire = np.zeros((1, self.constants.FULL_HISTORY_BUFFER_LEN, self.constants.DESIRE_LEN), dtype=np.float32)
self.full_prev_desired_curv = np.zeros((1, self.constants.FULL_HISTORY_BUFFER_LEN, self.constants.PREV_DESIRED_CURV_LEN), dtype=np.float32)
self.temporal_idxs = slice(-1-(self.constants.TEMPORAL_SKIP*(self.constants.INPUT_HISTORY_BUFFER_LEN-1)), None, self.constants.TEMPORAL_SKIP)
elif self.model_runner.is_20hz and not self.model_runner.is_20hz_3d:
self.full_features_buffer = np.zeros((self.constants.FULL_HISTORY_BUFFER_LEN + 1, self.constants.FEATURE_LEN), dtype=np.float32)
self.full_desire = np.zeros((self.constants.FULL_HISTORY_BUFFER_LEN + 1, self.constants.DESIRE_LEN), dtype=np.float32)
num_elements = self.numpy_inputs['features_buffer'].shape[1]
step_size = int(-100 / num_elements)
self.temporal_idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
self.desire_reshape_dims = (self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], -1,
self.numpy_inputs['desire'].shape[2])
@property
def mlsim(self) -> bool:
return bool(self.generation is not None and self.generation >= 11)
@property
def desire_key(self) -> str:
return next(key for key in self.numpy_inputs if key.startswith('desire'))
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[self.desire_key][0] = 0
new_desire = np.where(inputs[self.desire_key] - self.prev_desire > .99, inputs[self.desire_key], 0)
self.prev_desire[:] = inputs[self.desire_key]
inputs['desire'][0] = 0
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.prev_desire[:] = inputs['desire']
batch_inputs = {key: (new_desire if key == self.desire_key else inputs[key])
for key in self.input_queues.buffers
if not (key == 'features_buffer' and 'hidden_state' in self.numpy_inputs) and (key == self.desire_key or key in inputs)}
self.input_queues.enqueue(batch_inputs)
if self.model_runner.is_20hz_3d: # split models
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, self.constants.INPUT_HISTORY_BUFFER_LEN, self.constants.TEMPORAL_SKIP, -1)).max(axis=2)
elif self.model_runner.is_20hz and not self.model_runner.is_20hz_3d: # 20hz supercombo
self.full_desire[:-1] = self.full_desire[1:]
self.full_desire[-1] = new_desire
self.numpy_inputs['desire'][:] = self.full_desire.reshape(self.desire_reshape_dims).max(axis=2)
else: # not 20hz
length = inputs['desire'].shape[0]
self.numpy_inputs['desire'][0, :-1] = self.numpy_inputs['desire'][0, 1:]
self.numpy_inputs['desire'][0, -1, :length] = new_desire[:length]
for key in self.numpy_inputs:
if key in self.input_queues.buffers:
self.numpy_inputs[key][:] = self.input_queues.get(key)[key]
elif key in inputs:
if key in inputs and key not in ['desire']:
self.numpy_inputs[key][:] = inputs[key]
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.model_runner.vision_input_names}
@@ -175,27 +127,42 @@ class ModelState(ModelStateBase):
# Run model inference
outputs = self.model_runner.run_model()
if "lat_planner_solution" in outputs and "lat_planner_state" in self.numpy_inputs:
idx_n = outputs['lat_planner_solution'].shape[1]
t_idxs = [10.0 * ((i / (idx_n - 1))**2) for i in range(idx_n)]
self.numpy_inputs['lat_planner_state'][2] = np.interp(DT_MDL, t_idxs, outputs['lat_planner_solution'][0, :, 2])
self.numpy_inputs['lat_planner_state'][3] = np.interp(DT_MDL, t_idxs, outputs['lat_planner_solution'][0, :, 3])
if self.model_runner.is_20hz_3d: # split models
self.full_features_buffer[0, :-1] = self.full_features_buffer[0, 1:]
self.full_features_buffer[0, -1] = outputs['hidden_state'][0, :]
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
elif self.model_runner.is_20hz and not self.model_runner.is_20hz_3d: # 20hz supercombo
self.full_features_buffer[:-1] = self.full_features_buffer[1:]
self.full_features_buffer[-1] = outputs['hidden_state'][0, :]
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[self.temporal_idxs]
else: # not 20hz
feature_len = outputs['hidden_state'].shape[1]
self.numpy_inputs['features_buffer'][0, :-1] = self.numpy_inputs['features_buffer'][0, 1:]
self.numpy_inputs['features_buffer'][0, -1, :feature_len] = outputs['hidden_state'][0, :feature_len]
# Enqueue features buffer
self.input_queues.enqueue({'features_buffer': outputs['hidden_state'][0, :]})
self.numpy_inputs['features_buffer'][:] = self.input_queues.get('features_buffer')['features_buffer']
if "desired_curvature" in outputs:
input_name_prev = None
if "desired_curvature" in outputs and "prev_desired_curv" in self.numpy_inputs:
self.process_desired_curvature(outputs, 'prev_desired_curv')
if "prev_desired_curvs" in self.numpy_inputs.keys():
input_name_prev = 'prev_desired_curvs'
elif "prev_desired_curv" in self.numpy_inputs.keys():
input_name_prev = 'prev_desired_curv'
if input_name_prev is not None:
self.process_desired_curvature(outputs, input_name_prev)
return outputs
def process_desired_curvature(self, outputs, input_name):
self.input_queues.enqueue({input_name: outputs['desired_curvature'][0, :]})
self.numpy_inputs[input_name][:] = self.input_queues.get(input_name)[input_name]
if self.mlsim:
self.numpy_inputs[input_name][:] = 0 * self.input_queues.get(input_name)[input_name]
def process_desired_curvature(self, outputs, input_name_prev):
if self.model_runner.is_20hz_3d: # split models
self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:]
self.full_prev_desired_curv[0,-1,:] = outputs['desired_curvature'][0, :]
self.numpy_inputs[input_name_prev][:] = self.full_prev_desired_curv[0, self.temporal_idxs]
if self.mlsim:
self.numpy_inputs[input_name_prev][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs]
else:
length = outputs['desired_curvature'][0].size
self.numpy_inputs[input_name_prev][0, :-length, 0] = self.numpy_inputs[input_name_prev][0, length:, 0]
self.numpy_inputs[input_name_prev][0, -length:, 0] = outputs['desired_curvature'][0]
def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action:
@@ -256,13 +223,19 @@ def main(demo=False):
publish_state = PublishState()
params = Params()
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
frame_id = last_vipc_frame_id = run_count = 0
model_transform_main = model_transform_extra = np.zeros((3, 3), dtype=np.float32)
# setup filter to track dropped frames
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
frame_id = 0
last_vipc_frame_id = 0
run_count = 0
model_transform_main = np.zeros((3, 3), dtype=np.float32)
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
live_calib_seen = False
buf_main = buf_extra = None
meta_main = meta_extra = FrameMeta()
buf_main, buf_extra = None, None
meta_main = FrameMeta()
meta_extra = FrameMeta()
if demo:
@@ -349,19 +322,12 @@ def main(demo=False):
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.model_runner.vision_input_names}
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.model_runner.vision_input_names}
inputs:dict[str, np.ndarray] = {
model.desire_key: vec_desire,
'desire': vec_desire,
'traffic_convention': traffic_convention,
}
conditional_inputs = {
"lateral_control_params": lambda v_ego=v_ego, lat_delay=lat_delay: np.array([v_ego, lat_delay], dtype=np.float32),
"driving_style": lambda: np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32),
"nav_features": lambda: np.zeros(model.model_runner.input_shapes.get('nav_features')[1], dtype=np.float32),
"nav_instructions": lambda: np.zeros(model.model_runner.input_shapes.get('nav_instructions')[1], dtype=np.float32),
}
for key, value in conditional_inputs.items():
if key in model.numpy_inputs:
inputs[key] = value()
if "lateral_control_params" in model.numpy_inputs.keys():
inputs['lateral_control_params'] = np.array([v_ego, lat_delay], dtype=np.float32)
mt1 = time.perf_counter()
model_output = model.run(bufs, transforms, inputs, prepare_only)
@@ -1,5 +1,6 @@
import numpy as np
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
from openpilot.sunnypilot.models.helpers import get_active_bundle
def safe_exp(x, out=None):
@@ -24,6 +25,8 @@ def softmax(x, axis=-1):
class Parser:
def __init__(self, ignore_missing=False):
self.ignore_missing = ignore_missing
model_bundle = get_active_bundle()
self.generation = model_bundle.generation if model_bundle is not None else None
def check_missing(self, outs, name):
if name not in outs and not self.ignore_missing:
@@ -88,26 +91,28 @@ 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_plan_mhp(self, outs):
self.parse_mdn('plan', outs, in_N=SplitModelConstants.PLAN_MHP_N, out_N=SplitModelConstants.PLAN_MHP_SELECTION,
out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
def parse_dynamic_outputs(self, outs: dict[str, np.ndarray]) -> None:
if 'lead' in outs:
lead_mhp = self.is_mhp(outs, 'lead',
SplitModelConstants.LEAD_MHP_SELECTION * SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH)
lead_in_N, lead_out_N = (SplitModelConstants.LEAD_MHP_N, SplitModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0)
lead_out_shape = (SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH) if lead_mhp else \
(SplitModelConstants.LEAD_MHP_SELECTION, SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH)
self.parse_mdn('lead', outs, in_N=lead_in_N, out_N=lead_out_N, out_shape=lead_out_shape)
if self.generation >= 12 and \
outs['lead'].shape[1] == 2 * SplitModelConstants.LEAD_MHP_SELECTION * SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH:
self.parse_mdn('lead', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.LEAD_MHP_SELECTION, SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH))
else:
self.parse_mdn('lead', outs, in_N=SplitModelConstants.LEAD_MHP_N, out_N=SplitModelConstants.LEAD_MHP_SELECTION,
out_shape=(SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH))
if 'plan' in outs:
plan_mhp = self.is_mhp(outs, 'plan', SplitModelConstants.IDX_N * SplitModelConstants.PLAN_WIDTH)
plan_in_N, plan_out_N = (SplitModelConstants.PLAN_MHP_N, SplitModelConstants.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=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
if self.generation >= 12 and \
outs['plan'].shape[1] > 2 * SplitModelConstants.PLAN_WIDTH * SplitModelConstants.IDX_N:
self._parse_plan_mhp(outs)
elif self.generation >= 12:
self.parse_mdn('plan', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
else:
self._parse_plan_mhp(outs)
def split_outputs(self, outs: dict[str, np.ndarray]) -> None:
if 'desired_curvature' in outs:
@@ -1,275 +0,0 @@
import numpy as np
import pytest
from typing import Any
import openpilot.sunnypilot.models.helpers as helpers
import openpilot.sunnypilot.models.runners.helpers as runner_helpers
import openpilot.sunnypilot.modeld_v2.modeld as modeld_module
from openpilot.sunnypilot.modeld_v2.model_metadata_lookup import MODEL_METADATA
ModelState = modeld_module.ModelState
SHAPE_MODE_PARAMS = []
for _, meta in MODEL_METADATA.items():
mode = ''
if isinstance(meta, dict):
if meta.get('split'):
mode = 'split'
elif meta.get('non20hz'):
mode = 'non20hz'
elif meta.get('20hz'):
mode = '20hz'
input_shapes = {}
for k, v in meta.get('input_shapes', {}).items():
if k not in ["input_imgs", "big_input_imgs"]:
input_shapes[k] = v
if input_shapes:
SHAPE_MODE_PARAMS.append((input_shapes, mode))
# This creates a dummy runner, override, and bundle instance for the tests to run, without actually trying to load a physical model.
class DummyOverride:
def __init__(self, key: str, value: str) -> None:
self.key = key
self.value = value
class DummyBundle:
def __init__(self) -> None:
self.overrides = [DummyOverride('lat', '.1'), DummyOverride('long', '.3')]
self.generation = 10 # default to non-mlsim for buffer-update tests, as raising to 11 here will zero curvature buffer
class DummyModelRunner:
def __init__(self, input_shapes: dict[str, tuple[int, int, int]], constants: Any = None) -> None:
self.input_shapes = input_shapes
self.constants = constants or type('C', (), {
'FULL_HISTORY_BUFFER_LEN': 100,
'FEATURE_LEN': 512,
'DESIRE_LEN': 8,
'PREV_DESIRED_CURV_LEN': 1,
'INPUT_HISTORY_BUFFER_LEN': 25,
'TEMPORAL_SKIP': 4,
})()
self.vision_input_names: list[str] = []
shape = input_shapes.get('desire', (1, 0, 0)) # [batch, history, features]
if shape[1] == 25:
self.is_20hz = True
else:
self.is_20hz = False
# Minimal prepare/run methods so ModelState can be run without actually running the model
def prepare_inputs(self, imgs_cl, numpy_inputs, frames):
return None
def run_model(self):
return {
'hidden_state': np.zeros((1, self.constants.FEATURE_LEN), dtype=np.float32),
'desired_curvature': np.zeros((1, 1), dtype=np.float32),
}
@pytest.fixture
def shapes(request):
return request.param
@pytest.fixture
def bundle() -> DummyBundle:
return DummyBundle()
@pytest.fixture
def runner(shapes) -> DummyModelRunner:
return DummyModelRunner(shapes)
@pytest.fixture
def apply_patches(monkeypatch: pytest.MonkeyPatch, bundle: DummyBundle, runner: DummyModelRunner):
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None: bundle, raising=False)
monkeypatch.setattr(runner_helpers, 'get_model_runner', lambda: runner, raising=False)
monkeypatch.setattr(modeld_module, 'get_model_runner', lambda: runner, raising=False)
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None: bundle, raising=False)
# These are expected shapes and indices based on the time the model was presented
def get_expected_indices(shape, constants, mode, key=None):
if mode == 'split':
start = -1 - (constants.TEMPORAL_SKIP * (constants.INPUT_HISTORY_BUFFER_LEN - 1))
arr = np.arange(constants.FULL_HISTORY_BUFFER_LEN)
idxs = arr[start::constants.TEMPORAL_SKIP]
return idxs
elif mode == '20hz':
num_elements = shape[1]
step_size = int(-100 / num_elements)
idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
return idxs
elif mode == 'non20hz':
if key and shape[1] == constants.FULL_HISTORY_BUFFER_LEN:
return np.arange(constants.FULL_HISTORY_BUFFER_LEN)
return None
return None
@pytest.mark.parametrize("shapes,mode", SHAPE_MODE_PARAMS, indirect=["shapes"])
def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
state = ModelState(None)
constants = DummyModelRunner(shapes).constants
for key in shapes:
buf = state.input_queues.buffers.get(key, None)
idxs = state.input_queues.indices.get(key, None)
if buf is None:
continue # not all shapes are 3D, and the non-3D ones are not buffered
# Buffer shape logic
if mode == 'split':
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
expected_idxs = get_expected_indices(shapes[key], constants, 'split', key)
elif mode == '20hz':
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
expected_idxs = get_expected_indices(shapes[key], constants, '20hz', key)
elif mode == 'non20hz':
if key == 'features_buffer':
expected_shape = (1, shapes[key][1]*4, shapes[key][2])
else:
expected_shape = (1, shapes[key][1], shapes[key][2])
expected_idxs = get_expected_indices(shapes[key], constants, 'non20hz', key)
assert buf is not None, f"{key}: buffer not found"
assert buf.shape == expected_shape, f"{key}: buffer shape {buf.shape} != expected {expected_shape}"
if expected_idxs is not None:
assert np.all(idxs == expected_idxs), f"{key}: buffer idxs {idxs} != expected {expected_idxs}"
else:
assert idxs is None or idxs.size == 0, f"{key}: buffer idxs should be None or empty"
def legacy_buffer_update(buf, new_val, mode, key, constants, idxs, input_shape, prev_desire=None):
# This is what we compare the new dynamic logic to, to ensure it does the same thing
if mode == 'split':
if key == 'desire' or key.startswith('desire'):
buf[0,:-1] = buf[0,1:]
buf[0,-1] = new_val
return buf.reshape((1, constants.INPUT_HISTORY_BUFFER_LEN, constants.TEMPORAL_SKIP, -1)).max(axis=2)
elif key == 'features_buffer':
buf[0,:-1] = buf[0,1:]
buf[0,-1] = new_val
return buf[0, idxs]
elif key == 'prev_desired_curv':
buf[0,:-1] = buf[0,1:]
buf[0,-1,:] = new_val
return buf[0, idxs]
elif mode == '20hz':
if key == 'desire':
buf[:-1] = buf[1:]
buf[-1] = new_val
reshape_dims = (1, buf.shape[1], -1, buf.shape[2])
reshaped = buf.reshape(reshape_dims).max(axis=2)
# Slice to last shape[1] elements to match model input shape
input_len = reshaped.shape[1]
model_input_len = 25 # For 20hz mode, desire shape[1] is 25
if input_len > model_input_len:
reshaped = reshaped[:, -model_input_len:, :]
return reshaped
elif key == 'features_buffer':
buffer_history_len = buf.shape[1]
legacy_buf = np.zeros((buffer_history_len, buf.shape[2]), dtype=np.float32)
legacy_buf[:] = buf[0]
legacy_buf[:-1] = legacy_buf[1:]
legacy_buf[-1] = new_val
return legacy_buf[idxs]
elif key == 'prev_desired_curv':
buffer_history_len = buf.shape[1]
legacy_buf = np.zeros((buffer_history_len, buf.shape[2]), dtype=np.float32)
legacy_buf[:] = buf[0]
legacy_buf[:-1] = legacy_buf[1:]
legacy_buf[-1,:] = new_val
return legacy_buf[idxs]
elif mode == 'non20hz':
if key == 'desire':
desire_len = constants.DESIRE_LEN
if prev_desire is None:
prev_desire = np.zeros(desire_len, dtype=np.float32)
# Set first element to zero
new_val = new_val.copy()
new_val[0] = 0
# Shift buffer by desire len
buf[0][:-desire_len] = buf[0][desire_len:]
# Only insert new desire if rising edge
buf[0][-desire_len:] = np.where(new_val - prev_desire > 0.99, new_val, 0)
prev_desire[:] = new_val
return buf[0]
elif key == 'features_buffer':
feature_len = constants.FEATURE_LEN
buf[0, :-feature_len] = buf[0, feature_len:]
buf[0, -feature_len:] = new_val
return buf[0, -input_shape[1]:]
elif key == 'prev_desired_curv':
length = new_val.shape[0]
buf[0,:-length,0] = buf[0,length:,0]
buf[0,-length:,0] = new_val[:length]
return buf[0]
return None
def dynamic_buffer_update(state, key, new_val, mode):
if key == 'desire' or key.startswith('desire'):
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
for k, v in state.model_runner.input_shapes.items() if k != key}
inputs[key] = new_val.copy()
# ModelState.run expects desire as a pulse, so we zero the first element.
inputs[key][0] = 0
state.run({}, {}, inputs, prepare_only=False)
return state.numpy_inputs[key]
if key == 'features_buffer':
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
for k, v in state.model_runner.input_shapes.items() if k != 'features_buffer'}
def run_model_stub():
return {
'hidden_state': np.asarray(new_val, dtype=np.float32).reshape(1, -1),
}
state.model_runner.run_model = run_model_stub
state.run({}, {}, inputs, prepare_only=False)
return state.numpy_inputs['features_buffer'][0]
if key == 'prev_desired_curv':
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
for k, v in state.model_runner.input_shapes.items() if k != 'prev_desired_curv'}
def run_model_stub():
return {
'hidden_state': np.zeros((1, state.constants.FEATURE_LEN), dtype=np.float32),
'desired_curvature': np.asarray(new_val, dtype=np.float32).reshape(1, -1),
}
state.model_runner.run_model = run_model_stub
state.run({}, {}, inputs, prepare_only=False)
return state.numpy_inputs['prev_desired_curv'][0]
return None
@pytest.mark.parametrize("shapes,mode", SHAPE_MODE_PARAMS, indirect=["shapes"])
@pytest.mark.parametrize("key", ["desire", "features_buffer", "prev_desired_curv"])
def test_buffer_update_equivalence(shapes, mode, key, apply_patches):
state = ModelState(None)
if key == "desire":
desire_keys = [k for k in shapes.keys() if k.startswith('desire')]
if desire_keys:
actual_key = desire_keys[0] # Use the first (and likely only) desire key
else:
actual_key = key
if actual_key not in state.numpy_inputs:
pytest.skip()
constants = DummyModelRunner(shapes).constants
buf = state.input_queues.buffers.get(actual_key, None)
idxs = state.input_queues.indices.get(actual_key, None)
input_shape = shapes[actual_key]
prev_desire = np.zeros(constants.DESIRE_LEN, dtype=np.float32) if key == 'desire' else None
for step in range(20): # multiple steps to ensure history is built up
new_val = np.full((input_shape[2],), step, dtype=np.float32)
expected = legacy_buffer_update(buf, new_val, mode, actual_key, constants, idxs, input_shape, prev_desire)
actual = dynamic_buffer_update(state, actual_key, new_val, mode)
if expected is not None and actual is not None and expected.shape != actual.shape:
if expected.ndim == 2 and actual.ndim == 2 and expected.shape[1] == actual.shape[1]:
expected = expected[-actual.shape[0]:]
assert np.allclose(actual, expected), f"{mode} {actual_key}: dynamic buffer update does not match legacy logic"
+1 -1
View File
@@ -114,7 +114,7 @@ class ModelCache:
class ModelFetcher:
"""Handles fetching and caching of model data from remote source"""
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v7.json"
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v6.json"
def __init__(self, params: Params):
self.params = params
+2 -2
View File
@@ -19,8 +19,8 @@ from openpilot.system.hardware.hw import Paths
from pathlib import Path
# see the README.md for more details on the model selector versioning
CURRENT_SELECTOR_VERSION = 9
REQUIRED_MIN_SELECTOR_VERSION = 9
CURRENT_SELECTOR_VERSION = 8
REQUIRED_MIN_SELECTOR_VERSION = 8
USE_ONNX = os.getenv('USE_ONNX', PC)
+5 -4
View File
@@ -11,15 +11,16 @@ import pickle
CUSTOM_MODEL_PATH = Paths.model_root()
# Set device environment variable for hardware acceleration
# Set QCOM environment variable for TICI devices, potentially enabling hardware acceleration
USBGPU = "USBGPU" in os.environ
if USBGPU:
os.environ['DEV'] = 'AMD'
os.environ['AMD'] = '1'
os.environ['AMD_IFACE'] = 'USB'
elif TICI:
os.environ['DEV'] = 'QCOM'
os.environ['QCOM'] = '1'
else:
os.environ['DEV'] = 'LLVM'
os.environ['LLVM'] = '1'
os.environ['JIT'] = '2' # TODO: This may cause issues
class ModelData:
@@ -83,7 +83,7 @@ class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTiny
def _run_model(self) -> NumpyDict:
"""Runs the Tinygrad model inference and parses the outputs."""
outputs = self.model_run(**self.inputs).contiguous().realize().uop.base.buffer.numpy()
outputs = self.model_run(**self.inputs).numpy().flatten()
return self._parse_outputs(outputs)
def _parse_outputs(self, model_outputs: np.ndarray) -> NumpyDict:
+1 -1
View File
@@ -1 +1 @@
2ff2f49176a13bc7f856645d785b3b838a5c7ecf7f6cb37699fa0459ebf12453
71c14a02af81857565e5fcffaaddb57a76fe80b283fe3292da830dbc92e211b7
+19 -12
View File
@@ -4,10 +4,11 @@ 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.
"""
from typing import Any
from opendbc.car import structs
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.sunnypilot.car.hyundai.longitudinal.helpers import LongitudinalTuningType
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import get_nn_model_path
@@ -22,6 +23,22 @@ def log_fingerprint(CP: structs.CarParams) -> None:
sentry.capture_fingerprint(CP.carFingerprint, CP.brand)
def _initialize_custom_longitudinal_tuning(CI: CarInterfaceBase, CP: structs.CarParams, CP_SP: structs.CarParamsSP,
params: Params = None) -> None:
if params is None:
params = Params()
# Hyundai Custom Longitudinal Tuning
if CP.brand == 'hyundai':
hyundai_longitudinal_tuning = params.get("HyundaiLongitudinalTuning")
if hyundai_longitudinal_tuning == LongitudinalTuningType.DYNAMIC:
CP_SP.flags |= HyundaiFlagsSP.LONG_TUNING_DYNAMIC.value
if hyundai_longitudinal_tuning == LongitudinalTuningType.PREDICTIVE:
CP_SP.flags |= HyundaiFlagsSP.LONG_TUNING_PREDICTIVE.value
CP_SP = CI.get_longitudinal_tuning_sp(CP, CP_SP)
def _initialize_neural_network_lateral_control(CI: CarInterfaceBase, CP: structs.CarParams, CP_SP: structs.CarParamsSP,
params: Params = None, enabled: bool = False) -> None:
if params is None:
@@ -47,15 +64,5 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None:
CP = CI.CP
CP_SP = CI.CP_SP
_initialize_custom_longitudinal_tuning(CI, CP, CP_SP, params)
_initialize_neural_network_lateral_control(CI, CP, CP_SP, params)
def initialize_params(params) -> list[dict[str, Any]]:
keys: list = []
# hyundai
keys.extend([
"HyundaiLongitudinalTuning"
])
return [{k: params.get(k, return_default=True)} for k in keys]
@@ -9,28 +9,23 @@ from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.nnlc import NeuralNetworkL
class LatControlTorqueExt(NeuralNetworkLateralControl):
def __init__(self, lac_torque, CP, CP_SP, CI):
super().__init__(lac_torque, CP, CP_SP, CI)
def __init__(self, lac_torque, CP, CP_SP):
super().__init__(lac_torque, CP, CP_SP)
def update(self, CS, VM, pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
def update(self, 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, steer_limited_by_safety, output_torque):
desired_curvature, actual_curvature):
self._ff = ff
self._pid = pid
self._pid_log = pid_log
self._setpoint = setpoint
self._measurement = measurement
self._roll_compensation = roll_compensation
self._lateral_accel_deadzone = lateral_accel_deadzone
self._desired_lateral_accel = desired_lateral_accel
self._actual_lateral_accel = actual_lateral_accel
self._desired_curvature = desired_curvature
self._actual_curvature = actual_curvature
self._gravity_adjusted_lateral_accel = gravity_adjusted_lateral_accel
self._steer_limited_by_safety = steer_limited_by_safety
self._output_torque = output_torque
self.update_calculations(CS, VM, desired_lateral_accel)
self.update_neural_network_feedforward(CS, params, calibrated_pose)
return self._pid_log, self._output_torque
return self._ff, self._pid_log
@@ -7,7 +7,6 @@ See the LICENSE.md file in the root directory for more details.
import math
import numpy as np
from openpilot.common.pid import PIDController
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.selfdrive.modeld.constants import ModelConstants
@@ -44,10 +43,9 @@ def get_lookahead_value(future_vals, current_val):
class LatControlTorqueExtBase:
def __init__(self, lac_torque, CP, CP_SP, CI):
def __init__(self, lac_torque, CP, CP_SP):
self.model_v2 = None
self.model_valid = False
self.lac_torque = lac_torque
self.torque_params = lac_torque.torque_params
self.actual_lateral_jerk: float = 0.0
@@ -55,22 +53,17 @@ class LatControlTorqueExtBase:
self.lateral_jerk_measurement: float = 0.0
self.lookahead_lateral_jerk: float = 0.0
self.torque_from_lateral_accel_in_torque_space = CI.torque_from_lateral_accel_in_torque_space()
self.torque_from_lateral_accel = lac_torque.torque_from_lateral_accel
self._ff = 0.0
self._pid = PIDController(0.0, 0.0, k_f=0.0)
self._pid_log = None
self._setpoint = 0.0
self._measurement = 0.0
self._roll_compensation = 0.0
self._lateral_accel_deadzone = 0.0
self._desired_lateral_accel = 0.0
self._actual_lateral_accel = 0.0
self._desired_curvature = 0.0
self._actual_curvature = 0.0
self._gravity_adjusted_lateral_accel = 0.0
self._steer_limited_by_safety = False
self._output_torque = 0.0
# twilsonco's Lateral Neural Network Feedforward
# Instantaneous lateral jerk changes very rapidly, making it not useful on its own,
+3 -37
View File
@@ -9,8 +9,6 @@ import math
import numpy as np
from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction
from opendbc.sunnypilot.car.interfaces import LatControlInputs
from opendbc.sunnypilot.car.lateral_ext import get_friction as get_friction_in_torque_space
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.selfdrive.modeld.constants import ModelConstants
@@ -32,8 +30,8 @@ def roll_pitch_adjust(roll, pitch):
class NeuralNetworkLateralControl(LatControlTorqueExtBase):
def __init__(self, lac_torque, CP, CP_SP, CI):
super().__init__(lac_torque, CP, CP_SP, CI)
def __init__(self, lac_torque, CP, CP_SP):
super().__init__(lac_torque, CP, CP_SP)
self.params = Params()
self.enabled = self.params.get_bool("NeuralNetworkLateralControl")
self.has_nn_model = CP_SP.neuralNetworkLateralControl.model.path != MOCK_MODEL_PATH
@@ -59,44 +57,14 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase):
self.error_deque = deque(maxlen=history_check_frames[0])
self.past_future_len = len(self.past_times) + len(self.nn_future_times)
@property
def _nnlc_enabled(self):
return self.enabled and self.model_valid and self.has_nn_model
def update_limits(self):
if not self._nnlc_enabled:
return
self._pid.set_limits(self.lac_torque.steer_max, -self.lac_torque.steer_max)
def update_lateral_lag(self, lag):
super().update_lateral_lag(lag)
self.nn_future_times = [t + self.desired_lat_jerk_time for t in self.future_times]
def update_feedforward_torque_space(self, CS):
torque_from_setpoint = self.torque_from_lateral_accel_in_torque_space(LatControlInputs(self._setpoint, self._roll_compensation, CS.vEgo, CS.aEgo),
self.torque_params, gravity_adjusted=False)
torque_from_measurement = self.torque_from_lateral_accel_in_torque_space(LatControlInputs(self._measurement, self._roll_compensation, CS.vEgo, CS.aEgo),
self.torque_params, gravity_adjusted=False)
self._pid_log.error = float(torque_from_setpoint - torque_from_measurement)
self._ff = self.torque_from_lateral_accel_in_torque_space(LatControlInputs(self._gravity_adjusted_lateral_accel, self._roll_compensation,
CS.vEgo, CS.aEgo), self.torque_params, gravity_adjusted=True)
self._ff += get_friction_in_torque_space(self._desired_lateral_accel - self._actual_lateral_accel, self._lateral_accel_deadzone,
FRICTION_THRESHOLD, self.torque_params)
def update_output_torque(self, CS):
freeze_integrator = self._steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
self._output_torque = self._pid.update(self._pid_log.error,
feedforward=self._ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
def update_neural_network_feedforward(self, CS, params, calibrated_pose) -> None:
if not self._nnlc_enabled:
if not self.enabled or not self.model_valid or not self.has_nn_model:
return
self.update_feedforward_torque_space(CS)
low_speed_factor = float(np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)) ** 2
self._setpoint = self._desired_lateral_accel + low_speed_factor * self._desired_curvature
self._measurement = self._actual_lateral_accel + low_speed_factor * self._actual_curvature
@@ -160,5 +128,3 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase):
# apply friction override for cars with low NN friction response
if self.model.friction_override:
self._pid_log.error += get_friction(friction_input, self._lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
self.update_output_torque(CS)
@@ -3,7 +3,6 @@ from parameterized import parameterized
from cereal import car, log, messaging
from opendbc.car.car_helpers import interfaces
from opendbc.car.gm.values import CAR as GM
from opendbc.car.honda.values import CAR as HONDA
from opendbc.car.hyundai.values import CAR as HYUNDAI
from opendbc.car.toyota.values import CAR as TOYOTA
@@ -42,7 +41,7 @@ def generate_modelV2():
class TestNeuralNetworkLateralControl:
@parameterized.expand([HONDA.HONDA_CIVIC, TOYOTA.TOYOTA_RAV4, HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN, GM.CHEVROLET_BOLT_EUV])
@parameterized.expand([HONDA.HONDA_CIVIC, TOYOTA.TOYOTA_RAV4, HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN])
def test_saturation(self, car_name):
params = Params()
params.put_bool("NeuralNetworkLateralControl", True)
@@ -58,7 +57,6 @@ class TestNeuralNetworkLateralControl:
VM = VehicleModel(CP)
controller = LatControlTorque(CP.as_reader(), CP_SP.as_reader(), CI)
torque_params = CP.lateralTuning.torque
CS = car.CarState.new_message()
CS.vEgo = 30
@@ -79,23 +77,17 @@ class TestNeuralNetworkLateralControl:
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
controller.extension.update_lateral_lag(test_lag)
controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction)
controller.extension.update_limits()
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True)
assert lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
controller.extension.update_lateral_lag(test_lag)
controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction)
controller.extension.update_limits()
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False)
assert not lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
controller.extension.update_lateral_lag(test_lag)
controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction)
controller.extension.update_limits()
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False)
assert lac_log.saturated

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