Compare commits

..

2 Commits

Author SHA1 Message Date
Jason Wen 1a4a481769 typo 2025-08-23 16:48:28 -04:00
Jason Wen 711ac89a3f bump 2025-08-23 16:05:59 -04:00
114 changed files with 996 additions and 2992 deletions
+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
+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_')) {
+17 -30
View File
@@ -22,24 +22,34 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
## Installation
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging-c3-new` branch.
### If you want to use our newest branches (our rewrite)
> [!TIP]
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `release-c3` branch.
* sunnypilot not installed or you installed a version before 0.8.17?
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```https://staging-c3-new.sunnypilot.ai```.
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```release-c3.sunnypilot.ai```.
4. Complete the rest of the installation following the onscreen instructions.
* sunnypilot already installed and you installed a version after 0.8.17?
1. On the comma three, go to `Settings` ▶️ `Software`.
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging-c3-new`
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `release-c3`
| Branch | Installation URL |
|:------------:|:--------------------------------:|
| `release-c3` | https://release-c3.sunnypilot.ai |
| `staging-c3` | https://staging-c3.sunnypilot.ai |
| `dev-c3` | https://dev-c3.sunnypilot.ai |
### If you want to use our newest branches (our rewrite)
> [!TIP]
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
> [!IMPORTANT]
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
> You can still restore the latest sunnylink backup made on the old branches.
| Branch | Installation URL |
|:----------------:|:---------------------------------------------:|
@@ -49,31 +59,8 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
| `release-c3-new` | **Not yet available**. |
> [!TIP]
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'.
> [!NOTE]
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
<details>
<summary>Older legacy branches</summary>
### If you want to use our older legacy branches (*not recommended*)
> [**IMPORTANT**]
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
> You can still restore the latest sunnylink backup made on the old branches.
| Branch | Installation URL |
|:------------:|:--------------------------------:|
| `release-c3` | https://release-c3.sunnypilot.ai |
| `staging-c3` | https://staging-c3.sunnypilot.ai |
| `dev-c3` | https://dev-c3.sunnypilot.ai |
</details>
## 🎆 Pull Requests
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
+5 -9
View File
@@ -1,22 +1,18 @@
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 "Down To Ride (Default)"
+4 -7
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}},
@@ -146,7 +146,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
@@ -156,7 +155,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params
@@ -169,7 +167,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
@@ -202,7 +199,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}},
@@ -218,5 +215,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, ""}},
};
+2 -5
View File
@@ -14,7 +14,8 @@ class PIDController:
if isinstance(self._k_d, Number):
self._k_d = [[0], [self._k_d]]
self.set_limits(pos_limit, neg_limit)
self.pos_limit = pos_limit
self.neg_limit = neg_limit
self.i_rate = 1.0 / rate
self.speed = 0.0
@@ -40,10 +41,6 @@ class PIDController:
self.f = 0.0
self.control = 0
def set_limits(self, pos_limit, neg_limit):
self.pos_limit = pos_limit
self.neg_limit = neg_limit
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
self.speed = speed
self.p = float(error) * self.k_p
-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()
+4 -13
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
# 325 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|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>|||
@@ -127,7 +119,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|Hyundai|Ioniq Plug-in Hybrid 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>||
@@ -305,7 +297,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![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!
-2
View File
@@ -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)
+19 -23
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,24 +29,17 @@ 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):
pid_log = log.ControlsState.LateralTorqueState.new_message()
@@ -67,31 +61,33 @@ class LatControlTorque(LatControl):
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
pid_log.error = float(setpoint - measurement)
ff = gravity_adjusted_lateral_accel
torque_from_setpoint = self.torque_from_lateral_accel(LatControlInputs(setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
gravity_adjusted=False)
torque_from_measurement = self.torque_from_lateral_accel(LatControlInputs(measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
gravity_adjusted=False)
pid_log.error = float(torque_from_setpoint - torque_from_measurement)
ff = self.torque_from_lateral_accel(LatControlInputs(gravity_adjusted_lateral_accel, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
gravity_adjusted=True)
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
# Lateral acceleration torque controller extension updates
# Overrides stock ff and pid_log.error
ff, pid_log = self.extension.update(CS, VM, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_lataccel = self.pid.update(pid_log.error,
output_torque = self.pid.update(pid_log.error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
# Lateral acceleration torque controller extension updates
# Overrides pid_log.error and output_torque
pid_log, output_torque = self.extension.update(CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature, steer_limited_by_safety, output_torque)
pid_log.active = True
pid_log.p = float(self.pid.p)
pid_log.i = float(self.pid.i)
pid_log.d = float(self.pid.d)
pid_log.f = float(self.pid.f)
pid_log.output = float(-output_torque) # TODO: log lat accel?
pid_log.output = float(-output_torque)
pid_log.actualLateralAccel = float(actual_lateral_accel)
pid_log.desiredLateralAccel = float(desired_lateral_accel)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
@@ -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:1af87c38492444521632a0e75839b5684ee46bf255b3474773784bffb9fe4f57
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
+16 -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,23 @@ class Parser:
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH))
self.parse_binary_crossentropy('meta', outs)
self.parse_binary_crossentropy('lead_prob', outs)
lead_mhp = self.is_mhp(outs, 'lead', ModelConstants.LEAD_MHP_SELECTION * ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH)
lead_in_N, lead_out_N = (ModelConstants.LEAD_MHP_N, ModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0)
lead_out_shape = (ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH) if lead_mhp else \
(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH)
self.parse_mdn('lead', outs, in_N=lead_in_N, out_N=lead_out_N, out_shape=lead_out_shape)
if outs['lead'].shape[1] == 2 * ModelConstants.LEAD_MHP_SELECTION *ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH:
self.parse_mdn('lead', outs, in_N=0, out_N=0,
out_shape=(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
else:
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
return outs
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH)
plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0)
self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
if outs['plan'].shape[1] == 2 * ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH:
self.parse_mdn('plan', outs, in_N=0, out_N=0,
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
else:
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
if 'desired_curvature' in outs:
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
return outs
+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
543bd2347fa35f8300478a3893fdd0a03a7c1fe6
@@ -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;
+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"
),
+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}")
+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"),
+20 -151
View File
@@ -336,8 +336,8 @@ QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStrin
return "";
}
TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<TreeFolder> &items,
const QString &current, const QString &favParam, QWidget *parent) : DialogBase(parent) {
TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items,
const QString &current, QWidget *parent) : DialogBase(parent) {
QFrame *container = new QFrame(this);
container->setStyleSheet(R"(
QFrame { background-color: #1B1B1B; }
@@ -375,9 +375,6 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<TreeF
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
main_layout->addSpacing(25);
iconBlank = QIcon("../../sunnypilot/selfdrive/assets/icons/star-empty.png");
iconFilled = QIcon ("../../sunnypilot/selfdrive/assets/icons/star-filled.png");
treeWidget = new QTreeWidget(this);
treeWidget->setHeaderHidden(true);
treeWidget->setIndentation(50);
@@ -399,49 +396,34 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<TreeF
QScroller::grabGesture(treeWidget->viewport(), QScroller::LeftMouseButtonGesture);
// Create initial list of favorites from param
const QString favs = QString::fromStdString(params.get(favParam.toStdString()));
mapFavs = new QMap<QString, QList<QPushButton*>>();
favRefs = new QStringList(favs.split(";"));
for (const QString &item : *favRefs)
{
mapFavs->insert( item, {});
}
// Populate tree
QListIterator<TreeFolder> iter(items);
QListIterator<QPair<QString, QStringList>> iter(items);
while (iter.hasNext()) {
TreeFolder currItem = iter.next();
QString prevFolder;
QString currentFolder;
if (currItem.folder.isEmpty()) {
for (const TreeNode &item : currItem.items) {
QPair currItem = iter.next();
if (currItem.first.isEmpty()) {
for (const QString &item : currItem.second) {
QTreeWidgetItem *topLevel = new QTreeWidgetItem();
topLevel->setText(0, item.displayName);
topLevel->setData(0, Qt::UserRole, item.ref);
topLevel->setText(0, item);
topLevel->setFlags(topLevel->flags() | Qt::ItemIsSelectable);
treeWidget->addTopLevelItem(topLevel);
if (item.ref == current) {
if (item == current) {
topLevel->setSelected(true);
}
}
} else {
QList<QTreeWidgetItem*> folders = treeWidget->findItems(currItem.folder, Qt::MatchExactly, 0);
QTreeWidgetItem *folderItem = nullptr;
if (folders.isEmpty()) {
folderItem = new QTreeWidgetItem(treeWidget);
} else {
folderItem = folders.first();
}
QTreeWidgetItem *folderItem = new QTreeWidgetItem(treeWidget);
folderItem->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
folderItem->setText(0, " " + currItem.folder);
folderItem->setText(0, " " + currItem.first);
folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate);
folderItem->setFlags(folderItem->flags() & ~Qt::ItemIsSelectable);
for (const TreeNode item : currItem.items)
for (const QString &item : currItem.second)
{
QTreeWidgetItem *childItem = addChildItem(item.displayName, item.ref, folderItem);
if (item.ref == current) {
QTreeWidgetItem *childItem = new QTreeWidgetItem(folderItem);
childItem->setText(0, item);
childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable);
if (item == current) {
childItem->setSelected(true);
folderItem->setExpanded(true);
}
@@ -449,39 +431,6 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<TreeF
}
}
// Create favorites folder
favorites = new QTreeWidgetItem();
favorites->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
favorites->setText(0, " " + tr("Favorites"));
favorites->setFlags(favorites->flags() | Qt::ItemIsAutoTristate);
favorites->setFlags(favorites->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(1, favorites);
// Create favorite nodes
for (int i = favRefs->size() - 1; i >= 0; --i) {
QString item = favRefs->at(i);
if (item.isEmpty()) continue;
QTreeWidgetItemIterator treeIt(treeWidget);
QTreeWidgetItem *nodeItem = nullptr;
while (*treeIt) {
if (item == (*treeIt)->data(0, Qt::UserRole).toString()) {
nodeItem = (*treeIt);
break;
}
++treeIt;
}
if (nodeItem == nullptr) continue;
QTreeWidgetItem *childItem = addChildItem(nodeItem->text(0),
nodeItem->data(0, Qt::UserRole).toString(), favorites);
if (item == current) {
treeWidget->collapseAll();
childItem->setSelected(true);
favorites->setExpanded(true);
}
}
confirm_btn = new QPushButton(tr("Select"));
confirm_btn->setObjectName("confirm_btn");
confirm_btn->setEnabled(false);
@@ -489,7 +438,7 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<TreeF
QObject::connect(treeWidget, &QTreeWidget::itemSelectionChanged, [=]() {
QList<QTreeWidgetItem*> selectedItems = treeWidget->selectedItems();
if (!selectedItems.isEmpty()) {
selection = selectedItems.first()->data(0, Qt::UserRole).toString();
selection = selectedItems.first()->text(0);
confirm_btn->setEnabled(selection != current);
}
});
@@ -516,91 +465,11 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<TreeF
outer_layout->addWidget(container);
}
QString TreeOptionDialog::getSelection(const QString &prompt_text, const QList<TreeFolder> &items,
const QString &current, const QString &favParam, QWidget *parent) {
TreeOptionDialog d(prompt_text, items, current, favParam, parent);
QString TreeOptionDialog::getSelection(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items,
const QString &current, QWidget *parent) {
TreeOptionDialog d(prompt_text, items, current, parent);
if (d.exec()) {
return d.selection;
}
return "";
}
/**
* Handles the addition or removal of items from the "favorites" list based on the provided reference identifier.
*
* @param displayName The text label associated with the item to be added or removed in the favorites.
* @param ref A unique reference key identifying the item.
* @param btn A pointer to the QPushButton associated with the item. The button's icon is updated to indicate
* whether the item is currently favorited or not.
*
* If the item is already in the favorites, it is removed from the list, its associated buttons have their
* icons reset, and the favorites tree is updated accordingly. If the item is not in the favorites, it is
* added to the list, a new associated button is created, and the favorites tree is updated. The current
* state of the favorites is stored in the Params object as a semicolon-separated string.
*/
void TreeOptionDialog::handleFavorites(const QString &displayName, const QString &ref, QPushButton *btn) {
if (mapFavs->keys().contains(ref)) { // Remove from favorites
for (auto *itemBtn:mapFavs->value(ref))
{
itemBtn->setIcon(iconBlank);
}
mapFavs->remove(ref);
favRefs->removeAll(ref);
for (int i = 0; i < favorites->childCount(); ++i) {
QTreeWidgetItem* child = favorites->child(i);
if (child && child->data(0, Qt::UserRole).toString() == ref) {
favorites->removeChild(child);
}
}
} else { // Add to favorites
QPushButton *favBtn = new QPushButton();
btn->setIcon(iconFilled);
mapFavs->insert(ref, {btn, favBtn});
favRefs->append(ref);
addChildItem(displayName, ref, favorites, favBtn, true);
}
const QString favs =favRefs->join(";");
params.put("ModelManager_Favs", favs.toStdString());
}
/**
* Adds a child item to a given folder item within the QTreeWidget.
*
* @param displayName The text to display for the child item.
* @param ref A reference string that uniquely identifies the child item.
* @param folderItem The parent folder item to which the child item will be added.
* @param btn A pointer to a QPushButton associated with the child item. If nullptr, a new button will be created.
* @param addAtTop If true, the child item is added as the first child of the folder item; otherwise, it is appended to the end.
* @return A pointer to the created QTreeWidgetItem representing the child item.
*/
QTreeWidgetItem* TreeOptionDialog::addChildItem(const QString &displayName, const QString &ref, QTreeWidgetItem *folderItem, QPushButton *btn, bool addAtTop) {
QTreeWidgetItem *childItem = new QTreeWidgetItem();
if (btn == nullptr) {
btn = new QPushButton();
}
if (mapFavs->keys().contains(ref)) {
btn->setIcon(iconFilled);
(*mapFavs)[ref].append(btn);
} else {
btn->setIcon(iconBlank);
}
btn->setIconSize(QSize(100, 100));
QWidget *buttonContainer = new QWidget();
QHBoxLayout *layout = new QHBoxLayout(buttonContainer);
layout->addWidget(btn, 0, Qt::AlignRight);
childItem->setText(0, displayName);
childItem->setData(0, Qt::UserRole, ref);
childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable);
if (addAtTop) {
folderItem->insertChild(0, childItem);
} else {
folderItem->addChild(childItem);
}
treeWidget->setItemWidget(childItem, 0, buttonContainer);
connect(btn, &QPushButton::clicked, btn, [=]() {
handleFavorites(displayName, ref, btn);
});
return childItem;
}
+2 -24
View File
@@ -8,22 +8,9 @@
#include <QWidget>
#include <QTreeWidget>
#include "common/params.h"
#include "selfdrive/ui/qt/widgets/keyboard.h"
struct TreeNode {
QString folder;
QString displayName;
QString ref;
int index;
};
struct TreeFolder {
QString folder;
QList<TreeNode> items;
};
class DialogBase : public QDialog {
Q_OBJECT
@@ -88,20 +75,11 @@ class TreeOptionDialog : public DialogBase {
Q_OBJECT
public:
explicit TreeOptionDialog(const QString &prompt_text, const QList<TreeFolder> &items, const QString &current, const QString &favParam, QWidget *parent = nullptr);
static QString getSelection(const QString &prompt_text, const QList<TreeFolder> &items, const QString &current, const QString &favParam, QWidget *parent = nullptr);
void handleFavorites(const QString &displayName, const QString &ref, QPushButton* btn);
QTreeWidgetItem* addChildItem(const QString &displayName, const QString &ref, QTreeWidgetItem* folderItem, QPushButton* btn = nullptr, bool addAtTop = false);
explicit TreeOptionDialog(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items, const QString &current, QWidget *parent = nullptr);
static QString getSelection(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items, const QString &current, QWidget *parent = nullptr);
QString selection;
private:
QTreeWidget *treeWidget;
QPushButton *confirm_btn;
Params params;
QMap<QString, QList<QPushButton*>> *mapFavs;
QStringList *favRefs;
QTreeWidgetItem *favorites;
QIcon iconBlank;
QIcon iconFilled;
};
-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",
@@ -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);
@@ -27,10 +22,6 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare
enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true);
addItem(enableGithubRunner);
// Copyparty Toggle
enableCopyparty = new ParamControlSP("EnableCopyparty", tr("Enable Copyparty service"), tr("Copyparty is a very capable file server, you can use it to download your routes, view your logs and even make some edits on some files from your browser. Requires you to connect to your comma locally via it's IP."), "", this, false);
addItem(enableCopyparty);
// Quickboot Mode Toggle
prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true);
addItem(prebuiltToggle);
@@ -16,7 +16,6 @@ public:
explicit DeveloperPanelSP(SettingsWindow *parent);
private:
ParamControlSP *enableCopyparty;
ParamControlSP *enableGithubRunner;
ButtonControlSP *errorLogBtn;
ParamControlSP *prebuiltToggle;
@@ -259,18 +259,6 @@ QString ModelsPanel::GetActiveModelInternalName() {
return DEFAULT_MODEL;
}
/**
* @brief Gets the ref of the currently selected model bundle
* @return ref of the selected bundle or default model name
*/
QString ModelsPanel::GetActiveModelRef() {
if (model_manager.hasActiveBundle()) {
return QString::fromStdString(model_manager.getActiveBundle().getRef());
}
return DEFAULT_MODEL;
}
void ModelsPanel::updateModelManagerState() {
const SubMaster &sm = *(uiStateSP()->sm);
model_manager = sm["modelManagerSP"].getModelManagerSP();
@@ -284,31 +272,34 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
currentModelLblBtn->setEnabled(false);
currentModelLblBtn->setValue(tr("Fetching models..."));
QList<TreeNode> sortedModels;
struct ModelEntry {
QString folder;
QString displayName;
int index;
};
QList<ModelEntry> sortedModels;
QSet<QString> modelFolders;
QRegularExpression re("\\(([^)]*)\\)[^(]*$");
const auto bundles = model_manager.getAvailableBundles();
for (const auto &bundle : bundles) {
auto overrides = bundle.getOverrides();
QString folder;
QString gen;
for (const auto &override : overrides) {
if (override.getKey() == "folder") {
folder = QString::fromStdString(override.getValue().cStr());
gen = QString::fromStdString(override.getValue().cStr());
}
}
modelFolders.insert(folder);
sortedModels.append(TreeNode{
folder,
modelFolders.insert(gen);
sortedModels.append(ModelEntry{
gen,
QString::fromStdString(bundle.getDisplayName()),
QString::fromStdString(bundle.getRef()),
static_cast<int>(bundle.getIndex())
});
}
std::sort(sortedModels.begin(), sortedModels.end(),
[](const TreeNode &a, const TreeNode &b) {
[](const ModelEntry &a, const ModelEntry &b) {
return a.index > b.index;
});
@@ -331,46 +322,37 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
});
// Create the final items list using sorted folders
QList<TreeFolder> items;
QList<QPair<QString, QStringList>> items;
for (const auto &folderPair : folderMaxIndices) {
QList<TreeNode> folderModels;
QString folder = folderPair.first;
QStringList folderModels;
for (const auto &model : sortedModels) {
if (model.folder == folderPair.first) {
if (model.index == folderPair.second) {
QRegularExpressionMatch match = re.match(model.displayName);
if (match.hasMatch()) {
folder.append(" - (Updated: ").append(match.captured(1)).append(")");
}
}
folderModels.append(model);
folderModels.append(model.displayName);
}
}
items.append(TreeFolder{folder, folderModels});
items.append(qMakePair(folderPair.first, folderModels));
}
items.insert(0, TreeFolder{"", {
TreeNode{"", DEFAULT_MODEL, DEFAULT_MODEL, -1}
}});
items.insert(0, qMakePair(QString(""), QStringList{DEFAULT_MODEL}));
currentModelLblBtn->setValue(GetActiveModelInternalName());
const QString selectedBundleRef = TreeOptionDialog::getSelection(
tr("Select a Model"), items, GetActiveModelRef(), QString("ModelManager_Favs"), this);
const QString selectedBundleName = TreeOptionDialog::getSelection(
tr("Select a Model"), items, GetActiveModelName(), this);
if (selectedBundleRef.isEmpty() || !canContinueOnMeteredDialog()) {
if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) {
return;
}
// Handle "Stock" selection differently
if (selectedBundleRef == DEFAULT_MODEL) {
if (selectedBundleName == DEFAULT_MODEL) {
params.remove("ModelManager_ActiveBundle");
currentModelLblBtn->setValue(tr("Default"));
showResetParamsDialog();
} else {
// Find selected bundle and initiate download
for (const auto &bundle: bundles) {
if (QString::fromStdString(bundle.getRef()) == selectedBundleRef) {
if (QString::fromStdString(bundle.getDisplayName()) == selectedBundleName) {
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {
showResetParamsDialog();
@@ -20,7 +20,6 @@ public:
private:
QString GetActiveModelName();
QString GetActiveModelInternalName();
QString GetActiveModelRef();
void updateModelManagerState();
void showEvent(QShowEvent *event) override;
@@ -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",
@@ -28,13 +28,6 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
"../assets/offroad/icon_monitoring.png",
false,
},
{
"RainbowMode",
tr("Enable Tesla Rainbow Mode"),
RainbowizeWords(tr("A beautiful rainbow effect on the path the model wants to take.")) + "<br/><i>" + tr("It")+ " <b>" + tr("does not") + "</b> " + tr("affect driving in any way.") + "</i>",
"../assets/offroad/icon_monitoring.png",
false,
},
};
// Add regular toggles first
+1 -39
View File
@@ -48,43 +48,5 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea
painter.drawPolygon(right_blindspot_vertices);
}
}
bool rainbow = Params().getBool("RainbowMode");
//float v_ego = sm["carState"].getCarState().getVEgo();
if (rainbow) {
// Simple time-based animation
float time_offset = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f;
// simple linear gradient from bottom to top
QLinearGradient bg(0, surface_rect.height(), 0, 0);
// evenly spaced colors across the spectrum
// The animation shifts the entire spectrum smoothly
float animation_speed = 40.0f; // speed vroom vroom
float hue_offset = fmod(time_offset * animation_speed, 360.0f);
// 6-8 color stops for smooth transitions more color makes it laggy
const int num_stops = 7;
for (int i = 0; i < num_stops; i++) {
float position = static_cast<float>(i) / (num_stops - 1);
float hue = fmod(hue_offset + position * 360.0f, 360.0f);
float saturation = 0.9f;
float lightness = 0.6f;
// Alpha fades out towards the far end of the path
float alpha = 0.8f * (1.0f - position * 0.3f);
QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha);
bg.setColorAt(position, color);
}
painter.setBrush(bg);
painter.drawPolygon(track_vertices);
} else {
// Normal path rendering
ModelRenderer::drawPath(painter, model, surface_rect.height());
}
ModelRenderer::drawPath(painter, model, surface_rect.height());
}
@@ -750,24 +750,3 @@ public:
setFixedSize(400, 100);
}
};
inline QString RainbowizeWords(const QString &text) {
const QStringList colors = {
"#FF6F61", // soft coral red
"#FFA177", // warm peach
"#FFD966", // soft golden yellow
"#88D498", // mint green
"#6EC6FF", // sky blue
"#A78BFA", // soft lavender
"#F78FB3" // rose pink
};
QString result;
QStringList words = text.split(' ');
for (int i = 0; i < words.size(); ++i) {
result += QString("<font color='%1'>%2</font> ").arg(colors[i % colors.size()]).arg(words[i].toHtmlEscaped());
}
return result.trimmed();
}
@@ -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>
+16 -56
View File
@@ -974,6 +974,10 @@ The default software delay value is 0.2</source>
<source>Default</source>
<translation></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> . . openpilot . .</translation>
</message>
<message>
<source>Model download has started in the background.</source>
<translation> .</translation>
@@ -1018,38 +1022,6 @@ The default software delay value is 0.2</source>
<source>Cancel</source>
<translation></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>
@@ -2088,30 +2060,6 @@ Warning: You are on a metered connection!</source>
<source>Not Paired</source>
<translation> </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>
@@ -2257,6 +2205,18 @@ 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>LKAS </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>LKAS openpilot . , . comma connect에서 , .
참고: .</translation>
</message>
<message>
<source>Enable sunnypilot</source>
<translation>sunnypilot </translation>
+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>
@@ -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
+2 -2
View File
@@ -6,11 +6,11 @@ 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 json
import time
import platform
import os
import glob
import shutil
from datetime import datetime
from openpilot.common.params import Params
from openpilot.common.realtime import Ratekeeper, config_realtime_process
@@ -56,7 +56,7 @@ def cleanup_old_osm_data(files_to_remove: list[str]) -> None:
def request_refresh_osm_location_data(nations: list[str], states: list[str] = None) -> None:
params.put("OsmDownloadedDate", str(datetime.now().timestamp()))
params.put("OsmDownloadedDate", str(time.monotonic()))
params.put_bool("OsmDbUpdatesCheck", False)
osm_download_locations = {
+51 -35
View File
@@ -69,26 +69,24 @@ class ModelState(ModelStateBase):
# img buffers are managed in openCL transform code
self.numpy_inputs = {}
self.temporal_buffers = {}
self.temporal_idxs_map = {}
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)
# 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])) # Allow for higher history buffers in the future
feature_len = shape[2]
self.temporal_buffers[key] = np.zeros((1, buffer_history_len, feature_len), dtype=np.float32)
features_buffer_shape = self.model_runner.input_shapes.get('features_buffer')
if shape[1] in (24, 25) and features_buffer_shape is not None and features_buffer_shape[1] == 24: # 20Hz
step = int(-buffer_history_len / shape[1])
self.temporal_idxs_map[key] = np.arange(step, step * (shape[1] + 1), step)[::-1]
elif shape[1] == 25: # Split
skip = buffer_history_len // shape[1]
self.temporal_idxs_map[key] = np.arange(buffer_history_len)[-1 - (skip * (shape[1] - 1))::skip]
elif shape[1] == buffer_history_len: # non20hz
self.temporal_idxs_map[key] = np.arange(buffer_history_len)
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:
@@ -100,16 +98,19 @@ class ModelState(ModelStateBase):
inputs['desire'][0] = 0
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.prev_desire[:] = inputs['desire']
self.temporal_buffers['desire'][0,:-1] = self.temporal_buffers['desire'][0,1:]
self.temporal_buffers['desire'][0,-1] = new_desire
# Roll buffer and assign based on desire.shape[1] value
if self.temporal_buffers['desire'].shape[1] > self.numpy_inputs['desire'].shape[1]:
skip = self.temporal_buffers['desire'].shape[1] // self.numpy_inputs['desire'].shape[1]
self.numpy_inputs['desire'][:] = (
self.temporal_buffers['desire'][0].reshape(self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], skip, -1).max(axis=2))
else:
self.numpy_inputs['desire'][:] = self.temporal_buffers['desire'][0, self.temporal_idxs_map['desire']]
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 inputs and key not in ['desire']:
@@ -126,27 +127,42 @@ class ModelState(ModelStateBase):
# Run model inference
outputs = self.model_runner.run_model()
# Update features_buffer
self.temporal_buffers['features_buffer'][0, :-1] = self.temporal_buffers['features_buffer'][0, 1:]
self.temporal_buffers['features_buffer'][0, -1] = outputs['hidden_state'][0, :]
self.numpy_inputs['features_buffer'][:] = self.temporal_buffers['features_buffer'][0, self.temporal_idxs_map['features_buffer']]
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]
if "desired_curvature" in outputs:
input_name_prev = None
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 and input_name_prev in self.temporal_buffers:
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_prev):
self.temporal_buffers[input_name_prev][0,:-1] = self.temporal_buffers[input_name_prev][0,1:]
self.temporal_buffers[input_name_prev][0,-1,:] = outputs['desired_curvature'][0, :]
self.numpy_inputs[input_name_prev][:] = self.temporal_buffers[input_name_prev][0, self.temporal_idxs_map[input_name_prev]]
if self.mlsim:
self.numpy_inputs[input_name_prev][:] = 0*self.temporal_buffers[input_name_prev][0, self.temporal_idxs_map[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:
@@ -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,26 @@ 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.IDX_N * SplitModelConstants.PLAN_WIDTH:
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,256 +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
ModelState = modeld_module.ModelState
# These are the shapes extracted/loaded from the model onnx
SHAPE_MODE_PARAMS = [
({'desire': (1, 25, 8), 'features_buffer': (1, 25, 512), 'prev_desired_curv': (1, 25, 1)}, 'split'),
({'desire': (1, 25, 8), 'features_buffer': (1, 24, 512), 'prev_desired_curv': (1, 25, 1)}, '20hz'),
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1)}, 'non20hz'),
]
# 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.temporal_buffers.get(key, None)
idxs = state.temporal_idxs_map.get(key, None)
# 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):
# This is what we compare the new dynamic logic to, to ensure it does the same thing
if mode == 'split':
if key == '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':
length = new_val.shape[0]
buf[0,:-1,:length] = buf[0,1:,:length]
buf[0,-1,:length] = new_val[:length]
return buf[0]
elif key == 'features_buffer':
feature_len = new_val.shape[0]
buf[0,:-1,:feature_len] = buf[0,1:,:feature_len]
buf[0,-1,:feature_len] = new_val[:feature_len]
return buf[0]
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':
state.temporal_buffers['desire'][0,:-1] = state.temporal_buffers['desire'][0,1:]
state.temporal_buffers['desire'][0,-1] = new_val
if state.temporal_buffers['desire'].shape[1] > state.numpy_inputs['desire'].shape[1]:
skip = state.temporal_buffers['desire'].shape[1] // state.numpy_inputs['desire'].shape[1]
return state.temporal_buffers['desire'][0].reshape(
state.numpy_inputs['desire'].shape[0], state.numpy_inputs['desire'].shape[1], skip, -1
).max(axis=2)
else:
return state.temporal_buffers['desire'][0, state.temporal_idxs_map['desire']]
inputs = {'desire': np.zeros((1, state.constants.DESIRE_LEN), dtype=np.float32)}
for k, tb in state.temporal_buffers.items():
if k in state.temporal_idxs_map:
continue
buf_len = tb.shape[1]
if k in state.numpy_inputs:
out_len = state.numpy_inputs[k].shape[1]
if out_len <= buf_len:
state.temporal_idxs_map[k] = np.arange(buf_len)[-out_len:]
else:
state.temporal_idxs_map[k] = np.arange(buf_len)
else:
state.temporal_idxs_map[k] = np.arange(buf_len)
if key == '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':
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)
constants = DummyModelRunner(shapes).constants
buf = state.temporal_buffers.get(key, None)
idxs = state.temporal_idxs_map.get(key, None)
input_shape = shapes[key]
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, key, constants, idxs)
actual = dynamic_buffer_update(state, key, new_val, mode)
# Model returns the reduced numpy_inputs history, compare the last n entries so the test is checking the same slices.
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} {key}: dynamic buffer update does not match legacy logic"
-1
View File
@@ -66,7 +66,6 @@ class ModelParser:
model_bundle.is20hz = bundle.get("is_20hz", False)
model_bundle.minimumSelectorVersion = int(bundle["minimum_selector_version"])
model_bundle.overrides = ModelParser._parse_overrides(bundle.get("overrides", {}))
model_bundle.ref = bundle.get("ref")
return model_bundle
@@ -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
cee4a5f34c3c741fd67e4f130a7c21fd92258c9abfc0416c4d619d94e08a72eb
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3731604f80e83a1fdb7c258baf6530b81190eeec82e6443172e84a35b7a74c02
size 1088
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0c2a513b7f2da004f145b7d689654cc65137f1b146d484fbce7ce727a297b62c
size 861
@@ -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
-3
View File
@@ -1,3 +0,0 @@
# C3 specific hardware code
`c3` is known as `tici` and comma three by comma. Not to confuse it with `c3x` which is known as `tizi`.
-84
View File
@@ -1,84 +0,0 @@
[
{
"name": "xbl",
"url": "https://commadist.azureedge.net/agnosupdate/xbl-effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b.img.xz",
"hash": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b",
"hash_raw": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b",
"size": 3282256,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "ed61a650bea0c56652dd0fc68465d8fc722a4e6489dc8f257630c42c6adcdc89"
},
{
"name": "xbl_config",
"url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c.img.xz",
"hash": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c",
"hash_raw": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c",
"size": 98124,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "b12801ffaa81e58e3cef914488d3b447e35483ba549b28c6cd9deb4814c3265f"
},
{
"name": "abl",
"url": "https://commadist.azureedge.net/agnosupdate/abl-32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6.img.xz",
"hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6",
"hash_raw": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6",
"size": 274432,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6"
},
{
"name": "aop",
"url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz",
"hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"size": 184364,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180"
},
{
"name": "devcfg",
"url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz",
"hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"size": 40336,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f"
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4.img.xz",
"hash": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4",
"hash_raw": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4",
"size": 18515968,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "492ae27f569e8db457c79d0e358a7a6297d1a1c685c2b1ae6deba7315d3a6cb0"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img.xz",
"hash": "1468d50b7ad0fda0f04074755d21e786e3b1b6ca5dd5b17eb2608202025e6126",
"hash_raw": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "242aa5adad1c04e1398e00e2440d1babf962022eb12b89adf2e60ee3068946e7",
"alt": {
"hash": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img",
"size": 5368709120
}
}
]
@@ -1,92 +0,0 @@
#!/usr/bin/env bash
SP_C3_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
DIR="$( cd "$SP_C3_DIR/../../../.." >/dev/null 2>&1 && pwd )"
source "$SP_C3_DIR/launch_env.sh"
function agnos_init {
# TODO: move this to agnos
sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta
# set success flag for current boot slot
sudo abctl --set_success
# TODO: do this without udev in AGNOS
# udev does this, but sometimes we startup faster
sudo chgrp gpu /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0
sudo chmod 660 /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
MANIFEST="$SP_C3_DIR/agnos.json"
if $AGNOS_PY --verify $MANIFEST; then
sudo reboot
fi
$DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
fi
}
function launch {
# Remove orphaned git lock if it exists on boot
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
# Check to see if there's a valid overlay-based update available. Conditions
# are as follows:
#
# 1. The DIR init file has to exist, with a newer modtime than anything in
# the DIR Git repo. This checks for local development work or the user
# switching branches/forks, which should not be overwritten.
# 2. The FINALIZED consistent file has to exist, indicating there's an update
# that completed successfully and synced to disk.
if [ -f "${DIR}/.overlay_init" ]; then
find ${DIR}/.git -newer ${DIR}/.overlay_init | grep -q '.' 2> /dev/null
if [ $? -eq 0 ]; then
echo "${DIR} has been modified, skipping overlay update installation"
else
if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then
if [ ! -d /data/safe_staging/old_openpilot ]; then
echo "Valid overlay update found, installing"
LAUNCHER_LOCATION="${BASH_SOURCE[0]}"
mv $DIR /data/safe_staging/old_openpilot
mv "${STAGING_ROOT}/finalized" $DIR
cd $DIR
echo "Restarting launch script ${LAUNCHER_LOCATION}"
unset AGNOS_VERSION
exec "${LAUNCHER_LOCATION}"
else
echo "openpilot backup found, not updating"
# TODO: restore backup? This means the updater didn't start after swapping
fi
fi
fi
fi
# handle pythonpath
ln -sfn $(pwd) /data/pythonpath
export PYTHONPATH="$PWD"
# hardware specific init
if [ -f /AGNOS ]; then
agnos_init
fi
# write tmux scrollback to a file
tmux capture-pane -pq -S-1000 > /tmp/launch_log
# start manager
cd $DIR/system/manager
if [ ! -f $DIR/prebuilt ]; then
./build.py
fi
./manager.py
# if broken, keep on screen error
while true; do sleep 1; done
}
launch
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
export OMP_NUM_THREADS=1
export MKL_NUM_THREADS=1
export NUMEXPR_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="12.8"
fi
+6 -18
View File
@@ -381,22 +381,20 @@ def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: str = N
return {"success": 1}
def scan_dir(path: str, prefix: str, base: str | None = None) -> list[str]:
if base is None:
base = path
def scan_dir(path: str, prefix: str) -> list[str]:
files = []
# only walk directories that match the prefix
# (glob and friends traverse entire dir tree)
with os.scandir(path) as i:
for e in i:
rel_path = os.path.relpath(e.path, base)
rel_path = os.path.relpath(e.path, Paths.log_root())
if e.is_dir(follow_symlinks=False):
# add trailing slash
rel_path = os.path.join(rel_path, '')
# if prefix is a partial dir name, current dir will start with prefix
# if prefix is a partial file name, prefix with start with dir name
if rel_path.startswith(prefix) or prefix.startswith(rel_path):
files.extend(scan_dir(e.path, prefix, base))
files.extend(scan_dir(e.path, prefix))
else:
if rel_path.startswith(prefix):
files.append(rel_path)
@@ -404,12 +402,7 @@ def scan_dir(path: str, prefix: str, base: str | None = None) -> list[str]:
@dispatcher.add_method
def listDataDirectory(prefix='') -> list[str]:
internal_files = scan_dir(Paths.log_root(), prefix, Paths.log_root())
try:
external_files = scan_dir(Paths.log_root_external(), prefix, Paths.log_root_external())
except FileNotFoundError:
external_files = []
return sorted(set(internal_files + external_files))
return scan_dir(Paths.log_root(), prefix)
@dispatcher.add_method
@@ -434,13 +427,8 @@ def uploadFilesToUrls(files_data: list[UploadFileDict]) -> UploadFilesToUrlRespo
failed.append(file.fn)
continue
path_internal = os.path.join(Paths.log_root(), file.fn)
path_external = os.path.join(Paths.log_root_external(), file.fn)
if os.path.exists(path_internal) or os.path.exists(strip_zst_extension(path_internal)):
path = path_internal
elif os.path.exists(path_external) or os.path.exists(strip_zst_extension(path_external)):
path = path_external
else:
path = os.path.join(Paths.log_root(), file.fn)
if not os.path.exists(path) and not os.path.exists(strip_zst_extension(path)):
failed.append(file.fn)
continue
+34
View File
@@ -0,0 +1,34 @@
#if SENSOR_ID == 1
#define VIGNETTE_PROFILE_8DT0MM
#define BIT_DEPTH 12
#define PV_MAX 4096
#define BLACK_LVL 168
float4 normalize_pv(int4 parsed, float vignette_factor) {
float4 pv = (convert_float4(parsed) - BLACK_LVL) / (PV_MAX - BLACK_LVL);
return clamp(pv*vignette_factor, 0.0, 1.0);
}
float3 color_correct(float3 rgb) {
float3 corrected = rgb.x * (float3)(1.82717181, -0.31231438, 0.07307673);
corrected += rgb.y * (float3)(-0.5743977, 1.36858544, -0.53183455);
corrected += rgb.z * (float3)(-0.25277411, -0.05627105, 1.45875782);
return corrected;
}
float3 apply_gamma(float3 rgb, int expo_time) {
// tone mapping params
const float gamma_k = 0.75;
const float gamma_b = 0.125;
const float mp = 0.01; // ideally midpoint should be adaptive
const float rk = 9 - 100*mp;
// poly approximation for s curve
return (rgb > mp) ?
((rk * (rgb-mp) * (1-(gamma_k*mp+gamma_b)) * (1+1/(rk*(1-mp))) / (1+rk*(rgb-mp))) + gamma_k*mp + gamma_b) :
((rk * (rgb-mp) * (gamma_k*mp+gamma_b) * (1+1/(rk*mp)) / (1-rk*(rgb-mp))) + gamma_k*mp + gamma_b);
}
#endif
+58
View File
@@ -0,0 +1,58 @@
#if SENSOR_ID == 3
#define BGGR
#define VIGNETTE_PROFILE_4DT6MM
#define BIT_DEPTH 12
#define PV_MAX10 1023
#define PV_MAX12 4095
#define PV_MAX16 65536 // gamma curve is calibrated to 16bit
#define BLACK_LVL 48
float combine_dual_pvs(float lv, float sv, int expo_time) {
float svc = fmax(sv * expo_time, (float)(64 * (PV_MAX10 - BLACK_LVL)));
float svd = sv * fmin(expo_time, 8.0) / 8;
if (expo_time > 64) {
if (lv < PV_MAX10 - BLACK_LVL) {
return lv / (PV_MAX16 - BLACK_LVL);
} else {
return (svc / 64) / (PV_MAX16 - BLACK_LVL);
}
} else {
if (lv > 32) {
return (lv * 64 / fmax(expo_time, 8.0)) / (PV_MAX16 - BLACK_LVL);
} else {
return svd / (PV_MAX16 - BLACK_LVL);
}
}
}
float4 normalize_pv_hdr(int4 parsed, int4 short_parsed, float vignette_factor, int expo_time) {
float4 pl = convert_float4(parsed - BLACK_LVL);
float4 ps = convert_float4(short_parsed - BLACK_LVL);
float4 pv;
pv.s0 = combine_dual_pvs(pl.s0, ps.s0, expo_time);
pv.s1 = combine_dual_pvs(pl.s1, ps.s1, expo_time);
pv.s2 = combine_dual_pvs(pl.s2, ps.s2, expo_time);
pv.s3 = combine_dual_pvs(pl.s3, ps.s3, expo_time);
return clamp(pv*vignette_factor, 0.0, 1.0);
}
float4 normalize_pv(int4 parsed, float vignette_factor) {
float4 pv = (convert_float4(parsed) - BLACK_LVL) / (PV_MAX12 - BLACK_LVL);
return clamp(pv*vignette_factor, 0.0, 1.0);
}
float3 color_correct(float3 rgb) {
float3 corrected = rgb.x * (float3)(1.55361989, -0.268894615, -0.000593219);
corrected += rgb.y * (float3)(-0.421217301, 1.51883144, -0.69760146);
corrected += rgb.z * (float3)(-0.132402589, -0.249936825, 1.69819468);
return corrected;
}
float3 apply_gamma(float3 rgb, int expo_time) {
return (10 * rgb) / (1 + 9 * rgb);
}
#endif
+47
View File
@@ -0,0 +1,47 @@
#if SENSOR_ID == 2
#define VIGNETTE_PROFILE_8DT0MM
#define BIT_DEPTH 12
#define BLACK_LVL 64
float ox_lut_func(int x) {
if (x < 512) {
return x * 5.94873e-8;
} else if (512 <= x && x < 768) {
return 3.0458e-05 + (x-512) * 1.19913e-7;
} else if (768 <= x && x < 1536) {
return 6.1154e-05 + (x-768) * 2.38493e-7;
} else if (1536 <= x && x < 1792) {
return 0.0002448 + (x-1536) * 9.56930e-7;
} else if (1792 <= x && x < 2048) {
return 0.00048977 + (x-1792) * 1.91441e-6;
} else if (2048 <= x && x < 2304) {
return 0.00097984 + (x-2048) * 3.82937e-6;
} else if (2304 <= x && x < 2560) {
return 0.0019601 + (x-2304) * 7.659055e-6;
} else if (2560 <= x && x < 2816) {
return 0.0039207 + (x-2560) * 1.525e-5;
} else {
return 0.0078421 + (exp((x-2816)/273.0) - 1) * 0.0092421;
}
}
float4 normalize_pv(int4 parsed, float vignette_factor) {
// PWL
float4 pv = {ox_lut_func(parsed.s0), ox_lut_func(parsed.s1), ox_lut_func(parsed.s2), ox_lut_func(parsed.s3)};
return clamp(pv*vignette_factor*256.0, 0.0, 1.0);
}
float3 color_correct(float3 rgb) {
float3 corrected = rgb.x * (float3)(1.5664815, -0.29808738, -0.03973474);
corrected += rgb.y * (float3)(-0.48672447, 1.41914433, -0.40295248);
corrected += rgb.z * (float3)(-0.07975703, -0.12105695, 1.44268722);
return corrected;
}
float3 apply_gamma(float3 rgb, int expo_time) {
return -0.507089*exp(-12.54124638*rgb) + 0.9655*powr(rgb, 0.5) - 0.472597*rgb + 0.507089;
}
#endif
+5 -5
View File
@@ -21,16 +21,16 @@ class TiciFanController(BaseFanController):
self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_HW))
def update(self, cur_temp: float, ignition: bool) -> int:
self.controller.pos_limit = 100 if ignition else 30
self.controller.neg_limit = 30 if ignition else 0
self.controller.neg_limit = -(100 if ignition else 30)
self.controller.pos_limit = -(30 if ignition else 0)
if ignition != self.last_ignition:
self.controller.reset()
error = cur_temp - 75
fan_pwr_out = int(self.controller.update(
error = 75 - cur_temp
fan_pwr_out = -int(self.controller.update(
error=error,
feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100])
feedforward=np.interp(cur_temp, [60.0, 100.0], [0, -100])
))
self.last_ignition = ignition
-4
View File
@@ -20,10 +20,6 @@ class Paths:
else:
return '/data/media/0/realdata/'
@staticmethod
def log_root_external() -> str:
return '/mnt/external_realdata/'
@staticmethod
def swaglog_root() -> str:
if PC:
+6 -6
View File
@@ -67,17 +67,17 @@
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img.xz",
"hash": "1468d50b7ad0fda0f04074755d21e786e3b1b6ca5dd5b17eb2608202025e6126",
"hash_raw": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img.xz",
"hash": "49faee0e9b084abf0ea46f87722e3366bbd0435fb6b25cce189295c1ff368da1",
"hash_raw": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "242aa5adad1c04e1398e00e2440d1babf962022eb12b89adf2e60ee3068946e7",
"ondevice_hash": "db07761be0130e35a9d3ea6bec8df231260d3e767ae770850f18f10e14d0ab3f",
"alt": {
"hash": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img",
"hash": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img",
"size": 5368709120
}
}
+18 -18
View File
@@ -350,51 +350,51 @@
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img.xz",
"hash": "1468d50b7ad0fda0f04074755d21e786e3b1b6ca5dd5b17eb2608202025e6126",
"hash_raw": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img.xz",
"hash": "49faee0e9b084abf0ea46f87722e3366bbd0435fb6b25cce189295c1ff368da1",
"hash_raw": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "242aa5adad1c04e1398e00e2440d1babf962022eb12b89adf2e60ee3068946e7",
"ondevice_hash": "db07761be0130e35a9d3ea6bec8df231260d3e767ae770850f18f10e14d0ab3f",
"alt": {
"hash": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img",
"hash": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img",
"size": 5368709120
}
},
{
"name": "userdata_90",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-602d5103cba97e1b07f76508d5febb47cfc4463a7e31bd20e461b55c801feb0a.img.xz",
"hash": "6a11d448bac50467791809339051eed2894aae971c37bf6284b3b972a99ba3ac",
"hash_raw": "602d5103cba97e1b07f76508d5febb47cfc4463a7e31bd20e461b55c801feb0a",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-02f7abb4b667c04043c0c6950145aaebd704851261f32256d0f7e84a52059dda.img.xz",
"hash": "1eda66d4e31222fc2e792a62ae8e7d322fc643f0b23785e7527bb51a9fee97c7",
"hash_raw": "02f7abb4b667c04043c0c6950145aaebd704851261f32256d0f7e84a52059dda",
"size": 96636764160,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "e014d92940a696bf8582807259820ab73948b950656ed83a45da738f26083705"
"ondevice_hash": "679b650ee04b7b1ef610b63fde9b43569fded39ceacf88789b564de99c221ea1"
},
{
"name": "userdata_89",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-4d7f6d12a5557eb6e3cbff9a4cd595677456fdfddcc879eddcea96a43a9d8b48.img.xz",
"hash": "748e31a5fc01fc256c012e359c3382d1f98cce98feafe8ecc0fca3e47caef116",
"hash_raw": "4d7f6d12a5557eb6e3cbff9a4cd595677456fdfddcc879eddcea96a43a9d8b48",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-bab8399bbe3968f3c496f7bc83c2541b33acc1f47814c4ad95801bf5cb7e7588.img.xz",
"hash": "e63d3277285aae1f04fd7f4f48429ce35010f4843ab755f10d360c3aa788e484",
"hash_raw": "bab8399bbe3968f3c496f7bc83c2541b33acc1f47814c4ad95801bf5cb7e7588",
"size": 95563022336,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "c181b93050787adcfef730c086bcb780f28508d84e6376d9b80d37e5dc02b55e"
"ondevice_hash": "2947374fc5980ffe3c5b94b61cc1c81bc55214f494153ed234164801731f5dc0"
},
{
"name": "userdata_30",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_30-80a76c8e56bbd7536fd5e87e8daa12984e2960db4edeb1f83229b2baeecc4668.img.xz",
"hash": "09ff390e639e4373d772e1688d05a5ac77a573463ed1deeff86390686fa686f9",
"hash_raw": "80a76c8e56bbd7536fd5e87e8daa12984e2960db4edeb1f83229b2baeecc4668",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_30-22c874b4b66bbc000f3219abede8d62cb307f5786fd526a8473c61422765dea0.img.xz",
"hash": "12d9245711e8c49c51ff2c7b82d7301f2fcb1911edcddb35a105a80911859113",
"hash_raw": "22c874b4b66bbc000f3219abede8d62cb307f5786fd526a8473c61422765dea0",
"size": 32212254720,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "2c01ab470c02121c721ff6afc25582437e821686207f3afef659387afb69c507"
"ondevice_hash": "03c8b65c945207f887ed6c52d38b53d53d71c8597dcb0b63dfbb11f7cfff8d2b"
}
]
+6 -11
View File
@@ -9,26 +9,21 @@ STATS_DIR_FILE_LIMIT = 10000
STATS_SOCKET = "ipc:///tmp/stats"
STATS_FLUSH_TIME_S = 60
PATH_DICT = {
"internal": Paths.log_root(),
"external": Paths.log_root_external()
}
def get_available_percent(default: float, path_type="internal") -> float:
def get_available_percent(default: float) -> float:
try:
statvfs = os.statvfs(PATH_DICT[path_type])
statvfs = os.statvfs(Paths.log_root())
available_percent = 100.0 * statvfs.f_bavail / statvfs.f_blocks
except (OSError, KeyError):
except OSError:
available_percent = default
return available_percent
def get_available_bytes(default: int, path_type="internal") -> int:
def get_available_bytes(default: int) -> int:
try:
statvfs = os.statvfs(PATH_DICT[path_type])
statvfs = os.statvfs(Paths.log_root())
available_bytes = statvfs.f_bavail * statvfs.f_frsize
except (OSError, KeyError):
except OSError:
available_bytes = default
return available_bytes
-37
View File
@@ -1,9 +1,7 @@
#!/usr/bin/env python3
import os
import time
import shutil
import threading
from pathlib import Path
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
from openpilot.system.loggerd.config import get_available_bytes, get_available_percent
@@ -63,41 +61,6 @@ def deleter_thread(exit_event: threading.Event):
if any(name.endswith(".lock") for name in os.listdir(delete_path)):
continue
if Path(Paths.log_root_external()).is_mount():
out_of_bytes_external = get_available_bytes(default=MIN_BYTES + 1, path_type="external") < MIN_BYTES
out_of_percent_external = get_available_percent(default=MIN_PERCENT + 1, path_type="external") < MIN_PERCENT
if out_of_percent_external or out_of_bytes_external:
dirs_external = listdir_by_creation(Paths.log_root_external())
# remove the earliest external directory we can
for delete_dir_external in sorted(dirs_external):
delete_path_external = os.path.join(Paths.log_root_external(), delete_dir_external)
try:
cloudlog.warning(f"deleting {delete_path_external}")
shutil.rmtree(delete_path_external)
break
except OSError:
cloudlog.exception(f"issue deleting {delete_path_external}")
# move directory from internal to external
path_external = os.path.join(Paths.log_root_external(), delete_dir)
try:
cloudlog.warning(f"moving {delete_path} to {path_external}")
start = time.monotonic()
shutil.move(delete_path, path_external)
cloudlog.warning(f"moved {delete_path} to {path_external} in {time.monotonic() - start:.2f}s")
break
except Exception:
cloudlog.error(f"issue moving {delete_path} to {path_external}")
try:
cloudlog.warning(f"deleting {delete_path}")
shutil.rmtree(delete_path)
break
except OSError:
cloudlog.exception(f"issue deleting {delete_path}")
continue
try:
cloudlog.info(f"deleting {delete_path}")
shutil.rmtree(delete_path)
+1 -1
View File
@@ -44,7 +44,7 @@ def manager_init() -> None:
# set unset params to their default value
for k in params.all_keys():
default_value = params.get_default_value(k)
if default_value is not None and params.get(k) is None:
if default_value and params.get(k) is None:
params.put(k, default_value)
# Create folders needed for msgq
-14
View File
@@ -65,9 +65,6 @@ def use_github_runner(started, params, CP: car.CarParams) -> bool:
return not PC and params.get_bool("EnableGithubRunner") and (
not params.get_bool("NetworkMetered") and not params.get_bool("GithubRunnerSufficientVoltage"))
def use_copyparty(started, params, CP: car.CarParams) -> bool:
return bool(params.get_bool("EnableCopyparty"))
def sunnylink_ready_shim(started, params, CP: car.CarParams) -> bool:
"""Shim for sunnylink_ready to match the process manager signature."""
return sunnylink_ready(params)
@@ -181,15 +178,4 @@ if os.path.exists("./github_runner.sh"):
if os.path.exists("../../sunnypilot/sunnylink/uploader.py"):
procs += [PythonProcess("sunnylink_uploader", "sunnypilot.sunnylink.uploader", use_sunnylink_uploader_shim)]
if os.path.exists("../../third_party/copyparty/copyparty-sfx.py"):
sunnypilot_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
copyparty_args = [f"-v{Paths.crash_log_root()}:/swaglogs:r"]
copyparty_args += [f"-v{Paths.log_root()}:/routes:r"]
copyparty_args += [f"-v{Paths.model_root()}:/models:rw"]
copyparty_args += [f"-v{sunnypilot_root}:/sunnypilot:rw"]
copyparty_args += ["-p8080"]
copyparty_args += ["-z"]
copyparty_args += ["-q"]
procs += [NativeProcess("copyparty-sfx", "third_party/copyparty", ["./copyparty-sfx.py", *copyparty_args], and_(only_offroad, use_copyparty))]
managed_processes = {p.name: p for p in procs}
+1 -2
View File
@@ -46,10 +46,9 @@ class TestManager:
manager.main()
for k in params.all_keys():
default_value = params.get_default_value(k)
if default_value is not None:
if default_value:
assert params.get(k) == default_value
assert params.get("OpenpilotEnabledToggle")
assert params.get("RouteCount") == 0
@pytest.mark.skip("this test is flaky the way it's currently written, should be moved to test_onroad")
def test_clean_exit(self, subtests):
+3 -6
View File
@@ -67,8 +67,7 @@ class MouseEvent(NamedTuple):
class MouseState:
def __init__(self, scale: float = 1.0):
self._scale = scale
def __init__(self):
self._events: deque[MouseEvent] = deque(maxlen=MOUSE_THREAD_RATE) # bound event list
self._prev_mouse_event: list[MouseEvent | None] = [None] * MAX_TOUCH_SLOTS
@@ -103,10 +102,8 @@ class MouseState:
def _handle_mouse_event(self):
for slot in range(MAX_TOUCH_SLOTS):
mouse_pos = rl.get_touch_position(slot)
x = mouse_pos.x / self._scale if self._scale != 1.0 else mouse_pos.x
y = mouse_pos.y / self._scale if self._scale != 1.0 else mouse_pos.y
ev = MouseEvent(
MousePos(x, y),
MousePos(mouse_pos.x, mouse_pos.y),
slot,
rl.is_mouse_button_pressed(slot),
rl.is_mouse_button_released(slot),
@@ -136,7 +133,7 @@ class GuiApplication:
self._trace_log_callback = None
self._modal_overlay = ModalOverlay()
self._mouse = MouseState(self._scale)
self._mouse = MouseState()
self._mouse_events: list[MouseEvent] = []
# Debug variables
-4
View File
@@ -39,10 +39,6 @@ vec4 getGradientColor(vec2 pos) {
float t = clamp(dot(pos - gradientStart, normalizedDir) / gradientLength, 0.0, 1.0);
if (gradientColorCount <= 1) return gradientColors[0];
// handle t before first / after last stop
if (t <= gradientStops[0]) return gradientColors[0];
if (t >= gradientStops[gradientColorCount-1]) return gradientColors[gradientColorCount-1];
for (int i = 0; i < gradientColorCount - 1; i++) {
if (t >= gradientStops[i] && t <= gradientStops[i+1]) {
float segmentT = (t - gradientStops[i]) / (gradientStops[i+1] - gradientStops[i]);
+1 -1
View File
@@ -37,7 +37,7 @@ NM_DEVICE_IFACE = "org.freedesktop.NetworkManager.Device"
NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8
TETHERING_IP_ADDRESS = "192.168.43.1"
DEFAULT_TETHERING_PASSWORD = "swagswagcomma"
DEFAULT_TETHERING_PASSWORD = "12345678"
# NetworkManager device states

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