Compare commits

..

51 Commits

Author SHA1 Message Date
DevTekVE
74265e76ab dunno dunno... 2025-07-25 19:34:27 +02:00
DevTekVE
97f810e303 tweakx4 2025-07-25 18:35:03 +02:00
DevTekVE
427e42cd82 tweakx3 2025-07-25 18:29:27 +02:00
DevTekVE
42c433a39b tweakx2 2025-07-25 18:26:14 +02:00
DevTekVE
cf7d6eda4b tweak 2025-07-25 18:22:54 +02:00
DevTekVE
2e28cab9b8 refactor: consolidate model building steps into a single workflow 2025-07-25 18:09:26 +02:00
discountchubbs
75019229e9 maybe we could use a script instead that both build all
That both build all and sunnypilot-build-model reference
2025-07-25 07:29:47 -07:00
discountchubbs
5c4c91fd43 Revert "bc devtekve said. also, this is repetitive af"
This reverts commit 3a0c1562de.
2025-07-24 19:45:50 -07:00
discountchubbs
3a0c1562de bc devtekve said. also, this is repetitive af 2025-07-24 19:37:07 -07:00
discountchubbs
c38e644375 Update build-single-tinygrad-model.yaml 2025-07-24 18:35:24 -07:00
discountchubbs
a4c288d1c7 cleanup 2025-07-23 20:32:49 -07:00
discountchubbs
cb2c3f1909 try dynamic 2025-07-23 19:59:37 -07:00
discountchubbs
602597d291 No need to sleep 3 seconds, just send it 2025-07-23 19:07:15 -07:00
discountchubbs
3ec234d04b Update build-all-tinygrad-models.yaml 2025-07-23 13:24:02 -07:00
discountchubbs
a8e6f45270 Update build-all-tinygrad-models.yaml 2025-07-23 07:28:52 -07:00
discountchubbs
12fe42a22d Allow more dynamic short names
This should hopefully be future-proof for now.. It's robust enough to return the correct word-digit format (see example on how it generates from given display name below):

'Last Horizon V2 (November 22, 2024)' -> LHV2
'Alabama (November 25, 2024)' -> ALABAMA
'PlayStation (December 03, 2024)' -> PLAYSTAT
'Postal Service (December 09, 2024)' -> PS
'Null Pointer (December 13, 2024)' -> NP
'North America (December 16, 2024)' -> NA
'National Public Radio (December 18, 2024)' -> NPR
'Filet o Fish (March 7, 2025)' -> FOF
'Tomb Raider 2 (April 18, 2025)' -> TR2
'Tomb Raider 3 (April 22, 2025)' -> TR3
'Tomb Raider 4 (April 25, 2025)' -> TR4
'Tomb Raider 5 (April 25, 2025)' -> TR5
'Tomb Raider 6 (April 30, 2025)' -> TR6
'Tomb Raider 7 (May 07, 2025)' -> TR7
'Down to Ride (Revision: May 10, 2025)' -> DTR
'SP Vikander Model (May 16, 2025)' -> SPVM
'VFF Driving (May 15, 2025)' -> VFFD
'Secret Good Openpilot (May 16, 2025)' -> SGO
'Vegetarian Filet o Fish (May 29, 2025)' -> VFOF
'Down To Ride (Revision: May 30, 2025)' -> DTR
'Vegetarian Filet o Fish v2 (June 05, 2025)' -> VFOFV2
'Kerrygold Driving (June 08, 2025)' -> KD
'Tomb Raider 10 (June 16, 2025)' -> TR10
'Organic Kerrygold (June 17, 2025)' -> OK
'Liquid Crystal Driving (June 21, 2025)' -> LCD
'Vegetarian Filet o Fish v3 (June 21, 2025)' -> VFOFV3
'Vibe Model [Custom Model]' -> VMCM
'Tomb Raider 13 (June 27, 2025)' -> TR13
'Aggressive TR (June 28, 2025)' -> ATR
'Tomb Raider 14 (June 30, 2025)' -> TR14
'Cookiemonster Tomb Raider (July 02, 2025)' -> CTR
'Down to Ride (Revision: July 07, 2025)' -> DTR
'Simple Plan Driving (July 07, 2025)' -> SPD
'Down to Ride (Revision: July 08, 2025)' -> DTR
'Tomb Raider 15 (July 09, 2025)' -> TR15
'Tomb Raider 15 rev-2 (July 11, 2025)' -> TR15R2
'Le Tomb Raider 14 (July 14, 2025)' -> LTR14
'Le Tomb Raider 14h (July 17, 2025)' -> LTR14H
'Tomb Raider 16 (July 18, 2025)' -> TR16
'Tomb Raider 16v2 (July 21, 2025)' -> TR16V2
2025-07-23 07:28:24 -07:00
discountchubbs
139281e0fb Update build-all-tinygrad-models.yaml 2025-07-23 07:15:09 -07:00
discountchubbs
fcf7a9343c Update build-all-tinygrad-models.yaml 2025-07-23 07:14:41 -07:00
discountchubbs
05695bf8db Update build-all-tinygrad-models.yaml 2025-07-23 07:07:28 -07:00
discountchubbs
4a9185cb15 Update build-all-tinygrad-models.yaml 2025-07-23 07:01:42 -07:00
discountchubbs
838e16bf52 Update build-all-tinygrad-models.yaml 2025-07-23 06:57:37 -07:00
discountchubbs
2e0d1dc5a1 Update build-all-tinygrad-models.yaml 2025-07-23 06:46:19 -07:00
discountchubbs
6d24fe869b Update build-all-tinygrad-models.yaml 2025-07-23 06:43:11 -07:00
discountchubbs
23ab96246c Merge remote-tracking branch 'origin/sync-20250627-tinygrad' into sync-20250627-tinygrad 2025-07-22 21:09:08 -07:00
discountchubbs
d26838de09 Merge remote-tracking branch 'origin/master-new' into sync-20250627-tinygrad 2025-07-22 21:08:35 -07:00
James Vecellio-Grant
1fbbee65b2 Merge branch 'master-new' into sync-20250627-tinygrad 2025-07-22 21:07:43 -07:00
discountchubbs
de014108fe bump to 22Jul2025 2025-07-22 21:05:48 -07:00
Nayan
358c0a8747 ui: proper capitalization for default interactivity timeout (#1065)
No More Neuron Triggering Scream
2025-07-22 01:02:28 -04:00
James Vecellio-Grant
9f6f7896b0 sunnypilot modeld: Fix SP Model Generation (#1062)
simplify and fix SP model
2025-07-19 20:22:31 -07:00
Nayan
c46ecd18fa UI: Clear Model Cache (#1058)
* clear model cache

* add cache size

* move to model manager

* fix handling for default model

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2025-07-19 21:40:16 +02:00
DevTekVE
8e3e5b13aa sunnylink: Adding more keys to be backed up (#1061)
* Enhance parameter backup capabilities

- Added `BACKUP` attribute to various persistent parameters to ensure their inclusion in backup processes.

* Add more keys

---------

Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
2025-07-19 20:05:32 +02:00
James Vecellio-Grant
08f075ab23 ui: Link Advanced Controls (#1060) 2025-07-19 10:35:34 -07:00
Nayan
7555683105 UI: Advanced Controls Toggle (#1053)
* add advancedControl bool to SP Controls

* add Advanced Controls toggle in SP Dev Panel

* merge - ui: Init Developer Panel SP#1054

* because @discountchubbs did not want to commit directly to the pr 🤷‍♂️

* enable onroad too

* hide by default only on staging & release

---------

Co-authored-by: discountchubbs <alexgrant990@gmail.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-19 19:11:49 +02:00
James Vecellio-Grant
0d0f764a79 ui: Quickboot Mode Toggle (#1045)
* Fast boot mode

* Unused

* FastBoot -> QuickBoot

* Move down, since name change

* Chronological in manager too

* last one b4 merge

---------

Co-authored-by: Nayan <nayan8teen@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-19 07:55:13 -07:00
Nayan
e955590f11 chore: remove compile_db from build targets (#1059)
remove --compile_db flag
2025-07-19 16:39:34 +02:00
James Vecellio-Grant
1b570ef418 sunnypilot modeld: Refactor Modeld to Allow Dynamic Plan and Lead (#1030)
* Introduce zero inputs for Lead, and plan to conform with new SP model introduced Monday, July 7, 2025

* Clean this up

* We can revert this after dev-c3-new testing and ready to merge.

* This needs to be apart of the conditional else fail

* Add full conditional

* Update longitudinal_planner.py

* Mypy from myphone!

* red diff

* Make generation a property for clarity

* Even clearer!

* Affix to generation, while allowing older models to use this IF param is set.

* seems a bit repetitive yea?

* dynamic

* Make most outputs dynamic

* Rm toggle from refactor

* refactor(modeld): simplify MHP output parsing logic

- Introduced `_parse_mhp_output` helper to remove redundancy and streamline `parse_dynamic_outputs`.
- Ensures improved code maintainability and clarity.

* refactor(longitudinal_planner): streamline generation handling logic

- Simplified `generation` assignment with inline conditional for better readability.
- Adjusted `mlsim` logic to default to model simulation when `generation` is unset.

* for ease of syncs from now on

* fix

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-19 16:22:02 +02:00
James Vecellio-Grant
eae44df688 ui: Models panel Refine Software Delay Adjustments (#1048)
Allow lower values to match some makes liveDelay

Co-authored-by: Nayan <nayan8teen@gmail.com>
2025-07-19 15:57:01 +02:00
Nayan
7c1f3f646c UI: Model Folders (#1031)
* model folders.. yay

* scroll expanded item in view

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-19 15:52:54 +02:00
Kumar
3faf709387 ui: Visuals Panel Lead Chevron Info (#1033)
* lead info

* ui:chevron params

* Update system/manager/manager.py.

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-19 15:17:19 +02:00
James Vecellio-Grant
47833ed73a ui: Disable Updates Toggle (#1040)
* Disable Updates

* Add default to manager.py

* Add enabled state and alternate descriptions

---------

Co-authored-by: Nayan <nayan8teen@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-19 15:01:39 +02:00
James Vecellio-Grant
517919c7b7 ui: display recorded timestamp on error view (#827)
* Error log clear on manager start

* Error log clear on manager start

* 12 hour reset. may change soon

* Add last modified date

* unused

* rm space

* Squashed commit of the following:

commit bcf5b06c82
Author: discountchubbs <alexgrant990@gmail.com>
Date:   Thu Jul 17 14:14:29 2025 -0700

    Init Developer panel SP

commit cf8bbb70ab
Author: discountchubbs <alexgrant990@gmail.com>
Date:   Thu Jul 17 06:22:18 2025 -0700

    Chronological in manager too

commit ff9b873468
Author: discountchubbs <alexgrant990@gmail.com>
Date:   Thu Jul 17 06:19:59 2025 -0700

    Move down, since name change

commit 38f6cb8c57
Author: discountchubbs <alexgrant990@gmail.com>
Date:   Thu Jul 17 06:17:36 2025 -0700

    FastBoot -> QuickBoot

commit 38562db8dd
Author: discountchubbs <alexgrant990@gmail.com>
Date:   Wed Jul 16 11:20:17 2025 -0700

    Unused

commit c7a751b02d
Author: discountchubbs <alexgrant990@gmail.com>
Date:   Wed Jul 16 10:24:47 2025 -0700

    Fast boot mode

* * Depends on  #1054

* Update developer_panel.h

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-07-19 09:41:01 +02:00
Warren Togami
0b15b88104 NNLC: SIENNA_4TH_GEN gen 1, KIA_NERO_PHEV_2022 (#1013)
* `SIENNA_4TH_GEN` tuning Gen 1 (June 14th, 2025)
   2021-2023 Toyota Sienna
* `KIA_NIRO_PHEV_2022` tuning (July 15th, 2025)
   2021-2022 Kia Niro PHEV
* Fix: rename files to remove spaces in filenames for build compatibility
2025-07-19 09:30:39 +02:00
discountchubbs
3be432d633 The "FillMe" placeholder caused an extra 10 seconds of work 2025-07-11 11:38:51 -07:00
discountchubbs
e251449d00 Model recompiled successfully, initiate "revert me after SP model compiled"
This reverts commit 95706eb688.
2025-07-10 20:31:16 -07:00
discountchubbs
95706eb688 revert me after SP model compiled 2025-07-10 20:13:17 -07:00
discountchubbs
096662799b Revert "bump tinygrad"
This reverts commit f479dfd502.
2025-07-10 20:11:02 -07:00
discountchubbs
f479dfd502 bump tinygrad 2025-07-10 20:08:46 -07:00
discountchubbs
22eefbd0f7 Reformat metadata generator to match driving_models.json 2025-07-10 12:57:07 -07:00
discountchubbs
a044347f4e bump tinygrad_repo 2025-07-10 07:23:58 -07:00
James Vecellio-Grant
eca1d0888f Merge branch 'master-new' into sync-20250627-tinygrad 2025-07-10 07:20:12 -07:00
DevTekVE
264c0fe446 Tinygrad bump from sync-20250627 2025-06-27 17:21:48 +02:00
38 changed files with 892 additions and 280 deletions

View File

@@ -14,36 +14,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
json_file: ${{ steps.get-json.outputs.json_file }}
steps:
- name: Checkout docs repo
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot-docs
ref: gh-pages
path: docs
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
- name: Get next JSON version to use
id: get-json
run: |
cd docs/docs
latest=$(ls driving_models_v*.json | sed -E 's/.*_v([0-9]+)\.json/\1/' | sort -n | tail -1)
next=$((latest+1))
json_file="driving_models_v${next}.json"
cp "driving_models_v${latest}.json" "$json_file"
echo "json_file=$json_file" >> $GITHUB_OUTPUT
- name: Upload context for next jobs
uses: actions/upload-artifact@v4
with:
name: context
path: docs
build-all:
runs-on: ubuntu-latest
needs: setup
env:
JSON_FILE: docs/docs/${{ needs.setup.outputs.json_file }}
model_matrix: ${{ steps.set-matrix.outputs.model_matrix }}
steps:
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.0
@@ -55,6 +26,14 @@ jobs:
mkdir -p ~/.ssh
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
- name: Checkout docs repo (sunnypilot-docs, gh-pages)
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot-docs
ref: gh-pages
path: docs
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
- name: Clone GitLab docs repo
env:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
@@ -68,7 +47,11 @@ jobs:
- name: Set next recompiled dir
id: set-recompiled
run: |
cd gitlab_docs/models
cd gitlab_docs
echo "checkout models/"
git sparse-checkout set --no-cone models/
git checkout main
cd models
latest_dir=$(ls -d recompiled* 2>/dev/null | sed -E 's/recompiled([0-9]+)/\1/' | sort -n | tail -1)
if [[ -z "$latest_dir" ]]; then
next_dir=1
@@ -76,124 +59,57 @@ jobs:
next_dir=$((latest_dir+1))
fi
recompiled_dir="recompiled${next_dir}"
mkdir -p "$recompiled_dir"
echo "RECOMPILED_DIR=$recompiled_dir" >> $GITHUB_ENV
- name: Download context
uses: actions/download-artifact@v4
with:
name: context
path: .
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y jq gh
- name: Build all tinygrad models
id: trigger-builds
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
> triggered_run_ids.txt
BRANCH="${{ github.event.inputs.branch }}"
jq -c '.bundles[] | select(.runner=="tinygrad")' "$JSON_FILE" | while read -r bundle; do
ref=$(echo "$bundle" | jq -r '.ref')
display_name=$(echo "$bundle" | jq -r '.display_name' | sed 's/ ([^)]*)//g')
is_20hz=$(echo "$bundle" | jq -r '.is_20hz')
echo "Triggering build for: $display_name ($ref) [20Hz: $is_20hz]"
START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
gh workflow run sunnypilot-build-model.yaml \
--repo sunnypilot/sunnypilot \
--ref "$BRANCH" \
-f upstream_branch="$ref" \
-f custom_name="$display_name" \
-f is_20hz="$is_20hz"
for i in {1..24}; do
RUN_ID=$(gh run list --repo sunnypilot/sunnypilot --workflow=sunnypilot-build-model.yaml --branch="$BRANCH" --created ">$START_TIME" --limit=1 --json databaseId --jq '.[0].databaseId')
if [ -n "$RUN_ID" ]; then
break
fi
sleep 5
done
if [ -z "$RUN_ID" ]; then
echo "ould not find the triggered workflow run for $display_name ($ref)"
exit 1
fi
echo "$RUN_ID" >> triggered_run_ids.txt
done
- name: Wait for all model builds to finish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
SUCCESS_RUNS=()
FAILED_RUNS=()
declare -A RUN_ID_TO_NAME
while read -r RUN_ID; do
ARTIFACT_NAME=$(gh api repos/sunnypilot/sunnypilot/actions/runs/$RUN_ID/artifacts --jq '.artifacts[] | select(.name | startswith("model-")) | .name' || echo "unknown")
RUN_ID_TO_NAME["$RUN_ID"]="$ARTIFACT_NAME"
done < triggered_run_ids.txt
while read -r RUN_ID; do
echo "Watching run ID: $RUN_ID"
gh run watch "$RUN_ID" --repo sunnypilot/sunnypilot
CONCLUSION=$(gh run view "$RUN_ID" --repo sunnypilot/sunnypilot --json conclusion --jq '.conclusion')
ARTIFACT_NAME="${RUN_ID_TO_NAME[$RUN_ID]}"
echo "Run $RUN_ID ($ARTIFACT_NAME) concluded with: $CONCLUSION"
if [[ "$CONCLUSION" == "success" ]]; then
SUCCESS_RUNS+=("$RUN_ID")
else
FAILED_RUNS+=("$RUN_ID")
fi
done < triggered_run_ids.txt
if [[ ${#SUCCESS_RUNS[@]} -eq 0 ]]; then
echo "All model builds failed. Aborting."
if [ -d "$recompiled_dir" ]; then
echo "ERROR: $recompiled_dir already exists in GitLab repo"
exit 1
fi
mkdir -p "$recompiled_dir"
touch "$recompiled_dir/.gitkeep"
echo "RECOMPILED_DIR=$recompiled_dir" >> $GITHUB_ENV
echo "Created new recompiled dir: $recompiled_dir"
cd ../..
if [[ ${#FAILED_RUNS[@]} -gt 0 ]]; then
echo "WARNING: The following model builds failed:"
for RUN_ID in "${FAILED_RUNS[@]}"; do
echo "- $RUN_ID (${RUN_ID_TO_NAME[$RUN_ID]})"
done
echo "You may want to rerun these models manually."
fi
- name: Get next JSON version to use (from GitHub docs repo)
id: get-json
run: |
cd docs/docs
latest=$(ls driving_models_v*.json | sed -E 's/.*_v([0-9]+)\.json/\1/' | sort -n | tail -1)
next=$((latest+1))
json_file="driving_models_v${next}.json"
cp "driving_models_v${latest}.json" "$json_file"
echo "json_file=docs/docs/$json_file" >> $GITHUB_OUTPUT
echo "SRC_JSON_FILE=docs/docs/driving_models_v${latest}.json" >> $GITHUB_ENV
- name: Extract tinygrad models
id: set-matrix
working-directory: docs/docs
run: |
jq -c '[.bundles[] | select(.runner=="tinygrad") | {ref, display_name, is_20hz}]' "$(basename "${SRC_JSON_FILE}")" > matrix.json
echo "model_matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
- name: Download and extract all model artifacts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
get_and_build:
needs: [setup]
uses: ./.github/workflows/sunnypilot-build-model.yaml
strategy:
matrix:
model: ${{ fromJson(needs.setup.outputs.model_matrix) }}
name: Model [${{ matrix.model.display_name }}] from ref [${{ matrix.model.ref }}]
with:
upstream_branch: ${{ matrix.model.ref }}
custom_name: ${{ matrix.model.display_name }}
is_20hz: ${{ matrix.model.is_20hz || true }}
secrets: inherit
push_results:
needs: [setup, get_and_build ]
runs-on: ubuntu-latest
steps:
- name: Download all model artifacts
run: |
ARTIFACT_DIR="gitlab_docs/models/$RECOMPILED_DIR"
SUCCESS_RUNS=()
while read -r RUN_ID; do
CONCLUSION=$(gh run view "$RUN_ID" --repo sunnypilot/sunnypilot --json conclusion --jq '.conclusion')
if [[ "$CONCLUSION" == "success" ]]; then
SUCCESS_RUNS+=("$RUN_ID")
fi
done < triggered_run_ids.txt
for RUN_ID in "${SUCCESS_RUNS[@]}"; do
ARTIFACT_NAME=$(gh api repos/sunnypilot/sunnypilot/actions/runs/$RUN_ID/artifacts --jq '.artifacts[] | select(.name | startswith("model-")) | .name')
echo "Downloading artifact: $ARTIFACT_NAME from run: $RUN_ID"
mkdir -p "$ARTIFACT_DIR/$ARTIFACT_NAME"
echo "Created directory: $ARTIFACT_DIR/$ARTIFACT_NAME"
gh run download "$RUN_ID" --repo sunnypilot/sunnypilot -n "$ARTIFACT_NAME" --dir "$ARTIFACT_DIR/$ARTIFACT_NAME"
echo "Downloaded artifact zip(s) to: $ARTIFACT_DIR/$ARTIFACT_NAME"
ZIP_PATH=$(find "$ARTIFACT_DIR/$ARTIFACT_NAME" -type f -name '*.zip' | head -n1)
if [ -n "$ZIP_PATH" ]; then
echo "Unzipping $ZIP_PATH to $ARTIFACT_DIR/$ARTIFACT_NAME"
unzip -o "$ZIP_PATH" -d "$ARTIFACT_DIR/$ARTIFACT_NAME"
rm -f "$ZIP_PATH"
echo "Unzipped and removed $ZIP_PATH"
else
echo "No zip file found in $ARTIFACT_DIR/$ARTIFACT_NAME (This is NOT an error)."
fi
echo "Done processing $ARTIFACT_NAME"
mkdir -p "$ARTIFACT_DIR"
for name in $(ls ${{ github.workspace }}/output); do
mkdir -p "$ARTIFACT_DIR/$name"
cp -r ${{ github.workspace }}/output/$name/* "$ARTIFACT_DIR/$name/"
done
- name: Push recompiled dir to GitLab
@@ -212,7 +128,7 @@ jobs:
- name: Run json_parser.py to update JSON
run: |
python3 docs/json_parser.py \
--json-path "$JSON_FILE" \
--json-path "${{ needs.setup.outputs.json_file }}" \
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR"
- name: Push updated JSON to GitHub docs repo
@@ -221,6 +137,6 @@ jobs:
git config --global user.name "GitHub Action"
git config --global user.email "action@github.com"
git checkout gh-pages
git add docs/"$(basename $JSON_FILE)"
git commit -m "Update $(basename $JSON_FILE) after recompiling models" || echo "No changes to commit"
git add docs/"$(basename ${{ needs.setup.outputs.json_file }})"
git commit -m "Create $(basename ${{ needs.setup.outputs.json_file }}) after recompiling models" || echo "No changes to commit"
git push origin gh-pages

View File

@@ -3,6 +3,14 @@ name: Build Single Tinygrad Model and Push
on:
workflow_dispatch:
inputs:
runner_type:
description: 'Runner type'
required: false
default: '[self-hosted, james-mac]'
type: choice
options:
- ubuntu-latest
- '[self-hosted, james-mac]'
build_model_ref:
description: 'Branch to use for build-model workflow'
required: false
@@ -50,7 +58,7 @@ on:
jobs:
build-single:
runs-on: ubuntu-latest
runs-on: ${{ github.event.inputs.runner_type }}
env:
RECOMPILED_DIR: recompiled${{ github.event.inputs.recompiled_dir }}
JSON_FILE: docs/docs/driving_models_v${{ github.event.inputs.json_version }}.json

View File

@@ -9,6 +9,22 @@ env:
MODELS_DIR: ${{ github.workspace }}/selfdrive/modeld/models
on:
workflow_call:
inputs:
upstream_branch:
description: 'Upstream branch to build from'
required: true
default: 'master'
type: string
custom_name:
description: 'Custom name for the model (no date, only name)'
required: false
type: string
is_20hz:
description: 'Is this a 20Hz model'
required: false
type: boolean
default: true
workflow_dispatch:
inputs:
upstream_branch:
@@ -32,34 +48,53 @@ run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from
jobs:
get_model:
runs-on: ubuntu-latest
env:
REF: ${{ inputs.upstream_branch }}
outputs:
model_date: ${{ steps.commit-date.outputs.model_date }}
steps:
- uses: actions/checkout@v4
# Note: To allow dynamic models from both openpilot and sunnypilot (merges/mashups), we try commaai as default,
# and fallback to sunnypilot if the ref checkout fails.
- name: Checkout commaai/openpilot
id: checkout_upstream
continue-on-error: true
uses: actions/checkout@v4
with:
repository: ${{ env.UPSTREAM_REPO }}
ref: ${{ github.event.inputs.upstream_branch }}
repository: commaai/openpilot
ref: ${{ inputs.upstream_branch }}
submodules: recursive
path: openpilot
- name: Fallback to sunnypilot/sunnypilot
if: steps.checkout_upstream.outcome == 'failure'
uses: actions/checkout@v4
with:
repository: sunnypilot/sunnypilot
ref: ${{ inputs.upstream_branch }}
submodules: recursive
path: openpilot
- name: Get commit date
id: commit-date
run: |
# Get the commit date in YYYY-MM-DD format
cd ${{ github.workspace }}/openpilot
commit_date=$(git log -1 --format=%cd --date=format:'%B %d, %Y')
echo "model_date=${commit_date}" >> $GITHUB_OUTPUT
cat $GITHUB_OUTPUT
- run: git lfs pull
- run: |
cd ${{ github.workspace }}/openpilot
git lfs pull
- name: 'Upload Artifact'
uses: actions/upload-artifact@v4
with:
name: models
path: ${{ github.workspace }}/selfdrive/modeld/models/*.onnx
name: models-${{ env.REF }}
path: ${{ github.workspace }}/openpilot/selfdrive/modeld/models/*.onnx
build_model:
runs-on: self-hosted
needs: get_model
env:
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
REF: ${{ inputs.upstream_branch }}
steps:
- uses: actions/checkout@v4
with:
@@ -71,7 +106,7 @@ jobs:
with:
path: ${{env.SCONS_CACHE_DIR}}
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model-${{ github.sha }}
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
restore-keys: |
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model
@@ -114,7 +149,7 @@ jobs:
- name: Download model artifacts
uses: actions/download-artifact@v4
with:
name: models
name: models-${{ env.REF }}
path: ${{ github.workspace }}/selfdrive/modeld/models
- name: Build Model

View File

@@ -2,7 +2,7 @@
<tool name="uv Scons Build Debug" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec>
<option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) --compile_db --ccflags=\&quot;-fno-inline\&quot;&quot;" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) --ccflags=\&quot;-fno-inline\&quot;&quot;" />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
@@ -16,7 +16,7 @@
<tool name="uv Scons Build Release" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec>
<option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) --compile_db&quot; " />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc)&quot; " />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>

View File

@@ -5,8 +5,8 @@
inline static std::unordered_map<std::string, uint32_t> keys = {
{"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG},
{"AdbEnabled", PERSISTENT},
{"AlwaysOnDM", PERSISTENT},
{"AdbEnabled", PERSISTENT | BACKUP},
{"AlwaysOnDM", PERSISTENT | BACKUP},
{"ApiCache_Device", PERSISTENT},
{"ApiCache_FirehoseStats", PERSISTENT},
{"AssistNowToken", PERSISTENT},
@@ -78,7 +78,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"LocationFilterInitialState", PERSISTENT},
{"LongitudinalManeuverMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"LongitudinalPersonality", PERSISTENT | BACKUP},
{"NetworkMetered", PERSISTENT},
{"NetworkMetered", PERSISTENT | BACKUP},
{"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"Offroad_BadNvme", CLEAR_ON_MANAGER_START},
@@ -124,25 +124,29 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
// --- sunnypilot params --- //
{"ApiCache_DriveStats", PERSISTENT},
{"AutoLaneChangeBsmDelay", PERSISTENT},
{"AutoLaneChangeTimer", PERSISTENT},
{"AutoLaneChangeBsmDelay", PERSISTENT | BACKUP},
{"AutoLaneChangeTimer", PERSISTENT | BACKUP},
{"BlinkerMinLateralControlSpeed", PERSISTENT | BACKUP},
{"BlinkerPauseLateralControl", PERSISTENT | BACKUP},
{"Brightness", PERSISTENT | BACKUP},
{"CarParamsSP", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"CarParamsSPCache", CLEAR_ON_MANAGER_START},
{"CarParamsSPPersistent", PERSISTENT},
{"CarPlatformBundle", PERSISTENT},
{"CarPlatformBundle", PERSISTENT | BACKUP},
{"ChevronInfo", PERSISTENT | BACKUP},
{"CustomAccIncrementsEnabled", PERSISTENT | BACKUP},
{"CustomAccLongPressIncrement", PERSISTENT | BACKUP},
{"CustomAccShortPressIncrement", PERSISTENT | BACKUP},
{"DeviceBootMode", PERSISTENT | BACKUP},
{"EnableGithubRunner", PERSISTENT | BACKUP},
{"InteractivityTimeout", PERSISTENT | BACKUP},
{"IsDevelopmentBranch", CLEAR_ON_MANAGER_START},
{"MaxTimeOffroad", PERSISTENT | BACKUP},
{"Brightness", PERSISTENT | BACKUP},
{"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION},
{"OffroadMode", CLEAR_ON_MANAGER_START},
{"QuickBootToggle", PERSISTENT | BACKUP},
{"QuietMode", PERSISTENT | BACKUP},
{"ShowAdvancedControls", PERSISTENT | BACKUP},
// MADS params
{"Mads", PERSISTENT | BACKUP},
@@ -151,8 +155,8 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"MadsUnifiedEngagementMode", PERSISTENT | BACKUP},
// Model Manager params
{"DynamicModeldOutputs", PERSISTENT | BACKUP},
{"ModelManager_ActiveBundle", PERSISTENT},
{"ModelManager_ClearCache", CLEAR_ON_MANAGER_START},
{"ModelManager_DownloadIndex", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"ModelManager_LastSyncTime", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"ModelManager_ModelsCache", PERSISTENT | BACKUP},
@@ -174,9 +178,9 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"BackupManager_RestoreVersion", PERSISTENT},
// sunnypilot car specific params
{"HyundaiLongitudinalTuning", PERSISTENT},
{"HyundaiLongitudinalTuning", PERSISTENT | BACKUP},
{"DynamicExperimentalControl", PERSISTENT},
{"DynamicExperimentalControl", PERSISTENT | BACKUP},
{"BlindSpot", PERSISTENT | BACKUP},
// model panel params

View File

@@ -13,17 +13,32 @@ def create_short_name(full_name):
words = [re.sub(r'[^a-zA-Z0-9]', '', word) for word in clean_name.split() if re.sub(r'[^a-zA-Z0-9]', '', word)]
if len(words) == 1:
# If there's only one word, return it as is, lowercased, truncated to 8 characters
truncated = words[0][:8]
return truncated.lower() if truncated.isupper() else truncated
return words[0][:8].upper()
# Handle special case: Name + Version (e.g., "Word A1" -> "WordA1")
if len(words) == 2 and re.match(r'^[A-Za-z]\d+$', words[1]):
first_word = words[0].lower() if words[0].isupper() else words[0]
return (first_word + words[1])[:8]
return (words[0] + words[1])[:8].upper()
# Normal case: first letter and trailing numbers from each word
result = ''.join(word if word.isdigit() else word[0] + (re.search(r'\d+$', word) or [''])[0] for word in words)
result = ""
for word in words:
# Version or number patterns
if (re.match(r'^\d+[a-zA-Z]+$', word) or
re.match(r'^\d+[vVbB]\d+$', word) or
re.match(r'^[vVbB]\d+$', word) or
re.match(r'^\d{4}$', word)):
result += word.upper()
# All uppercase abbreviations (2-3 letters)
elif re.match(r'^[A-Z]{2,3}$', word):
result += word
# Letters+digits (for example tr15 rev2)
elif re.match(r'^[a-zA-Z]+[0-9]+$', word):
result += word[0].upper() + ''.join(re.findall(r'\d+', word))
elif word.isalpha():
result += word[0].upper()
elif word.isdigit():
result += word
else:
result += word[0].upper()
return result[:8]
@@ -58,14 +73,14 @@ def generate_metadata(model_path: Path, output_dir: Path, short_name: str):
"artifact": {
"file_name": tinygrad_file.name,
"download_uri": {
"url": "https://gitlab.com/sunnypilot/public/docs.sunnypilot.ai/-/raw/main/<FILLME>",
"url": "https://gitlab.com/sunnypilot/public/docs.sunnypilot.ai/-/raw/main/",
"sha256": tinygrad_hash
}
},
"metadata": {
"file_name": metadata_file.name,
"download_uri": {
"url": "https://gitlab.com/sunnypilot/public/docs.sunnypilot.ai/-/raw/main/<FILLME>",
"url": "https://gitlab.com/sunnypilot/public/docs.sunnypilot.ai/-/raw/main/",
"sha256": metadata_hash
}
}
@@ -83,12 +98,12 @@ def create_metadata_json(models: list, output_dir: Path, custom_name=None, short
"ref": upstream_branch,
"environment": "development",
"runner": "tinygrad",
"build_time": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
"models": models,
"overrides": {},
"index": -1,
"minimum_selector_version": "-1",
"generation": "-1",
"build_time": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
"overrides": {},
"models": models,
}
# Write metadata to output_dir

View File

@@ -90,7 +90,7 @@ class ModelState:
prev_desire: np.ndarray # for tracking the rising edge of the pulse
def __init__(self, context: CLContext):
self.LAT_SMOOTH_SECONDS = 0.0
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
with open(VISION_METADATA_PATH, 'rb') as f:
vision_metadata = pickle.load(f)
self.vision_input_shapes = vision_metadata['input_shapes']

View File

@@ -45,17 +45,6 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
});
addItem(experimentalLongitudinalToggle);
enableGithubRunner = new ParamControl("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "");
addItem(enableGithubRunner);
// error log button
errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
connect(errorLogBtn, &ButtonControl::clicked, [=]() {
std::string txt = util::read_file("/data/community/crashes/error.log");
ConfirmationDialog::rich(QString::fromStdString(txt), this);
});
addItem(errorLogBtn);
// Joystick and longitudinal maneuvers should be hidden on release branches
is_release = params.getBool("IsReleaseBranch");
@@ -104,8 +93,6 @@ void DeveloperPanel::updateToggles(bool _offroad) {
experimentalLongitudinalToggle->refresh();
// Handle specific controls visibility for release branches
enableGithubRunner->setVisible(!is_release);
errorLogBtn->setVisible(!is_release);
joystickToggle->setVisible(!is_release);
offroad = _offroad;

View File

@@ -16,10 +16,8 @@ private:
Params params;
ParamControl* adbToggle;
ParamControl* joystickToggle;
ButtonControl* errorLogBtn;
ParamControl* longManeuverToggle;
ParamControl* experimentalLongitudinalToggle;
ParamControl* enableGithubRunner;
bool is_release;
bool offroad = false;

View File

@@ -34,6 +34,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
}
}
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
painter.restore();
}
@@ -173,6 +174,173 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
(1 - t) * start.alphaF() + t * end.alphaF());
}
void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) {
auto *s = uiState();
auto &sm = *(s->sm);
if (!sm.alive("radarState")) return;
const auto &radar_state = sm["radarState"].getRadarState();
const auto &lead_one = radar_state.getLeadOne();
const auto &lead_two = radar_state.getLeadTwo();
// Check if we have any active leads
bool has_lead_one = lead_one.getStatus();
bool has_lead_two = lead_two.getStatus();
if (!has_lead_one && !has_lead_two) {
// Fade out status display
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
if (lead_status_alpha <= 0.0f) return;
} else {
// Fade in status display
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
}
// Draw status for each lead vehicle under its chevron
if (true) {
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
}
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
}
}
void ModelRenderer::drawLeadStatusAtPosition(QPainter &painter,
const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos,
int height, int width,
const QString &label) {
float d_rel = lead_data.getDRel();
float v_rel = lead_data.getVRel();
auto *s = uiState();
auto &sm = *(s->sm);
float v_ego = sm["carState"].getCarState().getVEgo();
int chevron_data = std::atoi(Params().get("ChevronInfo").c_str());
// Calculate chevron size (same logic as drawLead)
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
QFont content_font = painter.font();
content_font.setPixelSize(35);
content_font.setBold(true);
painter.setFont(content_font);
QFontMetrics fm(content_font);
bool is_metric = s->scene.is_metric;
QStringList text_lines;
const int chevron_types = 3;
const int chevron_all = chevron_types + 1; // All metrics (value 4)
QStringList chevron_text[chevron_types];
int position;
float val;
// Distance display (chevron_data == 1 or all)
if (chevron_data == 1 || chevron_data == chevron_all) {
position = 0;
val = std::max(0.0f, d_rel);
QString distance_unit = is_metric ? "m" : "ft";
if (!is_metric) {
val *= 3.28084f; // Convert meters to feet
}
chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit);
}
// Absolute velocity display (chevron_data == 2 or all)
if (chevron_data == 2 || chevron_data == chevron_all) {
position = (chevron_data == 2) ? 0 : 1;
val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH)));
chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph"));
}
// Time-to-contact display (chevron_data == 3 or all)
if (chevron_data == 3 || chevron_data == chevron_all) {
position = (chevron_data == 3) ? 0 : 2;
val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
chevron_text[position].append(ttc_str);
}
// Collect all non-empty text lines
for (int i = 0; i < chevron_types; ++i) {
if (!chevron_text[i].isEmpty()) {
text_lines.append(chevron_text[i]);
}
}
// If no text to display, return early
if (text_lines.isEmpty()) {
return;
}
// Text box dimensions
float str_w = 150; // Width of text area
float str_h = 45; // Height per line
// Position text below chevron, centered horizontally
float text_x = chevron_pos.x() - str_w / 2;
float text_y = chevron_pos.y() + sz + 15;
// Clamp to screen bounds
text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10);
// Shadow offset
QPoint shadow_offset(2, 2);
// Draw each line of text with shadow
for (int i = 0; i < text_lines.size(); ++i) {
if (!text_lines[i].isEmpty()) {
QRect textRect(text_x, text_y + (i * str_h), str_w, str_h);
// Draw shadow
painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha)));
painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()),
Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
// Determine text color based on content and danger level
QColor text_color;
// Check if this is a distance line (contains 'm' or 'ft')
if (text_lines[i].contains("m") || text_lines[i].contains("ft")) {
if (d_rel < 20.0f) {
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger
} else if (d_rel < 40.0f) {
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
} else {
text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe
}
}
// Enhanced color coding for time-to-contact
else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) {
float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat();
if (ttc_val < 3.0f) {
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent
} else if (ttc_val < 6.0f) {
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
} else {
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe
}
}
else {
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines
}
// Draw main text
painter.setPen(text_color);
painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
}
}
// Reset pen
painter.setPen(Qt::NoPen);
}
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &vd, const QRect &surface_rect) {
const float speedBuff = 10.;

View File

@@ -34,6 +34,12 @@ protected:
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out);
void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
QPolygonF *pvd, int max_idx, bool allow_invert = true);
void drawLeadStatus(QPainter &painter, int height, int width);
void drawLeadStatusAtPosition(QPainter &painter,
const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &chevron_pos,
int height, int width,
const QString &label);
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect);
void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line);
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
@@ -58,4 +64,9 @@ protected:
QPointF lead_vertices[2] = {};
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
QRectF clip_region;
float lead_status_alpha = 0.0f;
QPointF lead_status_pos;
QString lead_status_text;
QColor lead_status_color;
};

View File

@@ -2,6 +2,7 @@
#include <QPushButton>
#include <QButtonGroup>
#include <QScroller>
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h"
@@ -334,3 +335,141 @@ QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStrin
}
return "";
}
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; }
#confirm_btn[enabled="false"] { background-color: #2B2B2B; }
#confirm_btn:enabled { background-color: #465BEA; }
#confirm_btn:enabled:pressed { background-color: #3049F4; }
QTreeWidget {
background-color: transparent;
border: none;
}
QTreeWidget::item {
height: 135;
padding: 0px 50px;
margin: 5px;
text-align: left;
font-size: 55px;
font-weight: 300;
border-radius: 10px;
background-color: #4F4F4F;
color: white;
}
QTreeWidget::item:selected {
background-color: #465BEA;
}
QTreeWidget::branch {
background-color: transparent;
}
)");
QVBoxLayout *main_layout = new QVBoxLayout(container);
main_layout->setContentsMargins(55, 50, 55, 50);
QLabel *title = new QLabel(prompt_text, this);
title->setStyleSheet("font-size: 70px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
main_layout->addSpacing(25);
treeWidget = new QTreeWidget(this);
treeWidget->setHeaderHidden(true);
treeWidget->setIndentation(50);
treeWidget->setExpandsOnDoubleClick(false); // Disable double-click expansion
treeWidget->setAnimated(true);
treeWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
treeWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
treeWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
treeWidget->setDragEnabled(false);
treeWidget->setMouseTracking(true);
// Connect single-click to expand/collapse
QObject::connect(treeWidget, &QTreeWidget::itemClicked, [=](QTreeWidgetItem *item, int) {
if (item->childCount() > 0) {
item->setExpanded(!item->isExpanded());
treeWidget->scrollToItem(item->child(0), QAbstractItemView::EnsureVisible);
}
});
QScroller::grabGesture(treeWidget->viewport(), QScroller::LeftMouseButtonGesture);
// Populate tree
QListIterator<QPair<QString, QStringList>> iter(items);
while (iter.hasNext()) {
QPair currItem = iter.next();
if (currItem.first.isEmpty()) {
for (const QString &item : currItem.second) {
QTreeWidgetItem *topLevel = new QTreeWidgetItem();
topLevel->setText(0, item);
topLevel->setFlags(topLevel->flags() | Qt::ItemIsSelectable);
treeWidget->addTopLevelItem(topLevel);
if (item == current) {
topLevel->setSelected(true);
}
}
} else {
QTreeWidgetItem *folderItem = new QTreeWidgetItem(treeWidget);
folderItem->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
folderItem->setText(0, " " + currItem.first);
folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate);
folderItem->setFlags(folderItem->flags() & ~Qt::ItemIsSelectable);
for (const QString &item : currItem.second)
{
QTreeWidgetItem *childItem = new QTreeWidgetItem(folderItem);
childItem->setText(0, item);
childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable);
if (item == current) {
childItem->setSelected(true);
folderItem->setExpanded(true);
}
}
}
}
confirm_btn = new QPushButton(tr("Select"));
confirm_btn->setObjectName("confirm_btn");
confirm_btn->setEnabled(false);
QObject::connect(treeWidget, &QTreeWidget::itemSelectionChanged, [=]() {
QList<QTreeWidgetItem*> selectedItems = treeWidget->selectedItems();
if (!selectedItems.isEmpty()) {
selection = selectedItems.first()->text(0);
confirm_btn->setEnabled(selection != current);
}
});
ScrollView *scroll_view = new ScrollView(treeWidget, this);
scroll_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
main_layout->addWidget(scroll_view);
main_layout->addSpacing(35);
// cancel + confirm buttons
QHBoxLayout *blayout = new QHBoxLayout;
main_layout->addLayout(blayout);
blayout->setSpacing(50);
QPushButton *cancel_btn = new QPushButton(tr("Cancel"));
QObject::connect(cancel_btn, &QPushButton::clicked, this, &ConfirmationDialog::reject);
QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept);
blayout->addWidget(cancel_btn);
blayout->addWidget(confirm_btn);
QVBoxLayout *outer_layout = new QVBoxLayout(this);
outer_layout->setContentsMargins(50, 50, 50, 50);
outer_layout->addWidget(container);
}
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 "";
}

View File

@@ -6,6 +6,7 @@
#include <QString>
#include <QVBoxLayout>
#include <QWidget>
#include <QTreeWidget>
#include "selfdrive/ui/qt/widgets/keyboard.h"
@@ -69,3 +70,16 @@ public:
static QString getSelection(const QString &prompt_text, const QStringList &l, const QString &current, QWidget *parent);
QString selection;
};
class TreeOptionDialog : public DialogBase {
Q_OBJECT
public:
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;
};

View File

@@ -21,6 +21,7 @@ qt_src = [
"sunnypilot/qt/home.cc",
"sunnypilot/qt/offroad/exit_offroad_button.cc",
"sunnypilot/qt/offroad/offroad_home.cc",
"sunnypilot/qt/offroad/settings/developer_panel.cc",
"sunnypilot/qt/offroad/settings/device_panel.cc",
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc",

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) {
// 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);
QObject::connect(showAdvancedControls, &ParamControlSP::toggleFlipped, this, [=](bool) {
AbstractControlSP::UpdateAllAdvancedControls();
updateToggles(!uiState()->scene.started);
});
showAdvancedControls->showDescription();
// Github Runner Toggle
enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true);
addItem(enableGithubRunner);
// Quickboot Mode Toggle
prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true);
addItem(prebuiltToggle);
QObject::connect(prebuiltToggle, &ParamControl::toggleFlipped, [=](bool state) {
QString prebuiltPath = "/data/openpilot/prebuilt";
state ? QFile(prebuiltPath).open(QIODevice::WriteOnly) : QFile::remove(prebuiltPath);
prebuiltToggle->refresh();
});
prebuiltToggle->setVisible(false);
// Error log button
errorLogBtn = new ButtonControlSP(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
connect(errorLogBtn, &ButtonControlSP::clicked, [=]() {
QFileInfo file("/data/community/crashes/error.log");
QString text;
if (file.exists()) {
text = "<b>" + file.lastModified().toString("dd-MMM-yyyy hh:mm:ss ").toUpper() + "</b><br><br>";
}
text += QString::fromStdString(util::read_file("/data/community/crashes/error.log"));
ConfirmationDialog::rich(text, this);
});
addItem(errorLogBtn);
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
}
void DeveloperPanelSP::updateToggles(bool offroad) {
bool is_release = params.getBool("IsReleaseBranch");
bool is_tested = params.getBool("IsTestedBranch");
bool is_development = params.getBool("IsDevelopmentBranch");
bool disable_updates = params.getBool("DisableUpdates");
prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
prebuiltToggle->setEnabled(disable_updates);
params.putBool("QuickBootToggle", QFile::exists("/data/openpilot/prebuilt"));
prebuiltToggle->refresh();
prebuiltToggle->setDescription(disable_updates
? tr("When toggled on, this creates a prebuilt file to allow accelerated boot times. When toggled off, "
"it immediately removes the prebuilt file so compilation of locally edited cpp files can be made. "
"<br><br><b>To edit C++ files locally on device, you MUST first turn off this toggle so the changes can recompile.</b>")
: tr("Quickboot mode requires updates to be disabled.<br>Enable 'Disable Updates' in the Software panel first."));
enableGithubRunner->setVisible(!is_release);
errorLogBtn->setVisible(!is_release);
showAdvancedControls->setEnabled(true);
}
void DeveloperPanelSP::showEvent(QShowEvent *event) {
DeveloperPanel::showEvent(event);
updateToggles(!uiState()->scene.started);
AbstractControlSP::UpdateAllAdvancedControls();
prebuiltToggle->showDescription();
}

View File

@@ -0,0 +1,30 @@
/**
* 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 <QFile>
#include <QFileInfo>
#include "selfdrive/ui/qt/offroad/developer_panel.h"
class DeveloperPanelSP : public DeveloperPanel {
Q_OBJECT
public:
explicit DeveloperPanelSP(SettingsWindow *parent);
private:
ParamControlSP *enableGithubRunner;
ButtonControlSP *errorLogBtn;
ParamControlSP *prebuiltToggle;
Params params;
ParamControlSP *showAdvancedControls;
private slots:
void updateToggles(bool offroad);
protected:
void showEvent(QShowEvent *event) override;
};

View File

@@ -212,7 +212,7 @@ void DevicePanelSP::updateState() {
QString timeoutValue = QString::fromStdString(params.get("InteractivityTimeout"));
if (timeoutValue == "0") {
interactivityTimeout->setLabel("DEFAULT");
interactivityTimeout->setLabel("Default");
} else {
interactivityTimeout->setLabel(timeoutValue + "s");
}

View File

@@ -8,6 +8,8 @@
#include <algorithm>
#include <QJsonDocument>
#include <QStyle>
#include <QtConcurrent/QtConcurrent>
#include <QDir>
#include "common/model.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
@@ -66,6 +68,11 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels);
list->addItem(currentModelLblBtn);
clearModelCacheBtn = new ButtonControlSP(tr("Clear Model Cache"), tr("CLEAR"), "", this);
connect(clearModelCacheBtn, &ButtonControlSP::clicked, this, &ModelsPanel::clearModelCache);
list->addItem(clearModelCacheBtn);
// Create progress bars for downloads
supercomboProgressBar = createProgressBar(this);
QString supercomboType = tr("Driving Model");
@@ -89,14 +96,6 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
list->addItem(horizontal_line());
// Dynamic Modeld Outputs toggle
dynamicModeldOutputs = new ParamControlSP("DynamicModeldOutputs", tr("Allow Dynamic Model Outputs"),
tr("Enable this to allow potentially smoother Gas and Brake controls on all models produced "
"after September, 2024."),
"../assets/offroad/icon_shell.png");
dynamicModeldOutputs->showDescription();
list->addItem(dynamicModeldOutputs);
// LiveDelay toggle
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
lagd_toggle_control->showDescription();
@@ -106,7 +105,7 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
delay_control = new OptionControlSP("LagdToggledelay", tr("Adjust Software Delay"),
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
"\nThe default software delay value is 0.2"),
"", {10, 30}, 1, false, nullptr, true);
"", {5, 30}, 1, false, nullptr, true, true);
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat();
@@ -252,28 +251,73 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
currentModelLblBtn->setEnabled(false);
currentModelLblBtn->setValue(tr("Fetching models..."));
// Create mapping of bundle indices to display names
QMap<uint32_t, QString> index_to_bundle;
struct ModelEntry {
QString folder;
QString displayName;
int index;
};
QList<ModelEntry> sortedModels;
QSet<QString> modelFolders;
const auto bundles = model_manager.getAvailableBundles();
for (const auto &bundle: bundles) {
index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundle.getDisplayName()));
for (const auto &bundle : bundles) {
auto overrides = bundle.getOverrides();
QString gen;
for (const auto &override : overrides) {
if (override.getKey() == "folder") {
gen = QString::fromStdString(override.getValue().cStr());
}
}
modelFolders.insert(gen);
sortedModels.append(ModelEntry{
gen,
QString::fromStdString(bundle.getDisplayName()),
static_cast<int>(bundle.getIndex())
});
}
// Sort bundles by index in descending order
QStringList bundleNames;
// Add "Default" as the first option
bundleNames.append(DEFAULT_MODEL);
std::sort(sortedModels.begin(), sortedModels.end(),
[](const ModelEntry &a, const ModelEntry &b) {
return a.index > b.index;
});
auto indices = index_to_bundle.keys();
std::sort(indices.begin(), indices.end(), std::greater<uint32_t>());
for (const auto &index: indices) {
bundleNames.append(index_to_bundle[index]);
// Create a list of folder-maxIndex pairs for sorting
QList<QPair<QString, int>> folderMaxIndices;
for (const auto &folder : modelFolders) {
int maxIndex = -1;
for (const auto &model : sortedModels) {
if (model.folder == folder) {
maxIndex = std::max(maxIndex, model.index);
}
}
folderMaxIndices.append(qMakePair(folder, maxIndex));
}
// Sort folders by their highest model index
std::sort(folderMaxIndices.begin(), folderMaxIndices.end(),
[](const QPair<QString, int> &a, const QPair<QString, int> &b) {
return a.second > b.second;
});
// Create the final items list using sorted folders
QList<QPair<QString, QStringList>> items;
for (const auto &folderPair : folderMaxIndices) {
QStringList folderModels;
for (const auto &model : sortedModels) {
if (model.folder == folderPair.first) {
folderModels.append(model.displayName);
}
}
items.append(qMakePair(folderPair.first, folderModels));
}
items.insert(0, qMakePair(QString(""), QStringList{DEFAULT_MODEL}));
currentModelLblBtn->setValue(GetActiveModelInternalName());
const QString selectedBundleName = MultiOptionDialog::getSelection(
tr("Select a Model"), bundleNames, GetActiveModelName(), this);
const QString selectedBundleName = TreeOptionDialog::getSelection(
tr("Select a Model"), items, GetActiveModelName(), this);
if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) {
return;
@@ -312,7 +356,6 @@ void ModelsPanel::updateLabels() {
handleBundleDownloadProgress();
currentModelLblBtn->setEnabled(!is_onroad && !isDownloading());
currentModelLblBtn->setValue(GetActiveModelInternalName());
dynamicModeldOutputs->showDescription();
// Update lagdToggle description with current value
QString desc = tr("Enable this for the car to learn and adapt its steering response time. "
@@ -331,6 +374,8 @@ void ModelsPanel::updateLabels() {
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
delay_control->showDescription();
}
clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
}
/**
@@ -351,3 +396,32 @@ void ModelsPanel::showResetParamsDialog() {
params.remove("LiveTorqueParameters");
}
}
void ModelsPanel::clearModelCache() {
QString confirmMsg = tr("This will delete ALL downloaded models from the cache"
"<br/><u>except the currently active model</u>."
"<br/><br/>Are you sure you want to continue?");
QString content("<body><h2 style=\"text-align: center;\">" + tr("Driving Model Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + confirmMsg + "</p></body>");
if (showConfirmationDialog(
content,
tr("Clear Cache"))) {
params.putBool("ModelManager_ClearCache", true);
}
}
double ModelsPanel::calculateCacheSize() {
QFuture<qint64> future_ModelCacheSize = QtConcurrent::run([=]() {
QDir model_dir(QString::fromStdString(Path::model_root()));
QFileInfoList model_files = model_dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
qint64 totalSize = 0;
for (const QFileInfo &model_file : model_files) {
if (model_file.isFile()) {
totalSize += model_file.size();
}
}
return totalSize;
});
return static_cast<double>(future_ModelCacheSize) / (1024.0 * 1024.0);
}

View File

@@ -41,6 +41,8 @@ private:
cereal::ModelManagerSP::Reader model_manager;
cereal::ModelManagerSP::DownloadStatus download_status{};
cereal::ModelManagerSP::DownloadStatus prev_download_status{};
void clearModelCache();
double calculateCacheSize();
bool canContinueOnMeteredDialog() {
if (!is_metered) return true;
@@ -64,7 +66,6 @@ private:
bool is_onroad = false;
ButtonControlSP *currentModelLblBtn;
ParamControlSP *dynamicModeldOutputs;
ParamControlSP *lagd_toggle_control;
OptionControlSP *delay_control;
QProgressBar *supercomboProgressBar;
@@ -76,5 +77,6 @@ private:
QProgressBar *policyProgressBar;
QFrame *policyFrame;
Params params;
ButtonControlSP *clearModelCacheBtn;
};

View File

@@ -8,10 +8,10 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/offroad/developer_panel.h"
#include "selfdrive/ui/qt/offroad/firehose.h"
#include "selfdrive/ui/sunnypilot/qt/network/networking.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
@@ -91,7 +91,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"),
PanelInfo(" " + tr("Developer"), new DeveloperPanel(this), "../assets/icons/shell.png"),
PanelInfo(" " + tr("Developer"), new DeveloperPanelSP(this), "../assets/icons/shell.png"),
};
nav_btns = new QButtonGroup(this);

View File

@@ -18,6 +18,18 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
searchBranches(d.text());
}
});
// Disable Updates toggle
disableUpdatesToggle = new ParamControl("DisableUpdates",
tr("Disable Updates"),
tr("When enabled, software updates will be disabled. <b>This requires a reboot to take effect.</b>"),
"../assets/icons/icon_warning.png",
this, true);
disableUpdatesToggle->showDescription();
addItem(disableUpdatesToggle);
connect(disableUpdatesToggle, &ParamControl::toggleFlipped, this, &SoftwarePanelSP::handleDisableUpdatesToggled);
connect(uiState(), &UIState::offroadTransition, this, &SoftwarePanelSP::updateDisableUpdatesToggle);
updateDisableUpdatesToggle(!uiState()->scene.started);
}
/**
@@ -49,3 +61,27 @@ void SoftwarePanelSP::searchBranches(const QString &query) {
checkForUpdates();
}
}
void SoftwarePanelSP::handleDisableUpdatesToggled(bool state) {
if (ConfirmationDialog::confirm(tr("%1 updates requires a reboot.<br>Reboot now?")
.arg(state ? "Disabling" : "Enabling"), tr("Reboot"), this)) {
params.putBool("DoReboot", true);
} else {
params.putBool("DisableUpdates", !state);
disableUpdatesToggle->refresh();
}
}
void SoftwarePanelSP::updateDisableUpdatesToggle(bool offroad) {
bool enabled = offroad;
disableUpdatesToggle->setEnabled(enabled);
disableUpdatesToggle->setDescription(enabled
? tr("When enabled, software updates will be disabled.<br><b>This requires a reboot to take effect.</b>")
: tr("Please enable always offroad mode or turn off vehicle to adjust these toggles"));
}
void SoftwarePanelSP::showEvent(QShowEvent *event) {
SoftwarePanel::showEvent(event);
updateDisableUpdatesToggle(!uiState()->scene.started);
disableUpdatesToggle->showDescription();
}

View File

@@ -19,4 +19,10 @@ public:
private:
void searchBranches(const QString &query);
ParamControl *disableUpdatesToggle = nullptr;
void handleDisableUpdatesToggled(bool state);
private slots:
void updateDisableUpdatesToggle(bool offroad);
protected:
void showEvent(QShowEvent *event) override;
};

View File

@@ -12,7 +12,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
paramsRefresh();
});
main_layout = new QStackedLayout(this);
ListWidgetSP *list = new ListWidgetSP(this, false);
@@ -30,6 +30,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
},
};
// Add regular toggles first
for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) {
auto toggle = new ParamControlSP(param, title, desc, icon, this);
@@ -53,9 +54,20 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
param_watcher->addParam(param);
}
// Visuals: Display Metrics below Chevron
std::vector<QString> chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")};
chevron_info_settings = new ButtonParamControlSP(
"ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."),
"",
chevron_info_settings_texts,
200);
chevron_info_settings->showDescription();
list->addItem(chevron_info_settings);
param_watcher->addParam("ChevronInfo");
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen);
}
@@ -67,4 +79,8 @@ void VisualsPanel::paramsRefresh() {
for (auto toggle : toggles) {
toggle.second->refresh();
}
if (chevron_info_settings) {
chevron_info_settings->refresh();
}
}

View File

@@ -27,4 +27,5 @@ protected:
Params params;
std::map<std::string, ParamControlSP*> toggles;
ParamWatcher * param_watcher;
ButtonParamControlSP *chevron_info_settings;
};

View File

@@ -30,9 +30,24 @@ QFrame *vertical_space(int height, QWidget *parent) {
}
// AbstractControlSP
std::vector<AbstractControlSP*> AbstractControlSP::advanced_controls_;
AbstractControlSP::~AbstractControlSP() { UnregisterAdvancedControl(this); }
AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: AbstractControl(title, desc, icon, parent) {
void AbstractControlSP::RegisterAdvancedControl(AbstractControlSP *ctrl) { advanced_controls_.push_back(ctrl); }
void AbstractControlSP::UnregisterAdvancedControl(AbstractControlSP *ctrl) {
advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), ctrl), advanced_controls_.end());
}
void AbstractControlSP::UpdateAllAdvancedControls() {
bool visibility = Params().getBool("ShowAdvancedControls");
advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), nullptr), advanced_controls_.end());
for (auto *ctrl : advanced_controls_) ctrl->setVisible(visibility);
}
AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl)
: AbstractControl(title, desc, icon, parent), isAdvancedControl(advancedControl) {
if (isAdvancedControl) RegisterAdvancedControl(this);
main_layout = new QVBoxLayout(this);
main_layout->setMargin(0);
@@ -82,8 +97,8 @@ void AbstractControlSP::hideEvent(QHideEvent *e) {
}
}
AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: AbstractControlSP(title, desc, icon, parent) {
AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl)
: AbstractControlSP(title, desc, icon, parent, advancedControl) {
if (title_label != nullptr) {
delete title_label;
@@ -169,8 +184,8 @@ void AbstractControlSP_SELECTOR::hideEvent(QHideEvent *e) {
// controls
ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent)
: AbstractControlSP(title, desc, "", parent) {
ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent, bool advancedControl)
: AbstractControlSP(title, desc, "", parent, advancedControl) {
btn.setText(text);
btn.setStyleSheet(R"(
@@ -225,8 +240,8 @@ void ElidedLabelSP::paintEvent(QPaintEvent *event) {
// ParamControlSP
ParamControlSP::ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: ToggleControlSP(title, desc, icon, false, parent) {
ParamControlSP::ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl)
: ToggleControlSP(title, desc, icon, false, parent, advancedControl){
key = param.toStdString();
QObject::connect(this, &ParamControlSP::toggleFlipped, this, &ParamControlSP::toggleClicked);

View File

@@ -57,6 +57,7 @@ class AbstractControlSP : public AbstractControl {
Q_OBJECT
public:
~AbstractControlSP();
void setDescription(const QString &desc) override {
if (description) description->setText(desc);
}
@@ -81,13 +82,30 @@ public slots:
description->setVisible(true);
}
void setVisible(bool visible) override {
bool _visible = visible;
if (isAdvancedControl && !params.getBool("ShowAdvancedControls")) {
_visible = false;
}
AbstractControl::setVisible(_visible);
}
static void RegisterAdvancedControl(AbstractControlSP *ctrl);
static void UnregisterAdvancedControl(AbstractControlSP *ctrl);
static void UpdateAllAdvancedControls();
protected:
AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false);
void hideEvent(QHideEvent *e) override;
QVBoxLayout *main_layout;
ElidedLabelSP *value;
QLabel *description = nullptr;
bool isAdvancedControl;
private:
Params params;
static std::vector<AbstractControlSP*> advanced_controls_;
};
// AbstractControlSP_SELECTOR
@@ -97,7 +115,7 @@ class AbstractControlSP_SELECTOR : public AbstractControlSP {
protected:
QSpacerItem *spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed);
AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false);
void hideEvent(QHideEvent *e) override;
};
@@ -123,7 +141,7 @@ class ButtonControlSP : public AbstractControlSP {
Q_OBJECT
public:
ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr);
ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr, bool advancedControl = false);
inline void setText(const QString &text) { btn.setText(text); }
inline QString text() const { return btn.text(); }
inline void click() { btn.click(); }
@@ -142,7 +160,7 @@ class ToggleControlSP : public AbstractControlSP {
Q_OBJECT
public:
ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP(title, desc, icon, parent) {
ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr, bool advancedControl = false) : AbstractControlSP(title, desc, icon, parent, advancedControl) {
// space between toggle and title
icon_label = new QLabel(this);
hlayout->addWidget(icon_label);
@@ -173,7 +191,7 @@ class ParamControlSP : public ToggleControlSP {
Q_OBJECT
public:
ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr, bool advancedControl = false);
void setConfirmation(bool _confirm, bool _store_confirm) {
confirm = _confirm;
store_confirm = _store_confirm;
@@ -219,7 +237,7 @@ class MultiButtonControlSP : public AbstractControlSP_SELECTOR {
public:
MultiButtonControlSP(const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) {
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), button_texts(button_texts), is_inline_layout(inline_layout) {
const QString style = R"(
QPushButton {
border-radius: 20px;
@@ -371,8 +389,8 @@ class ButtonParamControlSP : public MultiButtonControlSP {
Q_OBJECT
public:
ButtonParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : MultiButtonControlSP(title, desc, icon,
button_texts, minimum_button_width, inline_layout) {
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon,
button_texts, minimum_button_width, inline_layout, advancedControl) {
key = param.toStdString();
int value = atoi(params.get(key).c_str());
@@ -499,7 +517,7 @@ private:
public:
OptionControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon,
const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false,
const QMap<QString, QString> *valMap = nullptr, bool scale_float = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) {
const QMap<QString, QString> *valMap = nullptr, bool scale_float = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) {
const QString style = R"(
QPushButton {
border-radius: 20px;

View File

@@ -103,7 +103,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
# temporal pose
temporal_pose = modelV2.temporalPose
temporal_pose = modelV2.temporalPoseDEPRECATED
temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist()
temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist()
temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist()

View File

@@ -100,7 +100,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
# temporal pose
temporal_pose = modelV2.temporalPose
temporal_pose = modelV2.temporalPoseDEPRECATED
if 'sim_pose' in net_output_data:
temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()

View File

@@ -1,5 +1,4 @@
import numpy as np
from openpilot.common.params import Params
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
from openpilot.sunnypilot.models.helpers import get_active_bundle
@@ -26,7 +25,6 @@ def softmax(x, axis=-1):
class Parser:
def __init__(self, ignore_missing=False):
self.ignore_missing = ignore_missing
self._params = Params()
model_bundle = get_active_bundle()
self.generation = model_bundle.generation if model_bundle is not None else None
@@ -93,29 +91,28 @@ class Parser:
outs[name] = pred_mu_final.reshape(final_shape)
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
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 self._params.get_bool("DynamicModeldOutputs") or (self.generation >= 12):
if 'lead' in outs:
if 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:
if outs['plan'].shape[1] > 2 * SplitModelConstants.PLAN_WIDTH * SplitModelConstants.IDX_N:
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))
else:
self.parse_mdn('plan', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
else:
if 'lead' in outs:
if 'lead' in outs:
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:
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))
out_shape=(SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH))
if 'plan' in outs:
if self.generation >= 12 and \
outs['plan'].shape[1] > 2 * SplitModelConstants.PLAN_WIDTH * SplitModelConstants.IDX_N:
self._parse_plan_mhp(outs)
elif self.generation >= 12:
self.parse_mdn('plan', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
else:
self._parse_plan_mhp(outs)
def split_outputs(self, outs: dict[str, np.ndarray]) -> None:
if 'desired_curvature' in outs:

View File

@@ -115,7 +115,7 @@ class ModelCache:
class ModelFetcher:
"""Handles fetching and caching of model data from remote source"""
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v4.json"
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v5.json"
def __init__(self, params: Params):
self.params = params

View File

@@ -19,8 +19,8 @@ from openpilot.system.hardware import PC
from openpilot.system.hardware.hw import Paths
from pathlib import Path
CURRENT_SELECTOR_VERSION = 7
REQUIRED_MIN_SELECTOR_VERSION = 5
CURRENT_SELECTOR_VERSION = 1
REQUIRED_MIN_SELECTOR_VERSION = 1
USE_ONNX = os.getenv('USE_ONNX', PC)

View File

@@ -178,6 +178,10 @@ class ModelManagerSP:
finally:
self.params.put("ModelManager_DownloadIndex", "")
if self.params.get("ModelManager_ClearCache", block=False, encoding="utf-8"):
self.clear_model_cache()
self.params.remove("ModelManager_ClearCache")
self._report_status()
rk.keep_time()
@@ -185,6 +189,31 @@ class ModelManagerSP:
cloudlog.exception(f"Error in main thread: {str(e)}")
rk.keep_time()
def clear_model_cache(self) -> None:
"""
Clears the model cache directory of all files except those in the active model bundle.
"""
# Get list of files used by active model bundle
active_files = []
if self.active_bundle is not None: # When the default model is active
for model in self.active_bundle.models:
if hasattr(model, 'artifact') and model.artifact.fileName:
active_files.append(model.artifact.fileName)
if hasattr(model, 'metadata') and model.metadata.fileName:
active_files.append(model.metadata.fileName)
# Remove all files except active ones
model_dir = Paths.model_root()
try:
for filename in os.listdir(model_dir):
if filename not in active_files:
file_path = os.path.join(model_dir, filename)
if os.path.isfile(file_path):
os.remove(file_path)
cloudlog.info("Model cache cleared, keeping active model files")
except Exception as e:
cloudlog.exception(f"Error clearing model cache: {str(e)}")
def main():
ModelManagerSP().main_thread()

View File

@@ -16,12 +16,12 @@ DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimen
class LongitudinalPlannerSP:
def __init__(self, CP: structs.CarParams, mpc):
self.dec = DynamicExperimentalController(CP, mpc)
model_bundle = get_active_bundle()
self.generation = model_bundle.generation if model_bundle is not None else None
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
@property
def mlsim(self) -> bool:
return bool(self.generation is not None and self.generation >= 11)
# If we don't have a generation set, we assume it's default model. Which as of today are mlsim.
return bool(self.generation is None or self.generation >= 11)
def get_mpc_mode(self) -> str | None:
if not self.dec.active():

View File

@@ -55,4 +55,8 @@ namespace Path {
return "/dev/shm";
#endif
}
inline std::string model_root() {
return Hardware::PC() ? Path::comma_home() + "/media/0/models" : "/data/media/0/models";
}
} // namespace Path

View File

@@ -50,12 +50,14 @@ def manager_init() -> None:
("BlindSpot", "0"),
("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h
("BlinkerPauseLateralControl", "0"),
("Brightness", "0"),
("ChevronInfo", "4"),
("CustomAccIncrementsEnabled", "0"),
("CustomAccLongPressIncrement", "5"),
("CustomAccShortPressIncrement", "1"),
("DeviceBootMode", "0"),
("DisableUpdates", "0"),
("DynamicExperimentalControl", "0"),
("DynamicModeldOutputs", "0"),
("HyundaiLongitudinalTuning", "0"),
("InteractivityTimeout", "0"),
("LagdToggle", "1"),
@@ -66,11 +68,12 @@ def manager_init() -> None:
("MadsUnifiedEngagementMode", "1"),
("MapdVersion", f"{VERSION}"),
("MaxTimeOffroad", "1800"),
("Brightness", "0"),
("ModelManager_LastSyncTime", "0"),
("ModelManager_ModelsCache", ""),
("NeuralNetworkLateralControl", "0"),
("QuickBootToggle", "0"),
("QuietMode", "0"),
("ShowAdvancedControls", "0" if build_metadata.tested_channel else "1"),
]
# device boot mode
@@ -102,6 +105,7 @@ def manager_init() -> None:
params.put("GitCommitDate", build_metadata.openpilot.git_commit_date)
params.put("GitBranch", build_metadata.channel)
params.put("GitRemote", build_metadata.openpilot.git_origin)
params.put_bool("IsDevelopmentBranch", build_metadata.development_channel)
params.put_bool("IsTestedBranch", build_metadata.tested_channel)
params.put_bool("IsReleaseBranch", build_metadata.release_channel)
params.put("HardwareSerial", serial)

View File

@@ -117,9 +117,13 @@ class BuildMetadata:
def master_channel(self) -> bool:
return self.channel in MASTER_SP_BRANCHES
@property
def development_channel(self) -> bool:
return self.channel.startswith("dev-") or self.channel.endswith("-prebuilt")
@property
def channel_type(self) -> str:
if self.channel.startswith("dev-"):
if self.development_channel:
return "development"
elif self.channel.startswith("staging-"):
return "staging"