Compare commits

...

9 Commits

Author SHA1 Message Date
Jason Wen
737a6c4236 [TICI] ci: point to master-tici for certain submodules (#1350)
* ci: point to master-tici for certain submodules

* new

* new

* minimal

* check

* revert

* try this out

* more
2025-10-09 20:33:28 -04:00
Jason Wen
86cd1b238d [TICI] relock after inplace metadrive update (#1321) (#1348)
relock after inplace metadrive update (#1321)

relock after inplace metadrive update (#36256)

* relock after inplace metadrive update

* Revert "relock after inplace metadrive update"

This reverts commit 18193ffe34b66085e18605e6c9289ddcd658844d.

* just the hash

(cherry picked from commit 4d53a26a06)


(cherry picked from commit cca3be3a96)

Co-authored-by: DevTekVE <devtekve@gmail.com>
Co-authored-by: Armand du Parc Locmaria <adpl33@gmail.com>
2025-10-09 19:11:59 -04:00
DevTekVE
772e62127e (TICI) bugfix: param store to support the latest param changes (#1246)
* bugfix: update parameter handling to use custom CarControlSP.Param and improve param retrieval

* bump opendbc

* bump opendbc
2025-09-14 20:29:26 +02:00
DevTekVE
e7a3ea1b32 (TICI) sync master and master-tici (#1245)
* SL: bugfix parameter handling in sunnylink restore and remote setting (#1234)

* refactor: improve parameter handling in sunnylink for robustness

- Updated `get_param_as_byte` to return `None` for nonexistent parameters.
- Enhanced param compression and encoding in `sunnylinkd`.

* refactor: centralize parameter restoration with new helper function

- Added `save_param_from_base64_encoded_string` to handle param decoding and saving.
- Updated backup manager and sunnylinkd to use the new method.
- Improved code readability and reduced duplication in parameter handling logic.

* don't bother

* clean

(cherry picked from commit b7f8dd11a5)

* UI: Developer UI (#1233)

(cherry picked from commit 1bb4ca2547)

* Revert & Reapply "UI: Developer UI" temporarily due to QT version mismatch (#1237)

* Revert "UI: Developer UI (#1233)"

This reverts commit 1bb4ca2547.

* Reapply "UI: Developer UI (#1233)"

This reverts commit b0a77049da.

* QColorConstants is not on device's QT version. Thanks @kumar for the fix

(cherry picked from commit 810a2d9448)

* Revert "UI: Developer UI" (#1238)

* Revert "Revert & Reapply "UI: Developer UI" temporarily due to QT version mismatch (#1237)"

This reverts commit 810a2d9448.

* Revert "UI: Developer UI (#1233)"

This reverts commit 1bb4ca2547.

(cherry picked from commit 1be13fdc55)

* Reapply "UI: Developer UI" (#1238) (#1239)

This reverts commit 1be13fdc55.

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
(cherry picked from commit 4f44d6e643)

---------

Co-authored-by: Nayan <nayan8teen@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-09-14 12:18:59 +02:00
Jason Wen
87af129090 tici: fix staging root updates (#1224) 2025-09-06 18:46:19 -04:00
Jason Wen
f6cf1336ef update: actually detect device type as TICI (#1222) 2025-09-06 18:08:26 -04:00
Jason Wen
51046dd7fd ci: restore cache based on master-tici 2025-09-06 17:43:36 -04:00
Jason Wen
2c85f29a0b staging-c3-new > staging-tici 2025-09-06 17:38:11 -04:00
Jason Wen
68af0c4a17 ci: update for master-tici (#1220)
* ci: master > master-ci

* disable reset and squash for master-tici

* disable release drafter

* docker img

* temp builds

* actual temp

* new ref

* Revert "actual temp"

This reverts commit 2e73befd17.

* wrong name

* Revert "wrong name"

This reverts commit 5efa687aaf.

* Reapply "actual temp"

This reverts commit 75a9d986fd.

* Revert "Reapply "actual temp""

This reverts commit 1f77d902d4.

* Reapply "wrong name"

This reverts commit ed24def13c.

* revert
2025-09-06 17:28:43 -04:00
49 changed files with 764 additions and 139 deletions

View File

@@ -29,11 +29,11 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
target: /^(?!master$).*/
target: /^(?!master-tici$).*/
exclude: /sunnypilot:.*/
change-to: ${{ github.base_ref }}
already-exists-action: close_this
already-exists-comment: "Your PR should be made against the `master` branch"
already-exists-comment: "Your PR should be made against the `master-tici` branch"
update-pr-labels:
name: Update fork's PR Labels

View File

@@ -5,7 +5,7 @@ on:
workflow_dispatch:
env:
BASE_IMAGE: sunnypilot-base
BASE_IMAGE: sunnypilot-tici-base
DOCKER_REGISTRY: ghcr.io/sunnypilot
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -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 $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/bash -c

View File

@@ -3,7 +3,7 @@ name: cereal validation
on:
push:
branches:
- master
- master-tici
pull_request:
paths:
- 'cereal/**'
@@ -16,7 +16,7 @@ on:
type: string
concurrency:
group: cereal-validation-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
group: cereal-validation-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master-tici' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
env:

View File

@@ -31,7 +31,7 @@ jobs:
strategy:
fail-fast: false
matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}}
uses: sunnypilot/sunnypilot/.github/workflows/ci_weekly_run.yaml@master
uses: sunnypilot/sunnypilot/.github/workflows/ci_weekly_run.yaml@master-tici
with:
run_number: ${{ matrix.run_number }}

View File

@@ -12,6 +12,6 @@ concurrency:
jobs:
selfdrive_tests:
uses: sunnypilot/sunnypilot/.github/workflows/selfdrive_tests.yaml@master
uses: sunnypilot/sunnypilot/.github/workflows/selfdrive_tests.yaml@master-tici
with:
run_number: ${{ inputs.run_number }}

View File

@@ -15,7 +15,7 @@ runs:
scons -j$(nproc) --cache-populate"
- name: Save scons cache
uses: actions/cache/save@v4
if: github.ref == 'refs/heads/master'
if: github.ref == 'refs/heads/master-tici'
with:
path: .ci_cache/scons_cache
key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}

View File

@@ -3,7 +3,7 @@ name: docs
on:
push:
branches:
- master
- master-tici
pull_request:
workflow_call:
inputs:
@@ -12,7 +12,7 @@ on:
required: true
type: string
concurrency:
group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master-tici' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
jobs:
@@ -35,13 +35,13 @@ jobs:
# Push to docs.comma.ai
- uses: actions/checkout@v4
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
if: github.ref == 'refs/heads/master-tici' && github.repository == 'sunnypilot/sunnypilot'
with:
path: openpilot-docs
ssh-key: ${{ secrets.OPENPILOT_DOCS_KEY }}
repository: sunnypilot/sunnypilot-docs
- name: Push
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
if: github.ref == 'refs/heads/master-tici' && github.repository == 'sunnypilot/sunnypilot'
run: |
set -x

View File

@@ -24,11 +24,11 @@ jobs:
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
with:
ref: master
ref: master-tici
wait-interval: 30
running-workflow-name: 'build prebuilt'
repo-token: ${{ secrets.GITHUB_TOKEN }}
check-regexp: ^((?!.*(build master-ci).*).)*$
check-regexp: ^((?!.*(build __nightly).*).)*$
- uses: actions/checkout@v4
with:
submodules: true

View File

@@ -19,6 +19,7 @@ jobs:
contents: write
pull-requests: write
runs-on: ubuntu-latest
if: False
steps:
- uses: release-drafter/release-drafter@v6
with:

View File

@@ -10,7 +10,7 @@ jobs:
env:
ImageOS: ubuntu24
container:
image: ghcr.io/sunnypilot/sunnypilot-base:latest
image: ghcr.io/sunnypilot/sunnypilot-tici-base:latest
runs-on: ubuntu-latest
if: github.repository == 'sunnypilot/sunnypilot'
permissions:
@@ -25,7 +25,7 @@ jobs:
if: ${{ github.event_name == 'schedule' }}
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
with:
ref: master
ref: master-tici
wait-interval: 30
running-workflow-name: 'build __nightly'
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -6,7 +6,7 @@ on:
workflow_dispatch:
env:
BASE_IMAGE: sunnypilot-base
BASE_IMAGE: sunnypilot-tici-base
BUILD: release/ci/docker_build_sp.sh base
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
@@ -28,7 +28,7 @@ jobs:
title: "[bot] Update translations"
body: "Automatic PR from repo-maintenance -> update_translations"
branch: "update-translations"
base: "master"
base: "master-tici"
delete-branch: true
labels: bot
@@ -36,7 +36,7 @@ jobs:
name: package_updates
runs-on: ubuntu-latest
container:
image: ghcr.io/sunnypilot/sunnypilot-base:latest
image: ghcr.io/sunnypilot/sunnypilot-tici-base:latest
if: github.repository == 'sunnypilot/sunnypilot'
steps:
- uses: actions/checkout@v4
@@ -68,7 +68,7 @@ jobs:
commit-message: Update Python packages
title: '[bot] Update Python packages'
branch: auto-package-updates
base: master
base: master-tici
delete-branch: true
body: 'Automatic PR from repo-maintenance -> package_updates'
labels: bot

View File

@@ -3,7 +3,7 @@ name: selfdrive
on:
push:
branches:
- master
- master-tici
pull_request:
workflow_dispatch:
workflow_call:
@@ -14,12 +14,12 @@ on:
type: string
concurrency:
group: selfdrive-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
group: selfdrive-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master-tici' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
env:
PYTHONWARNINGS: error
BASE_IMAGE: sunnypilot-base
BASE_IMAGE: sunnypilot-tici-base
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
@@ -68,10 +68,10 @@ jobs:
if: github.repository == 'sunnypilot/sunnypilot'
timeout-minutes: 3
run: |
if [ "${{ github.ref }}" != "refs/heads/master" ]; then
git fetch origin master:refs/remotes/origin/master
if [ "${{ github.ref }}" != "refs/heads/master-tici" ]; then
git fetch origin master-tici:refs/remotes/origin/master-tici
SUBMODULE_PATHS=$(git diff origin/master HEAD --name-only | grep -E '^[^/]+$' | while read path; do
SUBMODULE_PATHS=$(git diff origin/master-tici HEAD --name-only | grep -E '^[^/]+$' | while read path; do
if git ls-files --stage "$path" | grep -q "^160000"; then
echo "$path"
fi
@@ -97,7 +97,7 @@ jobs:
with:
submodules: true
- name: Setup docker push
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'sunnypilot/sunnypilot'
if: github.ref == 'refs/heads/master-tici' && github.event_name != 'pull_request' && github.repository == 'sunnypilot/sunnypilot'
run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
$DOCKER_LOGIN
@@ -129,7 +129,7 @@ jobs:
PYTHONWARNINGS: default
- name: Save Homebrew cache
uses: actions/cache/save@v4
if: github.ref == 'refs/heads/master'
if: github.ref == 'refs/heads/master-tici'
with:
path: ~/Library/Caches/Homebrew
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
@@ -146,7 +146,7 @@ jobs:
run: . .venv/bin/activate && scons -j$(nproc)
- name: Save scons cache
uses: actions/cache/save@v4
if: github.ref == 'refs/heads/master'
if: github.ref == 'refs/heads/master-tici'
with:
path: /tmp/scons_cache
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
@@ -305,5 +305,5 @@ jobs:
- name: Upload Test Report
uses: actions/upload-artifact@v4
with:
name: report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
name: report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master-tici' && 'master-tici' || github.event.number }}
path: selfdrive/ui/tests/test_ui/report_1/screenshots

View File

@@ -14,7 +14,7 @@ on:
upstream_branch:
description: 'Upstream branch to build from'
required: true
default: 'master'
default: 'master-tici'
type: string
custom_name:
description: 'Custom name for the model (no date, only name)'
@@ -35,7 +35,7 @@ on:
upstream_branch:
description: 'Upstream branch to build from'
required: true
default: 'master'
default: 'master-tici'
type: string
custom_name:
description: 'Custom name for the model (no date, only name)'

View File

@@ -8,14 +8,14 @@ env:
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
# Branch configurations
STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master' }} # vars are set on repo settings.
STAGING_C3_SOURCE_BRANCH: 'master-tici' # vars.STAGING_C3_SOURCE_BRANCH could be used, set on repo settings.
# Runtime configuration
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
on:
push:
branches: [ master, master-dev-c3-new ]
branches: [ master-tici ]
tags: [ 'release/*' ]
pull_request_target:
types: [ labeled ]

View File

@@ -1,7 +1,7 @@
name: Build dev-c3-new
env:
DEFAULT_SOURCE_BRANCH: "master"
DEFAULT_SOURCE_BRANCH: "master-tici"
DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
PR_LABEL: "dev-c3"
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
@@ -10,17 +10,17 @@ env:
on:
push:
branches:
- master
- master-tici
pull_request_target:
types: [ labeled ]
branches:
- 'master'
- 'master-tici'
workflow_dispatch:
inputs:
source_branch:
description: 'Source branch to reset from'
required: true
default: 'master'
default: 'master-tici'
type: string
target_branch:
description: 'Target branch to reset and squash into'
@@ -40,11 +40,7 @@ concurrency:
jobs:
reset-and-squash:
runs-on: ubuntu-latest
if: (
(github.event_name == 'workflow_dispatch')
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
)
if: False
steps:
- uses: actions/checkout@v4
with:

View File

@@ -2,19 +2,19 @@ name: "ui preview"
on:
push:
branches:
- master
- master-tici
pull_request_target:
types: [assigned, opened, synchronize, reopened, edited]
branches:
- 'master'
- 'master-tici'
paths:
- 'selfdrive/ui/**'
workflow_dispatch:
env:
UI_JOB_NAME: "Create UI Report"
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master-tici' && 'master-tici' || github.event.number }}
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master-tici' && github.sha || github.event.pull_request.head.sha }}
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}"
jobs:
@@ -55,7 +55,7 @@ jobs:
name: report-1-${{ env.REPORT_NAME }}
path: ${{ github.workspace }}/pr_ui
- name: Getting master ui
- name: Getting master-tici ui
uses: actions/checkout@v4
with:
repository: sunnypilot/ci-artifacts
@@ -63,8 +63,8 @@ jobs:
path: ${{ github.workspace }}/master_ui
ref: openpilot_master_ui
- name: Saving new master ui
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
- name: Saving new master-tici ui
if: github.ref == 'refs/heads/master-tici' && github.event_name == 'push'
working-directory: ${{ github.workspace }}/master_ui
run: |
git checkout --orphan=new_master_ui
@@ -93,9 +93,9 @@ jobs:
for ((i=0; i<${#A[*]}; i=i+1));
do
# Check if the master file exists
# Check if the master-tici file exists
if [ ! -f "${{ github.workspace }}/master_ui/${A[$i]}.png" ]; then
# This is a new file in PR UI that doesn't exist in master
# This is a new file in PR UI that doesn't exist in master-tici
DIFF="${DIFF}<details open>"
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$</summary>"
DIFF="${DIFF}<table>"
@@ -118,7 +118,7 @@ jobs:
DIFF="${DIFF}<table>"
DIFF="${DIFF}<tr>"
DIFF="${DIFF} <td> master <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
DIFF="${DIFF} <td> master-tici <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
DIFF="${DIFF}</tr>"

View File

@@ -1,4 +1,4 @@
FROM ghcr.io/sunnypilot/sunnypilot-base:latest
FROM ghcr.io/sunnypilot/sunnypilot-tici-base:latest
ENV PYTHONUNBUFFERED=1

View File

@@ -22,7 +22,7 @@ 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.
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging-tici` branch.
### If you want to use our newest branches (our rewrite)
> [!TIP]
@@ -31,25 +31,24 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
* 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: ```https://staging-tici.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: `staging-tici`
| Branch | Installation URL |
|:----------------:|:---------------------------------------------:|
| `staging-c3-new` | `https://staging-c3-new.sunnypilot.ai` |
| `dev-c3-new` | `https://dev-c3-new.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
| `release-c3-new` | **Not yet available**. |
| Branch | Installation URL |
|:---------------:|:---------------------------------------------:|
| `staging-tici` | `https://staging-tici.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
| `release-tici` | **Not yet available**. |
> [!TIP]
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'.
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-tici'.
> [!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.

View File

@@ -202,7 +202,20 @@ struct CarControlSP @0xa5cd762cd951a455 {
struct Param {
key @0 :Text;
value @1 :Text;
type @2 :ParamType;
value @3 :Data;
valueDEPRECATED @1 :Text; # The data type change may cause issues with backwards compatibility.
}
enum ParamType {
string @0;
bool @1;
int @2;
float @3;
time @4;
json @5;
bytes @6;
}
}

View File

@@ -146,6 +146,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},

View File

@@ -31,13 +31,25 @@ while read hash submodule ref; do
exit 1
fi
else
git -C $submodule fetch --depth 100 origin master
git -C $submodule branch -r --contains $hash | grep "origin/master"
if [ "$?" -eq 0 ]; then
echo "$submodule ok"
# Check against master-tici for specific submodules, master for others
if [ "$submodule" = "opendbc_repo" ] || [ "$submodule" = "panda" ]; then
git -C $submodule fetch --depth 100 origin master-tici
remote_hash=$(git -C $submodule rev-parse FETCH_HEAD)
if git -C $submodule merge-base --is-ancestor $hash $remote_hash; then
echo "$submodule ok"
else
echo "$submodule: $hash is not on master-tici"
exit 1
fi
else
echo "$submodule: $hash is not on master"
exit 1
git -C $submodule fetch --depth 100 origin master
git -C $submodule branch -r --contains $hash | grep "origin/master"
if [ "$?" -eq 0 ]; then
echo "$submodule ok"
else
echo "$submodule: $hash is not on master"
exit 1
fi
fi
fi
done <<< $(git submodule status --recursive)

View File

@@ -1,8 +1,8 @@
if [ "$1" = "base" ]; then
export DOCKER_IMAGE=sunnypilot-base
export DOCKER_IMAGE=sunnypilot-tici-base
export DOCKER_FILE=Dockerfile.sunnypilot_base
elif [ "$1" = "prebuilt" ]; then
export DOCKER_IMAGE=sunnypilot-prebuilt
export DOCKER_IMAGE=sunnypilot-tici-prebuilt
export DOCKER_FILE=Dockerfile.sunnypilot
else
echo "Invalid docker build image: '$1'"

View File

@@ -4,6 +4,9 @@
#include <map>
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#endif
void OnroadAlerts::updateState(const UIState &s) {
Alert a = getAlert(*(s.sm), s.scene.started_frame);
@@ -73,6 +76,12 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
}
QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2);
#ifdef SUNNYPILOT
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
const int adjustment = dev_ui_info > 1 && alert.size != cereal::SelfdriveState::AlertSize::FULL ? 30 : 0;
r = QRect(0 + margin, height() - h + margin - adjustment, width() - margin*2, h - margin*2);
#endif
QPainter p(this);
// draw background + gradient

View File

@@ -12,6 +12,7 @@
#include "selfdrive/ui/sunnypilot/qt/onroad/model.h"
#define ExperimentalButton ExperimentalButtonSP
#define ModelRenderer ModelRendererSP
#define HudRenderer HudRendererSP
#else
#include "selfdrive/ui/qt/onroad/buttons.h"
#include "selfdrive/ui/qt/onroad/hud.h"

View File

@@ -73,6 +73,11 @@ void DriverMonitorRenderer::draw(QPainter &painter, const QRect &surface_rect) {
float y = surface_rect.height() - offset;
float opacity = is_active ? 0.65f : 0.2f;
#ifdef SUNNYPILOT
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
y -= dev_ui_info > 1 ? 50 : 0;
#endif
drawIcon(painter, QPoint(x, y), dm_img, QColor(0, 0, 0, 70), opacity);
QPointF keypoints[std::size(DEFAULT_FACE_KPTS_3D)];

View File

@@ -39,6 +39,7 @@ qt_src = [
"sunnypilot/qt/offroad/settings/visuals_panel.cc",
"sunnypilot/qt/onroad/annotated_camera.cc",
"sunnypilot/qt/onroad/buttons.cc",
"sunnypilot/qt/onroad/developer_ui/developer_ui.cc",
"sunnypilot/qt/onroad/hud.cc",
"sunnypilot/qt/onroad/model.cc",
"sunnypilot/qt/onroad/onroad_home.cc",

View File

@@ -72,6 +72,15 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
list->addItem(chevron_info_settings);
param_watcher->addParam("ChevronInfo");
// Visuals: Developer UI Info (Dev UI)
std::vector<QString> dev_ui_settings_texts{tr("Off"), tr("Right"), tr("Right &&\nBottom")};
dev_ui_settings = new ButtonParamControlSP(
"DevUIInfo", tr("Developer UI"), tr("Display real-time parameters and metrics from various sources."),
"",
dev_ui_settings_texts,
380);
list->addItem(dev_ui_settings);
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
@@ -90,4 +99,7 @@ void VisualsPanel::paramsRefresh() {
if (chevron_info_settings) {
chevron_info_settings->refresh();
}
if (dev_ui_settings) {
dev_ui_settings->refresh();
}
}

View File

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

View File

@@ -14,3 +14,8 @@ AnnotatedCameraWidgetSP::AnnotatedCameraWidgetSP(VisionStreamType type, QWidget
void AnnotatedCameraWidgetSP::updateState(const UIState &s) {
AnnotatedCameraWidget::updateState(s);
}
void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) {
AnnotatedCameraWidget::showEvent(event);
ui_update_params_sp(uiState());
}

View File

@@ -15,4 +15,7 @@ class AnnotatedCameraWidgetSP : public AnnotatedCameraWidget {
public:
explicit AnnotatedCameraWidgetSP(VisionStreamType type, QWidget *parent = nullptr);
void updateState(const UIState &s) override;
protected:
void showEvent(QShowEvent *event) override;
};

View File

@@ -0,0 +1,227 @@
/**
* 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 <cmath>
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h"
// Add Relative Distance to Primary Lead Car
// Unit: Meters
UiElement DeveloperUi::getDRel(bool lead_status, float lead_d_rel) {
QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-";
QColor color = QColor(255, 255, 255, 255);
if (lead_status) {
// Orange if close, Red if very close
if (lead_d_rel < 5) {
color = QColor(255, 0, 0, 255);
} else if (lead_d_rel < 15) {
color = QColor(255, 188, 0, 255);
}
}
return UiElement(value, "REL DIST", "m", color);
}
// Add Relative Velocity vs Primary Lead Car
// Unit: kph if metric, else mph
UiElement DeveloperUi::getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit) {
QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-";
QColor color = QColor(255, 255, 255, 255);
if (lead_status) {
// Red if approaching faster than 10mph
// Orange if approaching (negative)
if (lead_v_rel < -4.4704) {
color = QColor(255, 0, 0, 255);
} else if (lead_v_rel < 0) {
color = QColor(255, 188, 0, 255);
}
}
return UiElement(value, "REL SPEED", speed_unit, color);
}
// Add Real Steering Angle
// Unit: Degrees
UiElement DeveloperUi::getSteeringAngleDeg(float angle_steers, bool lat_active, bool steer_override) {
QString value = QString("%1%2%3").arg(QString::number(angle_steers, 'f', 1)).arg("°").arg("");
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
// Red if large steering angle
// Orange if moderate steering angle
if (std::fabs(angle_steers) > 180) {
color = QColor(255, 0, 0, 255);
} else if (std::fabs(angle_steers) > 90) {
color = QColor(255, 188, 0, 255);
}
return UiElement(value, "REAL STEER", "", color);
}
// Add Actual Lateral Acceleration (roll compensated) when using Torque
// Unit: m/s²
UiElement DeveloperUi::getActualLateralAccel(float curvature, float v_ego, float roll, bool lat_active, bool steer_override) {
double actualLateralAccel = (curvature * pow(v_ego, 2)) - (roll * 9.81);
QString value = QString::number(actualLateralAccel, 'f', 2);
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
return UiElement(value, "ACTUAL L.A.", "m/s²", color);
}
// Add Desired Steering Angle when using PID
// Unit: Degrees
UiElement DeveloperUi::getSteeringAngleDesiredDeg(bool lat_active, float steer_angle_desired, float angle_steers) {
QString value = lat_active ? QString("%1%2%3").arg(QString::number(steer_angle_desired, 'f', 1)).arg("°").arg("") : "-";
QColor color = QColor(255, 255, 255, 255);
if (lat_active) {
// Red if large steering angle
// Orange if moderate steering angle
if (std::fabs(angle_steers) > 180) {
color = QColor(255, 0, 0, 255);
} else if (std::fabs(angle_steers) > 90) {
color = QColor(255, 188, 0, 255);
} else {
color = QColor(0, 255, 0, 255);
}
}
return UiElement(value, "DESIRED STEER", "", color);
}
// Add Device Memory (RAM) Usage
// Unit: Percent
UiElement DeveloperUi::getMemoryUsagePercent(int memory_usage_percent) {
QString value = QString("%1%2").arg(QString::number(memory_usage_percent, 'd', 0)).arg("%");
QColor color = (memory_usage_percent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255);
return UiElement(value, "RAM", "", color);
}
// Add Vehicle Current Acceleration
// Unit: m/s²
UiElement DeveloperUi::getAEgo(float a_ego) {
QString value = QString::number(a_ego, 'f', 1);
QColor color = QColor(255, 255, 255, 255);
return UiElement(value, "ACC.", "m/s²", color);
}
// Add Relative Velocity to Primary Lead Car
// Unit: kph if metric, else mph
UiElement DeveloperUi::getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit) {
QString value = lead_status ? QString::number((lead_v_rel + v_ego) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-";
QColor color = QColor(255, 255, 255, 255);
if (lead_status) {
// Red if approaching faster than 10mph
// Orange if approaching (negative)
if (lead_v_rel < -4.4704) {
color = QColor(255, 0, 0, 255);
} else if (lead_v_rel < 0) {
color = QColor(255, 188, 0, 255);
}
}
return UiElement(value, "L.S.", speed_unit, color);
}
// Add Friction Coefficient Raw from torqued
// Unit: None
UiElement DeveloperUi::getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid) {
QString value = QString::number(friction_coefficient_filtered, 'f', 3);
QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
return UiElement(value, "FRIC.", "", color);
}
// Add Lateral Acceleration Factor Raw from torqued
// Unit: m/s²
UiElement DeveloperUi::getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid) {
QString value = QString::number(lat_accel_factor_filtered, 'f', 3);
QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
return UiElement(value, "L.A.", "m/s²", color);
}
// Add Steering Torque from Car EPS
// Unit: Newton Meters
UiElement DeveloperUi::getSteeringTorqueEps(float steering_torque_eps) {
QString value = QString::number(std::fabs(steering_torque_eps), 'f', 1);
QColor color = QColor(255, 255, 255, 255);
return UiElement(value, "E.T.", "N·dm", color);
}
// Add Bearing Degree and Direction from Car (Compass)
// Unit: Meters
UiElement DeveloperUi::getBearingDeg(float bearing_accuracy_deg, float bearing_deg) {
QString value = (bearing_accuracy_deg != 180.00) ? QString("%1%2%3").arg(QString::number(bearing_deg, 'd', 0)).arg("°").arg("") : "-";
QColor color = QColor(255, 255, 255, 255);
QString dir_value;
if (bearing_accuracy_deg != 180.00) {
if (((bearing_deg >= 337.5) && (bearing_deg <= 360)) || ((bearing_deg >= 0) && (bearing_deg <= 22.5))) {
dir_value = "N";
} else if ((bearing_deg > 22.5) && (bearing_deg < 67.5)) {
dir_value = "NE";
} else if ((bearing_deg >= 67.5) && (bearing_deg <= 112.5)) {
dir_value = "E";
} else if ((bearing_deg > 112.5) && (bearing_deg < 157.5)) {
dir_value = "SE";
} else if ((bearing_deg >= 157.5) && (bearing_deg <= 202.5)) {
dir_value = "S";
} else if ((bearing_deg > 202.5) && (bearing_deg < 247.5)) {
dir_value = "SW";
} else if ((bearing_deg >= 247.5) && (bearing_deg <= 292.5)) {
dir_value = "W";
} else if ((bearing_deg > 292.5) && (bearing_deg < 337.5)) {
dir_value = "NW";
}
} else {
dir_value = "OFF";
}
return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color);
}
// Add Altitude of Current Location
// Unit: Meters
UiElement DeveloperUi::getAltitude(float gps_accuracy, float altitude) {
QString value = (gps_accuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-";
QColor color = QColor(255, 255, 255, 255);
return UiElement(value, "ALT.", "m", color);
}
// Add Actuators Output
// Unit: Degree (angle) or m/s² (torque)
UiElement DeveloperUi::getActuatorsOutputLateral(cereal::CarParams::SteerControlType steerControlType,
cereal::CarControl::Actuators::Reader &actuators,
float desiredCurvature, float v_ego, float roll, bool lat_active, bool steer_override) {
QString label;
QString value;
QString unit;
if (steerControlType == cereal::CarParams::SteerControlType::ANGLE) {
label = "DESIRED STEER";
value = QString("%1%2%3").arg(QString::number(actuators.getSteeringAngleDeg(), 'f', 1)).arg("°").arg("");
} else {
label = "DESIRED L.A.";
double desiredLateralAccel = (desiredCurvature * pow(v_ego, 2)) - (roll * 9.81);
value = QString::number(desiredLateralAccel, 'f', 2);
unit = "m/s²";
}
value = lat_active ? value : "-";
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
return UiElement(value, label, unit, color);
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h"
class DeveloperUi {
public:
static UiElement getDRel(bool lead_status, float lead_d_rel);
static UiElement getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit);
static UiElement getSteeringAngleDeg(float angle_steers, bool lat_active, bool steer_override);
static UiElement getActualLateralAccel(float curvature, float v_ego, float roll, bool lat_active, bool steer_override);
static UiElement getSteeringAngleDesiredDeg(bool lat_active, float steer_angle_desired, float angle_steers);
static UiElement getMemoryUsagePercent(int memory_usage_percent);
static UiElement getAEgo(float a_ego);
static UiElement getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit);
static UiElement getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid);
static UiElement getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid);
static UiElement getSteeringTorqueEps(float steering_torque_eps);
static UiElement getBearingDeg(float bearing_accuracy_deg, float bearing_deg);
static UiElement getAltitude(float gps_accuracy, float altitude);
static UiElement getActuatorsOutputLateral(cereal::CarParams::SteerControlType steerControlType,
cereal::CarControl::Actuators::Reader &actuators,
float desiredCurvature, float v_ego, float roll, bool lat_active, bool steer_override);
};

View File

@@ -0,0 +1,19 @@
/**
* 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 <QColor>
struct UiElement {
QString value{};
QString label{};
QString units{};
QColor color{};
explicit UiElement(const QString &value = "", const QString &label = "", const QString &units = "", const QColor &color = QColor(255, 255, 255, 255))
: value(value), label(label), units(units), color(color) {}
};

View File

@@ -7,12 +7,202 @@
#include "selfdrive/ui/sunnypilot/qt/onroad/hud.h"
#include "selfdrive/ui/qt/util.h"
HudRendererSP::HudRendererSP() {}
void HudRendererSP::updateState(const UIState &s) {
HudRenderer::updateState(s);
const SubMaster &sm = *(s.sm);
const bool cs_alive = sm.alive("controlsState");
const auto cs = sm["controlsState"].getControlsState();
const auto car_state = sm["carState"].getCarState();
const auto car_control = sm["carControl"].getCarControl();
const auto radar_state = sm["radarState"].getRadarState();
const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1;
const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation();
const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters();
const auto car_params = sm["carParams"].getCarParams();
static int reverse_delay = 0;
bool reverse_allowed = false;
if (int(car_state.getGearShifter()) != 4) {
reverse_delay = 0;
reverse_allowed = false;
} else {
reverse_delay += 50;
if (reverse_delay >= 1000) {
reverse_allowed = true;
}
}
reversing = reverse_allowed;
is_metric = s.scene.is_metric;
// Handle older routes where vEgoCluster is not set
v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0;
float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo();
speed = cs_alive ? std::max<float>(0.0, v_ego) : 0.0;
speed *= is_metric ? MS_TO_KPH : MS_TO_MPH;
latActive = car_control.getLatActive();
steerOverride = car_state.getSteeringPressed();
devUiInfo = s.scene.dev_ui_info;
speedUnit = is_metric ? tr("km/h") : tr("mph");
lead_d_rel = radar_state.getLeadOne().getDRel();
lead_v_rel = radar_state.getLeadOne().getVRel();
lead_status = radar_state.getLeadOne().getStatus();
steerControlType = car_params.getSteerControlType();
actuators = car_control.getActuators();
torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE;
angleSteers = car_state.getSteeringAngleDeg();
desiredCurvature = cs.getDesiredCurvature();
curvature = cs.getCurvature();
roll = sm["liveParameters"].getLiveParameters().getRoll();
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
altitude = gpsLocation.getAltitude();
vEgo = car_state.getVEgo();
aEgo = car_state.getAEgo();
steeringTorqueEps = car_state.getSteeringTorqueEps();
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
bearingDeg = gpsLocation.getBearingDeg();
torquedUseParams = ltp.getUseParams();
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
liveValid = ltp.getLiveValid();
}
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
HudRenderer::draw(p, surface_rect);
if (!reversing) {
// Bottom Dev UI
if (devUiInfo == 2) {
QRect rect_bottom(surface_rect.left(), surface_rect.bottom() - 60, surface_rect.width(), 61);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 100));
p.drawRect(rect_bottom);
drawBottomDevUI(p, rect_bottom.left(), rect_bottom.center().y());
}
// Right Dev UI
if (devUiInfo != 0) {
QRect rect_right(surface_rect.right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 170);
drawRightDevUI(p, surface_rect.right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rect_right.height());
}
}
}
void HudRendererSP::drawText(QPainter &p, int x, int y, const QString &text, QColor color) {
QRect real_rect = p.fontMetrics().boundingRect(text);
real_rect.moveCenter({x, y - real_rect.height() / 2});
p.setPen(color);
p.drawText(real_rect.x(), real_rect.bottom(), text);
}
int HudRendererSP::drawRightDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
p.setFont(InterFont(28, QFont::Bold));
x += 92;
y += 80;
drawText(p, x, y, label);
p.setFont(InterFont(30 * 2, QFont::Bold));
y += 65;
drawText(p, x, y, value, color);
p.setFont(InterFont(28, QFont::Bold));
if (units.length() > 0) {
p.save();
x += 120;
y -= 25;
p.translate(x, y);
p.rotate(-90);
drawText(p, 0, 0, units);
p.restore();
}
return 130;
}
void HudRendererSP::drawRightDevUI(QPainter &p, int x, int y) {
int rh = 5;
int ry = y;
UiElement dRelElement = DeveloperUi::getDRel(lead_status, lead_d_rel);
rh += drawRightDevUIElement(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color);
ry = y + rh;
UiElement vRelElement = DeveloperUi::getVRel(lead_status, lead_v_rel, is_metric, speedUnit);
rh += drawRightDevUIElement(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color);
ry = y + rh;
UiElement steeringAngleDegElement = DeveloperUi::getSteeringAngleDeg(angleSteers, latActive, steerOverride);
rh += drawRightDevUIElement(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color);
ry = y + rh;
UiElement actuatorsOutputLateralElement = DeveloperUi::getActuatorsOutputLateral(steerControlType, actuators, desiredCurvature, vEgo, roll, latActive, steerOverride);
rh += drawRightDevUIElement(p, x, ry, actuatorsOutputLateralElement.value, actuatorsOutputLateralElement.label, actuatorsOutputLateralElement.units, actuatorsOutputLateralElement.color);
ry = y + rh;
UiElement actualLateralAccelElement = DeveloperUi::getActualLateralAccel(curvature, vEgo, roll, latActive, steerOverride);
rh += drawRightDevUIElement(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color);
}
int HudRendererSP::drawBottomDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
p.setFont(InterFont(38, QFont::Bold));
QFontMetrics fm(p.font());
QRect init_rect = fm.boundingRect(label + " ");
QRect real_rect = fm.boundingRect(init_rect, 0, label + " ");
real_rect.moveCenter({x, y});
QRect init_rect2 = fm.boundingRect(value);
QRect real_rect2 = fm.boundingRect(init_rect2, 0, value);
real_rect2.moveTop(real_rect.top());
real_rect2.moveLeft(real_rect.right() + 10);
QRect init_rect3 = fm.boundingRect(units);
QRect real_rect3 = fm.boundingRect(init_rect3, 0, units);
real_rect3.moveTop(real_rect.top());
real_rect3.moveLeft(real_rect2.right() + 10);
p.setPen(Qt::white);
p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, label);
p.setPen(color);
p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, value);
p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, units);
return 430;
}
void HudRendererSP::drawBottomDevUI(QPainter &p, int x, int y) {
int rw = 90;
UiElement aEgoElement = DeveloperUi::getAEgo(aEgo);
rw += drawBottomDevUIElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color);
UiElement vEgoLeadElement = DeveloperUi::getVEgoLead(lead_status, lead_v_rel, vEgo, is_metric, speedUnit);
rw += drawBottomDevUIElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color);
if (torqueLateral && torquedUseParams) {
UiElement frictionCoefficientFilteredElement = DeveloperUi::getFrictionCoefficientFiltered(frictionCoefficientFiltered, liveValid);
rw += drawBottomDevUIElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color);
UiElement latAccelFactorFilteredElement = DeveloperUi::getLatAccelFactorFiltered(latAccelFactorFiltered, liveValid);
rw += drawBottomDevUIElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color);
} else {
UiElement steeringTorqueEpsElement = DeveloperUi::getSteeringTorqueEps(steeringTorqueEps);
rw += drawBottomDevUIElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color);
UiElement bearingDegElement = DeveloperUi::getBearingDeg(bearingAccuracyDeg, bearingDeg);
rw += drawBottomDevUIElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color);
}
UiElement altitudeElement = DeveloperUi::getAltitude(gpsAccuracy, altitude);
rw += drawBottomDevUIElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color);
}

View File

@@ -7,9 +7,8 @@
#pragma once
#include <QPainter>
#include "selfdrive/ui/qt/onroad/hud.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h"
class HudRendererSP : public HudRenderer {
Q_OBJECT
@@ -18,4 +17,40 @@ public:
HudRendererSP();
void updateState(const UIState &s) override;
void draw(QPainter &p, const QRect &surface_rect) override;
private:
Params params;
void drawText(QPainter &p, int x, int y, const QString &text, QColor color = Qt::white);
void drawRightDevUI(QPainter &p, int x, int y);
int drawRightDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
int drawBottomDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
void drawBottomDevUI(QPainter &p, int x, int y);
bool lead_status;
float lead_d_rel;
float lead_v_rel;
bool torqueLateral;
float angleSteers;
float desiredCurvature;
float curvature;
float roll;
int memoryUsagePercent;
int devUiInfo;
float gpsAccuracy;
float altitude;
float vEgo;
float aEgo;
float steeringTorqueEps;
float bearingAccuracyDeg;
float bearingDeg;
bool torquedUseParams;
float latAccelFactorFiltered;
float frictionCoefficientFiltered;
bool liveValid;
QString speedUnit;
bool latActive;
bool steerOverride;
bool reversing;
cereal::CarParams::SteerControlType steerControlType;
cereal::CarControl::Actuators::Reader actuators;
};

View File

@@ -18,13 +18,22 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) {
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState",
"pandaStates", "carParams", "driverMonitoringState", "carState", "driverStateV2",
"wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan",
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP"
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
"carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters",
"carStateSP", "liveParameters"
});
// update timer
timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, this, &UIStateSP::update);
timer->start(1000 / UI_FREQ);
// Param watcher for UIScene param updates
param_watcher = new ParamWatcher(this);
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
ui_update_params_sp(this);
});
param_watcher->addParam("DevUIInfo");
}
// This method overrides completely the update method from the parent class intentionally.
@@ -39,6 +48,11 @@ void UIStateSP::update() {
emit uiUpdate(*this);
}
void ui_update_params_sp(UIStateSP *s) {
auto params = Params();
s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str());
}
DeviceSP::DeviceSP(QObject *parent) : Device(parent) {
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &DeviceSP::update);
QObject::connect(this, &Device::displayPowerChanged, this, &DeviceSP::handleDisplayPowerChanged);

View File

@@ -13,6 +13,7 @@
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
class UIStateSP : public UIState {
Q_OBJECT
@@ -73,6 +74,7 @@ private slots:
private:
std::vector<RoleModel> sunnylinkRoles = {};
std::vector<UserModel> sunnylinkUsers = {};
ParamWatcher *param_watcher;
};
UIStateSP *uiStateSP();
@@ -92,3 +94,5 @@ private:
DeviceSP *deviceSP();
inline DeviceSP *device() { return deviceSP(); }
void ui_update_params_sp(UIStateSP *s);

View File

@@ -0,0 +1,12 @@
/**
* 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
typedef struct UISceneSP : UIScene {
int dev_ui_info = 0;
} UISceneSP;

View File

@@ -66,6 +66,11 @@ typedef struct UIScene {
uint64_t started_frame;
} UIScene;
#ifdef SUNNYPILOT
#include "sunnypilot/ui_scene.h"
#define UIScene UISceneSP
#endif
class UIState : public QObject {
Q_OBJECT

View File

@@ -73,7 +73,7 @@ class ControlsExt:
# MADS state
CC_SP.mads = sm['selfdriveStateSP'].mads
CC_SP.params = self.param_store.publish()
CC_SP.params = self.param_store.param_list
return CC_SP

View File

@@ -4,39 +4,41 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import capnp
from cereal import custom
from opendbc.car import structs
from openpilot.common.params import Params
from sunnypilot.sunnylink.utils import get_param_as_byte
class ParamStore:
keys: list[str]
values: dict[str, str]
_params: dict[str, custom.CarControlSP.Param]
def __init__(self, CP: structs.CarParams):
universal_params: list[str] = []
brand_params: list[str] = []
self.keys = universal_params + brand_params
self.values = {}
self.cached_params_list: list[capnp.lib.capnp._DynamicStructBuilder] | None = None
self._params = {}
self.frame = 0
def update(self, params: Params) -> None:
if self.frame % 300 == 0:
old_values = dict(self.values)
self.values = {k: params.get(k) or "0" for k in self.keys}
if old_values != self.values:
self.cached_params_list = None
self.frame += 1
if self.frame % 300 != 0:
return
def publish(self) -> list[capnp.lib.capnp._DynamicStructBuilder]:
if self.cached_params_list is None:
# TODO-SP: Why are we doing a list instead of a dictionary here?
self.cached_params_list = [custom.CarControlSP.Param(key=k, value=self.values[k]) for k in self.keys]
return self.cached_params_list
for key in self.keys:
param_type = params.get_type(key).name.lower() # Using string instead of number because its "loose" dependency, and could change by OP at anytime.
# Over engineering opportunity: It's possible this conversion is slow, we may check the value as params returns it for cache purposes. Not today.
param_value = get_param_as_byte(key, params)
if (existing_param := self._params.get(key)) is not None and existing_param.value == param_value:
continue
self._params[key] = custom.CarControlSP.Param(key=key, value=param_value, type=param_type)
@property
def param_list(self) -> list[custom.CarControlSP.Param]:
return [v for k,v in self._params.items()]

View File

@@ -23,7 +23,7 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce
import cereal.messaging as messaging
from sunnypilot.sunnylink.api import SunnylinkApi
from sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready, get_param_as_byte
from sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready, get_param_as_byte, save_param_from_base64_encoded_string
SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sunnypilot.ai')
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
@@ -184,14 +184,18 @@ def getParams(params_keys: list[str], compression: bool = False) -> str | dict[s
try:
param_keys_validated = [key for key in params_keys if key in getParamsAllKeys()]
params_dict: dict[str, list[dict[str, str | bool | int ]]] = {"params": [
{
params_dict: dict[str, list[dict[str, str | bool | int]]] = {"params": []}
for key in param_keys_validated:
value = get_param_as_byte(key)
if value is None:
continue
params_dict["params"].append({
"key": key,
"value": base64.b64encode(gzip.compress(get_param_as_byte(key)) if compression else get_param_as_byte(key)).decode('utf-8'),
"value": base64.b64encode(gzip.compress(value) if compression else value).decode('utf-8'),
"type": int(params.get_type(key).value),
"is_compressed": compression
} for key in param_keys_validated
]}
})
response = {str(param.get('key')): str(param.get('value')) for param in params_dict.get("params", [])}
response |= {"params": json.dumps(params_dict.get("params", []))} # Upcoming for settings v1
@@ -204,15 +208,9 @@ def getParams(params_keys: list[str], compression: bool = False) -> str | dict[s
@dispatcher.add_method
def saveParams(params_to_update: dict[str, str], compression: bool = False) -> None:
params = Params()
params_dict = {key: base64.b64decode(value) for key, value in params_to_update.items()}
if compression:
params_dict = {key: gzip.decompress(value) for key, value in params_dict.items()}
for key, value in params_dict.items():
for key, value in params_to_update.items():
try:
params.put(key, value)
save_param_from_base64_encoded_string(key, value, compression)
except Exception as e:
cloudlog.error(f"sunnylinkd.saveParams.exception {e}")

View File

@@ -12,7 +12,7 @@ from enum import Enum
from typing import Any
from openpilot.common.git import get_branch
from openpilot.common.params import Params, ParamKeyType, ParamKeyFlag
from openpilot.common.params import Params, ParamKeyFlag
from openpilot.common.realtime import Ratekeeper
from openpilot.common.swaglog import cloudlog
from openpilot.system.version import get_version
@@ -20,7 +20,7 @@ from openpilot.system.version import get_version
from cereal import messaging, custom
from sunnypilot.sunnylink.api import SunnylinkApi
from sunnypilot.sunnylink.backups.utils import decrypt_compressed_data, encrypt_compress_data, SnakeCaseEncoder
from sunnypilot.sunnylink.utils import get_param_as_byte
from sunnypilot.sunnylink.utils import get_param_as_byte, save_param_from_base64_encoded_string
class OperationType(Enum):
@@ -173,8 +173,7 @@ class BackupManagerSP:
self._update_progress(75.0, OperationType.RESTORE)
# Apply configuration
all_values_encoded = self._get_metadata_value(backup_metadata, "all_values_encoded", "false")
self._apply_config(config_data, str(all_values_encoded).lower() == "true")
self._apply_config(config_data)
self.restore_status = custom.BackupManagerSP.Status.completed
self._update_progress(100.0, OperationType.RESTORE)
@@ -187,7 +186,7 @@ class BackupManagerSP:
self._report_status()
return False
def _apply_config(self, config_data: dict[str, str], all_values_encoded: bool = False) -> None:
def _apply_config(self, config_data: dict[str, str]) -> None:
"""Applies configuration data from a backup, but only for parameters marked as backupable."""
backupable_params = [k.decode('utf-8') for k in self.params.all_keys(ParamKeyFlag.BACKUP)]
backupable_set_lower = {p.lower() for p in backupable_params}
@@ -199,26 +198,8 @@ class BackupManagerSP:
if param.lower() in backupable_set_lower:
# Find real param name (with correct casing)
real_param = next(p for p in backupable_params if p.lower() == param.lower())
param_type = self.params.get_type(real_param)
try:
value = base64.b64decode(encoded_value) if all_values_encoded else encoded_value
if param_type != ParamKeyType.BYTES:
value = value.decode('utf-8') # type: ignore
if param_type == ParamKeyType.STRING:
value = value
elif param_type == ParamKeyType.BOOL:
value = value.lower() in ('true', '1', 'yes') # type: ignore
elif param_type == ParamKeyType.INT:
value = int(value) # type: ignore
elif param_type == ParamKeyType.FLOAT:
value = float(value) # type: ignore
elif param_type == ParamKeyType.TIME:
value = str(value)
elif param_type == ParamKeyType.JSON:
value = json.loads(value)
self.params.put(real_param, value)
save_param_from_base64_encoded_string(real_param, encoded_value)
restored_count += 1
except Exception as e:
cloudlog.error(f"Failed to restore param {param}: {str(e)}")

View File

@@ -1,3 +1,5 @@
import base64
import gzip
import json
from sunnypilot.sunnylink.api import SunnylinkApi, UNREGISTERED_SUNNYLINK_DONGLE_ID
from openpilot.common.params import Params, ParamKeyType
@@ -58,13 +60,57 @@ def get_api_token():
print(f"API Token: {token}")
def get_param_as_byte(param_name: str) -> bytes:
params = Params()
def get_param_as_byte(param_name: str, params=None) -> bytes | None:
"""Get a parameter as bytes. Returns None if the parameter does not exist."""
params = params or Params() # Use existing Params instance if provided
param = params.get(param_name)
param_type = params.get_type(param_name)
if param is None:
return None
param_type = params.get_type(param_name)
if param_type == ParamKeyType.BYTES:
return bytes(param)
elif param_type == ParamKeyType.JSON:
return json.dumps(param).encode('utf-8')
return str(param).encode('utf-8')
def save_param_from_base64_encoded_string(param_name: str, base64_encoded_data: str, is_compressed=False) -> None:
"""Save a parameter from bytes. Overwrites the parameter if it already exists."""
params = Params()
# Find real param name (with correct casing)
param_type = params.get_type(param_name)
value = base64.b64decode(base64_encoded_data)
if is_compressed:
value = gzip.decompress(value)
# We convert to string anything that isn't bytes first. We later transform further.
param_value = _convert_param_to_type(value, param_type)
params.put(param_name, param_value)
def _convert_param_to_type(value: bytes, param_type: ParamKeyType) -> bytes | str | int | float | bool | dict | None:
"""
Convert a byte value to the specified param type. Used internally when getting a Param to convert it to the right type.
If this method looks familiar, it's because on SP we have a similar one in openpilot/sunnypilot/car/__init__.py.
"""
# We convert to string anything that isn't bytes first. We later transform further.
if param_type != ParamKeyType.BYTES:
value = value.decode('utf-8') # type: ignore
if param_type == ParamKeyType.STRING:
value = value
elif param_type == ParamKeyType.BOOL:
value = value.lower() in ('true', '1', 'yes') # type: ignore
elif param_type == ParamKeyType.INT:
value = int(value) # type: ignore
elif param_type == ParamKeyType.FLOAT:
value = float(value) # type: ignore
elif param_type == ParamKeyType.TIME:
value = str(value) # type: ignore
elif param_type == ParamKeyType.JSON:
value = json.loads(value)
return value

View File

@@ -9,3 +9,5 @@ export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="12.8"
fi
export STAGING_ROOT="/data/safe_staging"

View File

@@ -331,7 +331,7 @@ def hardware_thread(end_event, hw_queue) -> None:
# - TIZI, or
# - TICI and channel_type is "tici"
build_metadata = get_build_metadata()
is_unsupported_combo = TICI and build_metadata.channel_type != "tici"
is_unsupported_combo = TICI and HARDWARE.get_device_type() == "tici" and build_metadata.channel_type != "tici"
startup_conditions["not_tici"] = not is_unsupported_combo
onroad_conditions["not_tici"] = not is_unsupported_combo
set_offroad_alert("Offroad_TiciSupport", is_unsupported_combo, extra_text=build_metadata.channel)

View File

@@ -11,7 +11,7 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date
RELEASE_SP_BRANCHES = ['release-c3']
TESTED_SP_BRANCHES = ['staging-c3', 'staging-c3-new']
TESTED_SP_BRANCHES = ['staging-c3', 'staging-c3-new', 'staging-tici']
MASTER_SP_BRANCHES = ['master']
RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] + RELEASE_SP_BRANCHES
TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] + TESTED_SP_BRANCHES

2
uv.lock generated
View File

@@ -912,7 +912,7 @@ dependencies = [
{ name = "yapf" },
]
wheels = [
{ url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:fbf0ea9be67e65cd45d38ff930e3d49f705dd76c9ddbd1e1482e3f87b61efcef" },
{ url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" },
]
[package.metadata]