diff --git a/.github/labeler.yaml b/.github/labeler.yaml
index 47d0d480a9..0c5d1a687a 100644
--- a/.github/labeler.yaml
+++ b/.github/labeler.yaml
@@ -44,7 +44,7 @@ subaru:
tesla:
- changed-files:
- - any-glob-to-all-files: 'selfdrive/car/telsa/*'
+ - any-glob-to-all-files: 'selfdrive/car/tesla/*'
toyota:
- changed-files:
diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml
index cf8bdc7109..f8d2fc3cf1 100644
--- a/.github/workflows/badges.yaml
+++ b/.github/workflows/badges.yaml
@@ -7,7 +7,7 @@ on:
env:
BASE_IMAGE: openpilot-base
DOCKER_REGISTRY: ghcr.io/commaai
- RUN: docker run --shm-size 1G -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
+ 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
jobs:
badges:
diff --git a/.github/workflows/ci_weekly_report.yaml b/.github/workflows/ci_weekly_report.yaml
index c5c98b46b5..22b8745872 100644
--- a/.github/workflows/ci_weekly_report.yaml
+++ b/.github/workflows/ci_weekly_report.yaml
@@ -10,38 +10,30 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
+env:
+ CI_RUNS: ${{ github.event.inputs.ci_runs || '50' }}
+
jobs:
setup:
if: github.repository == 'commaai/openpilot'
runs-on: ubuntu-latest
outputs:
- ci_runs: ${{ steps.ci_runs_setup.outputs.value }}
+ ci_runs: ${{ steps.ci_runs_setup.outputs.matrix }}
steps:
- id: ci_runs_setup
+ name: CI_RUNS=${{ env.CI_RUNS }}
run: |
- CI_RUNS=${{ inputs.ci_runs || '50' }}
- mylist="value=["
+ matrix=$(python3 -c "import json; print(json.dumps({ 'run_number' : list(range(${{ env.CI_RUNS }})) }))")
+ echo "matrix=$matrix" >> $GITHUB_OUTPUT
- for i in $(seq 1 $CI_RUNS);
- do
- if [ $i != $CI_RUNS ]; then
- mylist+="\"$i\", "
- else
- mylist+="\"$i\"]"
- fi
- done
-
- echo "$mylist" >> $GITHUB_OUTPUT
- echo "Number of CI runs for report: $CI_RUNS"
ci_matrix_run:
needs: [ setup ]
strategy:
fail-fast: false
- matrix:
- value: ${{fromJSON(needs.setup.outputs.ci_runs)}}
+ matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}}
uses: commaai/openpilot/.github/workflows/ci_weekly_run.yaml@master
with:
- run_number: ${{ matrix.value }}
+ run_number: ${{ matrix.run_number }}
report:
needs: [ci_matrix_run]
@@ -62,62 +54,48 @@ jobs:
})
var report = {}
jobs.slice(1, jobs.length-1).forEach(job => {
- const jobName = job.name.split('/')[2].trim();
+ if (job.conclusion === "skipped") return;
+ const jobName = job.name.split(" / ")[2];
+ const runRegex = /\((.*?)\)/;
+ const run = job.name.match(runRegex)[1];
report[jobName] = report[jobName] || { successes: [], failures: [], cancelled: [] };
switch (job.conclusion) {
case "success":
- report[jobName].successes.push(job.html_url); break;
+ report[jobName].successes.push({ "run_number": run, "link": job.html_url}); break;
case "failure":
- report[jobName].failures.push(job.html_url); break;
+ report[jobName].failures.push({ "run_number": run, "link": job.html_url }); break;
case "cancelled":
- report[jobName].cancelled.push(job.html_url); break;
+ report[jobName].cancelled.push({ "run_number": run, "link": job.html_url }); break;
}
});
- return JSON.stringify(report);
+ return JSON.stringify({"jobs": report});
- name: Add job results to summary
env:
JOB_RESULTS: ${{ fromJSON(steps.get-job-results.outputs.result) }}
run: |
- echo $JOB_RESULTS > job_results.json
- generate_html_table() {
- echo "
"
- echo ""
- echo " "
- echo " | Job | "
- echo " Succeeded ✅ | "
- echo " Failed ❌ | "
- echo " Cancelled (timed out) ⏰ | "
- echo "
"
- echo ""
- jq -r '
- "",
- keys[] as $job |
- "",
- " | \($job) | ",
- " ",
- " ",
- " (\(.[$job].successes | length))",
- " \(.[$job].successes[]) ",
- " ",
- " | ",
- " ",
- " ",
- " (\(.[$job].failures | length))",
- " \(.[$job].failures[]) ",
- " ",
- " | ",
- " ",
- " ",
- " (\(.[$job].cancelled | length))",
- " \(.[$job].cancelled[]) ",
- " ",
- " | ",
- "
"
- ' job_results.json
- echo ""
- echo "
"
- }
- echo "# CI Job Summary" >> $GITHUB_STEP_SUMMARY
- generate_html_table >> $GITHUB_STEP_SUMMARY
+ cat <> template.html
+
+
+
+ |
+ Job |
+ ✅ Passing |
+ ❌ Failure Details |
+
+
+
+ {% for key in jobs.keys() %}
+ | {% for i in range(5) %}{% if i+1 <= (5 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }}) %}🟩{% else %}🟥{% endif %}{% endfor%} |
+ {{ key }} |
+ {{ 100 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }} }}% |
+ {% if jobs[key]["failures"]|length > 0 %}{% for failure in jobs[key]["failures"] %}Log for run #{{ failure['run_number'] }} {% endfor %} {% else %}{% endif %} |
+
+
{% endfor %}
+
+ EOF
+ pip install jinja2-cli
+ echo $JOB_RESULTS | jinja2 template.html > report.html
+ echo "# CI Test Report - ${{ env.CI_RUNS }} Runs" >> $GITHUB_STEP_SUMMARY
+ cat report.html >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000000..f352a6a416
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,97 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "master", "*-c3", "master*" ]
+ pull_request:
+ branches: [ "master", "*-c3", "master*" ]
+ schedule:
+ - cron: '39 7 * * 2'
+
+jobs:
+ analyze:
+ name: Analyze (${{ matrix.language }})
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
+ # - https://gh.io/supported-runners-and-hardware-resources
+ # - https://gh.io/using-larger-runners (GitHub.com only)
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
+ timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
+ permissions:
+ # required for all workflows
+ security-events: write
+
+ # required to fetch internal or private CodeQL packs
+ packages: read
+
+ # only required for workflows in private repositories
+ actions: read
+ contents: read
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - language: c-cpp
+ build-mode: autobuild
+ - language: javascript-typescript
+ build-mode: none
+ - language: python
+ build-mode: none
+ # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
+ # Use `c-cpp` to analyze code written in C, C++ or both
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ # If the analyze step fails for one of the languages you are analyzing with
+ # "We were unable to automatically build your code", modify the matrix above
+ # to set the build mode to "manual" for that language. Then modify this step
+ # to build your code.
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+ - if: matrix.build-mode == 'manual'
+ shell: bash
+ run: |
+ echo 'If you are using a "manual" build mode for one or more of the' \
+ 'languages you are analyzing, replace this with the commands to build' \
+ 'your code, for example:'
+ echo ' make bootstrap'
+ echo ' make release'
+ exit 1
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 908f06e034..26b60ffec2 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -15,31 +15,24 @@ 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 }}
cancel-in-progress: true
-env:
- BASE_IMAGE: openpilot-base
-
- BUILD: selfdrive/test/docker_build.sh base
-
- RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -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
-
jobs:
docs:
name: build docs
runs-on: ubuntu-latest
- timeout-minutes: 45
- if: false # TODO: replace this with the new docs
+ timeout-minutes: 1
steps:
- uses: actions/checkout@v4
with:
submodules: true
- - uses: ./.github/workflows/setup-with-retry
- - name: Build openpilot
- run: |
- ${{ env.RUN }} "scons -j$(nproc)"
+
+ # Build
- name: Build docs
run: |
- ${{ env.RUN }} "apt update && apt install -y doxygen && cd docs && make -j$(nproc) html"
+ # TODO: can we install just the "docs" dependency group without the normal deps?
+ pip install mkdocs
+ mkdocs build
+ # Push to docs.comma.ai
- uses: actions/checkout@v4
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
with:
@@ -54,16 +47,17 @@ jobs:
source release/identity.sh
cd openpilot-docs
-
git checkout --orphan tmp
git rm -rf .
- cp -r ../build/docs/html/ docs/
- cp -r ../docs/README.md .
+ # copy over docs
+ cp -r ../docs_site/ docs/
+
+ # GitHub pages config
touch docs/.nojekyll
echo -n docs.comma.ai > docs/CNAME
- git add -f .
+ git add -f .
git commit -m "build docs"
# docs live in different repo to not bloat openpilot's full clone size
diff --git a/.github/workflows/jenkins-pr-trigger.yaml b/.github/workflows/jenkins-pr-trigger.yaml
new file mode 100644
index 0000000000..db1d524018
--- /dev/null
+++ b/.github/workflows/jenkins-pr-trigger.yaml
@@ -0,0 +1,45 @@
+name: jenkins scan
+
+on:
+ issue_comment:
+ types: [created, edited]
+
+jobs:
+ # TODO: gc old branches in a separate job in this workflow
+ scan-comments:
+ runs-on: ubuntu-latest
+ if: ${{ github.event.issue.pull_request }}
+ steps:
+ - name: Check for trigger phrase
+ id: check_comment
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const triggerPhrase = "trigger-jenkins";
+ const comment = context.payload.comment.body;
+ const commenter = context.payload.comment.user.login;
+
+ const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ username: commenter
+ });
+
+ const hasWriteAccess = permissions.permission === 'write' || permissions.permission === 'admin';
+
+ return (hasWriteAccess && comment.includes(triggerPhrase));
+ result-encoding: json
+
+ - name: Checkout repository
+ if: steps.check_comment.outputs.result == 'true'
+ uses: actions/checkout@v4
+ with:
+ ref: refs/pull/${{ github.event.issue.number }}/head
+
+ - name: Push to tmp-jenkins branch
+ if: steps.check_comment.outputs.result == 'true'
+ run: |
+ git config --global user.name "github-actions[bot]"
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git checkout -b tmp-jenkins-${{ github.event.issue.number }}
+ GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
diff --git a/.github/workflows/mirror_to_gitlab.yaml b/.github/workflows/mirror_to_gitlab.yaml
new file mode 100644
index 0000000000..38c95bac20
--- /dev/null
+++ b/.github/workflows/mirror_to_gitlab.yaml
@@ -0,0 +1,46 @@
+name: Mirror to GitLab
+
+on:
+ push:
+ delete:
+ workflow_dispatch: # This enables manual triggering
+
+jobs:
+ sync:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Wait for other instances of this workflow to conclude
+ uses: softprops/turnstyle@8db075d65b19bf94e6e8687b504db69938dc3c65
+ with:
+ same-branch-only: 'true'
+ abort-after-seconds: 300
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Checkout Repository
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.ref }}
+ fetch-depth: 0 # Fetch full history
+
+ - name: Set up Git
+ run: |
+ git config --global user.name 'GitHub Action'
+ git config --global user.email 'action@github.com'
+
+ - name: Set up SSH
+ uses: webfactory/ssh-agent@v0.9.0
+ with:
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
+
+ - name: Add GitLab public keys
+ run: |
+ ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
+
+ # Note: If you have issues with "push rejected missing LFS" or something make sure you disabled LFS on the GITLAB repo if you intend to use a different LFS repo other than the target repo.
+ - name: Sync and commit changes
+ id: sync-and-commit
+ run: |
+ # Add GitLab remote
+ git remote add gitlab git@gitlab.com:sunnypilot/sunnyhaibin/sunnypilot-github-mirror.git
+ git push -u --force gitlab ${{ github.ref }}
diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml
index cf35804492..3893c7fa5e 100644
--- a/.github/workflows/repo-maintenance.yaml
+++ b/.github/workflows/repo-maintenance.yaml
@@ -46,17 +46,13 @@ jobs:
python3 -m ensurepip --upgrade
pip3 install uv
uv lock --upgrade
- - name: pre-commit autoupdate
- run: |
- git config --global --add safe.directory '*'
- pre-commit autoupdate
- name: Create Pull Request
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
with:
author: Vehicle Researcher
token: ${{ secrets.ACTIONS_CREATE_PR_PAT }}
- commit-message: Update Python packages and pre-commit hooks
- title: '[bot] Update Python packages and pre-commit hooks'
+ commit-message: Update Python packages
+ title: '[bot] Update Python packages'
branch: auto-package-updates
base: master
delete-branch: true
diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml
index c47c5d383b..f452fc7ba3 100644
--- a/.github/workflows/selfdrive_tests.yaml
+++ b/.github/workflows/selfdrive_tests.yaml
@@ -25,7 +25,7 @@ env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: selfdrive/test/docker_build.sh base
- RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PRE_COMMIT_HOME=/tmp/pre-commit -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/pre-commit:/tmp/pre-commit -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
+ RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 -n logical
@@ -45,7 +45,6 @@ jobs:
- name: Build devel
timeout-minutes: 1
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
- - uses: ./.github/workflows/setup-pre-commit
- uses: ./.github/workflows/setup-with-retry
- name: Check submodules
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
@@ -62,20 +61,22 @@ jobs:
cd $STRIPPED_DIR
${{ env.RUN }} "release/check-dirty.sh && \
MAX_EXAMPLES=5 $PYTEST -m 'not slow' selfdrive/car"
- - name: pre-commit
- timeout-minutes: 3
+ - name: static analysis
+ timeout-minutes: 1
run: |
cd $GITHUB_WORKSPACE
- cp .pre-commit-config.yaml $STRIPPED_DIR
cp pyproject.toml $STRIPPED_DIR
cd $STRIPPED_DIR
- ${{ env.RUN }} "unset PYTHONWARNINGS && SKIP=check-added-large-files,check-hooks-apply,check-useless-excludes pre-commit run --all && chmod -R 777 /tmp/pre-commit"
+ ${{ env.RUN }} "scripts/lint.sh"
build:
strategy:
matrix:
- arch: ${{ fromJson('["x86_64"]') }} # TODO: Re-add build test for aarch64 once we switched to ubuntu-2404
- runs-on: ubuntu-latest
+ arch: ${{ fromJson(
+ ((github.repository == 'commaai/openpilot') &&
+ ((github.event_name != 'pull_request') ||
+ (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }}
+ runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v4
with:
@@ -84,7 +85,7 @@ jobs:
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
- : # (TODO: Re-add this once we test other archs) echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV"
+ echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV"
$DOCKER_LOGIN
- uses: ./.github/workflows/setup-with-retry
with:
@@ -93,7 +94,7 @@ jobs:
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache
build_mac:
- name: build macos
+ name: build macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
@@ -103,11 +104,36 @@ jobs:
- name: Install dependencies
run: ./tools/mac_setup.sh
env:
- SKIP_PROMPT: 1
# package install has DeprecationWarnings
PYTHONWARNINGS: default
- - name: Test openpilot environment
- run: . .venv/bin/activate && scons -h
+ - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
+ - name: Getting scons cache
+ uses: 'actions/cache@v4'
+ with:
+ path: /tmp/scons_cache
+ key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
+ restore-keys: |
+ scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}
+ scons-${{ runner.arch }}-macos
+ - name: Building openpilot
+ run: . .venv/bin/activate && scons -j$(nproc)
+
+ docker_push_multiarch:
+ name: docker push multiarch tag
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
+ needs: [build]
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: false
+ - name: Setup docker
+ run: |
+ $DOCKER_LOGIN
+ - name: Merge x64 and arm64 tags
+ run: |
+ export PUSH_IMAGE=true
+ scripts/retry.sh selfdrive/test/docker_tag_multiarch.sh base x86_64 aarch64
static_analysis:
name: static analysis
@@ -118,13 +144,12 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true
- - uses: ./.github/workflows/setup-pre-commit
- uses: ./.github/workflows/setup-with-retry
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- - name: pre-commit
- timeout-minutes: 4
- run: ${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all && chmod -R 777 /tmp/pre-commit"
+ - name: static analysis
+ timeout-minutes: 1
+ run: ${{ env.RUN }} "scripts/lint.sh"
unit_tests:
name: unit tests
@@ -310,8 +335,10 @@ jobs:
})
create_ui_report:
+ # This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
name: Create UI Report
runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
@@ -328,55 +355,5 @@ jobs:
- name: Upload Test Report
uses: actions/upload-artifact@v4
with:
- name: report-${{ inputs.run_number }}
- path: selfdrive/ui/tests/test_ui/report_${{ inputs.run_number }}
- - name: Get changes to selfdrive/ui
- if: ${{ github.event_name == 'pull_request' }}
- id: changed-files
- uses: tj-actions/changed-files@v44
- with:
- files: |
- selfdrive/ui/**
- - name: Checkout ci-artifacts
- if: ${{ github.event_name == 'pull_request' && steps.changed-files.outputs.any_changed == 'true' }}
- uses: actions/checkout@v4
- with:
- repository: commaai/ci-artifacts
- ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
- path: ${{ github.workspace }}/ci-artifacts
- ref: master
- - name: Push Screenshots
- if: ${{ github.event_name == 'pull_request' && steps.changed-files.outputs.any_changed == 'true' }}
- working-directory: ${{ github.workspace }}/ci-artifacts
- run: |
- git checkout -b openpilot/pr-${{ github.event.pull_request.number }}
- git config user.name "GitHub Actions Bot"
- git config user.email "<>"
- sudo mv ${{ github.workspace }}/selfdrive/ui/tests/test_ui/report/screenshots/* .
- git add .
- git commit -m "screenshots for PR #${{ github.event.pull_request.number }}"
- git push origin openpilot/pr-${{ github.event.pull_request.number }} --force
- - name: Comment Screenshots on PR
- if: ${{ github.event_name == 'pull_request' && steps.changed-files.outputs.any_changed == 'true' }}
- uses: thollander/actions-comment-pull-request@v2
- with:
- message: |
-
- ## UI Screenshots
-
-
-  |
-  |
-
-
-  |
-  |
-
-
-  |
-  |
-
-
- comment_tag: run_id_screenshots
- pr_number: ${{ github.event.pull_request.number }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ name: report-${{ github.event.number }}
+ path: selfdrive/ui/tests/test_ui/report_1/screenshots
diff --git a/.github/workflows/setup-pre-commit/action.yaml b/.github/workflows/setup-pre-commit/action.yaml
deleted file mode 100644
index f07a106861..0000000000
--- a/.github/workflows/setup-pre-commit/action.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: 'set up pre-commit environment'
-
-runs:
- using: "composite"
- steps:
- - uses: ./.github/workflows/auto-cache
- with:
- path: .ci_cache/pre-commit
- key: pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
- restore-keys: |
- pre-commit-
- save: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' }}
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index a26691ea45..f7f92a0387 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -5,8 +5,8 @@ on:
workflow_dispatch:
env:
- DAYS_BEFORE_PR_CLOSE: 3
- DAYS_BEFORE_PR_STALE: 14
+ DAYS_BEFORE_PR_CLOSE: 2
+ DAYS_BEFORE_PR_STALE: 9
jobs:
stale:
diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml
index b5eae7214e..3c615d28cc 100644
--- a/.github/workflows/tools_tests.yaml
+++ b/.github/workflows/tools_tests.yaml
@@ -21,7 +21,7 @@ env:
BUILD: selfdrive/test/docker_build.sh base
- RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -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
+ RUN: docker run --shm-size 2G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -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
jobs:
@@ -74,12 +74,6 @@ jobs:
scons-${{ runner.arch }}-ubuntu2004
- name: Building openpilot
run: uv run scons -u -j$(nproc)
- - name: Saving scons cache
- uses: actions/cache/save@v4
- if: github.ref == 'refs/heads/master'
- with:
- path: /tmp/scons_cache
- key: scons-${{ runner.arch }}-ubuntu2004-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
devcontainer:
name: devcontainer
diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml
new file mode 100644
index 0000000000..bc19e23778
--- /dev/null
+++ b/.github/workflows/ui_preview.yaml
@@ -0,0 +1,91 @@
+name: "ui preview"
+on:
+ pull_request_target:
+ types: [assigned, opened, synchronize, reopened, edited]
+ branches:
+ - 'master'
+ paths:
+ - 'selfdrive/ui/**'
+
+env:
+ UI_JOB_NAME: "Create UI Report"
+
+jobs:
+ preview:
+ if: github.repository == 'commaai/openpilot'
+ name: preview
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ contents: read
+ pull-requests: write
+ actions: read
+ steps:
+ - name: Waiting for ui test to start
+ run: sleep 30
+
+ - name: Wait for ui report
+ uses: lewagon/wait-on-check-action@v1.3.4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ check-name: ${{ env.UI_JOB_NAME }}
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ allowed-conclusions: success
+ wait-interval: 20
+
+ - name: Get workflow run ID
+ id: get_run_id
+ run: |
+ echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT
+
+ - name: Checkout ci-artifacts
+ uses: actions/checkout@v4
+ with:
+ repository: commaai/ci-artifacts
+ ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
+ path: ${{ github.workspace }}/ci-artifacts
+ ref: master
+
+ - name: Download artifact
+ id: download-artifact
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ run_id: ${{ steps.get_run_id.outputs.run_id }}
+ search_artifacts: true
+ name: report-${{ github.event.number }}
+ path: ${{ github.workspace }}/ci-artifacts
+
+ - name: Push Screenshots
+ working-directory: ${{ github.workspace }}/ci-artifacts
+ run: |
+ git checkout -b openpilot/pr-${{ github.event.number }}
+ git config user.name "GitHub Actions Bot"
+ git config user.email "<>"
+ git add ${{ github.workspace }}/ci-artifacts/*
+ git commit -m "screenshots for PR #${{ github.event.number }}"
+ git push origin openpilot/pr-${{ github.event.number }} --force
+
+ - name: Comment Screenshots on PR
+ uses: thollander/actions-comment-pull-request@v2
+ with:
+ message: |
+
+ ## UI Screenshots
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+ comment_tag: run_id_screenshots
+ pr_number: ${{ github.event.number }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 61e828aa17..1dfb3f3514 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,8 @@ model2.png
a.out
.hypothesis
+/docs_site/
+
*.dylib
*.DSYM
*.d
@@ -103,4 +105,4 @@ Pipfile
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
-.ionide
\ No newline at end of file
+.ionide
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 21958355c5..23881001fe 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,10 @@ variables:
GIT_CONFIG_USER_EMAIL: "gitlab@pipeline.com"
GIT_CONFIG_USER_NAME: "Gitlab Pipeline"
PUBLIC_REPO_URL: "https://github.com/sunnyhaibin/sunnypilot"
+ BASE_BUILD_NUMER: 3000
+
+ EXTRA_VERSION_IDENTIFIER: "${CI_PIPELINE_IID}"
+ NEW_BRANCH: ${CI_COMMIT_REF_NAME}-prebuilt
stages:
- build
@@ -23,44 +27,43 @@ default:
- x86
.default_before_script: &default_before_script
- - 'export VERSION=$(eval $VERSION)${EXTRA_VERSION_IDENTIFIER}'
+ - 'if [ "$EXTRA_VERSION_IDENTIFIER" = "$CI_PIPELINE_IID" ]; then export EXTRA_VERSION_IDENTIFIER=$((CI_PIPELINE_IID + BASE_BUILD_NUMER)); fi'
+ - 'export VERSION=$(eval $VERSION)-${EXTRA_VERSION_IDENTIFIER}'
- 'mkdir -p "${BUILD_DIR}/"'
- 'git config --global user.email "${GIT_CONFIG_USER_EMAIL}"'
- 'git config --global user.name "${GIT_CONFIG_USER_NAME}"'
-workflow: # If running on any branch other than main, use the `aws-datacontracts-dev` account; otherwise use `aws-datacontracts`
+workflow: # If running on any branch other than main.
rules:
# We are an MR, but it's a draft, we won't proceed with anything.
- if: '$CI_MERGE_REQUEST_TITLE =~ /^wip:/i || $CI_MERGE_REQUEST_TITLE =~ /^draft:/i'
when: never
- # We are a merge request
- - if: $CI_MERGE_REQUEST_IID #|| $CI_COMMIT_REF_NAME == "gitlab-pipelines" # TBD once merged
- variables:
- EXTRA_VERSION_IDENTIFIER: "-${CI_PIPELINE_IID}"
- NEW_BRANCH: ${CI_COMMIT_REF_NAME}-prebuilt
- when: always
# Below are the rules when a commit is done (code has been added to the branch)
# Commit to master-dev-c3
- if: $CI_COMMIT_REF_NAME == $DEV_C3_SOURCE_BRANCH
variables:
- EXTRA_VERSION_IDENTIFIER: "-${CI_PIPELINE_IID}"
+ EXTRA_VERSION_IDENTIFIER: "${CI_PIPELINE_IID}"
NEW_BRANCH: "dev-c3"
+ AUTO_BUILD: true
when: always
#commit made to main (master)
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
variables:
NEW_BRANCH: "staging-c3"
VERSION: 'cat common/version.h | grep COMMA_VERSION | sed -e "s/[^0-9|.]//g"'
- EXTRA_VERSION_IDENTIFIER: "-staging"
+ EXTRA_VERSION_IDENTIFIER: "staging"
+ AUTO_PUBLISH: true
+ AUTO_BUILD: true
when: always
# if tag
- if: $CI_COMMIT_TAG
variables:
NEW_BRANCH: "release-c3"
VERSION: 'cat common/version.h | grep COMMA_VERSION | sed -e "s/[^0-9|.]//g"'
- EXTRA_VERSION_IDENTIFIER: "-release"
+ EXTRA_VERSION_IDENTIFIER: "release"
+ AUTO_BUILD: true
- when: always
@@ -92,7 +95,7 @@ build:
- sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py # comment panda jungle when prebuilt
- scons -j$(nproc) cache_dir=${CI_DIR}/scons_cache --minimal
- touch ${BUILD_DIR}/prebuilt
- - sudo rm -rf ${OUTPUT_DIR}
+ - sudo rm -rf ${OUTPUT_DIR}
- mkdir -p ${OUTPUT_DIR}
# We first include the paths we want to keep, even if we later will be excluding the other things on those paths
- rsync -avm
@@ -129,43 +132,22 @@ build:
--exclude='**/selfdrive/ui/**/*.h'
--exclude='**/selfdrive/ui/qt/offroad/sunnypilot/'
--exclude='**/.git/'
+ --exclude='**/SConstruct'
+ --exclude='**/SConscript'
--delete-excluded
--chown=comma:comma
${BUILD_DIR}/ ${OUTPUT_DIR}/
after_script:
- # cleanup build dir after doing work
+ # cleanup build dir after doing work
- find $BUILD_DIR/ -mindepth 1 -delete
artifacts:
paths:
- ${OUTPUT_DIR}/
tags: [ 'sunnypilot', 'tici' ]
rules:
- - if: $CI_MERGE_REQUEST_IID
- when: manual
- - if: $NEW_BRANCH
+ - if: $AUTO_BUILD
when: always
-
-check no source code sent:
- image: alpine
- stage: sanity
- variables:
- FORBIDDEN_FILE_EXTENSIONS: "*.a,*.o,*.os,*.pyc,moc_*,*.cc,Jenkinsfile,supercombo.onnx,.sconsign.dblite"
- FORBIDDEN_DIR_PATTERNS: "*panda/certs,*panda/crypto,*release,*.github,*selfdrive/ui/replay,*__pycache__"
- REQUIRED_FILE_EXTENSIONS: "*.py,*.json"
- REQUIRED_DIR_PATTERNS: "*selfdrive/ui,*openpilot"
- before_script:
- - apk update && apk upgrade
- - apk add bash findutils
- script:
- - cd ${OUTPUT_DIR}
- - echo "Checking that we have properly cleaned up"
- - ${CI_DIR}/sanity_check.sh "$FORBIDDEN_FILE_EXTENSIONS" "$FORBIDDEN_DIR_PATTERNS" true
- - echo "Checking that our sanity check works and also checking that some required files are indeed found"
- - ${CI_DIR}/sanity_check.sh "$REQUIRED_FILE_EXTENSIONS" "$REQUIRED_DIR_PATTERNS" false
- rules:
- - if: $NEW_BRANCH
- when: on_success
- - when: never
+ - when: manual
.publish_base: &publish_base
image: alpine
@@ -175,9 +157,6 @@ check no source code sent:
needs:
- job: build
artifacts: true
- - job: "check no source code sent"
- artifacts: false
- optional: false
before_script:
- 'apk update && apk upgrade'
- 'apk add git bash openssh'
@@ -194,17 +173,19 @@ check no source code sent:
- echo "${GIT_ORIGIN}"
- echo "Calling to publish [${CI_DIR}/publish.sh ${CI_PROJECT_DIR} ${OUTPUT_DIR} ${NEW_BRANCH} ${VERSION} ${GIT_ORIGIN}]"
- git config --global --add safe.directory ${OUTPUT_DIR}
- - $CI_DIR/publish.sh "${CI_PROJECT_DIR}" "${OUTPUT_DIR}" "${NEW_BRANCH}" "${VERSION}" "${GIT_ORIGIN}" "${EXTRA_VERSION_IDENTIFIER}"
+ - $CI_DIR/publish.sh "${CI_PROJECT_DIR}" "${OUTPUT_DIR}" "${NEW_BRANCH}" "${VERSION}" "${GIT_ORIGIN}" "-${EXTRA_VERSION_IDENTIFIER}"
allow_failure: false
publish to private gitlab prebuilt:
extends: ".publish_base"
variables:
- GIT_ORIGIN: git@gitlab.com:sunnypilot/public/sunnypilot.git
+ GIT_ORIGIN: git@gitlab.com:sunnypilot/public/sunnypilot-prebuilts.git
rules:
- - if: $NEW_BRANCH
+ - if: $AUTO_BUILD
when: on_success
- - when: never
+ - if: $CI_MERGE_REQUEST_IID
+ when: on_success
+ - when: manual
publish to public github prebuilt:
extends: ".publish_base"
@@ -213,9 +194,9 @@ publish to public github prebuilt:
GIT_CONFIG_USER_EMAIL: "jason.wen@sunnypilot.ai"
GIT_CONFIG_USER_NAME: "Jason Wen"
rules:
- - if: $NEW_BRANCH
- when: manual
- - when: never
+ - if: $AUTO_PUBLISH
+ when: on_success
+ - when: manual
.notify_discord: ¬ify_discord
image: alpine
@@ -249,6 +230,9 @@ notify new dev build:
needs: ["publish to public github prebuilt"] # This notify shall only happen after a publish to github public
variables:
TEMPLATE: "discord_template_notify_dev_public.json"
+ before_script:
+ - !reference [".notify_discord", "before_script"]
+ - export EXTRA_VERSION_IDENTIFIER=$((CI_PIPELINE_IID + BASE_BUILD_NUMER))
rules:
- if: $NEW_BRANCH == "dev-c3"
variables:
diff --git a/.gitmodules b/.gitmodules
index 2fb99d017c..a61bed1e2f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,21 +1,18 @@
[submodule "panda"]
path = panda
- url = ../../sunnyhaibin/panda-special.git
+ url = https://github.com/sunnyhaibin/panda.git
[submodule "opendbc"]
- path = opendbc
- url = https://github.com/sunnyhaibin/opendbc.git
+ path = opendbc_repo
+ url = https://github.com/sunnypilot/opendbc.git
[submodule "msgq"]
path = msgq_repo
- url = https://github.com/commaai/msgq.git
+ url = https://github.com/sunnypilot/msgq.git
[submodule "rednose_repo"]
path = rednose_repo
url = https://github.com/commaai/rednose.git
-[submodule "body"]
- path = body
- url = https://github.com/commaai/body.git
[submodule "teleoprtc_repo"]
path = teleoprtc_repo
url = https://github.com/commaai/teleoprtc
[submodule "tinygrad"]
path = tinygrad_repo
- url = https://github.com/geohot/tinygrad.git
+ url = https://github.com/tinygrad/tinygrad.git
diff --git a/.importlinter b/.importlinter
new file mode 100644
index 0000000000..f77ea24b39
--- /dev/null
+++ b/.importlinter
@@ -0,0 +1,41 @@
+[importlinter]
+root_packages =
+ openpilot
+
+[importlinter:contract:1]
+name = Forbid imports from openpilot.selfdrive.car to openpilot.system
+type = forbidden
+source_modules =
+ openpilot.selfdrive.car
+forbidden_modules =
+ openpilot.system
+ openpilot.body
+ openpilot.docs
+ openpilot.msgq
+ openpilot.panda
+ openpilot.rednose
+ openpilot.release
+ openpilot.teleoprtc
+ openpilot.tinygrad
+ignore_imports =
+ openpilot.selfdrive.car.card -> openpilot.common.realtime
+ openpilot.selfdrive.car.card -> openpilot.selfdrive.controls.lib.events
+ openpilot.selfdrive.car.interfaces -> openpilot.selfdrive.controls.lib.events
+ openpilot.selfdrive.car.tests.test_models -> openpilot.tools.lib.logreader
+ openpilot.selfdrive.car.tests.test_models -> openpilot.selfdrive.car.card
+ openpilot.selfdrive.car.tests.test_models -> openpilot.tools.lib.route
+ openpilot.selfdrive.car.tests.test_models -> openpilot.system.hardware.hw
+ openpilot.selfdrive.car.tests.test_models -> openpilot.selfdrive.test.helpers
+ openpilot.selfdrive.car.isotp_parallel_query -> openpilot.common.swaglog
+ openpilot.selfdrive.car.fw_versions -> openpilot.common.swaglog
+ openpilot.selfdrive.car.disable_ecu -> openpilot.common.swaglog
+ openpilot.selfdrive.car.vin -> openpilot.common.swaglog
+ openpilot.selfdrive.car.ecu_addrs -> openpilot.common.swaglog
+ openpilot.selfdrive.car.car_helpers -> openpilot.common.swaglog
+ openpilot.selfdrive.car.car_helpers -> openpilot.system.version
+ openpilot.selfdrive.car.interfaces -> openpilot.selfdrive.controls.lib.drive_helpers
+ openpilot.selfdrive.car.tests.test_car_interfaces -> openpilot.selfdrive.controls.lib.latcontrol_angle
+ openpilot.selfdrive.car.tests.test_car_interfaces -> openpilot.selfdrive.controls.lib.longcontrol
+ openpilot.selfdrive.car.tests.test_car_interfaces -> openpilot.selfdrive.controls.lib.latcontrol_torque
+ openpilot.selfdrive.car.tests.test_car_interfaces -> openpilot.selfdrive.controls.lib.latcontrol_pid
+unmatched_ignore_imports_alerting = warn
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index 676fc514da..0000000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,98 +0,0 @@
-exclude: '^(tinygrad_repo)'
-repos:
-- repo: meta
- hooks:
- - id: check-hooks-apply
- - id: check-useless-excludes
-- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.6.0
- hooks:
- - id: check-ast
- exclude: '^(third_party)/'
- - id: check-json
- exclude: '.devcontainer/devcontainer.json|.vscode/' # these support JSON with comments
- - id: check-toml
- - id: check-xml
- - id: check-yaml
- - id: check-merge-conflict
- - id: check-symlinks
- - id: check-executables-have-shebangs
- - id: check-shebang-scripts-are-executable
- - id: check-added-large-files
- exclude: '(docs/CARS.md)|(uv.lock)|(third_party/acados/include/blasfeo/include/blasfeo_d_kernel.h)'
- args:
- - --maxkb=120
- - --enforce-all
-- repo: https://github.com/codespell-project/codespell
- rev: v2.3.0
- hooks:
- - id: codespell
- exclude: '^(third_party/)|(body/)|(msgq/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(selfdrive/ui/translations/.*.ts)|(uv.lock)'
- args:
- # if you've got a short variable name that's getting flagged, add it here
- - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn
- - --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US
-- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.5.0
- hooks:
- - id: ruff
- exclude: '^(third_party/)|(msgq/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)'
-- repo: local
- hooks:
- - id: mypy
- name: mypy
- entry: mypy
- language: system
- types: [python]
- args:
- - --local-partial-types
- - --explicit-package-bases
- exclude: '^(third_party/)|(body/)|(msgq/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)'
-- repo: local
- hooks:
- - id: cppcheck
- name: cppcheck
- entry: cppcheck
- language: system
- types: [c++]
- exclude: '^(third_party/)|(msgq/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)|(installer/)'
- args:
- - --error-exitcode=1
- - --language=c++
- - --quiet
- - --force
- - -j8
- - --library=qt
- - --include=third_party/kaitai/kaitaistream.h
-- repo: https://github.com/cpplint/cpplint
- rev: 1.6.1
- hooks:
- - id: cpplint
- exclude: '^(third_party/)|(msgq/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)'
- args:
- - --quiet
- - --counting=total
- - --linelength=240
- # https://google.github.io/styleguide/cppguide.html
- # relevant rules are whitelisted, see all options with: cpplint --filter=
- - --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces
-- repo: https://github.com/MarcoGorelli/cython-lint
- rev: v0.16.2
- hooks:
- - id: cython-lint
- exclude: '^(third_party/)|(msgq/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)'
- args:
- - --max-line-length=240
- - --ignore=E111, E302, E305
-- repo: local
- hooks:
- - id: test_translations
- name: test translations
- entry: pytest selfdrive/ui/tests/test_translations.py
- language: system
- pass_filenames: false
- files: '^selfdrive/ui/translations/'
-- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.28.6
- hooks:
- - id: check-github-workflows
diff --git a/CHANGELOGS.md b/CHANGELOGS.md
index f8d25f602c..87021fa9e1 100644
--- a/CHANGELOGS.md
+++ b/CHANGELOGS.md
@@ -36,14 +36,16 @@ sunnypilot - 0.9.8.0 (2024-xx-xx)
* In response to the official deprecation of support for Smart DSU (SDSU) and Radar CAN Filter in the upstream ([commaai/openpilot#32777](https://github.com/commaai/openpilot/pull/32777)), sunnypilot will continue maintaining software support for Smart DSU (SDSU) and Radar CAN Filter
* UPDATED: Continued support for Mapbox navigation
* In response to the official temporary deprecation of support for Mapbox navigation in the upstream ([commaai/openpilot#32773](https://github.com/commaai/openpilot/pull/32773)), sunnypilot will continue maintaining software support for Mapbox navigation
-* NEW❗: Toyota - Automatic Brake Hold (AHB) thanks to AlexandreSato!
- * When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold
- * NOTE: Only for Toyota/Lexus vehicles with TSS2/LSS2
* NEW❗: Toyota - Automatic Door Locking and Unlocking thanks to AlexandreSato, cydia2020, and dragonpilot-community!
* Auto Lock by Speed: All doors are automatically locked when vehicle speed is approximately 6 mph (10 km/h) or higher
* Auto Unlock by Shift to P: All doors are automatically unlocked when shifting the shift lever to P
* FIXED: Driving Personality:
* Maniac mode now correctly enforced when selected
+* UI Updates
+ * Display Metrics Below Chevron
+ * NEW❗: Time to Lead Car
+ * Displays the time to reach the position previously occupied by the lead car
+ * NEW❗: Display Distance, Speed, and Time to Lead Car simultaneously
* Kia Ceed Plug-in Hybrid Non-SCC 2022 support thanks to TerminatorNL!
sunnypilot - 0.9.7.1 (2024-06-13)
diff --git a/HOW-TOS.md b/HOW-TOS.md
new file mode 100644
index 0000000000..a66786c200
--- /dev/null
+++ b/HOW-TOS.md
@@ -0,0 +1,77 @@
+# How Tos
+This page is a repository of useful how-tos as a supplement for additional information.
+
+Table of Contents
+=======================
+
+* [Radar Tracks](#Radar-Tracks)
+ * [Enable Radar Tracks](#-Enable-Radar-Tracks)
+* [Enable Mapbox Navigation](#-Enable-Mapbox-Navigation)
+
+---
+
+📡 Radar Tracks
+
+Radar tracks can now be enabled manually on applicable cars through SSH thanks to [@greghogan](https://github.com/greghogan) and [@pd0wm](https://github.com/pd0wm).
+
+Some Hyundai radars can be reconfigured to output (debug) radar points on bus 1.
+Reconfiguration is done over UDS by reading/writing to 0x0142 using the Read/Write Data By Identifier
+endpoints (0x22 & 0x2E). This script checks your radar firmware version against a list of known
+firmware versions. If you want to try on a new radar, make sure to note the default config value
+in case it is different from the other radars and you need to revert the changes.
+After changing the config the car should not show any faults when openpilot is not running.
+These config changes are persistent across car reboots. You need to run this script again
+to go back to the default values.
+
+**USE AT YOUR OWN RISK!** Stock system safety features, like AEB and FCW, might be affected by these changes.
+
+**How radar points can be used along with vision:**
+* Current OP long policy is identify with vision first, if vision sees a vehicle match it to a radar point. If vision sees nothing you get a false negative and no lead car detection. (Source: [Hubblesphere#7894 from comma.ai community Discord](https://discord.com/channels/469524606043160576/872899198738104330/872913890793635872))
+
+### 🚨 Enable Radar Tracks
+
+***(EXPERIMENTAL, as of January 1st, 2022)***
+
+***(Only applicable to some Hyundai, Kia, and Genesis cars, as of January 1st, 2022)***
+
+*(Base on version 0.8.12 [`devel`](https://github.com/commaai/openpilot/tree/devel))*
+
+**USE AT YOUR OWN RISK!** Stock system safety features, like AEB and FCW, might be affected by these changes.
+
+1. Ensure the car is at the `OFF` ignition position.
+2. Connect your compatible comma device (EON, C2, C3) to the car. comma device power should be ON.
+3. Use a laptop or applicable device to connect to your comma device via SSH. (Tips: Instructions to SSH in [HERE](https://github.com/commaai/openpilot/wiki/SSH))
+4. In the SSH terminal after successfully connected to your comma device, execute the following commands:
+ 1. `pkill -f openpilot`
+ 2. `python /data/openpilot/selfdrive/debug/hyundai_enable_radar_points.py`
+ 3. Follow the instructions in the script:
+ * `Power on the vehicle keeping the engine off (press start button twice) then type OK to continue`.
+ * If successful, the following message should appear: `[DONE]. Restart your vehicle and ensure there are no faults`.
+ * If the script did not run successfully, reach out to the community in [Sunnyhaibin's Openpilot Discord Server](https://discord.gg/wRW3meAgtx) or `#hyundai-kia-genesis channel` on [commaai community Discord Server](https://discord.comma.ai) for assistance.
+ 4. Reboot your comma device:
+ 1. C3: `sudo reboot`
+ 2. C2 or EON: `reboot`
+5. Once your comma device is rebooted, start your car with engine on (with or without comma device connected). Ensure that there are no faults from the car. If there are faults, reach out to the community in [Sunnyhaibin's Openpilot Discord Server](https://discord.gg/wRW3meAgtx) or `#hyundai-kia-genesis channel` on [commaai community Discord Server](https://discord.comma.ai) for assistance.
+6. Go for a quick drive and drive behind a lead car with varied follow distance. Then, come back and allow the drive to upload its `rlogs` in [comma Connect](https://connect.comma.ai).
+7. With all `rlogs` uploaded, open the drive in Cabana from [comma Connect](https://connect.comma.ai). Load DBC -> `hyundai_kia_mando_front_radar.dbc`, then search `RADAR_TRACK_50x` (`x` could be anything), open any of them, and look at `LONG_DIST`.
+8. If the radar tracks data is relevant with the lead car you drove behind, you are done! Your car now have radar tracks enabled.
+
+
+🗺 Enable Mapbox Navigation
+
+1) Create a free mapbox account. Account will ask for a credit card for verification. You will not be charged for the free tier.
+2) On the Dashboard, you will see a section called Access Tokens. Click `Create a Token`. Name it whatever you like. Set the scopes to allow everything for both Public and Secret. Copy both of these keys. **YOU WON'T BE ABLE TO ACCESS THE SECRET KEY AFTER THIS WINDOW.**
+3) Once rebooted, connect your C3 to a network with internet access and find the C3’s IP address.
+4) In a browser, navigate to that IP with **port 8082** (i.e 192.168.1.69:8082). You should be greeted with the Comma logo and a public key input field.
+5) Paste your Public token (pk.xx), click enter, paste your Secret key (sk.xx), click enter. You can now search for places. This page will be available at your devices’s IP address/port 8082 to search for destinations.
+6) To set Home and Work addresses, search for a place, select Home/Work from the dropdown and click Navigate. For non-Home/Work destinations, select Recent Places.
*At this time, it is not possible to search directly on the C3.*
+
+**TIPS:**
+- If your C3 is showing a black screen that says “Map Loading”, performing a reboot via the UI should fix it.
+- If your phone can create a Hotspot, you are able to connect the C3 to your phone hotspot and use your phone browser to search for places.
+- In the Navigation panel on the C3, you can select Home, Work, and from a list of Recent Places you have navigated to without needing a browser (assuming the C3 is connected to the internet.)
+
+**IMPORTANT NOTE:** Your C3 will require an active internet connection to download map data, generate driving directions, and ETA. Once map data and directions are downloaded, it *is* possible to use it offline, however nothing will update (such as new driving direction after a missed turn, updated ETA, map data further into your drive etc.)
+
+***NAVIGATION NOTE:** At this time, mapbox does not support alphanumeric addresses (i.e W123N1234 Main St). There is currently no known workaround for this.*
+
diff --git a/Jenkinsfile b/Jenkinsfile
index 321237716b..4380b2766b 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -83,7 +83,7 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1, resourceSelectStrategy: 'random') {
docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') {
- timeout(time: 20, unit: 'MINUTES') {
+ timeout(time: 35, unit: 'MINUTES') {
retry (3) {
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
}
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000000..650b6f55b3
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+# Custom MIT License
+
+Copyright (c) 2024, Haibin Wen, SUNNYPILOT LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to view and modify the Software, subject to the following conditions:
+
+1. **Permission Required**: Permission Required for Commercial, For-Profit, or Closed Source Use: Use of the Software, in whole or in part, for any commercial purposes, for-profit projects, or in closed source projects requires explicit written permission from the original author(s).
+
+2. **Redistribution**: Any redistribution of the Software, modified or unmodified, must retain this license notice and the following acknowledgment:
+ "This software is licensed under a custom license requiring permission for use."
+
+3. **Visibility**: Any project that uses the Software must visibly mention the following acknowledgment:
+ "This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use."
+
+4. **No Warranty**: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Contact sunnypilot Support for permission requests.
+
+---
+
+Haibin Wen, SUNNYPILOT LLC
diff --git a/README.md b/README.md
index c78052ad6b..5986adbe33 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,6 @@ Join the official sunnypilot Discord server to stay up to date with all the late
To use sunnypilot in a car, you need the following:
* A supported device to run this software
* a [comma three](https://comma.ai/shop/products/three), or
- * a comma two (only with older versions below 0.8.13)
* This software
* One of [the 250+ supported cars](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
@@ -115,40 +114,12 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
Requires further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
-comma two
-------
-
-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://smiskol.com/fork/sunnyhaibin/0.8.12-4-prod```
-4. Complete the rest of the installation following the onscreen instructions.
-
-Requires further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
-
-
-
-
- SSH (More Versatile)
-
-
-Prerequisites: [How to SSH](https://github.com/commaai/openpilot/wiki/SSH)
-
-If you are looking to install sunnypilot via SSH, run the following command in an SSH terminal after connecting to your device:
-
comma three:
------
* [`release-c3`](https://github.com/sunnyhaibin/openpilot/tree/release-c3):
```
- cd /data; rm -rf ./openpilot; git clone -b release-c3 --recurse-submodules https://github.com/sunnyhaibin/sunnypilot.git openpilot; cd openpilot; sudo reboot
- ```
-
-comma two:
-------
-* [`0.8.12-prod-personal-hkg`](https://github.com/sunnyhaibin/openpilot/tree/0.8.12-prod-personal-hkg):
-
- ```
- cd /data; rm -rf ./openpilot; git clone -b 0.8.12-prod-personal-hkg --recurse-submodules https://github.com/sunnyhaibin/sunnypilot.git openpilot; cd openpilot; sudo reboot
+ cd /data && rm -rf ./openpilot && git clone -b release-c3 --recurse-submodules https://github.com/sunnyhaibin/sunnypilot.git openpilot && cd openpilot && sudo reboot
```
After running the command to install the desired branch, your comma device should reboot.
@@ -170,16 +141,16 @@ After running the command to install the desired branch, your comma device shoul
* Speed Limit Control (SLC) - Set speed limit based on map data or car interface (if applicable)
* HKG only: Highway Driving Assist (HDA) status integration - Use cars native speed sign detection to set desired speed (on applicable HKG cars only)
- [**Gap Adjust Cruise (GAC)**](#gap-adjust-cruise) - Allow `GAP`/`INTERVAL`/`DISTANCE` button on the steering wheel or on-screen button to adjust the follow distance from the lead car. See table below for options
- - [**Quiet Drive 🤫**](#-quiet-drive) - Toggle to mute all notification sounds (excluding driver safety warnings)
- - [**Auto Lane Change Timer**](#Auto-Lane-Change-Timer) - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set
- - [**Force Car Recognition (FCR)**](#Force-Car-Recognition-) - Use a selector to force your car to be recognized by sunnypilot
- - [**Fix sunnypilot No Offroad**](#Fix-sunnypilot-No-Offroad) - Enforce sunnypilot to go offroad and turns off after shutting down the car. This feature fixes non-official devices running sunnypilot without comma power
- - [**Enable ACC+MADS with RES+/SET-**](#Enable-ACC+MADS-with-RES+/SET-) - Engage both ACC and MADS with a single press of RES+ or SET- button
- - [**Offline OSM Maps**](#Offline-OSM-Maps) - OSM database can now be downloaded locally for offline use[^2]. This enables offline SLC, V-TSC and M-TSC. Currently available for US South, US West, US Northeast, Florida, Taiwan, South Africa and New Zealand
- - [**Various Live Tuning**](#Various-Live-Tuning) - Ability to tailor your driving experience on the fly:
- * Enforce Torque Lateral Control - Use the newest [torque controller](https://blog.comma.ai/0815release/#torque-controller) for all vehicles.
- * Torque Lateral Control Live Tune - Ability to adjust the torque controller’s `FRICTION` and `LAT_ACCEL_FACTOR` values to suit your vehicle.
- * Torque Lateral Controller Self-Tune - Enable automatic turning for the Torque controller.
+- [**Quiet Drive 🤫**](#-quiet-drive) - Toggle to mute all notification sounds (excluding driver safety warnings)
+- [**Auto Lane Change Timer**](#Auto-Lane-Change-Timer) - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set
+- [**Force Car Recognition (FCR)**](#Force-Car-Recognition-) - Use a selector to force your car to be recognized by sunnypilot
+- [**Fix sunnypilot No Offroad**](#Fix-sunnypilot-No-Offroad) - Enforce sunnypilot to go offroad and turns off after shutting down the car. This feature fixes non-official devices running sunnypilot without comma power
+- [**Enable ACC+MADS with RES+/SET-**](#Enable-ACC+MADS-with-RES+/SET-) - Engage both ACC and MADS with a single press of RES+ or SET- button
+- [**Offline OSM Maps**](#Offline-OSM-Maps) - OSM database can now be downloaded locally for offline use[^2]. This enables offline SLC, V-TSC and M-TSC. Currently available for US South, US West, US Northeast, Florida, Taiwan, South Africa and New Zealand
+- [**Various Live Tuning**](#Various-Live-Tuning) - Ability to tailor your driving experience on the fly:
+ * Enforce Torque Lateral Control - Use the newest [torque controller](https://blog.comma.ai/0815release/#torque-controller) for all vehicles.
+ * Torque Lateral Control Live Tune - Ability to adjust the torque controller’s `FRICTION` and `LAT_ACCEL_FACTOR` values to suit your vehicle.
+ * Torque Lateral Controller Self-Tune - Enable automatic turning for the Torque controller.
### Visual Enhancements
* **M.A.D.S Status Icon** - Dedicated icon to display M.A.D.S. engagement status
@@ -355,7 +326,7 @@ Example:
---
-How-To instructions can be found in [HOW-TOS.md](https://github.com/sunnyhaibin/openpilot/blob/(!)README/HOW-TOS.md).
+How-To instructions can be found in [HOW-TOS.md](HOW-TOS.md).
diff --git a/SConstruct b/SConstruct
index da70e4e587..1280ab7690 100644
--- a/SConstruct
+++ b/SConstruct
@@ -72,6 +72,12 @@ AddOption('--minimal',
default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS)
help='the minimum build to run openpilot. no tests, tools, etc.')
+AddOption('--stock-ui',
+ action='store_true',
+ dest='stock_ui',
+ default=False,
+ help='Build stock UI instead of sunnypilot UI')
+
## Architecture name breakdown (arch)
## - larch64: linux tici aarch64
## - aarch64: linux pc aarch64
@@ -104,7 +110,6 @@ if arch == "larch64":
libpath = [
"/usr/local/lib",
- "/usr/lib",
"/system/vendor/lib64",
f"#third_party/acados/{arch}/lib",
]
@@ -172,9 +177,12 @@ if arch != "Darwin":
ldflags += ["-Wl,--as-needed", "-Wl,--no-undefined"]
# Enable swaglog include in submodules
-cflags += ['-DSWAGLOG="\\"common/swaglog.h\\""']
cxxflags += ['-DSWAGLOG="\\"common/swaglog.h\\""']
+if not GetOption('stock_ui'):
+ cflags += ["-DSUNNYPILOT"]
+ cxxflags += ["-DSUNNYPILOT"]
+
ccflags_option = GetOption('ccflags')
if ccflags_option:
ccflags += ccflags_option.split(' ')
@@ -208,7 +216,6 @@ env = Environment(
"#third_party/json11",
"#third_party/linux/include",
"#third_party/snpe/include",
- "#third_party/qrcode",
"#third_party",
"#cereal",
"#msgq",
@@ -236,7 +243,7 @@ env = Environment(
COMPILATIONDB_USE_ABSPATH=True,
REDNOSE_ROOT="#",
tools=["default", "cython", "compilation_db", "rednose_filter"],
- toolpath=["#rednose_repo/site_scons/site_tools"],
+ toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
)
if arch == "Darwin":
@@ -276,7 +283,8 @@ if arch == "Darwin":
else:
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
-Export('envCython')
+np_version = SCons.Script.Value(np.__version__)
+Export('envCython', 'np_version')
# Qt build environment
qt_env = env.Clone()
@@ -320,7 +328,7 @@ try:
except SCons.Errors.UserError:
qt_env.Tool('qt')
-qt_env['CPPPATH'] += qt_dirs# + ["#selfdrive/ui/qt/"]
+qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"]
qt_flags = [
"-D_REENTRANT",
"-DQT_NO_DEBUG",
@@ -369,7 +377,6 @@ Export('messaging')
# Build other submodules
SConscript([
- 'body/board/SConscript',
'opendbc/can/SConscript',
'panda/SConscript',
])
diff --git a/body b/body
deleted file mode 160000
index 0e74db67ae..0000000000
--- a/body
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0e74db67ae6aaa7c30054bd4335dcafe69a5aa72
diff --git a/cereal/car.capnp b/cereal/car.capnp
index 0ee2be7499..e77e8c2943 100644
--- a/cereal/car.capnp
+++ b/cereal/car.capnp
@@ -137,7 +137,6 @@ struct CarEvent @0x9b1657f34caf3ad3 {
speedLimitPreActive @139;
speedLimitConfirmed @140;
torqueNNLoad @141;
- spAutoBrakeHold @142;
radarCanErrorDEPRECATED @15;
communityFeatureDisallowedDEPRECATED @62;
diff --git a/cereal/log.capnp b/cereal/log.capnp
index 4bd8f8ac64..df1aef8796 100644
--- a/cereal/log.capnp
+++ b/cereal/log.capnp
@@ -137,8 +137,6 @@ struct FrameData {
requestId @28 :UInt32;
encodeId @1 :UInt32;
- frameType @7 :FrameType;
-
# Timestamps
timestampEof @2 :UInt64;
timestampSof @8 :UInt64;
@@ -158,7 +156,7 @@ struct FrameData {
temperaturesC @24 :List(Float32);
- enum FrameType {
+ enum FrameTypeDEPRECATED {
unknown @0;
neo @1;
chffrAndroid @2;
@@ -175,6 +173,7 @@ struct FrameData {
frameLengthDEPRECATED @3 :Int32;
globalGainDEPRECATED @5 :Int32;
+ frameTypeDEPRECATED @7 :FrameTypeDEPRECATED;
androidCaptureResultDEPRECATED @9 :AndroidCaptureResult;
lensPosDEPRECATED @11 :Int32;
lensSagDEPRECATED @12 :Float32;
@@ -337,9 +336,9 @@ enum LaneChangeDirection {
struct CanData {
address @0 :UInt32;
- busTime @1 :UInt16;
dat @2 :Data;
src @3 :UInt8;
+ busTimeDEPRECATED @1 :UInt16;
}
struct DeviceState @0xa4d8b5af2aa492eb {
@@ -1252,6 +1251,38 @@ struct LiveLocationKalman {
}
}
+
+struct LivePose {
+ # More info on reference frames:
+ # https://github.com/commaai/openpilot/tree/master/common/transformations
+ orientationNED @0 :XYZMeasurement;
+ velocityDevice @1 :XYZMeasurement;
+ accelerationDevice @2 :XYZMeasurement;
+ angularVelocityDevice @3 :XYZMeasurement;
+
+ inputsOK @4 :Bool = false;
+ posenetOK @5 :Bool = false;
+ sensorsOK @6 :Bool = false;
+
+ filterState @7 :FilterState;
+
+ struct XYZMeasurement {
+ x @0 :Float32;
+ y @1 :Float32;
+ z @2 :Float32;
+ xStd @3 :Float32;
+ yStd @4 :Float32;
+ zStd @5 :Float32;
+ valid @6 :Bool;
+ }
+
+ struct FilterState {
+ value @0 : List(Float64);
+ std @1 : List(Float64);
+ valid @2 : Bool;
+ }
+}
+
struct ProcLog {
cpuTimes @0 :List(CPUTimes);
mem @1 :Mem;
@@ -2293,6 +2324,7 @@ struct Event {
carParams @69: Car.CarParams;
driverMonitoringState @71: DriverMonitoringState;
liveLocationKalman @72 :LiveLocationKalman;
+ livePose @129 :LivePose;
modelV2 @75 :ModelDataV2;
drivingModelData @128 :DrivingModelData;
driverStateV2 @92 :DriverStateV2;
diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py
index 4ba55cf7b9..9646047de3 100644
--- a/cereal/messaging/__init__.py
+++ b/cereal/messaging/__init__.py
@@ -2,7 +2,8 @@
from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \
set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event
from msgq.ipc_pyx import MultiplePublishersError, IpcError
-from msgq import fake_event_handle, pub_sock, sub_sock, drain_sock_raw, context
+from msgq import fake_event_handle, pub_sock, sub_sock, drain_sock_raw
+import msgq
import os
import capnp
@@ -17,8 +18,12 @@ from cereal.services import SERVICE_LIST
NO_TRAVERSAL_LIMIT = 2**64-1
-def log_from_bytes(dat: bytes) -> capnp.lib.capnp._DynamicStructReader:
- with log.Event.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as msg:
+def reset_context():
+ msgq.context = Context()
+
+
+def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event) -> capnp.lib.capnp._DynamicStructReader:
+ with struct.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as msg:
return msg
diff --git a/cereal/messaging/tests/test_messaging.py b/cereal/messaging/tests/test_messaging.py
old mode 100755
new mode 100644
index 429c2d3c53..58fa7f7b28
--- a/cereal/messaging/tests/test_messaging.py
+++ b/cereal/messaging/tests/test_messaging.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
import os
import capnp
import multiprocessing
@@ -6,8 +5,8 @@ import numbers
import random
import threading
import time
-import unittest
from parameterized import parameterized
+import pytest
from cereal import log, car
import cereal.messaging as messaging
@@ -28,12 +27,6 @@ def zmq_sleep(t=1):
if "ZMQ" in os.environ:
time.sleep(t)
-def zmq_expected_failure(func):
- if "ZMQ" in os.environ:
- return unittest.expectedFailure(func)
- else:
- return func
-
# TODO: this should take any capnp struct and returrn a msg with random populated data
def random_carstate():
@@ -58,12 +51,12 @@ def delayed_send(delay, sock, dat):
threading.Timer(delay, send_func).start()
-class TestMessaging(unittest.TestCase):
+class TestMessaging:
def setUp(self):
# TODO: ZMQ tests are too slow; all sleeps will need to be
# replaced with logic to block on the necessary condition
if "ZMQ" in os.environ:
- raise unittest.SkipTest
+ pytest.skip()
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
@@ -75,9 +68,9 @@ class TestMessaging(unittest.TestCase):
msg = messaging.new_message(evt)
except capnp.lib.capnp.KjException:
msg = messaging.new_message(evt, random.randrange(200))
- self.assertLess(time.monotonic() - msg.logMonoTime, 0.1)
- self.assertFalse(msg.valid)
- self.assertEqual(evt, msg.which())
+ assert (time.monotonic() - msg.logMonoTime) < 0.1
+ assert not msg.valid
+ assert evt == msg.which()
@parameterized.expand(events)
def test_pub_sock(self, evt):
@@ -99,8 +92,8 @@ class TestMessaging(unittest.TestCase):
# no wait and no msgs in queue
msgs = func(sub_sock)
- self.assertIsInstance(msgs, list)
- self.assertEqual(len(msgs), 0)
+ assert isinstance(msgs, list)
+ assert len(msgs) == 0
# no wait but msgs are queued up
num_msgs = random.randrange(3, 10)
@@ -108,9 +101,9 @@ class TestMessaging(unittest.TestCase):
pub_sock.send(messaging.new_message(sock).to_bytes())
time.sleep(0.1)
msgs = func(sub_sock)
- self.assertIsInstance(msgs, list)
- self.assertTrue(all(isinstance(msg, expected_type) for msg in msgs))
- self.assertEqual(len(msgs), num_msgs)
+ assert isinstance(msgs, list)
+ assert all(isinstance(msg, expected_type) for msg in msgs)
+ assert len(msgs) == num_msgs
def test_recv_sock(self):
sock = "carState"
@@ -120,14 +113,14 @@ class TestMessaging(unittest.TestCase):
# no wait and no msg in queue, socket should timeout
recvd = messaging.recv_sock(sub_sock)
- self.assertTrue(recvd is None)
+ assert recvd is None
# no wait and one msg in queue
msg = random_carstate()
pub_sock.send(msg.to_bytes())
time.sleep(0.01)
recvd = messaging.recv_sock(sub_sock)
- self.assertIsInstance(recvd, capnp._DynamicStructReader)
+ assert isinstance(recvd, capnp._DynamicStructReader)
# https://github.com/python/mypy/issues/13038
assert_carstate(msg.carState, recvd.carState)
@@ -139,16 +132,16 @@ class TestMessaging(unittest.TestCase):
# no msg in queue, socket should timeout
recvd = messaging.recv_one(sub_sock)
- self.assertTrue(recvd is None)
+ assert recvd is None
# one msg in queue
msg = random_carstate()
pub_sock.send(msg.to_bytes())
recvd = messaging.recv_one(sub_sock)
- self.assertIsInstance(recvd, capnp._DynamicStructReader)
+ assert isinstance(recvd, capnp._DynamicStructReader)
assert_carstate(msg.carState, recvd.carState)
- @zmq_expected_failure
+ @pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
def test_recv_one_or_none(self):
sock = "carState"
pub_sock = messaging.pub_sock(sock)
@@ -157,13 +150,13 @@ class TestMessaging(unittest.TestCase):
# no msg in queue, socket shouldn't block
recvd = messaging.recv_one_or_none(sub_sock)
- self.assertTrue(recvd is None)
+ assert recvd is None
# one msg in queue
msg = random_carstate()
pub_sock.send(msg.to_bytes())
recvd = messaging.recv_one_or_none(sub_sock)
- self.assertIsInstance(recvd, capnp._DynamicStructReader)
+ assert isinstance(recvd, capnp._DynamicStructReader)
assert_carstate(msg.carState, recvd.carState)
def test_recv_one_retry(self):
@@ -179,7 +172,7 @@ class TestMessaging(unittest.TestCase):
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
p.start()
time.sleep(sock_timeout*15)
- self.assertTrue(p.is_alive())
+ assert p.is_alive()
p.terminate()
# wait 15 socket timeouts before sending
@@ -187,9 +180,6 @@ class TestMessaging(unittest.TestCase):
delayed_send(sock_timeout*15, pub_sock, msg.to_bytes())
start_time = time.monotonic()
recvd = messaging.recv_one_retry(sub_sock)
- self.assertGreaterEqual(time.monotonic() - start_time, sock_timeout*15)
- self.assertIsInstance(recvd, capnp._DynamicStructReader)
+ assert (time.monotonic() - start_time) >= sock_timeout*15
+ assert isinstance(recvd, capnp._DynamicStructReader)
assert_carstate(msg.carState, recvd.carState)
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/cereal/messaging/tests/test_pub_sub_master.py b/cereal/messaging/tests/test_pub_sub_master.py
old mode 100755
new mode 100644
index 81a1cf2d57..ba5b397aad
--- a/cereal/messaging/tests/test_pub_sub_master.py
+++ b/cereal/messaging/tests/test_pub_sub_master.py
@@ -1,8 +1,6 @@
-#!/usr/bin/env python3
import random
import time
from typing import Sized, cast
-import unittest
import cereal.messaging as messaging
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
@@ -10,9 +8,9 @@ from cereal.messaging.tests.test_messaging import events, random_sock, random_so
zmq_sleep
-class TestSubMaster(unittest.TestCase):
+class TestSubMaster:
- def setUp(self):
+ def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
@@ -21,21 +19,21 @@ class TestSubMaster(unittest.TestCase):
sm = messaging.SubMaster(events)
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
- self.assertEqual(len(cast(Sized, p)), len(events))
+ assert len(cast(Sized, p)) == len(events)
def test_init_state(self):
socks = random_socks()
sm = messaging.SubMaster(socks)
- self.assertEqual(sm.frame, -1)
- self.assertFalse(any(sm.updated.values()))
- self.assertFalse(any(sm.alive.values()))
- self.assertTrue(all(t == 0. for t in sm.recv_time.values()))
- self.assertTrue(all(f == 0 for f in sm.recv_frame.values()))
- self.assertTrue(all(t == 0 for t in sm.logMonoTime.values()))
+ assert sm.frame == -1
+ assert not any(sm.updated.values())
+ assert not any(sm.alive.values())
+ assert all(t == 0. for t in sm.recv_time.values())
+ assert all(f == 0 for f in sm.recv_frame.values())
+ assert all(t == 0 for t in sm.logMonoTime.values())
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
- self.assertEqual(len(cast(Sized, p)), len(socks))
+ assert len(cast(Sized, p)) == len(socks)
def test_getitem(self):
sock = "carState"
@@ -59,8 +57,8 @@ class TestSubMaster(unittest.TestCase):
msg = messaging.new_message(sock)
pub_sock.send(msg.to_bytes())
sm.update(1000)
- self.assertEqual(sm.frame, i)
- self.assertTrue(all(sm.updated.values()))
+ assert sm.frame == i
+ assert all(sm.updated.values())
def test_update_timeout(self):
sock = random_sock()
@@ -70,9 +68,9 @@ class TestSubMaster(unittest.TestCase):
start_time = time.monotonic()
sm.update(timeout)
t = time.monotonic() - start_time
- self.assertGreaterEqual(t, timeout/1000.)
- self.assertLess(t, 5)
- self.assertFalse(any(sm.updated.values()))
+ assert t >= timeout/1000.
+ assert t < 5
+ assert not any(sm.updated.values())
def test_avg_frequency_checks(self):
for poll in (True, False):
@@ -118,12 +116,12 @@ class TestSubMaster(unittest.TestCase):
pub_sock.send(msg.to_bytes())
time.sleep(0.01)
sm.update(1000)
- self.assertEqual(sm[sock].vEgo, n)
+ assert sm[sock].vEgo == n
-class TestPubMaster(unittest.TestCase):
+class TestPubMaster:
- def setUp(self):
+ def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
@@ -156,8 +154,4 @@ class TestPubMaster(unittest.TestCase):
if capnp:
msg.clear_write_flag()
msg = msg.to_bytes()
- self.assertEqual(msg, recvd, i)
-
-
-if __name__ == "__main__":
- unittest.main()
+ assert msg == recvd, i
diff --git a/cereal/messaging/tests/test_services.py b/cereal/messaging/tests/test_services.py
old mode 100755
new mode 100644
index ec96c90bc2..01e859a5a3
--- a/cereal/messaging/tests/test_services.py
+++ b/cereal/messaging/tests/test_services.py
@@ -1,26 +1,21 @@
-#!/usr/bin/env python3
import os
import tempfile
from typing import Dict
-import unittest
from parameterized import parameterized
import cereal.services as services
from cereal.services import SERVICE_LIST
-class TestServices(unittest.TestCase):
+class TestServices:
@parameterized.expand(SERVICE_LIST.keys())
def test_services(self, s):
service = SERVICE_LIST[s]
- self.assertTrue(service.frequency <= 104)
- self.assertTrue(service.decimation != 0)
+ assert service.frequency <= 104
+ assert service.decimation != 0
def test_generated_header(self):
with tempfile.NamedTemporaryFile(suffix=".h") as f:
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name}")
- self.assertEqual(ret, 0, "generated services header is not valid C")
-
-if __name__ == "__main__":
- unittest.main()
+ assert ret == 0, "generated services header is not valid C"
diff --git a/cereal/services.py b/cereal/services.py
index 6d860ccdcf..5bc8dcfac2 100755
--- a/cereal/services.py
+++ b/cereal/services.py
@@ -16,13 +16,13 @@ _services: dict[str, tuple] = {
"gyroscope2": (True, 100., 100),
"accelerometer": (True, 104., 104),
"accelerometer2": (True, 100., 100),
- "magnetometer": (True, 25., 25),
+ "magnetometer": (True, 25.),
"lightSensor": (True, 100., 100),
"temperatureSensor": (True, 2., 200),
"temperatureSensor2": (True, 2., 200),
"gpsNMEA": (True, 9.),
"deviceState": (True, 2., 1),
- "can": (True, 100., 1223), # decimation gives ~5 msgs in a full segment
+ "can": (True, 100., 2053), # decimation gives ~3 msgs in a full segment
"controlsState": (True, 100., 10),
"pandaStates": (True, 10., 1),
"peripheralState": (True, 2., 1),
@@ -38,7 +38,7 @@ _services: dict[str, tuple] = {
"carState": (True, 100., 10),
"carControl": (True, 100., 10),
"carOutput": (True, 100., 10),
- "longitudinalPlan": (True, 20., 5),
+ "longitudinalPlan": (True, 20., 10),
"procLog": (True, 0.5, 15),
"gpsLocationExternal": (True, 10., 10),
"gpsLocation": (True, 1., 1),
@@ -47,9 +47,10 @@ _services: dict[str, tuple] = {
"gnssMeasurements": (True, 10., 10),
"clocks": (True, 0.1, 1),
"ubloxRaw": (True, 20.),
- "liveLocationKalman": (True, 20., 5),
+ "livePose": (True, 20., 4),
+ "liveLocationKalman": (True, 20.),
"liveParameters": (True, 20., 5),
- "cameraOdometry": (True, 20., 5),
+ "cameraOdometry": (True, 20., 10),
"lateralPlanDEPRECATED": (True, 20., 5),
"thumbnail": (True, 0.2, 1),
"onroadEvents": (True, 1., 1),
diff --git a/common/params.cc b/common/params.cc
index b056adc3d1..d3648793b0 100644
--- a/common/params.cc
+++ b/common/params.cc
@@ -24,8 +24,8 @@ int fsync_dir(const std::string &path) {
int result = -1;
int fd = HANDLE_EINTR(open(path.c_str(), O_RDONLY, 0755));
if (fd >= 0) {
- result = fsync(fd);
- close(fd);
+ result = HANDLE_EINTR(fsync(fd));
+ HANDLE_EINTR(close(fd));
}
return result;
}
@@ -320,7 +320,6 @@ std::unordered_map keys = {
{"TorqueLateralJerk", PERSISTENT | BACKUP},
{"TorqueMaxLatAccel", PERSISTENT | BACKUP},
{"TorquedOverride", PERSISTENT | BACKUP},
- {"ToyotaAutoHold", PERSISTENT | BACKUP},
{"ToyotaAutoLockBySpeed", PERSISTENT | BACKUP},
{"ToyotaAutoUnlockByShifter", PERSISTENT | BACKUP},
{"ToyotaEnhancedBsm", PERSISTENT | BACKUP},
@@ -338,6 +337,8 @@ std::unordered_map keys = {
{"SunnylinkCache_Users", PERSISTENT},
{"SunnylinkCache_Roles", PERSISTENT},
+ {"EnableGitlabRunner", PERSISTENT | BACKUP},
+ {"EnableSunnylinkUploader", PERSISTENT | BACKUP},
// PFEIFER - MAPD {{
{"MapdVersion", PERSISTENT},
diff --git a/common/transformations/camera.py b/common/transformations/camera.py
index dc3ca5f388..2e68b5e37c 100644
--- a/common/transformations/camera.py
+++ b/common/transformations/camera.py
@@ -47,9 +47,9 @@ class DeviceCameraConfig:
yield cam, getattr(self, cam)
_ar_ox_fisheye = CameraConfig(1928, 1208, 567.0) # focal length probably wrong? magnification is not consistent across frame
-_os_fisheye = CameraConfig(2688, 1520, 567.0 / 2 * 3)
+_os_fisheye = CameraConfig(2688 // 2, 1520 // 2, 567.0 / 4 * 3)
_ar_ox_config = DeviceCameraConfig(CameraConfig(1928, 1208, 2648.0), _ar_ox_fisheye, _ar_ox_fisheye)
-_os_config = DeviceCameraConfig(CameraConfig(2688, 1520, 2648.0 * 2 / 3), _os_fisheye, _os_fisheye)
+_os_config = DeviceCameraConfig(CameraConfig(2688 // 2, 1520 // 2, 1522.0 * 3 / 4), _os_fisheye, _os_fisheye)
_neo_config = DeviceCameraConfig(CameraConfig(1164, 874, 910.0), CameraConfig(816, 612, 650.0), _NoneCameraConfig())
DEVICE_CAMERAS = {
diff --git a/docs/CARS.md b/docs/CARS.md
index 769fe6e27a..8fafc483f2 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
-# 288 Supported Cars
+# 287 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|
Hardware Needed
|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -25,8 +25,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
-|Chrysler|Pacifica Hybrid 2017|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
-|Chrysler|Pacifica Hybrid 2018|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chrysler|Pacifica Hybrid 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
diff --git a/docs/README.md b/docs/README.md
index 838b00e7ae..08dd4fa8bc 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,3 +1,26 @@
-# openpilot-docs
+# openpilot docs
-These docs are autogenerated from [this folder](https://github.com/commaai/openpilot/tree/master/docs) in the main openpilot repository.
\ No newline at end of file
+This is the source for [docs.comma.ai](https://docs.comma.ai).
+The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
+
+## Development
+NOTE: Those commands must be run in the root directory of openpilot, **not /docs**
+
+**1. Install the docs dependencies**
+``` bash
+pip install .[docs]
+```
+
+**2. Build the new site**
+``` bash
+mkdocs build
+```
+
+**3. Run the new site locally**
+``` bash
+mkdocs serve
+```
+
+References:
+* https://www.mkdocs.org/getting-started/
+* https://github.com/ntno/mkdocs-terminal
diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md
index 10cf244fd6..477c7511ca 100644
--- a/docs/WORKFLOW.md
+++ b/docs/WORKFLOW.md
@@ -29,7 +29,7 @@ pytest
cd system/loggerd && pytest .
# run the linter
-pre-commit run --all
+op lint
```
## Testing
diff --git a/docs/new/docs/car-porting/brand-port.md b/docs/car-porting/brand-port.md
similarity index 100%
rename from docs/new/docs/car-porting/brand-port.md
rename to docs/car-porting/brand-port.md
diff --git a/docs/new/docs/car-porting/model-port.md b/docs/car-porting/model-port.md
similarity index 100%
rename from docs/new/docs/car-porting/model-port.md
rename to docs/car-porting/model-port.md
diff --git a/docs/car-porting/what-is-a-car-port.md b/docs/car-porting/what-is-a-car-port.md
new file mode 100644
index 0000000000..b918bb50e0
--- /dev/null
+++ b/docs/car-porting/what-is-a-car-port.md
@@ -0,0 +1,22 @@
+# What is a car port?
+
+A car port enables openpilot support on a particular car. Each car model openpilot supports needs to be individually ported. All car ports live in `openpilot/selfdrive/car/`.
+
+The complexity of a car port varies depending on many factors including:
+* existing openpilot support for similar cars
+* architecture and APIs available in the car
+
+
+# Structure of a car port
+* `interface.py`: Interface for the car, defines the CarInterface class
+* `carstate.py`: Reads CAN from car and builds openpilot CarState message
+* `carcontroller.py`: Builds CAN messages to send to car
+* `values.py`: Limits for actuation, general constants for cars, and supported car documentation
+* `radar_interface.py`: Interface for parsing radar points from the car
+
+
+# Overiew
+
+[Jason Young](https://github.com/jyoung8607) gave a talk at COMMA_CON with an overview of the car porting process. The talk is available on YouTube:
+
+https://youtu.be/KcfzEHB6ms4?si=5szh1PX6TksOCKmM
diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md
new file mode 100644
index 0000000000..46e8e834d2
--- /dev/null
+++ b/docs/concepts/glossary.md
@@ -0,0 +1,8 @@
+# openpilot glossary
+
+* **route**:
+* **segment**: routes are split into one minute chunks called segments.
+* **panda**: this is . See the repo.
+* **onroad**:
+* **offroad**:
+* **comma 3X**:
diff --git a/system/loggerd/README.md b/docs/concepts/logs.md
similarity index 86%
rename from system/loggerd/README.md
rename to docs/concepts/logs.md
index 714e4242a0..46ab2897df 100644
--- a/system/loggerd/README.md
+++ b/docs/concepts/logs.md
@@ -1,10 +1,8 @@
-# loggerd
+# Logging
openpilot records routes in one minute chunks called segments. A route starts on the rising edge of ignition and ends on the falling edge.
-Check out our [python library](https://github.com/commaai/openpilot/blob/master/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
-
-## log types
+Check out our [Python library](https://github.com/commaai/openpilot/blob/master/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
For each segment, openpilot records the following log types:
@@ -15,9 +13,10 @@ rlogs contain all the messages passed amongst openpilot's processes. See [cereal
## {f,e,d}camera.hevc
Each camera stream is H.265 encoded and written to its respective file.
-* fcamera.hevc is the road camera
-* ecamera.hevc is the wide road camera
-* dcamera.hevc is the driver camera
+
+* `fcamera.hevc` is the road camera
+* `ecamera.hevc` is the wide road camera
+* `dcamera.hevc` is the driver camera
## qlog.bz2 & qcamera.ts
diff --git a/docs/concepts/safety.md b/docs/concepts/safety.md
new file mode 120000
index 0000000000..f286ad4b15
--- /dev/null
+++ b/docs/concepts/safety.md
@@ -0,0 +1 @@
+../SAFETY.md
\ No newline at end of file
diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md
new file mode 100644
index 0000000000..c79bec1ac6
--- /dev/null
+++ b/docs/contributing/architecture.md
@@ -0,0 +1 @@
+# Architecture
diff --git a/docs/contributing/roadmap.md b/docs/contributing/roadmap.md
new file mode 100644
index 0000000000..22622a7a96
--- /dev/null
+++ b/docs/contributing/roadmap.md
@@ -0,0 +1,30 @@
+# Roadmap
+
+This is the roadmap for the next major openpilot releases. Also check out
+
+* [Milestones](https://github.com/commaai/openpilot/milestones) for minor releases
+* [Projects](https://github.com/commaai/openpilot/projects?query=is%3Aopen) for shorter-term projects not tied to releases
+* [Bounties](https://comma.ai/bounties) for paid individual issues
+
+## openpilot 0.10
+
+openpilot 0.10 will be the first release with a driving policy trained in
+a [learned simulator](https://youtu.be/EqQNZXqzFSI).
+
+* Driving model trained in a learned simlator
+* Always-on driver monitoring (behind a toggle)
+* GPS removed from the driving stack
+* 100KB qlogs
+* `master-ci` pushed after 1000 hours of hardware-in-the-loop testing
+* Car interface code moved into [opendbc](https://github.com/commaai/opendbc)
+* openpilot on PC for Linux x86, Linux arm64, and Mac (Apple Silicon)
+
+## openpilot 1.0
+
+openpilot 1.0 will feature a fully end-to-end driving policy.
+
+* End-to-end longitudinal control in Chill mode
+* Automatic Emergency Braking (AEB)
+* Driver monitoring with sleep detection
+* Rolling updates/releases pushed out by CI
+* [panda safety 1.0](https://github.com/orgs/commaai/projects/27)
diff --git a/docs/new/docs/getting-started/what-is-openpilot.md b/docs/getting-started/what-is-openpilot.md
similarity index 62%
rename from docs/new/docs/getting-started/what-is-openpilot.md
rename to docs/getting-started/what-is-openpilot.md
index de45ae035b..b3c56c8410 100644
--- a/docs/new/docs/getting-started/what-is-openpilot.md
+++ b/docs/getting-started/what-is-openpilot.md
@@ -1,6 +1,6 @@
# What is openpilot?
-[openpilot](http://github.com/commaai/openpilot) is an open source driver assistance system. Currently, openpilot performs the functions of Adaptive Cruise Control (ACC), Automated Lane Centering (ALC), Forward Collision Warning (FCW), and Lane Departure Warning (LDW) for a growing variety of [supported car makes, models, and model years](docs/CARS.md). In addition, while openpilot is engaged, a camera-based Driver Monitoring (DM) feature alerts distracted and asleep drivers. See more about [the vehicle integration](docs/INTEGRATION.md) and [limitations](docs/LIMITATIONS.md).
+[openpilot](http://github.com/commaai/openpilot) is an open source driver assistance system. Currently, openpilot performs the functions of Adaptive Cruise Control (ACC), Automated Lane Centering (ALC), Forward Collision Warning (FCW), and Lane Departure Warning (LDW) for a growing variety of [supported car makes, models, and model years](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). In addition, while openpilot is engaged, a camera-based Driver Monitoring (DM) feature alerts distracted and asleep drivers. See more about [the vehicle integration](https://github.com/commaai/openpilot/blob/master/docs/INTEGRATION.md) and [limitations](https://github.com/commaai/openpilot/blob/master/docs/LIMITATIONS.md).
## How do I use it?
diff --git a/tools/ssh/README.md b/docs/how-to/connect-to-comma.md
similarity index 60%
rename from tools/ssh/README.md
rename to docs/how-to/connect-to-comma.md
index 588ea71579..d9c0c701c8 100644
--- a/tools/ssh/README.md
+++ b/docs/how-to/connect-to-comma.md
@@ -1,39 +1,42 @@
-# SSH
+# connect to a comma 3/3X
-## Quick Start
+A comma 3/3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
+
+## Serial Console
+
+On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
+Connect the comma 3/3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
+
+On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/serial/connect.sh` can be used to connect.
+
+On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script.
+
+## SSH
In order to SSH into your device, you'll need a GitHub account with SSH keys. See this [GitHub article](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) for getting your account setup with SSH keys.
* Enable SSH in your device's settings
* Enter your GitHub username in the device's settings
* Connect to your device
- * Username: `comma`
- * Port: `22` or `8022`
+ * Username: `comma`
+ * Port: `22`
Here's an example command for connecting to your device using its tethered connection:
`ssh comma@192.168.43.1`
For doing development work on device, it's recommended to use [SSH agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding).
-## Notes
+### Notes
The public keys are only fetched from your GitHub account once. In order to update your device's authorized keys, you'll need to re-enter your GitHub username.
The `id_rsa` key in this directory only works while your device is in the setup state with no software installed. After installation, that default key will be removed.
-See the [community wiki](https://github.com/commaai/openpilot/wiki/SSH) for more detailed instructions and information.
+#### ssh.comma.ai proxy
-# Connecting to ssh.comma.ai
-SSH into your comma device from anywhere with `ssh.comma.ai`. Requires a [comma prime subscription](https://comma.ai/connect).
+With a [comma prime subscription](https://comma.ai/connect), you can SSH into your comma device from anywhere.
-## Setup
-
-With software version 0.6.1 or newer, enter your GitHub username on your device under Developer Settings. Your GitHub authorized public keys will become your authorized SSH keys for `ssh.comma.ai`. You can add any additional keys in `/system/comma/home/.ssh/authorized_keys.persist`.
-
-## Recommended .ssh/config
-
-With the below SSH configuration, you can type `ssh comma-{dongleid}` to connect to your device through `ssh.comma.ai`.
-For example: `ssh comma-ffffffffffffffff`
+With the below SSH configuration, you can type `ssh comma-{dongleid}` to connect to your device through `ssh.comma.ai`.
```
Host comma-*
@@ -41,20 +44,21 @@ Host comma-*
User comma
IdentityFile ~/.ssh/my_github_key
ProxyCommand ssh %h@ssh.comma.ai -W %h:%p
+
Host ssh.comma.ai
Hostname ssh.comma.ai
Port 22
IdentityFile ~/.ssh/my_github_key
```
-## One-off connection
+### One-off connection
```
ssh -i ~/.ssh/my_github_key -o ProxyCommand="ssh -i ~/.ssh/my_github_key -W %h:%p -p %p %h@ssh.comma.ai" comma@ffffffffffffffff
```
(Replace `ffffffffffffffff` with your dongle_id)
-## ssh.comma.ai host key fingerprint
+### ssh.comma.ai host key fingerprint
```
Host key fingerprint is SHA256:X22GOmfjGb9J04IA2+egtdaJ7vW9Fbtmpz9/x8/W1X4
diff --git a/docs/how-to/replay-a-drive.md b/docs/how-to/replay-a-drive.md
new file mode 100644
index 0000000000..084b6bf825
--- /dev/null
+++ b/docs/how-to/replay-a-drive.md
@@ -0,0 +1,14 @@
+# Replay
+
+Replaying is a critical tool for openpilot development and debugging.
+
+## Replaying a route
+*Hardware required: none*
+
+Just run `tools/replay/replay --demo`.
+
+## Replaying CAN data
+*Hardware required: jungle and comma 3/3X*
+
+1. Connect your PC to a jungle.
+2.
diff --git a/docs/how-to/turn-the-speed-blue.md b/docs/how-to/turn-the-speed-blue.md
new file mode 100644
index 0000000000..643023fdf4
--- /dev/null
+++ b/docs/how-to/turn-the-speed-blue.md
@@ -0,0 +1,98 @@
+# Turn the speed blue
+*A getting started guide for openpilot development*
+
+In 30 minutes, we'll get an openpilot development environment setup on your computer and make some changes to openpilot's UI.
+
+And if you have a comma 3/3X, we'll deploy the change to your device for testing.
+
+## 1. Setup your development environment
+
+Run this to clone openpilot and install all the dependencies:
+```bash
+curl -fsSL openpilot.comma.ai | bash
+```
+
+Navigate to openpilot folder & activate a Python virtual environment
+```bash
+cd openpilot
+source .venv/bin/activate
+```
+
+Then, compile openpilot:
+```bash
+scons -j8
+```
+
+## 2. Run replay
+
+We'll run the `replay` tool with the demo route to get data streaming for testing our UI changes.
+```bash
+# in terminal 1
+tools/replay/replay --demo
+
+# in terminal 2
+selfdrive/ui/ui
+```
+
+The openpilot UI should launch and show a replay of the demo route.
+
+If you have your own comma device, you can replace `--demo` with one of your own routes from comma connect.
+
+## 3. Make the speed blue
+
+Search for “mph” with git grep in the `ui` folder.
+```bash
+$ git grep "mph" selfdrive/ui/
+paint.cc: ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
+```
+
+The line right above contains the actual speed. Unfortunately, COLOR_BLUE isn’t defined, but a git grep of COLOR_WHITE shows it’s nvgRGBA(255, 255, 255, 255). Personally, I like a lighter blue, so I went with #8080FF.
+```bash
+$ git diff
+diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc
+index 821d95115..cc996eaa1 100644
+--- a/selfdrive/ui/paint.cc
++++ b/selfdrive/ui/paint.cc
+@@ -175,8 +175,8 @@ static void ui_draw_vision_speed(UIState *s) {
+ const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? 3.6 : 2.2369363));
+ const std::string speed_str = std::to_string((int)std::nearbyint(speed));
+ nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
+- ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold");
+- ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
++ ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, nvgRGBA(128, 128, 255, 255), "sans-bold");
++ ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, nvgRGBA(128, 128, 255, 200), "sans-regular");
+ }
+
+ static void ui_draw_vision_event(UIState *s) {
+```
+
+
+## 4. Rebuild UI, and admire your work
+
+```
+scons -j8 && selfdrive/ui/ui
+```
+
+
+
+## 5. Push your fork to GitHub
+
+Click fork on GitHub. Then, push with:
+```bash
+git remote rm origin
+git remote add origin git@github.com:/openpilot.git
+git add .
+git commit -m "Make the speed blue."
+git push --set-upstream origin master
+```
+
+## 6. Run your fork on device in your car!
+
+Uninstall openpilot from your device through the settings. Then, enter the URL for your very own installer:
+```
+installer.comma.ai//master
+```
+
+## 7. Admire your work IRL
+
+
diff --git a/docs/index.md b/docs/index.md
new file mode 120000
index 0000000000..74ea27aeeb
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1 @@
+getting-started/what-is-openpilot.md
\ No newline at end of file
diff --git a/docs/new/README.md b/docs/new/README.md
deleted file mode 100644
index 13980d1e18..0000000000
--- a/docs/new/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-This is the source for a new https://docs.comma.ai. It's not hosted anywhere yet, but it's easy to run locally.
-
-https://www.mkdocs.org/getting-started/
-
-```
-pip install mkdocs mkdocs-terminal
-mkdocs serve
-```
-
-inspiration:
-* https://rerun.io/docs/
-* https://docs.expo.dev/
diff --git a/docs/new/docs/car-porting/what-is-a-car-port.md b/docs/new/docs/car-porting/what-is-a-car-port.md
deleted file mode 100644
index d089d9513e..0000000000
--- a/docs/new/docs/car-porting/what-is-a-car-port.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# What is a car port?
-
-All car ports live in `openpilot/selfdrive/car/`.
-
-* interface.py: Interface for the car, defines the CarInterface class
-* carstate.py: Reads CAN from car and builds openpilot CarState message
-* carcontroller.py: Builds CAN messages to send to car
-* values.py: Limits for actuation, general constants for cars, and supported car documentation
-* radar_interface.py: Interface for parsing radar points from the car
diff --git a/docs/new/docs/how-to/turning-the-speed-blue.md b/docs/new/docs/how-to/turning-the-speed-blue.md
deleted file mode 100644
index f68faa7b9f..0000000000
--- a/docs/new/docs/how-to/turning-the-speed-blue.md
+++ /dev/null
@@ -1,4 +0,0 @@
-This section is for how-to's on common workflows.
-
-They'll be like this blog post we wrote:
-https://blog.comma.ai/turning-the-speed-blue/
diff --git a/docs/new/mkdocs.yml b/docs/new/mkdocs.yml
deleted file mode 100644
index b0f571954d..0000000000
--- a/docs/new/mkdocs.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-site_name: openpilot docs
-docs_dir: docs
-repo_url: https://github.com/commaai/openpilot/
-
-theme:
- name: terminal
- features:
- - navigation.side.toc.hide
-
-nav:
- - Getting Started:
- - What is openpilot?: getting-started/what-is-openpilot.md
- - How-to:
- - Turn the speed blue: how-to/turning-the-speed-blue.md
- - Car Porting:
- - What is a car port?: car-porting/what-is-a-car-port.md
- - Porting a car brand: car-porting/brand-port.md
- - Porting a car model: car-porting/model-port.md
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000000..743b8fa1b7
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,38 @@
+site_name: openpilot docs
+repo_url: https://github.com/commaai/openpilot/
+site_url: https://docs.comma.ai
+
+exclude_docs: README.md
+
+strict: true
+docs_dir: docs
+site_dir: docs_site/
+
+theme:
+ name: readthedocs
+ navigation_depth: 3
+
+nav:
+ - Getting Started:
+ - What is openpilot?: getting-started/what-is-openpilot.md
+ - How-to:
+ - Turn the speed blue: how-to/turn-the-speed-blue.md
+ - Connect to a comma 3/3X: how-to/connect-to-comma.md
+ #- Replay a drive: how-to/replay-a-drive.md
+ - Concepts:
+ - Logs: concepts/logs.md
+ - Safety: concepts/safety.md
+ - Car Porting:
+ - What is a car port?: car-porting/what-is-a-car-port.md
+ - Porting a car brand: car-porting/brand-port.md
+ - Porting a car model: car-porting/model-port.md
+ - Contributing:
+ - Roadmap: contributing/roadmap.md
+ #- Architecture: contributing/architecture.md
+ - Contributing Guide →: https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md
+ - Links:
+ - Blog →: https://blog.comma.ai
+ - Bounties →: https://comma.ai/bounties
+ - GitHub →: https://github.com/commaai
+ - Discord →: https://discord.comma.ai
+ - X →: https://x.com/comma_ai
diff --git a/msgq_repo b/msgq_repo
index da77480a96..e6c0716e41 160000
--- a/msgq_repo
+++ b/msgq_repo
@@ -1 +1 @@
-Subproject commit da77480a969b3a48ee8c03839e7b6a66f6b5ed97
+Subproject commit e6c0716e413927f7ea3df6088282e4f4eadfbe78
diff --git a/opendbc b/opendbc
deleted file mode 160000
index adc1fffe60..0000000000
--- a/opendbc
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit adc1fffe60c7f503a252969c112c991f6fcaa37e
diff --git a/opendbc b/opendbc
new file mode 120000
index 0000000000..7cd9a5bd1e
--- /dev/null
+++ b/opendbc
@@ -0,0 +1 @@
+opendbc_repo/opendbc
\ No newline at end of file
diff --git a/opendbc_repo b/opendbc_repo
new file mode 160000
index 0000000000..529474a50e
--- /dev/null
+++ b/opendbc_repo
@@ -0,0 +1 @@
+Subproject commit 529474a50e235db51085d8255b24092009107ccb
diff --git a/panda b/panda
index ff1ef85735..6a6cd44519 160000
--- a/panda
+++ b/panda
@@ -1 +1 @@
-Subproject commit ff1ef85735c053529a840436d33ab5f16840724e
+Subproject commit 6a6cd445195c4d897d1b39b0fe1b1783304804e8
diff --git a/pyproject.toml b/pyproject.toml
index 4b20c84e9f..90b6ecf983 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,7 +26,7 @@ dependencies = [
"pycapnp",
"Cython",
"setuptools",
- "numpy < 2.0.0", # control does not support numpy 2
+ "numpy",
# body / webrtcd
"aiohttp",
@@ -52,27 +52,30 @@ dependencies = [
"websocket_client",
# acados deps
- "casadi",
+ "casadi @ https://github.com/commaai/casadi/releases/download/nightly-release-3.6.6/casadi-3.6.6-cp312-none-manylinux2014_aarch64.whl ; (python_version == '3.12' and platform_machine == 'aarch64')", # TODO: Go back to pypi casadi when they fix aarch64 for python312
+ "casadi; platform_machine != 'aarch64' or python_version != '3.12'",
"future-fstrings",
# these should be removed
"psutil",
"pycryptodome", # used in updated/casync, panda, body, and a test
- #logreader
- "zstd",
+ # logreader
+ "zstandard",
]
[project.optional-dependencies]
docs = [
"Jinja2",
+ "natsort",
+ "mkdocs",
]
testing = [
"coverage",
"hypothesis ==6.47.*",
+ "import-linter",
"mypy",
- "pre-commit",
"pytest",
"pytest-cov",
"pytest-cpp",
@@ -83,30 +86,25 @@ testing = [
"pytest-asyncio",
"pytest-mock",
"pytest-repeat",
- "ruff"
+ "ruff",
+ "codespell",
]
dev = [
"av",
"azure-identity",
"azure-storage-blob",
- "breathe",
- "control",
"dictdiffer",
"flaky",
"inputs",
"lru-dict",
"matplotlib",
- "metadrive-simulator; platform_machine != 'aarch64'",
- "mpld3",
- "myst-parser",
- "natsort",
- "opencv-python-headless",
+ "metadrive-simulator@git+https://github.com/commaai/metadrive@opencv_headless ; platform_machine != 'aarch64'",
"parameterized >=0.8, <0.9",
- #pprofile = "*"
+ #"pprofile",
"pyautogui",
- "pygame",
"pyopencl; platform_machine != 'aarch64'", # broken on arm64
+ "pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version
"pywinctl",
"pyprof2calltree",
"rerun-sdk",
@@ -116,12 +114,8 @@ dev = [
# this is only pinned since 5.15.11 is broken
"pyqt5 ==5.15.2; platform_machine == 'x86_64'", # no aarch64 wheels for macOS/linux
-
]
-[tool.uv.sources]
-metadrive-simulator = { git = "https://github.com/commaai/metadrive.git", branch = "opencv_headless" }
-
[project.urls]
Homepage = "https://comma.ai"
@@ -132,9 +126,12 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = [ "." ]
+[tool.hatch.metadata]
+allow-direct-references = true
+
[tool.pytest.ini_options]
minversion = "6.0"
-addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup"
+addopts = "--ignore=openpilot/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup"
cpp_files = "test_*"
cpp_harness = "selfdrive/test/cpp_harness.py"
python_files = "test_*.py"
@@ -167,6 +164,13 @@ testpaths = [
"cereal/messaging/tests",
]
+[tool.codespell]
+count = true
+quiet-level = 3
+# if you've got a short variable name that's getting flagged, add it here
+ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn"
+builtin = "clear,rare,informal,usage,code,names,en-GB_to_en-US"
+
[tool.mypy]
python_version = "3.11"
plugins = [
@@ -223,7 +227,7 @@ lint.ignore = [
"UP038", # (x, y) -> x|y for isinstance
]
line-length = 160
-target-version="py311"
+target-version ="py311"
exclude = [
"body",
"cereal",
@@ -235,7 +239,8 @@ exclude = [
"teleoprtc_repo",
"third_party",
]
-lint.flake8-implicit-str-concat.allow-multiline=false
+lint.flake8-implicit-str-concat.allow-multiline = false
+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
"selfdrive".msg = "Use openpilot.selfdrive"
"common".msg = "Use openpilot.common"
@@ -247,5 +252,6 @@ lint.flake8-implicit-str-concat.allow-multiline=false
[tool.coverage.run]
concurrency = ["multiprocessing", "thread"]
+
[tool.ruff.format]
quote-style = "preserve"
diff --git a/rednose_repo b/rednose_repo
index 72b3479bab..023a6195db 160000
--- a/rednose_repo
+++ b/rednose_repo
@@ -1 +1 @@
-Subproject commit 72b3479bababc658f24cc7aa0dc8bb550f0474fc
+Subproject commit 023a6195dbfc2fa37dedc7383ed271fe639287c0
diff --git a/release/ci/discord_template_notify_dev_public.json b/release/ci/discord_template_notify_dev_public.json
index d1a7b37f96..c933ea10b4 100644
--- a/release/ci/discord_template_notify_dev_public.json
+++ b/release/ci/discord_template_notify_dev_public.json
@@ -2,7 +2,7 @@
"embeds": [
{
"title": "🎉 sunnypilot `${NEW_BRANCH}` New Update 🎉",
- "description": "[sunnypilot](${PUBLIC_REPO_URL}): Build #${CI_PIPELINE_IID} of branch [${NEW_BRANCH}](${PUBLIC_REPO_URL}/tree/${NEW_BRANCH}) has been published.\n\nDrive safe! 🚗💨",
+ "description": "[sunnypilot](${PUBLIC_REPO_URL}): Build #${EXTRA_VERSION_IDENTIFIER} of branch [${NEW_BRANCH}](${PUBLIC_REPO_URL}/tree/${NEW_BRANCH}) has been published.\n\nDrive safe! 🚗💨",
"color": 4321431
}
]
diff --git a/release/ci/sync-lfs.sh b/release/ci/sync-lfs.sh
old mode 100644
new mode 100755
index a328724351..778d310051
--- a/release/ci/sync-lfs.sh
+++ b/release/ci/sync-lfs.sh
@@ -4,4 +4,4 @@ mv .lfsconfig-comma .lfsconfig
git lfs fetch --all; git lfs pull
mv .lfsconfig .lfsconfig-comma
mv .lfsconfig.bak .lfsconfig
-git lfs fetch --all; git lfs push origin main
+git lfs fetch --all; git lfs push --all origin
diff --git a/release/release_files.py b/release/release_files.py
index 31d1518d74..017af66cd3 100755
--- a/release/release_files.py
+++ b/release/release_files.py
@@ -10,14 +10,12 @@ ROOT = HERE + "/.."
# - minimizing release download size
# - keeping the diff readable
blacklist = [
- "body/STL/",
-
"panda/drivers/",
"panda/examples/",
"panda/tests/safety/",
- "opendbc/.*.dbc$",
- "opendbc/generator/",
+ "opendbc_repo/dbc/.*.dbc$",
+ "opendbc_repo/dbc/generator/",
"cereal/.*test.*",
"^common/tests/",
@@ -28,7 +26,6 @@ blacklist = [
"selfdrive/car/tests/test_models.*",
"^tools/",
- "^scripts/",
"^tinygrad_repo/",
"matlab.*.md",
@@ -118,55 +115,55 @@ whitelist = [
"tinygrad_repo/tinygrad/.*.py",
# TODO: do this automatically
- "opendbc/comma_body.dbc",
- "opendbc/chrysler_ram_hd_generated.dbc",
- "opendbc/chrysler_ram_dt_generated.dbc",
- "opendbc/chrysler_pacifica_2017_hybrid_generated.dbc",
- "opendbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc",
- "opendbc/gm_global_a_powertrain_generated.dbc",
- "opendbc/gm_global_a_object.dbc",
- "opendbc/gm_global_a_chassis.dbc",
- "opendbc/FORD_CADS.dbc",
- "opendbc/ford_fusion_2018_adas.dbc",
- "opendbc/ford_lincoln_base_pt.dbc",
- "opendbc/honda_accord_2018_can_generated.dbc",
- "opendbc/acura_ilx_2016_can_generated.dbc",
- "opendbc/acura_rdx_2018_can_generated.dbc",
- "opendbc/acura_rdx_2020_can_generated.dbc",
- "opendbc/honda_civic_touring_2016_can_generated.dbc",
- "opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc",
- "opendbc/honda_crv_touring_2016_can_generated.dbc",
- "opendbc/honda_crv_ex_2017_can_generated.dbc",
- "opendbc/honda_crv_ex_2017_body_generated.dbc",
- "opendbc/honda_crv_executive_2016_can_generated.dbc",
- "opendbc/honda_fit_ex_2018_can_generated.dbc",
- "opendbc/honda_odyssey_exl_2018_generated.dbc",
- "opendbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc",
- "opendbc/honda_insight_ex_2019_can_generated.dbc",
- "opendbc/acura_ilx_2016_nidec.dbc",
- "opendbc/honda_civic_ex_2022_can_generated.dbc",
- "opendbc/hyundai_canfd.dbc",
- "opendbc/hyundai_kia_generic.dbc",
- "opendbc/hyundai_kia_mando_front_radar_generated.dbc",
- "opendbc/mazda_2017.dbc",
- "opendbc/nissan_x_trail_2017_generated.dbc",
- "opendbc/nissan_leaf_2018_generated.dbc",
- "opendbc/subaru_global_2017_generated.dbc",
- "opendbc/subaru_global_2020_hybrid_generated.dbc",
- "opendbc/subaru_outback_2015_generated.dbc",
- "opendbc/subaru_outback_2019_generated.dbc",
- "opendbc/subaru_forester_2017_generated.dbc",
- "opendbc/toyota_tnga_k_pt_generated.dbc",
- "opendbc/toyota_new_mc_pt_generated.dbc",
- "opendbc/toyota_nodsu_pt_generated.dbc",
- "opendbc/toyota_adas.dbc",
- "opendbc/toyota_tss2_adas.dbc",
- "opendbc/vw_golf_mk4.dbc",
- "opendbc/vw_mqb_2010.dbc",
- "opendbc/tesla_can.dbc",
- "opendbc/tesla_radar_bosch_generated.dbc",
- "opendbc/tesla_radar_continental_generated.dbc",
- "opendbc/tesla_powertrain.dbc",
+ "opendbc_repo/dbc/comma_body.dbc",
+ "opendbc_repo/dbc/chrysler_ram_hd_generated.dbc",
+ "opendbc_repo/dbc/chrysler_ram_dt_generated.dbc",
+ "opendbc_repo/dbc/chrysler_pacifica_2017_hybrid_generated.dbc",
+ "opendbc_repo/dbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc",
+ "opendbc_repo/dbc/gm_global_a_powertrain_generated.dbc",
+ "opendbc_repo/dbc/gm_global_a_object.dbc",
+ "opendbc_repo/dbc/gm_global_a_chassis.dbc",
+ "opendbc_repo/dbc/FORD_CADS.dbc",
+ "opendbc_repo/dbc/ford_fusion_2018_adas.dbc",
+ "opendbc_repo/dbc/ford_lincoln_base_pt.dbc",
+ "opendbc_repo/dbc/honda_accord_2018_can_generated.dbc",
+ "opendbc_repo/dbc/acura_ilx_2016_can_generated.dbc",
+ "opendbc_repo/dbc/acura_rdx_2018_can_generated.dbc",
+ "opendbc_repo/dbc/acura_rdx_2020_can_generated.dbc",
+ "opendbc_repo/dbc/honda_civic_touring_2016_can_generated.dbc",
+ "opendbc_repo/dbc/honda_civic_hatchback_ex_2017_can_generated.dbc",
+ "opendbc_repo/dbc/honda_crv_touring_2016_can_generated.dbc",
+ "opendbc_repo/dbc/honda_crv_ex_2017_can_generated.dbc",
+ "opendbc_repo/dbc/honda_crv_ex_2017_body_generated.dbc",
+ "opendbc_repo/dbc/honda_crv_executive_2016_can_generated.dbc",
+ "opendbc_repo/dbc/honda_fit_ex_2018_can_generated.dbc",
+ "opendbc_repo/dbc/honda_odyssey_exl_2018_generated.dbc",
+ "opendbc_repo/dbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc",
+ "opendbc_repo/dbc/honda_insight_ex_2019_can_generated.dbc",
+ "opendbc_repo/dbc/acura_ilx_2016_nidec.dbc",
+ "opendbc_repo/dbc/honda_civic_ex_2022_can_generated.dbc",
+ "opendbc_repo/dbc/hyundai_canfd.dbc",
+ "opendbc_repo/dbc/hyundai_kia_generic.dbc",
+ "opendbc_repo/dbc/hyundai_kia_mando_front_radar_generated.dbc",
+ "opendbc_repo/dbc/mazda_2017.dbc",
+ "opendbc_repo/dbc/nissan_x_trail_2017_generated.dbc",
+ "opendbc_repo/dbc/nissan_leaf_2018_generated.dbc",
+ "opendbc_repo/dbc/subaru_global_2017_generated.dbc",
+ "opendbc_repo/dbc/subaru_global_2020_hybrid_generated.dbc",
+ "opendbc_repo/dbc/subaru_outback_2015_generated.dbc",
+ "opendbc_repo/dbc/subaru_outback_2019_generated.dbc",
+ "opendbc_repo/dbc/subaru_forester_2017_generated.dbc",
+ "opendbc_repo/dbc/toyota_tnga_k_pt_generated.dbc",
+ "opendbc_repo/dbc/toyota_new_mc_pt_generated.dbc",
+ "opendbc_repo/dbc/toyota_nodsu_pt_generated.dbc",
+ "opendbc_repo/dbc/toyota_adas.dbc",
+ "opendbc_repo/dbc/toyota_tss2_adas.dbc",
+ "opendbc_repo/dbc/vw_golf_mk4.dbc",
+ "opendbc_repo/dbc/vw_mqb_2010.dbc",
+ "opendbc_repo/dbc/tesla_can.dbc",
+ "opendbc_repo/dbc/tesla_radar_bosch_generated.dbc",
+ "opendbc_repo/dbc/tesla_radar_continental_generated.dbc",
+ "opendbc_repo/dbc/tesla_powertrain.dbc",
]
# Sunnypilot whitelist
diff --git a/scripts/git_rewrite/rewrite-git-history.sh b/scripts/git_rewrite/rewrite-git-history.sh
new file mode 100755
index 0000000000..5a97e6bbad
--- /dev/null
+++ b/scripts/git_rewrite/rewrite-git-history.sh
@@ -0,0 +1,390 @@
+#!/bin/bash -e
+
+SRC=/tmp/openpilot/
+SRC_CLONE=/tmp/openpilot-clone/
+OUT=/tmp/openpilot-tiny/
+
+REWRITE_IGNORE_BRANCHES=(
+ dashcam3
+ devel
+ master-ci
+ nightly
+ release2
+ release3
+ release3-staging
+)
+
+VALIDATE_IGNORE_FILES=(
+ ".github/ISSUE_TEMPLATE/bug_report.md"
+ ".github/pull_request_template.md"
+)
+
+DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
+cd $DIR
+
+LOGS_DIR=$DIR/git-rewrite-$(date +"%Y-%m-%dT%H:%M:%S%z")
+mkdir -p $LOGS_DIR
+
+GIT_REWRITE_LOG=$LOGS_DIR/git-rewrite-log.txt
+BRANCH_DIFF_LOG=$LOGS_DIR/branch-diff-log.txt
+COMMIT_DIFF_LOG=$LOGS_DIR/commit-diff-log.txt
+
+START_TIME=$(date +%s)
+exec > >(while IFS= read -r line; do
+ CURRENT_TIME=$(date +%s)
+ ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
+ echo "[${ELAPSED_TIME}s] $line"
+done | tee -a "$GIT_REWRITE_LOG") 2>&1
+
+# INSTALL git-filter-repo
+if [ ! -f /tmp/git-filter-repo ]; then
+ echo "Installing git-filter-repo..."
+ curl -sSo /tmp/git-filter-repo https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo
+ chmod +x /tmp/git-filter-repo
+fi
+
+# MIRROR openpilot
+if [ ! -d $SRC ]; then
+ echo "Mirroring openpilot..."
+ git clone --mirror https://github.com/commaai/openpilot.git $SRC # 4.18 GiB (488034 objects)
+
+ cd $SRC
+
+ echo "Starting size $(du -sh .)"
+
+ git remote update
+
+ # the git-filter-repo analysis is bliss - can be found in the repo root/filter-repo/analysis
+ echo "Analyzing with git-filter-repo..."
+ /tmp/git-filter-repo --force --analyze
+
+ echo "Pushing to openpilot-archive..."
+ # push to archive repo - in smaller parts because the 2 GB push limit - https://docs.github.com/en/get-started/using-git/troubleshooting-the-2-gb-push-limit
+ ARCHIVE_REPO=git@github.com:commaai/openpilot-archive.git
+ git push --prune $ARCHIVE_REPO +refs/heads/master:refs/heads/master # push master first so it's the default branch (when openpilot-archive is an empty repo)
+ git push --prune $ARCHIVE_REPO +refs/heads/*:refs/heads/* # 956.39 MiB (110725 objects)
+ git push --prune $ARCHIVE_REPO +refs/tags/*:refs/tags/* # 1.75 GiB (21694 objects)
+ # git push --mirror $ARCHIVE_REPO || true # fails to push refs/pull/* (deny updating a hidden ref) for pull requests
+ # we fail and continue - more reading: https://stackoverflow.com/a/34266401/639708 and https://blog.plataformatec.com.br/2013/05/how-to-properly-mirror-a-git-repository/
+fi
+
+# REWRITE master and tags
+if [ ! -d $SRC_CLONE ]; then
+ echo "Cloning $SRC..."
+ GIT_LFS_SKIP_SMUDGE=1 git clone $SRC $SRC_CLONE
+
+ cd $SRC_CLONE
+
+ echo "Checking out old history..."
+
+ git checkout tags/v0.7.1 > /dev/null 2>&1
+ # checkout as main, since we need master ref later
+ git checkout -b main
+
+ echo "Creating setup commits..."
+
+ # rm these so we don't get conflicts later
+ git rm -r cereal opendbc panda selfdrive/ui/ui > /dev/null
+ git commit -m "removed conflicting files" > /dev/null
+
+ # skip-smudge to get rid of some lfs errors that it can't find the reference of some lfs files
+ # we don't care about fetching/pushing lfs right now
+ git lfs install --skip-smudge --local
+
+ # squash initial setup commits
+ git cherry-pick -n -X theirs 6c33a5c..59b3d06 > /dev/null
+ git commit -m "switching to master" > /dev/null
+
+ # squash the two commits
+ git reset --soft HEAD~2
+ git commit -m "switching to master" -m "$(git log --reverse --format=%B 6c33a5c..59b3d06)" -m "removed conflicting files" > /dev/null
+
+ # get commits we want to cherry-pick
+ # will start with the next commit after #59b3d06 tools is local now
+ COMMITS=$(git rev-list --reverse 59b3d06..master)
+
+ # we need this for logging
+ TOTAL_COMMITS=$(echo $COMMITS | wc -w | xargs)
+ CURRENT_COMMIT_NUMBER=0
+
+ # empty this file
+ > commit-map.txt
+
+ echo "Rewriting master commits..."
+
+ for COMMIT in $COMMITS; do
+ CURRENT_COMMIT_NUMBER=$((CURRENT_COMMIT_NUMBER + 1))
+ # echo -ne "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Cherry-picking commit: $COMMIT"\\r
+ echo "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Cherry-picking commit: $COMMIT"
+
+ # set environment variables to preserve author/committer and dates
+ export GIT_AUTHOR_NAME=$(git show -s --format='%an' $COMMIT)
+ export GIT_AUTHOR_EMAIL=$(git show -s --format='%ae' $COMMIT)
+ export GIT_COMMITTER_NAME=$(git show -s --format='%cn' $COMMIT)
+ export GIT_COMMITTER_EMAIL=$(git show -s --format='%ce' $COMMIT)
+ export GIT_AUTHOR_DATE=$(git show -s --format='%ad' $COMMIT)
+ export GIT_COMMITTER_DATE=$(git show -s --format='%cd' $COMMIT)
+
+ # cherry-pick the commit
+ if ! GIT_OUTPUT=$(git cherry-pick -m 1 -X theirs $COMMIT 2>&1); then
+ # check if the failure is because of an empty commit
+ if [[ "$GIT_OUTPUT" == *"The previous cherry-pick is now empty"* ]]; then
+ echo "Empty commit detected. Skipping commit $COMMIT"
+ git cherry-pick --skip
+ # log it was empty to the mapping file
+ echo "$COMMIT EMPTY" >> commit-map.txt
+ else
+ # handle other errors or conflicts
+ echo "Cherry-pick failed. Handling error..."
+ echo "$GIT_OUTPUT"
+ exit 1
+ fi
+ else
+ # capture the new commit hash
+ NEW_COMMIT=$(git rev-parse HEAD)
+
+ # save the old and new commit hashes to the mapping file
+ echo "$COMMIT $NEW_COMMIT" >> commit-map.txt
+
+ # append the old commit ID to the commit message
+ git commit --amend -m "$(git log -1 --pretty=%B)" -m "Former-commit-id: $COMMIT" > /dev/null
+ fi
+
+ # prune every 3000 commits to avoid gc errors
+ if [ $((CURRENT_COMMIT_NUMBER % 3000)) -eq 0 ]; then
+ echo "Pruning repo..."
+ git gc
+ fi
+ done
+
+ echo "Rewriting tags..."
+
+ # remove all old tags
+ git tag -l | xargs git tag -d
+
+ # read each line from the tag-commit-map.txt
+ while IFS=' ' read -r TAG OLD_COMMIT; do
+ # search for the new commit in commit-map.txt corresponding to the old commit
+ NEW_COMMIT=$(grep "^$OLD_COMMIT " "commit-map.txt" | awk '{print $2}')
+
+ # check if this is a rebased commit
+ if [ -z "$NEW_COMMIT" ]; then
+ # if not, then just use old commit hash
+ NEW_COMMIT=$OLD_COMMIT
+ fi
+
+ echo "Rewriting tag $TAG from commit $NEW_COMMIT"
+ git tag -f "$TAG" "$NEW_COMMIT"
+ done < "$DIR/tag-commit-map.txt"
+
+ # uninstall lfs since we don't want to touch (push to) lfs right now
+ # git push will also push lfs, if we don't uninstall (--local so just for this repo)
+ git lfs uninstall --local
+
+ # force push new master
+ git push --force origin main:master
+
+ # force push new tags
+ git push --force --tags
+fi
+
+# REWRITE branches based on master
+if [ ! -f "$SRC_CLONE/rewrite-branches-done" ]; then
+ cd $SRC_CLONE
+ > rewrite-branches-done
+
+ # empty file
+ > $BRANCH_DIFF_LOG
+
+ echo "Rewriting branches based on master..."
+
+ # will store raw diffs here, if exist
+ mkdir -p differences
+
+ # get a list of all branches except master and REWRITE_IGNORE_BRANCHES
+ BRANCHES=$(git branch -r | grep -v ' -> ' | sed 's/.*origin\///' | grep -v '^master$' | grep -v -f <(echo "${REWRITE_IGNORE_BRANCHES[*]}" | tr ' ' '\n'))
+
+ for BRANCH in $BRANCHES; do
+ # check if the branch is based on master history
+ MERGE_BASE=$(git merge-base master origin/$BRANCH) || true
+ if [ -n "$MERGE_BASE" ]; then
+ echo "Rewriting branch: $BRANCH"
+
+ # create a new branch based on the new master
+ NEW_MERGE_BASE=$(grep "^$MERGE_BASE " "commit-map.txt" | awk '{print $2}')
+ if [ -z "$NEW_MERGE_BASE" ]; then
+ echo "Error: could not find new merge base for branch $BRANCH" >> $BRANCH_DIFF_LOG
+ continue
+ fi
+ git checkout -b ${BRANCH}_new $NEW_MERGE_BASE
+
+ # get the range of commits unique to this branch
+ COMMITS=$(git rev-list --reverse $MERGE_BASE..origin/${BRANCH})
+
+ HAS_ERROR=0
+
+ # simple delimiter
+ echo "BRANCH ${BRANCH}" >> commit-map.txt
+
+ for COMMIT in $COMMITS; do
+ # set environment variables to preserve author/committer and dates
+ export GIT_AUTHOR_NAME=$(git show -s --format='%an' $COMMIT)
+ export GIT_AUTHOR_EMAIL=$(git show -s --format='%ae' $COMMIT)
+ export GIT_COMMITTER_NAME=$(git show -s --format='%cn' $COMMIT)
+ export GIT_COMMITTER_EMAIL=$(git show -s --format='%ce' $COMMIT)
+ export GIT_AUTHOR_DATE=$(git show -s --format='%ad' $COMMIT)
+ export GIT_COMMITTER_DATE=$(git show -s --format='%cd' $COMMIT)
+
+ # cherry-pick the commit
+ if ! GIT_OUTPUT=$(git cherry-pick -m 1 -X theirs $COMMIT 2>&1); then
+ # check if the failure is because of an empty commit
+ if [[ "$GIT_OUTPUT" == *"The previous cherry-pick is now empty"* ]]; then
+ echo "Empty commit detected. Skipping commit $COMMIT"
+ git cherry-pick --skip
+ # log it was empty to the mapping file
+ echo "$COMMIT EMPTY" >> commit-map.txt
+ else
+ # handle other errors or conflicts
+ echo "Cherry-pick of ${BRANCH} branch failed. Removing branch upstream..." >> $BRANCH_DIFF_LOG
+ echo "$GIT_OUTPUT" > "$LOGS_DIR/branch-${BRANCH}"
+ git cherry-pick --abort
+ git push --delete origin ${BRANCH}
+ HAS_ERROR=1
+ break
+ fi
+ else
+ # capture the new commit hash
+ NEW_COMMIT=$(git rev-parse HEAD)
+
+ # save the old and new commit hashes to the mapping file
+ echo "$COMMIT $NEW_COMMIT" >> commit-map.txt
+
+ # append the old commit ID to the commit message
+ git commit --amend -m "$(git log -1 --pretty=%B)" -m "Former-commit-id: $COMMIT" > /dev/null
+ fi
+ done
+
+ # force push the new branch
+ if [ $HAS_ERROR -eq 0 ]; then
+ # git lfs goes haywire here, so we need to install and uninstall
+ # git lfs install --skip-smudge --local
+ git lfs uninstall --local > /dev/null
+ git push -f origin ${BRANCH}_new:${BRANCH}
+ fi
+
+ # clean up local branch
+ git checkout master > /dev/null
+ git branch -D ${BRANCH}_new > /dev/null
+ else
+ echo "Deleting branch $BRANCH as it's not based on master history" >> $BRANCH_DIFF_LOG
+ git push --delete origin ${BRANCH}
+ fi
+ done
+fi
+
+# VALIDATE cherry-pick
+if [ ! -f "$SRC_CLONE/validation-done" ]; then
+ cd $SRC_CLONE
+ > validation-done
+
+ TOTAL_COMMITS=$(grep -cve '^\s*$' commit-map.txt)
+ CURRENT_COMMIT_NUMBER=0
+ COUNT_SAME=0
+ COUNT_DIFF=0
+
+ # empty file
+ > $COMMIT_DIFF_LOG
+
+ echo "Validating commits..."
+
+ # will store raw diffs here, if exist
+ mkdir -p differences
+
+ # read each line from commit-map.txt
+ while IFS=' ' read -r OLD_COMMIT NEW_COMMIT; do
+ if [ "$NEW_COMMIT" == "EMPTY" ]; then
+ continue
+ fi
+ if [ "$OLD_COMMIT" == "BRANCH" ]; then
+ echo "Branch ${NEW_COMMIT} below:" >> $COMMIT_DIFF_LOG
+ continue
+ fi
+ CURRENT_COMMIT_NUMBER=$((CURRENT_COMMIT_NUMBER + 1))
+ # retrieve short hashes and dates for the old and new commits
+ OLD_COMMIT_SHORT=$(git rev-parse --short $OLD_COMMIT)
+ NEW_COMMIT_SHORT=$(git rev-parse --short $NEW_COMMIT)
+ OLD_DATE=$(git show -s --format='%cd' $OLD_COMMIT)
+ NEW_DATE=$(git show -s --format='%cd' $NEW_COMMIT)
+
+ # echo -ne "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Comparing old commit $OLD_COMMIT_SHORT ($OLD_DATE) with new commit $NEW_COMMIT_SHORT ($NEW_DATE)"\\r
+ echo "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Comparing old commit $OLD_COMMIT_SHORT ($OLD_DATE) with new commit $NEW_COMMIT_SHORT ($NEW_DATE)"
+
+ # generate lists of files and their hashes for the old and new commits, excluding ignored files
+ OLD_FILES=$(git ls-tree -r $OLD_COMMIT | grep -vE "$(IFS='|'; echo "${VALIDATE_IGNORE_FILES[*]}")")
+ NEW_FILES=$(git ls-tree -r $NEW_COMMIT | grep -vE "$(IFS='|'; echo "${VALIDATE_IGNORE_FILES[*]}")")
+
+ # Compare the diffs
+ if diff <(echo "$OLD_FILES") <(echo "$NEW_FILES") > /dev/null; then
+ # echo "Old commit $OLD_COMMIT_SHORT and new commit $NEW_COMMIT_SHORT are equivalent."
+ COUNT_SAME=$((COUNT_SAME + 1))
+ else
+ echo "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Difference found between old commit $OLD_COMMIT_SHORT and new commit $NEW_COMMIT_SHORT" >> $COMMIT_DIFF_LOG
+ COUNT_DIFF=$((COUNT_DIFF + 1))
+ set +e
+ diff -u <(echo "$OLD_FILES") <(echo "$NEW_FILES") > "$LOGS_DIR/commit-$CURRENT_COMMIT_NUMBER-$OLD_COMMIT_SHORT-$NEW_COMMIT_SHORT"
+ set -e
+ fi
+ done < "commit-map.txt"
+
+ echo "Summary:" >> $COMMIT_DIFF_LOG
+ echo "Equivalent commits: $COUNT_SAME" >> $COMMIT_DIFF_LOG
+ echo "Different commits: $COUNT_DIFF" >> $COMMIT_DIFF_LOG
+fi
+
+if [ ! -d $OUT ]; then
+ cp -r $SRC $OUT
+
+ cd $OUT
+
+ # remove all non-master branches
+ # git branch | grep -v "^ master$" | grep -v "\*" | xargs git branch -D
+
+ # echo "cleaning up refs"
+ # delete pull request refs since we can't alter them anyway (https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally#error-failed-to-push-some-refs)
+ # git for-each-ref --format='%(refname)' | grep '^refs/pull/' | xargs -I {} git update-ref -d {}
+
+ echo "importing new lfs files"
+ # import "almost" everything to lfs
+ BRANCHES=$(git for-each-ref --format='%(refname)' refs/heads/ | sed 's%refs/heads/%%g' | grep -v -f <(echo "${REWRITE_IGNORE_BRANCHES[*]}" | tr ' ' '\n') | tr '\n' ' ')
+ git lfs migrate import --include="*.dlc,*.onnx,*.svg,*.png,*.gif,*.ttf,*.wav,selfdrive/car/tests/test_models_segs.txt,system/hardware/tici/updater,selfdrive/ui/qt/spinner_larch64,selfdrive/ui/qt/text_larch64,third_party/**/*.a,third_party/**/*.so,third_party/**/*.so.*,third_party/**/*.dylib,third_party/acados/*/t_renderer,third_party/qt5/larch64/bin/lrelease,third_party/qt5/larch64/bin/lupdate,third_party/catch2/include/catch2/catch.hpp,*.apk,*.apkpatch,*.jar,*.pdf,*.jpg,*.mp3,*.thneed,*.tar.gz,*.npy,*.csv,*.a,*.so*,*.dylib,*.o,*.b64,selfdrive/hardware/tici/updater,selfdrive/boardd/tests/test_boardd,selfdrive/ui/qt/spinner_aarch64,installer/updater/updater,selfdrive/debug/profiling/simpleperf/**/*,selfdrive/hardware/eon/updater,selfdrive/ui/qt/text_aarch64,selfdrive/debug/profiling/pyflame/**/*,installer/installers/installer_openpilot,installer/installers/installer_dashcam,selfdrive/ui/text/text,selfdrive/ui/android/text/text,selfdrive/ui/spinner/spinner,selfdrive/visiond/visiond,selfdrive/loggerd/loggerd,selfdrive/sensord/sensord,selfdrive/sensord/gpsd,selfdrive/ui/android/spinner/spinner,selfdrive/ui/qt/spinner,selfdrive/ui/qt/text,_stringdefs.py,dfu-util-aarch64-linux,dfu-util-aarch64,dfu-util-x86_64-linux,dfu-util-x86_64,stb_image.h,clpeak3,clwaste,apk/**/*,external/**/*,phonelibs/**/*,third_party/boringssl/**/*,flask/**/*,panda/**/*,board/**/*,messaging/**/*,opendbc/**/*,tools/cabana/chartswidget.cc,third_party/nanovg/**/*,selfdrive/controls/lib/lateral_mpc/lib_mpc_export/**/*,selfdrive/ui/paint.cc,werkzeug/**/*,pyextra/**/*,third_party/android_hardware_libhardware/**/*,selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/**/*,selfdrive/locationd/laikad.py,selfdrive/locationd/test/test_laikad.py,tools/gpstest/test_laikad.py,selfdrive/locationd/laikad_helpers.py,tools/nui/**/*,jsonrpc/**/*,selfdrive/controls/lib/longitudinal_mpc/lib_mpc_export/**/*,selfdrive/controls/lib/lateral_mpc/mpc_export/**/*,selfdrive/camerad/cameras/camera_qcom.cc,selfdrive/manager.py,selfdrive/modeld/models/driving.cc,third_party/curl/**/*,selfdrive/modeld/thneed/debug/**/*,selfdrive/modeld/thneed/include/**/*,third_party/openmax/**/*,selfdrive/controls/lib/longitudinal_mpc/mpc_export/**/*,selfdrive/controls/lib/longitudinal_mpc_model/lib_mpc_export/**/*,Pipfile,Pipfile.lock,gunicorn/**/*,*.qm,jinja2/**/*,click/**/*,dbcs/**/*,websocket/**/*" $BRANCHES
+
+ echo "reflog and gc"
+ # this is needed after lfs import
+ git reflog expire --expire=now --all
+ git gc --prune=now --aggressive
+
+ # check the git-filter-repo analysis again - can be found in the repo root/filter-repo/analysis
+ echo "Analyzing with git-filter-repo..."
+ /tmp/git-filter-repo --force --analyze
+
+ echo "New size is $(du -sh .)"
+fi
+
+cd $OUT
+
+# fetch all lfs files from https://github.com/commaai/openpilot.git
+# some lfs files are missing on gitlab, but they can be found on github
+git config lfs.url https://github.com/commaai/openpilot.git/info/lfs
+git config lfs.pushurl ssh://git@github.com/commaai/openpilot.git
+git lfs fetch --all || true
+
+# also fetch all lfs files from https://gitlab.com/commaai/openpilot-lfs.git
+git config lfs.url https://gitlab.com/commaai/openpilot-lfs.git/info/lfs
+git config lfs.pushurl ssh://git@gitlab.com/commaai/openpilot-lfs.git
+git lfs fetch --all || true
+
+# final push - will also push lfs
+# TODO: switch to git@github.com:commaai/openpilot.git when ready
+# git push --mirror git@github.com:commaai/openpilot-tiny.git
+# using this instead to ignore refs/pull/* - since this is also what --mirror does - https://blog.plataformatec.com.br/2013/05/how-to-properly-mirror-a-git-repository/
+git push --prune git@github.com:commaai/openpilot-tiny.git +refs/heads/*:refs/heads/* +refs/tags/*:refs/tags/*
diff --git a/scripts/git_rewrite/tag-commit-map.txt b/scripts/git_rewrite/tag-commit-map.txt
new file mode 100644
index 0000000000..66b1fb00c1
--- /dev/null
+++ b/scripts/git_rewrite/tag-commit-map.txt
@@ -0,0 +1,82 @@
+v0.1 e94a30bec07e719c5a7b037ca1f4db8312702cce
+v0.2 449b482cc3236ccf31829830b4f6a44b2dcc06c2
+v0.2.1 17d9becd3c673091b22f09aa02559a9ed9230f50
+v0.2.2 a64b9aa9b8cb5863c917b6926516291a63c02fe5
+v0.2.3 adaa4ed350acda4067fc0b455ad15b54cdf4c768
+v0.2.4 ecc565aa3fdc4c7e719aadc000e1fdc4d80d4fe0
+v0.2.5 29c58b45882ac79595356caf98580c1d2a626011
+v0.2.6 6c3afeec0fb439070b2912978b8dbb659033b1d9
+v0.2.7 c6ba5dc5391d3ca6cda479bf1923b88ce45509a0
+v0.2.8 95a349abcc050712c50d4d85a1c8a804eee7f6c2
+v0.2.9 693bcb0f83478f2651db6bac9be5ca5ad60d03f3
+v0.3.0 c5d8aec28b5230d34ae4b677c2091cc3dec7e3e8
+v0.3.1 41e3a0f699f5c39cb61a15c0eb7a4aa816d47c24
+v0.3.2 7fe46f1e1df5dec08a940451ba0feefd5c039165
+v0.3.3 5cf91d0496688fed4f2a6c7021349b1fc0e057a2
+v0.3.4 1b8c44b5067525a5d266b6e99799d8097da76a29
+v0.3.5 b111277f464cf66fa34b67819a83ea683e0f64df
+v0.4.0.2 da52d065a4c4f52d6017a537f3a80326f5af8bdc
+v0.4.1 4474b9b3718653aeb0aee26422caefb90460cc0e
+v0.4.2 28c0797d30175043bbfa31307b63aab4197cf996
+v0.4.4 9a9ff839a9b70cb2601d7696af743f5652395389
+v0.4.5 37285038d3f91fa1b49159c4a35a8383168e644f
+v0.4.6 c6df34f55ba8c5a911b60d3f9eb20e3fa45f68c1
+v0.4.7 ae5cb7a0dab8b1bed9d52292f9b4e8e66a0f8ec9
+v0.5 de33bc46452b1046387ee2b3a03191b2c71135fb
+v0.5.1 8f22f52235c48eada586795ac57edb22688e4d08
+v0.5.2 0129a8a4ff8da5314e8e4d4d3336e89667ff6d54
+v0.5.3 285c52eb693265a0a530543e9ca0aeb593a2a55e
+v0.5.4 a422246dc30bce11e970514f13f7c110f4470cc3
+v0.5.5 8f3539a27b28851153454eb737da9624cccaed2d
+v0.5.6 860a48765d1016ba226fb2c64aea35a45fe40e4a
+v0.5.7 9ce3045f139ee29bf0eea5ec59dfe7df9c3d2c51
+v0.5.8 2cee2e05ba0f3824fdbb8b957958800fa99071a1
+v0.5.9 ad145da3bcded0fe75306df02061d07a633963c3
+v0.5.10 ff4c1557d8358f158f4358788ff18ef93d2470ef
+v0.5.11 d1866845df423c6855e2b365ff230cf7d89a420b
+v0.5.12 f6e8ef27546e9a406724841e75f8df71cc4c2c97
+v0.5.13 dd34ccfe288ebda8e2568cf550994ae890379f45
+v0.6 60a20537c5f3fcc7f11946d81aebc8f90c08c117
+v0.6.1 cf5c4aeacb1703d0ffd35bdb5297d3494fee9a22
+v0.6.2 095ef5f9f60fca1b269aabcc3cfd322b17b9e674
+v0.6.3 d5f9caa82d80cdcc7f1b7748f2cf3ccbf94f82a3
+v0.6.4 58f376002e0c654fbc2de127765fa297cf694a33
+v0.6.5 70d17cd69b80e7627dcad8fd5b6438f2309ac307
+v0.6.6 d4eb5a6eafdd4803d09e6f3963918216cca5a81f
+v0.7 a2ae18d1dbd1e59c38ce22fa25ddffbd1d3084e3
+v0.7.1 1e1de64a1e59476b7b3d3558b92149246d5c3292
+v0.7.2 59bd58c940673b4c4a6a86f299022614bcf42b22
+v0.7.3 d7acd8b68f8131e0e714400cf124a3e228638643
+v0.7.4 e93649882c5e914eec4a8b8b593dc0587e497033
+v0.7.5 8abc0afe464626a461d2c7e192c912eeebeccc65
+v0.7.6 69aacd9d179fe6dd3110253a099c38b34cff7899
+v0.7.7 f1caed7299cdba5e45635d8377da6cc1e5fd7072
+v0.7.8 2189fe8741b635d8394d55dee28959425cfd5ad0
+v0.7.9 86dc54b836a973f132ed26db9f5a60b29f9b25b2
+v0.7.10 47a42ff432db8a2494e922ca5e767e58020f0446
+v0.7.11 f46ed718ba8d6bb4d42cd7b0f0150c406017c373
+v0.8 d56e04c0d960c8d3d4ab88b578dc508a2b4e07dc
+v0.8.1 cd6f26664cb8d32a13847d6648567c47c580e248
+v0.8.2 7cc0999aebfe63b6bb6dd83c1dff62c3915c4820
+v0.8.3 986500fe2f10870018f1fba1e5465476b8915977
+v0.8.4 f0d0b82b8d6f5f450952113e234d0a5a49e80c48
+v0.8.5 f5d9ddc6c2a2802a61e5ce590c6b6688bf736a69
+v0.8.6 75904ed7452c6cbfb2a70cd379a899d8a75b97c2
+v0.8.7 4f9e568019492126e236da85b5ca0a059f292900
+v0.8.8 a949a49d5efaaf2d881143d23e9fb5ff9e28e88c
+v0.8.9 a034926264cd1025c69d6ceb3fe444965f960b75
+v0.8.10 59accdd814398b884167c0f41dbf46dcccf0c29c
+v0.8.11 d630ec9092f039cb5e51c5dd6d92fc47b91407e4
+v0.8.12 57871c99031cf597ffa0d819057ac1401e129f32
+v0.8.13 e43e6e876513450d235124fcb711f1724ed9814c
+v0.8.14 71901c94dbbaa2f9f156a80c14cc7ea65219fc7c
+v0.8.15 5a7c2f90361e72e9c35e88abd2e11acdc4aba354
+v0.8.16 f41dc62a12cc0f3cb8c5453c0caa0ba21e1bd01e
+v0.9.0 58b84fb401a804967aa0dd5ee66fafa90194fd30
+v0.9.1 89f68bf0cbf53a81b0553d3816fdbe522f941fa1
+v0.9.2 c7d3b28b93faa6c955fb24bc64031512ee985ee9
+v0.9.3 8704c1ff952b5c85a44f50143bbd1a4f7b4887e2
+v0.9.4 fa310d9e2542cf497d92f007baec8fd751ffa99c
+v0.9.5 3b1e9017c560499786d8a0e46aaaeea65037acac
+v0.9.6 0b4d08fab8e35a264bc7383e878538f8083c33e5
+v0.9.7 f8cb04e4a8b032b72a909f68b808a50936184bee
diff --git a/scripts/lint.sh b/scripts/lint.sh
new file mode 100755
index 0000000000..3cf548f2bb
--- /dev/null
+++ b/scripts/lint.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
+cd $DIR/../
+
+# TODO: bring back rest of pre-commit checks:
+# https://github.com/commaai/openpilot/blob/4b11c9e914707df9def598616995be2a5d355a6a/.pre-commit-config.yaml#L2
+
+ruff check .
diff --git a/scripts/pyupgrade.sh b/scripts/pyupgrade.sh
deleted file mode 100755
index 19aac4b5e2..0000000000
--- a/scripts/pyupgrade.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-pip install --upgrade pyupgrade
-
-git ls-files '*.py' | grep -v 'third_party/' | xargs pyupgrade --py311-plus
diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py
index 6aebd2bd90..a772d9c16a 100644
--- a/selfdrive/car/__init__.py
+++ b/selfdrive/car/__init__.py
@@ -7,10 +7,12 @@ from dataclasses import replace
import capnp
from cereal import car
+from panda.python.uds import SERVICE_TYPE
from openpilot.common.numpy_fast import clip, interp
from openpilot.common.utils import Freezable
from openpilot.selfdrive.car.docs_definitions import CarDocs
+DT_CTRL = 0.01 # car state and control loop timestep (s)
# kg of standard extra cargo to count for drive, gas, etc...
STD_CARGO_KG = 136.
@@ -43,10 +45,28 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca
return events
-def create_mads_event(mads_event_lock: bool) -> capnp.lib.capnp._DynamicStructBuilder:
- be = car.CarState.ButtonEvent(pressed=mads_event_lock)
- be.type = ButtonType.altButton1
- return be
+class ButtonEvents:
+ def __init__(self) -> None:
+ self.is_mads: bool = False
+
+ @staticmethod
+ def create_cancel_event(long_enabled: bool, prev_long_enabled: bool) -> list[capnp.lib.capnp._DynamicStructBuilder]:
+ events: list[capnp.lib.capnp._DynamicStructBuilder] = []
+
+ if not long_enabled and prev_long_enabled:
+ events.append(car.CarState.ButtonEvent(pressed=True,
+ type=ButtonType.cancel))
+ return events
+
+ def create_mads_event(self, mads_enabled: bool, prev_mads_enabled: bool) -> list[capnp.lib.capnp._DynamicStructBuilder]:
+ events: list[capnp.lib.capnp._DynamicStructBuilder] = []
+
+ mads_changed = prev_mads_enabled != mads_enabled
+ if (mads_changed and not self.is_mads) or (not mads_changed and self.is_mads):
+ events.append(car.CarState.ButtonEvent(pressed=mads_changed, type=ButtonType.altButton1))
+ self.is_mads = not self.is_mads
+
+ return events
def gen_empty_fingerprint():
@@ -198,7 +218,7 @@ def create_gas_interceptor_command(packer, gas_amount, idx):
values["GAS_COMMAND"] = gas_amount * 255.
values["GAS_COMMAND2"] = gas_amount * 255.
- dat = packer.make_can_msg("GAS_COMMAND", 0, values)[2]
+ dat = packer.make_can_msg("GAS_COMMAND", 0, values)[1]
checksum = crc8_pedal(dat[:-1])
values["CHECKSUM_PEDAL"] = checksum
@@ -206,8 +226,39 @@ def create_gas_interceptor_command(packer, gas_amount, idx):
return packer.make_can_msg("GAS_COMMAND", 0, values)
+def apply_center_deadzone(error, deadzone):
+ if (error > - deadzone) and (error < deadzone):
+ error = 0.
+ return error
+
+
+def rate_limit(new_value, last_value, dw_step, up_step):
+ return clip(new_value, last_value + dw_step, last_value + up_step)
+
+
+def get_friction(lateral_accel_error: float, lateral_accel_deadzone: float, friction_threshold: float,
+ torque_params: car.CarParams.LateralTorqueTuning, friction_compensation: bool) -> float:
+ friction_interp = interp(
+ apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone),
+ [-friction_threshold, friction_threshold],
+ [-torque_params.friction, torque_params.friction]
+ )
+ friction = float(friction_interp) if friction_compensation else 0.0
+ return friction
+
+
def make_can_msg(addr, dat, bus):
- return [addr, 0, dat, bus]
+ return [addr, dat, bus]
+
+
+def make_tester_present_msg(addr, bus, subaddr=None, suppress_response=False):
+ dat = [0x02, SERVICE_TYPE.TESTER_PRESENT]
+ if subaddr is not None:
+ dat.insert(0, subaddr)
+ dat.append(0x80 if suppress_response else 0x0) # sub-function
+
+ dat.extend([0x0] * (8 - len(dat)))
+ return make_can_msg(addr, bytes(dat), bus)
def get_safety_config(safety_model, safety_param = None):
diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py
index 259126c416..0771d7a471 100644
--- a/selfdrive/car/body/carcontroller.py
+++ b/selfdrive/car/body/carcontroller.py
@@ -1,7 +1,7 @@
import numpy as np
-from openpilot.common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker
+from openpilot.selfdrive.car import DT_CTRL
from openpilot.selfdrive.car.body import bodycan
from openpilot.selfdrive.car.body.values import SPEED_FROM_RPM
from openpilot.selfdrive.car.interfaces import CarControllerBase
@@ -17,7 +17,7 @@ MAX_TURN_INTEGRATOR = 0.1 # meters
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.frame = 0
+ super().__init__(dbc_name, CP, VM)
self.packer = CANPacker(dbc_name)
# PIDs
diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py
index f797a7ecf8..50564d3ed8 100644
--- a/selfdrive/car/body/interface.py
+++ b/selfdrive/car/body/interface.py
@@ -1,7 +1,6 @@
import math
from cereal import car
-from openpilot.common.realtime import DT_CTRL
-from openpilot.selfdrive.car import get_safety_config
+from openpilot.selfdrive.car import DT_CTRL, get_safety_config
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
from openpilot.selfdrive.car.body.values import SPEED_FROM_RPM
diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py
index 63e7d22da4..222dcf9619 100755
--- a/selfdrive/car/card.py
+++ b/selfdrive/car/card.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
import os
+import threading
import time
+from types import SimpleNamespace
import cereal.messaging as messaging
@@ -9,11 +11,13 @@ from cereal import car
from panda import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
-from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL
+from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.selfdrive.pandad import can_list_to_can_capnp
+from openpilot.selfdrive.car import DT_CTRL
from openpilot.selfdrive.car.car_helpers import get_car, get_one_can
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
+from openpilot.selfdrive.car.param_manager import ParamManager
from openpilot.selfdrive.controls.lib.events import Events
REPLAY = "REPLAY" in os.environ
@@ -56,7 +60,6 @@ class Car:
self.mads_disengage_lateral_on_brake = self.params.get_bool("DisengageLateralOnBrake")
self.mads_dlob = self.enable_mads and self.mads_disengage_lateral_on_brake
self.mads_ndlob = self.enable_mads and not self.mads_disengage_lateral_on_brake
- self.sp_toyota_auto_brake_hold = self.params.get_bool("ToyotaAutoHold")
self.CP.alternativeExperience = 0
if not self.disengage_on_accelerator:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
@@ -64,8 +67,6 @@ class Car:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ENABLE_MADS
elif self.mads_ndlob:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.MADS_DISABLE_DISENGAGE_LATERAL_ON_BRAKE
- if self.sp_toyota_auto_brake_hold:
- self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
if self.CP.customStockLongAvailable and self.CP.pcmCruise and self.params.get_bool("CustomStockLong"):
self.CP.pcmCruiseSpeed = False
@@ -93,6 +94,10 @@ class Car:
self.events = Events()
+ self.param_manager: ParamManager = ParamManager()
+ self.param_manager.update(self.params)
+ self._params_list: SimpleNamespace = self.param_manager.get_params()
+
# card is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None)
@@ -101,7 +106,7 @@ class Car:
# Update carState from CAN
can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True)
- CS = self.CI.update(self.CC_prev, can_strs)
+ CS = self.CI.update(self.CC_prev, can_strs, self._params_list)
self.sm.update(0)
@@ -189,10 +194,23 @@ class Car:
self.initialized_prev = initialized
self.CS_prev = CS.as_reader()
+ def sp_params_thread(self, event: threading.Event) -> None:
+ while not event.is_set():
+ self.param_manager.update(self.params)
+ self._params_list = self.param_manager.get_params()
+ time.sleep(0.1)
+
def card_thread(self):
- while True:
- self.step()
- self.rk.monitor_time()
+ event = threading.Event()
+ thread = threading.Thread(target=self.sp_params_thread, args=(event, ))
+ try:
+ thread.start()
+ while True:
+ self.step()
+ self.rk.monitor_time()
+ finally:
+ event.set()
+ thread.join()
def main():
diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py
index f9277af62e..8a805cd2a1 100644
--- a/selfdrive/car/chrysler/carcontroller.py
+++ b/selfdrive/car/chrysler/carcontroller.py
@@ -1,22 +1,22 @@
+from cereal import car
+
import cereal.messaging as messaging
from common.conversions import Conversions as CV
from opendbc.can.packer import CANPacker
from openpilot.common.params import Params
-from openpilot.common.realtime import DT_CTRL
-from openpilot.selfdrive.car import apply_meas_steer_torque_limits
+from openpilot.selfdrive.car import DT_CTRL, apply_meas_steer_torque_limits
from openpilot.selfdrive.car.chrysler import chryslercan
from openpilot.selfdrive.car.chrysler.values import RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags, ChryslerFlagsSP
from openpilot.selfdrive.car.interfaces import CarControllerBase, FORWARD_GEARS
from openpilot.selfdrive.controls.lib.drive_helpers import FCA_V_CRUISE_MIN
-BUTTONS_STATES = ["accelCruise", "decelCruise", "cancel", "resumeCruise"]
+ButtonType = car.CarState.ButtonEvent.Type
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.apply_steer_last = 0
- self.frame = 0
self.hud_count = 0
self.last_lkas_falling_edge = 0
@@ -28,9 +28,6 @@ class CarController(CarControllerBase):
self.sm = messaging.SubMaster(['longitudinalPlanSP'])
self.param_s = Params()
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.speed_limit_control_enabled = False
- self.last_speed_limit_sign_tap = False
self.last_speed_limit_sign_tap_prev = False
self.speed_limit = 0.
self.speed_limit_offset = 0
@@ -70,23 +67,19 @@ class CarController(CarControllerBase):
self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed
self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed
- if self.frame % 200 == 0:
- self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc")
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap")
- self.v_cruise_min = FCA_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1)
+ self.v_cruise_min = FCA_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)
can_sends = []
if not self.CP.pcmCruiseSpeed:
- if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap:
+ if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap:
self.sl_force_active_timer = self.frame
self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False)
- self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap
+ self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap
- sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
- sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
- sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
+ sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
+ sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
+ sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
slc_active = not sl_inactive and not sl_temp_inactive
self.slc_active_stock = slc_active
@@ -104,7 +97,7 @@ class CarController(CarControllerBase):
self.last_button_frame = CS.button_counter
if ram_cars:
- if CS.buttonStates["cancel"]:
+ if any(b.type == ButtonType.cancel for b in CS.out.buttonEvents):
can_sends.append(chryslercan.create_cruise_buttons(self.packer, CS.button_counter, das_bus, self.CP, cancel=True))
else:
can_sends.append(chryslercan.create_cruise_buttons(self.packer, CS.button_counter, das_bus, self.CP,
@@ -189,8 +182,10 @@ class CarController(CarControllerBase):
# multikyd methods, sunnyhaibin logic
def get_cruise_buttons_status(self, CS):
- if not CS.out.cruiseState.enabled or any(CS.buttonStates[button_state] for button_state in BUTTONS_STATES):
- self.timer = 40
+ if not CS.out.cruiseState.enabled:
+ for be in CS.out.buttonEvents:
+ if be.type in (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.resumeCruise) and be.pressed:
+ self.timer = 40
elif self.timer:
self.timer -= 1
else:
@@ -274,8 +269,8 @@ class CarController(CarControllerBase):
return min(target_speed_kph, curve_speed)
def get_button_control(self, CS, final_speed, v_cruise_kph_prev):
- self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1))
- self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH))
+ self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1))
+ self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH))
cruise_button = self.get_button_type(self.button_type)
return cruise_button
diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py
index d29be9226e..a58681175e 100644
--- a/selfdrive/car/chrysler/carstate.py
+++ b/selfdrive/car/chrysler/carstate.py
@@ -3,7 +3,7 @@ from openpilot.common.conversions import Conversions as CV
from opendbc.can.parser import CANParser
from opendbc.can.can_define import CANDefine
from openpilot.selfdrive.car.interfaces import CarStateBase
-from openpilot.selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS, BUTTON_STATES
+from openpilot.selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS, BUTTONS
class CarState(CarStateBase):
@@ -29,8 +29,7 @@ class CarState(CarStateBase):
self.lkas_heartbit = None
self.lkas_disabled = False
- self.buttonStates = BUTTON_STATES.copy()
- self.buttonStatesPrev = BUTTON_STATES.copy()
+ self.button_states = {button.event_type: False for button in BUTTONS}
def update(self, cp, cp_cam):
@@ -41,7 +40,6 @@ class CarState(CarStateBase):
self.prev_mads_enabled = self.mads_enabled
self.prev_lkas_enabled = self.lkas_enabled
- self.buttonStatesPrev = self.buttonStates.copy()
# lock info
ret.doorOpen = any([cp.vl["BCM_1"]["DOOR_OPEN_FL"],
@@ -76,6 +74,16 @@ class CarState(CarStateBase):
unit=1,
)
+ # Buttons
+ for button in BUTTONS:
+ state = (cp.vl[button.can_addr][button.can_msg] in button.values)
+ if self.button_states[button.event_type] != state:
+ event = car.CarState.ButtonEvent.new_message()
+ event.type = button.event_type
+ event.pressed = state
+ self.button_events.append(event)
+ self.button_states[button.event_type] = state
+
# button presses
ret.leftBlinker, ret.rightBlinker = ret.leftBlinkerOn, ret.rightBlinkerOn = self.update_blinker_from_stalk(200, cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1,
cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2)
@@ -116,11 +124,6 @@ class CarState(CarStateBase):
ret.leftBlindspot = cp.vl["BSM_1"]["LEFT_STATUS"] == 1
ret.rightBlindspot = cp.vl["BSM_1"]["RIGHT_STATUS"] == 1
- self.buttonStates["accelCruise"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Accel"])
- self.buttonStates["decelCruise"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Decel"])
- self.buttonStates["cancel"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Cancel"])
- self.buttonStates["resumeCruise"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Resume"])
-
self.lkas_car_model = cp_cam.vl["DAS_6"]["CAR_MODEL"]
self.button_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"]
self.cruise_buttons = cp.vl["CRUISE_BUTTONS"]
diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py
index 1bc6a32431..5198c661d7 100644
--- a/selfdrive/car/chrysler/fingerprints.py
+++ b/selfdrive/car/chrysler/fingerprints.py
@@ -4,35 +4,6 @@ from openpilot.selfdrive.car.chrysler.values import CAR
Ecu = car.CarParams.Ecu
FW_VERSIONS = {
- CAR.CHRYSLER_PACIFICA_2017_HYBRID: {
- (Ecu.combinationMeter, 0x742, None): [
- b'68239262AH',
- b'68239262AI',
- b'68239262AJ',
- b'68239263AH',
- b'68239263AJ',
- ],
- (Ecu.srs, 0x744, None): [
- b'68238840AH',
- ],
- (Ecu.fwdRadar, 0x753, None): [
- b'68226356AI',
- ],
- (Ecu.eps, 0x75a, None): [
- b'68288309AC',
- b'68288309AD',
- ],
- (Ecu.engine, 0x7e0, None): [
- b'68277480AV ',
- b'68277480AX ',
- b'68277480AZ ',
- ],
- (Ecu.hybrid, 0x7e2, None): [
- b'05190175BF',
- b'05190175BH',
- b'05190226AK',
- ],
- },
CAR.CHRYSLER_PACIFICA_2018: {
(Ecu.combinationMeter, 0x742, None): [
b'68227902AF',
@@ -55,6 +26,7 @@ FW_VERSIONS = {
],
(Ecu.fwdRadar, 0x753, None): [
b'04672758AA',
+ b'04672758AB',
b'68226356AF',
b'68226356AH',
b'68226356AI',
@@ -76,6 +48,7 @@ FW_VERSIONS = {
b'68352654AE ',
b'68366851AH ',
b'68366853AE ',
+ b'68366853AG ',
b'68372861AF ',
],
(Ecu.transmission, 0x7e1, None): [
@@ -89,6 +62,7 @@ FW_VERSIONS = {
b'68277374AD',
b'68277374AN',
b'68367471AC',
+ b'68367471AD',
b'68380571AB',
],
},
@@ -181,26 +155,39 @@ FW_VERSIONS = {
},
CAR.CHRYSLER_PACIFICA_2018_HYBRID: {
(Ecu.combinationMeter, 0x742, None): [
+ b'68239262AH',
+ b'68239262AI',
+ b'68239262AJ',
+ b'68239263AH',
+ b'68239263AJ',
b'68358439AE',
b'68358439AG',
],
(Ecu.srs, 0x744, None): [
+ b'68238840AH',
b'68358990AC',
b'68405939AA',
],
(Ecu.fwdRadar, 0x753, None): [
b'04672758AA',
+ b'68226356AI',
],
(Ecu.eps, 0x75a, None): [
+ b'68288309AC',
b'68288309AD',
b'68525339AA',
],
(Ecu.engine, 0x7e0, None): [
+ b'68277480AV ',
+ b'68277480AX ',
+ b'68277480AZ ',
b'68366580AI ',
b'68366580AK ',
b'68366580AM ',
],
(Ecu.hybrid, 0x7e2, None): [
+ b'05190175BF',
+ b'05190175BH',
b'05190226AI',
b'05190226AK',
b'05190226AM',
@@ -245,6 +232,7 @@ FW_VERSIONS = {
b'68416680AE ',
b'68416680AF ',
b'68416680AG ',
+ b'68444228AC ',
b'68444228AD ',
b'68444228AE ',
b'68444228AF ',
@@ -402,6 +390,7 @@ FW_VERSIONS = {
b'68294051AI',
b'68294052AG',
b'68294052AH',
+ b'68294059AI',
b'68294063AG',
b'68294063AH',
b'68294063AI',
@@ -412,6 +401,7 @@ FW_VERSIONS = {
b'68434858AC',
b'68434859AC',
b'68434860AC',
+ b'68453471AD',
b'68453483AC',
b'68453483AD',
b'68453487AD',
@@ -436,11 +426,13 @@ FW_VERSIONS = {
b'68527346AE',
b'68527361AD',
b'68527375AD',
+ b'68527381AD',
b'68527381AE',
b'68527382AE',
b'68527383AD',
b'68527383AE',
b'68527387AE',
+ b'68527397AD',
b'68527403AC',
b'68527403AD',
b'68546047AF',
@@ -531,6 +523,7 @@ FW_VERSIONS = {
b'05036066AE ',
b'05036193AA ',
b'05149368AA ',
+ b'05149374AA ',
b'05149591AD ',
b'05149591AE ',
b'05149592AE ',
@@ -561,6 +554,7 @@ FW_VERSIONS = {
b'68455145AC ',
b'68455145AE ',
b'68455146AC ',
+ b'68460927AA ',
b'68467915AC ',
b'68467916AC ',
b'68467936AC ',
@@ -579,6 +573,7 @@ FW_VERSIONS = {
b'68539650AF',
b'68539651AD',
b'68586101AA ',
+ b'68586102AA ',
b'68586105AB ',
b'68629919AC ',
b'68629922AC ',
@@ -617,6 +612,8 @@ FW_VERSIONS = {
b'68520867AE',
b'68520867AF',
b'68520870AC',
+ b'68520871AC',
+ b'68528325AE',
b'68540431AB',
b'68540433AB',
b'68551676AA',
diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py
index d09fa7ac56..ec1587e4e4 100755
--- a/selfdrive/car/chrysler/interface.py
+++ b/selfdrive/car/chrysler/interface.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
from cereal import car
from panda import Panda
-from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
-from openpilot.selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerFlagsSP, BUTTON_STATES
+from openpilot.selfdrive.car import create_button_events, get_safety_config
+from openpilot.selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerFlagsSP
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
ButtonType = car.CarState.ButtonEvent.Type
@@ -13,7 +13,6 @@ GearShifter = car.CarState.GearShifter
class CarInterface(CarInterfaceBase):
def __init__(self, CP, CarController, CarState):
super().__init__(CP, CarController, CarState)
- self.buttonStatesPrev = BUTTON_STATES.copy()
@staticmethod
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs):
@@ -41,8 +40,8 @@ class CarInterface(CarInterfaceBase):
ret.flags |= ChryslerFlags.HIGHER_MIN_STEERING_SPEED.value
# Chrysler
- if candidate in (CAR.CHRYSLER_PACIFICA_2017_HYBRID, CAR.CHRYSLER_PACIFICA_2018, CAR.CHRYSLER_PACIFICA_2018_HYBRID, \
- CAR.CHRYSLER_PACIFICA_2019_HYBRID, CAR.CHRYSLER_PACIFICA_2020, CAR.DODGE_DURANGO):
+ if candidate in (CAR.CHRYSLER_PACIFICA_2018, CAR.CHRYSLER_PACIFICA_2018_HYBRID, CAR.CHRYSLER_PACIFICA_2019_HYBRID,
+ CAR.CHRYSLER_PACIFICA_2020, CAR.DODGE_DURANGO):
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
@@ -92,21 +91,17 @@ class CarInterface(CarInterfaceBase):
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- self.sp_update_params()
- buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})
-
- for button in self.CS.buttonStates:
- if self.CS.buttonStates[button] != self.buttonStatesPrev[button]:
- be = car.CarState.ButtonEvent.new_message()
- be.type = button
- be.pressed = self.CS.buttonStates[button]
- buttonEvents.append(be)
+ self.CS.button_events = [
+ *self.CS.button_events,
+ *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}),
+ *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}),
+ ]
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled,
- buttonEvents, c.vCruise,
+ self.CS.button_events, c.vCruise,
enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.resumeCruise) if not self.CP.pcmCruiseSpeed else
(ButtonType.accelCruise, ButtonType.decelCruise),
resume_button=(ButtonType.resumeCruise,) if not self.CP.pcmCruiseSpeed else
@@ -116,7 +111,7 @@ class CarInterface(CarInterfaceBase):
if self.enable_mads:
if not self.CS.prev_mads_enabled and self.CS.mads_enabled:
self.CS.madsEnabled = True
- if self.CS.prev_lkas_enabled != 1 and self.CS.lkas_enabled == 1:
+ if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events):
self.CS.madsEnabled = not self.CS.madsEnabled
self.CS.lkas_disabled = not self.CS.lkas_disabled
self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled)
@@ -125,7 +120,7 @@ class CarInterface(CarInterfaceBase):
self.CS.madsEnabled = self.get_sp_started_mads(ret, self.CS)
if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed:
- if any(b.type == ButtonType.cancel for b in buttonEvents):
+ if any(b.type == ButtonType.cancel for b in self.CS.button_events):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
if self.get_sp_pedal_disengage(ret):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
@@ -143,17 +138,10 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button))
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
# events
events = self.create_common_events(ret, c, extra_gears=[car.CarState.GearShifter.low], pcm_enable=False)
@@ -180,6 +168,4 @@ class CarInterface(CarInterfaceBase):
ret.events = events.to_msg()
- self.buttonStatesPrev = self.CS.buttonStates.copy()
-
return ret
diff --git a/selfdrive/car/chrysler/radar_interface.py b/selfdrive/car/chrysler/radar_interface.py
index d982958422..f1d863d1e1 100755
--- a/selfdrive/car/chrysler/radar_interface.py
+++ b/selfdrive/car/chrysler/radar_interface.py
@@ -39,7 +39,6 @@ def _address_to_track(address):
class RadarInterface(RadarInterfaceBase):
def __init__(self, CP):
super().__init__(CP)
- self.CP = CP
self.rcp = _create_radar_can_parser(CP.carFingerprint)
self.updated_messages = set()
self.trigger_msg = LAST_MSG
diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py
index 85835d453a..51e9d8ba57 100644
--- a/selfdrive/car/chrysler/values.py
+++ b/selfdrive/car/chrysler/values.py
@@ -1,3 +1,4 @@
+from collections import namedtuple
from enum import IntFlag
from dataclasses import dataclass, field
@@ -8,6 +9,7 @@ from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarPar
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16
Ecu = car.CarParams.Ecu
+Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
class ChryslerFlags(IntFlag):
@@ -38,34 +40,30 @@ class ChryslerCarSpecs(CarSpecs):
class CAR(Platforms):
# Chrysler
- CHRYSLER_PACIFICA_2017_HYBRID = ChryslerPlatformConfig(
- [ChryslerCarDocs("Chrysler Pacifica Hybrid 2017")],
- ChryslerCarSpecs(mass=2242., wheelbase=3.089, steerRatio=16.2),
- )
CHRYSLER_PACIFICA_2018_HYBRID = ChryslerPlatformConfig(
- [ChryslerCarDocs("Chrysler Pacifica Hybrid 2018")],
- CHRYSLER_PACIFICA_2017_HYBRID.specs,
+ [ChryslerCarDocs("Chrysler Pacifica Hybrid 2017-18")],
+ ChryslerCarSpecs(mass=2242., wheelbase=3.089, steerRatio=16.2),
)
CHRYSLER_PACIFICA_2019_HYBRID = ChryslerPlatformConfig(
[ChryslerCarDocs("Chrysler Pacifica Hybrid 2019-24")],
- CHRYSLER_PACIFICA_2017_HYBRID.specs,
+ CHRYSLER_PACIFICA_2018_HYBRID.specs,
)
CHRYSLER_PACIFICA_2018 = ChryslerPlatformConfig(
[ChryslerCarDocs("Chrysler Pacifica 2017-18")],
- CHRYSLER_PACIFICA_2017_HYBRID.specs,
+ CHRYSLER_PACIFICA_2018_HYBRID.specs,
)
CHRYSLER_PACIFICA_2020 = ChryslerPlatformConfig(
[
ChryslerCarDocs("Chrysler Pacifica 2019-20"),
ChryslerCarDocs("Chrysler Pacifica 2021-23", package="All"),
],
- CHRYSLER_PACIFICA_2017_HYBRID.specs,
+ CHRYSLER_PACIFICA_2018_HYBRID.specs,
)
# Dodge
DODGE_DURANGO = ChryslerPlatformConfig(
[ChryslerCarDocs("Dodge Durango 2020-21")],
- CHRYSLER_PACIFICA_2017_HYBRID.specs,
+ CHRYSLER_PACIFICA_2018_HYBRID.specs,
)
# Jeep
@@ -113,12 +111,12 @@ class CarControllerParams:
self.STEER_MAX = 261 # higher than this faults the EPS
-BUTTON_STATES = {
- "accelCruise": False,
- "decelCruise": False,
- "cancel": False,
- "resumeCruise": False,
-}
+BUTTONS = [
+ Button(car.CarState.ButtonEvent.Type.accelCruise, "CRUISE_BUTTONS", "ACC_Accel", [1]),
+ Button(car.CarState.ButtonEvent.Type.decelCruise, "CRUISE_BUTTONS", "ACC_Decel", [1]),
+ Button(car.CarState.ButtonEvent.Type.cancel, "CRUISE_BUTTONS", "ACC_Cancel", [1]),
+ Button(car.CarState.ButtonEvent.Type.resumeCruise, "CRUISE_BUTTONS", "ACC_Resume", [1]),
+]
STEER_THRESHOLD = 120
diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py
index 756cd7f963..0ff827593e 100755
--- a/selfdrive/car/ecu_addrs.py
+++ b/selfdrive/car/ecu_addrs.py
@@ -4,22 +4,13 @@ import time
import cereal.messaging as messaging
from panda.python.uds import SERVICE_TYPE
-from openpilot.selfdrive.car import make_can_msg
+from openpilot.selfdrive.car import make_tester_present_msg
from openpilot.selfdrive.car.fw_query_definitions import EcuAddrBusType
from openpilot.selfdrive.pandad import can_list_to_can_capnp
from openpilot.common.swaglog import cloudlog
-def make_tester_present_msg(addr, bus, subaddr=None):
- dat = [0x02, SERVICE_TYPE.TESTER_PRESENT, 0x0]
- if subaddr is not None:
- dat.insert(0, subaddr)
-
- dat.extend([0x0] * (8 - len(dat)))
- return make_can_msg(addr, bytes(dat), bus)
-
-
-def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: int = None) -> bool:
+def _is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: int = None) -> bool:
# ISO-TP messages are always padded to 8 bytes
# tester present response is always a single frame
dat_offset = 1 if subaddr is not None else 0
@@ -33,7 +24,7 @@ def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subadd
return False
-def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> set[EcuAddrBusType]:
+def _get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> set[EcuAddrBusType]:
addr_list = [0x700 + i for i in range(256)] + [0x18da00f1 + (i << 8) for i in range(256)]
queries: set[EcuAddrBusType] = {(addr, None, bus) for addr in addr_list}
responses = queries
@@ -58,7 +49,7 @@ def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, que
continue
subaddr = None if (msg.address, None, msg.src) in responses else msg.dat[0]
- if (msg.address, subaddr, msg.src) in responses and is_tester_present_response(msg, subaddr):
+ if (msg.address, subaddr, msg.src) in responses and _is_tester_present_response(msg, subaddr):
if debug:
print(f"CAN-RX: {hex(msg.address)} - 0x{bytes.hex(msg.dat)}")
if (msg.address, subaddr, msg.src) in ecu_responses:
@@ -94,7 +85,7 @@ if __name__ == "__main__":
set_obd_multiplexing(params, not args.no_obd)
print("Getting ECU addresses ...")
- ecu_addrs = get_all_ecu_addrs(logcan, sendcan, args.bus, args.timeout, debug=args.debug)
+ ecu_addrs = _get_all_ecu_addrs(logcan, sendcan, args.bus, args.timeout, debug=args.debug)
print()
print("Found ECUs on rx addresses:")
diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py
index 1128a31c29..dc93c38246 100644
--- a/selfdrive/car/fingerprints.py
+++ b/selfdrive/car/fingerprints.py
@@ -130,7 +130,8 @@ MIGRATION = {
# Removal of platform_str, see https://github.com/commaai/openpilot/pull/31868/
"COMMA BODY": BODY.COMMA_BODY,
- "CHRYSLER PACIFICA HYBRID 2017": CHRYSLER.CHRYSLER_PACIFICA_2017_HYBRID,
+ "CHRYSLER PACIFICA HYBRID 2017": CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID,
+ "CHRYSLER_PACIFICA_2017_HYBRID": CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID,
"CHRYSLER PACIFICA HYBRID 2018": CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID,
"CHRYSLER PACIFICA HYBRID 2019": CHRYSLER.CHRYSLER_PACIFICA_2019_HYBRID,
"CHRYSLER PACIFICA 2018": CHRYSLER.CHRYSLER_PACIFICA_2018,
diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py
index 7be3b2ebe9..d090adf694 100644
--- a/selfdrive/car/ford/carcontroller.py
+++ b/selfdrive/car/ford/carcontroller.py
@@ -5,10 +5,10 @@ from openpilot.selfdrive.car import apply_std_steer_angle_limits
from openpilot.selfdrive.car.ford import fordcan
from openpilot.selfdrive.car.ford.values import CarControllerParams, FordFlags
from openpilot.selfdrive.car.interfaces import CarControllerBase
-from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX
LongCtrlState = car.CarControl.Actuators.LongControlState
VisualAlert = car.CarControl.HUDControl.VisualAlert
+V_CRUISE_MAX = 145
def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_curvature, v_ego_raw):
@@ -25,11 +25,10 @@ def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_c
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.VM = VM
self.packer = CANPacker(dbc_name)
self.CAN = fordcan.CanBus(CP)
- self.frame = 0
self.apply_curvature_last = 0
self.main_on_last = False
@@ -92,6 +91,7 @@ class CarController(CarControllerBase):
if not CC.longActive or gas < CarControllerParams.MIN_GAS:
gas = CarControllerParams.INACTIVE_GAS
stopping = CC.actuators.longControlState == LongCtrlState.stopping
+ # TODO: look into using the actuators packet to send the desired speed
can_sends.append(fordcan.create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping, v_ego_kph=V_CRUISE_MAX))
### ui ###
diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py
index 21e2f12def..e52454f81d 100644
--- a/selfdrive/car/ford/carstate.py
+++ b/selfdrive/car/ford/carstate.py
@@ -3,7 +3,7 @@ from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
from openpilot.common.conversions import Conversions as CV
from openpilot.selfdrive.car.ford.fordcan import CanBus
-from openpilot.selfdrive.car.ford.values import DBC, CarControllerParams, FordFlags, BUTTON_STATES
+from openpilot.selfdrive.car.ford.values import DBC, CarControllerParams, FordFlags, BUTTONS
from openpilot.selfdrive.car.interfaces import CarStateBase
GearShifter = car.CarState.GearShifter
@@ -24,15 +24,14 @@ class CarState(CarStateBase):
self.lkas_enabled = None
self.prev_lkas_enabled = None
- self.buttonStates = BUTTON_STATES.copy()
- self.buttonStatesPrev = BUTTON_STATES.copy()
+
+ self.button_states = {button.event_type: False for button in BUTTONS}
def update(self, cp, cp_cam):
ret = car.CarState.new_message()
self.prev_mads_enabled = self.mads_enabled
self.prev_lkas_enabled = self.lkas_enabled
- self.buttonStatesPrev = self.buttonStates.copy()
# Occasionally on startup, the ABS module recalibrates the steering pinion offset, so we need to block engagement
# The vehicle usually recovers out of this state within a minute of normal driving
@@ -87,6 +86,16 @@ class CarState(CarStateBase):
else:
ret.gearShifter = GearShifter.drive
+ # Buttons
+ for button in BUTTONS:
+ state = (cp.vl[button.can_addr][button.can_msg] in button.values)
+ if self.button_states[button.event_type] != state:
+ event = car.CarState.ButtonEvent.new_message()
+ event.type = button.event_type
+ event.pressed = state
+ self.button_events.append(event)
+ self.button_states[button.event_type] = state
+
# safety
ret.stockFcw = bool(cp_cam.vl["ACCDATA_3"]["FcwVisblWarn_B_Rq"])
ret.stockAeb = bool(cp_cam.vl["ACCDATA_2"]["CmbbBrkDecel_B_Rq"])
@@ -112,13 +121,6 @@ class CarState(CarStateBase):
self.lkas_enabled = bool(cp.vl["Steering_Data_FD1"]["TjaButtnOnOffPress"])
- self.buttonStates["accelCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnSetIncPress"])
- self.buttonStates["decelCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnSetDecPress"])
- self.buttonStates["cancel"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnCnclPress"])
- self.buttonStates["setCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnSetPress"])
- self.buttonStates["resumeCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAsllButtnResPress"])
- self.buttonStates["gapAdjustCruise"] = bool(cp.vl["Steering_Data_FD1"]["AccButtnGapTogglePress"])
-
# Stock steering buttons so that we can passthru blinkers etc.
self.buttons_stock_values = cp.vl["Steering_Data_FD1"]
# Stock values from IPMA so that we can retain some stock functionality
diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py
index 2cfd61a191..46d09ec22d 100644
--- a/selfdrive/car/ford/fordcan.py
+++ b/selfdrive/car/ford/fordcan.py
@@ -112,7 +112,7 @@ def create_lat_ctl2_msg(packer, CAN: CanBus, mode: int, path_offset: float, path
}
# calculate checksum
- dat = packer.make_can_msg("LateralMotionControl2", 0, values)[2]
+ dat = packer.make_can_msg("LateralMotionControl2", 0, values)[1]
values["LatCtlPath_No_Cs"] = calculate_lat_ctl2_checksum(mode, counter, dat)
return packer.make_can_msg("LateralMotionControl2", CAN.main, values)
diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py
index 0c3449dc14..a3bbd7ecf5 100644
--- a/selfdrive/car/ford/interface.py
+++ b/selfdrive/car/ford/interface.py
@@ -1,9 +1,9 @@
from cereal import car
from panda import Panda
from openpilot.common.conversions import Conversions as CV
-from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
+from openpilot.selfdrive.car import create_button_events, get_safety_config
from openpilot.selfdrive.car.ford.fordcan import CanBus
-from openpilot.selfdrive.car.ford.values import Ecu, FordFlags, BUTTON_STATES
+from openpilot.selfdrive.car.ford.values import Ecu, FordFlags
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
ButtonType = car.CarState.ButtonEvent.Type
@@ -15,8 +15,6 @@ class CarInterface(CarInterfaceBase):
def __init__(self, CP, CarController, CarState):
super().__init__(CP, CarController, CarState)
- self.buttonStatesPrev = BUTTON_STATES.copy()
-
@staticmethod
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs):
ret.carName = "ford"
@@ -74,34 +72,30 @@ class CarInterface(CarInterfaceBase):
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- self.sp_update_params()
- buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})
-
- for button in self.CS.buttonStates:
- if self.CS.buttonStates[button] != self.buttonStatesPrev[button]:
- be = car.CarState.ButtonEvent.new_message()
- be.type = button
- be.pressed = self.CS.buttonStates[button]
- buttonEvents.append(be)
+ self.CS.button_events = [
+ *self.CS.button_events,
+ *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}),
+ *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}),
+ ]
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled,
- buttonEvents, c.vCruise)
+ self.CS.button_events, c.vCruise)
if ret.cruiseState.available:
if self.enable_mads:
if not self.CS.prev_mads_enabled and self.CS.mads_enabled:
self.CS.madsEnabled = True
- if not self.CS.prev_lkas_enabled and self.CS.lkas_enabled:
+ if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events):
self.CS.madsEnabled = not self.CS.madsEnabled
self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled)
else:
self.CS.madsEnabled = False
if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0):
- if any(b.type == ButtonType.cancel for b in buttonEvents):
+ if any(b.type == ButtonType.cancel for b in self.CS.button_events):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
if self.get_sp_pedal_disengage(ret):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
@@ -114,16 +108,10 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button))
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False)
@@ -134,7 +122,4 @@ class CarInterface(CarInterfaceBase):
ret.events = events.to_msg()
- # update previous car states
- self.buttonStatesPrev = self.CS.buttonStates.copy()
-
return ret
diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py
index edfee3ea1b..04bd592e22 100644
--- a/selfdrive/car/ford/values.py
+++ b/selfdrive/car/ford/values.py
@@ -1,5 +1,6 @@
import copy
import re
+from collections import namedtuple
from dataclasses import dataclass, field, replace
from enum import Enum, IntFlag
@@ -11,6 +12,7 @@ from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, Ca
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, LiveFwVersions, OfflineFwVersions, Request, StdQueries, p16
Ecu = car.CarParams.Ecu
+Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
class CarControllerParams:
@@ -46,16 +48,6 @@ class FordFlags(IntFlag):
CANFD = 1
-BUTTON_STATES = {
- "accelCruise": False,
- "decelCruise": False,
- "cancel": False,
- "setCruise": False,
- "resumeCruise": False,
- "gapAdjustCruise": False
-}
-
-
class RADAR:
DELPHI_ESR = 'ford_fusion_2018_adas'
DELPHI_MRR = 'FORD_CADS'
@@ -154,6 +146,15 @@ class CAR(Platforms):
)
+BUTTONS = [
+ Button(car.CarState.ButtonEvent.Type.accelCruise, "Steering_Data_FD1", "CcAslButtnSetIncPress", [1]),
+ Button(car.CarState.ButtonEvent.Type.decelCruise, "Steering_Data_FD1", "CcAslButtnSetDecPress", [1]),
+ Button(car.CarState.ButtonEvent.Type.cancel, "Steering_Data_FD1", "CcAslButtnCnclPress", [1]),
+ Button(car.CarState.ButtonEvent.Type.setCruise, "Steering_Data_FD1", "CcAslButtnSetPress", [1]),
+ Button(car.CarState.ButtonEvent.Type.resumeCruise, "Steering_Data_FD1", "CcAsllButtnResPress", [1]),
+]
+
+
# FW response contains a combined software and part number
# A-Z except no I, O or W
# e.g. NZ6A-14C204-AAA
diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py
index 2aa4811e4e..350b9a7d9b 100644
--- a/selfdrive/car/gm/carcontroller.py
+++ b/selfdrive/car/gm/carcontroller.py
@@ -1,9 +1,8 @@
from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.common.numpy_fast import interp
-from openpilot.common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker
-from openpilot.selfdrive.car import apply_driver_steer_torque_limits
+from openpilot.selfdrive.car import DT_CTRL, apply_driver_steer_torque_limits
from openpilot.selfdrive.car.gm import gmcan
from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons
from openpilot.selfdrive.car.interfaces import CarControllerBase
@@ -20,12 +19,11 @@ MIN_STEER_MSG_INTERVAL_MS = 15
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.start_time = 0.
self.apply_steer_last = 0
self.apply_gas = 0
self.apply_brake = 0
- self.frame = 0
self.last_steer_frame = 0
self.last_button_frame = 0
self.cancel_counter = 0
diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py
index e833e77636..a70bcccd06 100644
--- a/selfdrive/car/gm/gmcan.py
+++ b/selfdrive/car/gm/gmcan.py
@@ -64,7 +64,7 @@ def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop):
"GasRegenAlwaysOne3": 1,
}
- dat = packer.make_can_msg("ASCMGasRegenCmd", bus, values)[2]
+ dat = packer.make_can_msg("ASCMGasRegenCmd", bus, values)[1]
values["GasRegenChecksum"] = (((0xff - dat[1]) & 0xff) << 16) | \
(((0xff - dat[2]) & 0xff) << 8) | \
((0x100 - dat[3] - idx) & 0xff)
diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py
index 22a8d619a6..6ca796755e 100755
--- a/selfdrive/car/gm/interface.py
+++ b/selfdrive/car/gm/interface.py
@@ -6,11 +6,10 @@ from panda import Panda
from openpilot.common.basedir import BASEDIR
from openpilot.common.conversions import Conversions as CV
-from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
+from openpilot.selfdrive.car import create_button_events, get_safety_config, get_friction
from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG
from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus
from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel
-from openpilot.selfdrive.controls.lib.drive_helpers import get_friction
ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
@@ -204,14 +203,12 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback)
- self.sp_update_params()
- buttonEvents = []
distance_button = 0
# Don't add event if transitioning from INIT, unless it's to an actual button
if self.CS.cruise_buttons != CruiseButtons.UNPRESS or self.CS.prev_cruise_buttons != CruiseButtons.INIT:
- buttonEvents = [
+ self.CS.button_events = [
*create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT,
unpressed_btn=CruiseButtons.UNPRESS),
*create_button_events(self.CS.distance_button, self.CS.prev_distance_button,
@@ -219,27 +216,32 @@ class CarInterface(CarInterfaceBase):
]
distance_button = self.CS.distance_button
+ self.CS.button_events = [
+ *self.CS.button_events,
+ *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}),
+ ]
+
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
if not self.CP.pcmCruise:
- if any(b.type == ButtonType.accelCruise and b.pressed for b in buttonEvents):
+ if any(b.type == ButtonType.accelCruise and b.pressed for b in self.CS.button_events):
self.CS.accEnabled = True
self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled,
- buttonEvents, c.vCruise)
+ self.CS.button_events, c.vCruise)
if ret.cruiseState.available:
if self.enable_mads:
if not self.CS.prev_mads_enabled and self.CS.mads_enabled:
self.CS.madsEnabled = True
- if self.CS.prev_lkas_enabled != 1 and self.CS.lkas_enabled == 1:
+ if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events):
self.CS.madsEnabled = not self.CS.madsEnabled
self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled)
else:
self.CS.madsEnabled = False
if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0):
- if any(b.type == ButtonType.cancel for b in buttonEvents):
+ if any(b.type == ButtonType.cancel for b in self.CS.button_events):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
if self.get_sp_pedal_disengage(ret):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
@@ -252,17 +254,10 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(distance_button))
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
# The ECM allows enabling on falling edge of set, but only rising edge of resume
events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low,
diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py
index dd40d07712..2202b25350 100644
--- a/selfdrive/car/honda/carcontroller.py
+++ b/selfdrive/car/honda/carcontroller.py
@@ -5,13 +5,12 @@ import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV
from openpilot.common.numpy_fast import clip, interp
from openpilot.common.params import Params
-from openpilot.common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker
-from openpilot.selfdrive.car import create_gas_interceptor_command
+from openpilot.selfdrive.car import DT_CTRL, rate_limit, make_tester_present_msg, create_gas_interceptor_command
from openpilot.selfdrive.car.honda import hondacan
from openpilot.selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
from openpilot.selfdrive.car.interfaces import CarControllerBase
-from openpilot.selfdrive.controls.lib.drive_helpers import rate_limit, HONDA_V_CRUISE_MIN
+from openpilot.selfdrive.controls.lib.drive_helpers import HONDA_V_CRUISE_MIN
VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState
@@ -110,11 +109,10 @@ def rate_limit_steer(new_steer, last_steer):
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.packer = CANPacker(dbc_name)
self.params = CarControllerParams(CP)
self.CAN = hondacan.CanBus(CP)
- self.frame = 0
self.braking = False
self.brake_steady = 0.
@@ -132,9 +130,6 @@ class CarController(CarControllerBase):
self.sm = messaging.SubMaster(['longitudinalPlanSP'])
self.param_s = Params()
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.speed_limit_control_enabled = False
- self.last_speed_limit_sign_tap = False
self.last_speed_limit_sign_tap_prev = False
self.speed_limit = 0.
self.speed_limit_offset = 0
@@ -173,11 +168,7 @@ class CarController(CarControllerBase):
self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed
self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed
- if self.frame % 200 == 0:
- self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc")
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap")
- self.v_cruise_min = HONDA_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1)
+ self.v_cruise_min = HONDA_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)
actuators = CC.actuators
hud_control = CC.hudControl
@@ -213,14 +204,14 @@ class CarController(CarControllerBase):
self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V))
if not self.CP.pcmCruiseSpeed:
- if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap:
+ if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap:
self.sl_force_active_timer = self.frame
self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False)
- self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap
+ self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap
- sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
- sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
- sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
+ sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
+ sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
+ sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
slc_active = not sl_inactive and not sl_temp_inactive
self.slc_active_stock = slc_active
@@ -231,7 +222,7 @@ class CarController(CarControllerBase):
# tester present - w/ no response (keeps radar disabled)
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and self.CP.openpilotLongitudinalControl:
if self.frame % 10 == 0:
- can_sends.append((0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 1))
+ can_sends.append(make_tester_present_msg(0x18DAB0F1, 1, suppress_response=True))
# Send steering command.
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_steer, CC.latActive, self.CP.carFingerprint,
@@ -432,8 +423,8 @@ class CarController(CarControllerBase):
return min(target_speed_kph, curve_speed)
def get_button_control(self, CS, final_speed, v_cruise_kph_prev):
- self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1))
- self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH))
+ self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1))
+ self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH))
cruise_button = self.get_button_type(self.button_type)
return cruise_button
diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py
index d412f81142..ae8746d958 100644
--- a/selfdrive/car/honda/carstate.py
+++ b/selfdrive/car/honda/carstate.py
@@ -242,7 +242,7 @@ class CarState(CarStateBase):
ret.brakePressed = (cp.vl["POWERTRAIN_DATA"]["BRAKE_PRESSED"] != 0) or self.brake_switch_active
ret.brake = cp.vl["VSA_STATUS"]["USER_BRAKE"]
- ret.cruiseState.enabled = self.pcm_cruise_enabled = cp.vl["POWERTRAIN_DATA"]["ACC_STATUS"] != 0
+ ret.cruiseState.enabled = cp.vl["POWERTRAIN_DATA"]["ACC_STATUS"] != 0
ret.cruiseState.available = bool(cp.vl[self.main_on_sig_msg]["MAIN_ON"])
# Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models
diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py
index 4025026048..647d72860c 100644
--- a/selfdrive/car/honda/fingerprints.py
+++ b/selfdrive/car/honda/fingerprints.py
@@ -576,6 +576,7 @@ FW_VERSIONS = {
b'28102-5MX-A900\x00\x00',
b'28102-5MX-A910\x00\x00',
b'28102-5MX-C001\x00\x00',
+ b'28102-5MX-C610\x00\x00',
b'28102-5MX-C910\x00\x00',
b'28102-5MX-D001\x00\x00',
b'28102-5MX-D710\x00\x00',
diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py
index 1aba4d3119..1459b301a2 100755
--- a/selfdrive/car/honda/interface.py
+++ b/selfdrive/car/honda/interface.py
@@ -174,11 +174,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 239], [0, 239]]
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.,20], [0.,20]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.4,0.3], [0,0]]
- tire_stiffness_factor = 0.8467
- ret.longitudinalTuning.kpBP = [0., 5., 35.]
- ret.longitudinalTuning.kpV = [2.4, 1.6, 0.8]
- ret.longitudinalTuning.kiBP = [0., 35.]
- ret.longitudinalTuning.kiV = [0.2, 0.16]
elif candidate in (CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
@@ -266,9 +261,8 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_body)
- self.sp_update_params()
- buttonEvents = [
+ self.CS.button_events = [
*create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT),
*create_button_events(self.CS.cruise_setting, self.CS.prev_cruise_setting, SETTINGS_BUTTONS_DICT),
]
@@ -276,35 +270,43 @@ class CarInterface(CarInterfaceBase):
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled,
- buttonEvents, c.vCruise)
+ self.CS.button_events, c.vCruise)
if ret.cruiseState.available:
if self.enable_mads:
if not self.CS.prev_mads_enabled and self.CS.mads_enabled:
self.CS.madsEnabled = True
- if self.CS.prev_cruise_setting != 1 and self.CS.cruise_setting == 1:
+ if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events):
self.CS.madsEnabled = not self.CS.madsEnabled
self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled)
else:
self.CS.madsEnabled = False
- if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed:
- if any(b.type == ButtonType.cancel for b in buttonEvents):
+ min_enable_speed_pcm = self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed
+
+ if not self.CP.pcmCruise or min_enable_speed_pcm or not self.CP.pcmCruiseSpeed:
+ if any(b.type == ButtonType.cancel for b in self.CS.button_events):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
if self.get_sp_pedal_disengage(ret):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
- ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled
+ ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads or min_enable_speed_pcm \
+ else False if self.CP.pcmCruise \
+ else self.CS.accEnabled
- if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed:
+ if min_enable_speed_pcm:
if ret.gasPressed and not ret.cruiseState.enabled:
self.CS.accEnabled = False
- self.CS.accEnabled = self.CS.pcm_cruise_enabled
+ if ret.cruiseState.enabled and not self.CS.out.cruiseState.enabled:
+ self.CS.accEnabled = True
+ elif not ret.cruiseState.enabled:
+ self.CS.accEnabled = False
- ret, self.CS = self.get_sp_common_state(ret, self.CS,
- min_enable_speed_pcm=(self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed),
- gap_button=(self.CS.cruise_setting == 3))
+ ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=(self.CS.cruise_setting == 3))
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
# events
events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low], pcm_enable=False)
diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py
index f640f6529a..013b253e0b 100644
--- a/selfdrive/car/hyundai/carcontroller.py
+++ b/selfdrive/car/hyundai/carcontroller.py
@@ -3,9 +3,8 @@ import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV
from openpilot.common.numpy_fast import clip
from openpilot.common.params import Params
-from openpilot.common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker
-from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance
+from openpilot.selfdrive.car import DT_CTRL, apply_driver_steer_torque_limits, common_fault_avoidance, make_tester_present_msg
from openpilot.selfdrive.car.hyundai import hyundaicanfd, hyundaican
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, HyundaiFlagsSP, Buttons, CarControllerParams, CANFD_CAR, CAR, CAMERA_SCC_CAR, LEGACY_SAFETY_MODE_CAR
@@ -48,12 +47,11 @@ def process_hud_alert(enabled, fingerprint, hud_control):
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.CAN = CanBus(CP)
self.params = CarControllerParams(CP)
self.packer = CANPacker(dbc_name)
self.angle_limit_counter = 0
- self.frame = 0
self.accel_last = 0
self.apply_steer_last = 0
@@ -72,9 +70,6 @@ class CarController(CarControllerBase):
self.sm = messaging.SubMaster(sub_services)
self.param_s = Params()
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.speed_limit_control_enabled = False
- self.last_speed_limit_sign_tap = False
self.last_speed_limit_sign_tap_prev = False
self.speed_limit = 0.
self.speed_limit_offset = 0
@@ -127,11 +122,7 @@ class CarController(CarControllerBase):
self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed
self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed
- if self.frame % 200 == 0:
- self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc")
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap")
- self.v_cruise_min = HYUNDAI_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1)
+ self.v_cruise_min = HYUNDAI_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)
actuators = CC.actuators
hud_control = CC.hudControl
@@ -175,14 +166,14 @@ class CarController(CarControllerBase):
blinking_icon = (self.frame - self.disengage_blink) * DT_CTRL < 1.0 if self.lat_disengage_init else False
if not self.CP.pcmCruiseSpeed:
- if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap:
+ if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap:
self.sl_force_active_timer = self.frame
self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False)
- self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap
+ self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap
- sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
- sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
- sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
+ sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
+ sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
+ sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
slc_active = not sl_inactive and not sl_temp_inactive
self.slc_active_stock = slc_active
@@ -202,11 +193,11 @@ class CarController(CarControllerBase):
addr, bus = 0x7d0, 0
if self.CP.flags & HyundaiFlags.CANFD_HDA2.value:
addr, bus = 0x730, self.CAN.ECAN
- can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus])
+ can_sends.append(make_tester_present_msg(addr, bus, suppress_response=True))
# for blinkers
if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS:
- can_sends.append([0x7b1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", self.CAN.ECAN])
+ can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True))
# CAN-FD platforms
if self.CP.carFingerprint in CANFD_CAR:
@@ -235,7 +226,7 @@ class CarController(CarControllerBase):
if hda2:
can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame))
if self.frame % 2 == 0:
- can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled and CS.out.cruiseState.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
+ can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CS, CC.enabled and CS.out.cruiseState.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
set_speed_in_units, hud_control))
self.accel_last = accel
else:
@@ -433,8 +424,8 @@ class CarController(CarControllerBase):
return min(target_speed_kph, curve_speed)
def get_button_control(self, CS, final_speed, v_cruise_kph_prev):
- self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1))
- self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH))
+ self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1))
+ self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH))
cruise_button = self.get_button_type(self.button_type)
return cruise_button
diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py
index d8329c5ba7..fcd44cc1cd 100644
--- a/selfdrive/car/hyundai/carstate.py
+++ b/selfdrive/car/hyundai/carstate.py
@@ -62,6 +62,11 @@ class CarState(CarStateBase):
self.escc_aeb_dec_cmd = 0
self._speed_limit_clu = 0
+ def get_main_enabled(self, ret) -> bool:
+ if self.prev_main_buttons != 1 and self.main_buttons[-1] == 1:
+ self.mainEnabled = not self.mainEnabled
+ return ret.cruiseState.available and self.mainEnabled
+
def update(self, cp, cp_cam):
if self.CP.carFingerprint in CANFD_CAR:
return self.update_canfd(cp, cp_cam)
@@ -112,7 +117,7 @@ class CarState(CarStateBase):
# cruise state
if self.CP.openpilotLongitudinalControl:
# These are not used for engage/disengage since openpilot keeps track of state using the buttons
- ret.cruiseState.available = cp.vl["TCS13"]["ACCEnable"] == 0 and self.mainEnabled
+ ret.cruiseState.available = cp.vl["TCS13"]["ACCEnable"] == 0
ret.cruiseState.enabled = cp.vl["TCS13"]["ACC_REQ"] == 1
ret.cruiseState.standstill = False
ret.cruiseState.nonAdaptive = False
@@ -207,17 +212,12 @@ class CarState(CarStateBase):
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
if self.CP.openpilotLongitudinalControl:
- if self.prev_main_buttons != 1:
- if self.main_buttons[-1] == 1:
- self.mainEnabled = not self.mainEnabled
- ret.cruiseState.available = ret.cruiseState.available and self.mainEnabled
+ ret.cruiseState.available = self.get_main_enabled(ret)
self.prev_mads_enabled = self.mads_enabled
self.prev_lfa_enabled = self.lfa_enabled
if self.CP.spFlags & HyundaiFlagsSP.SP_CAN_LFA_BTN:
self.lfa_enabled = cp.vl["BCM_PO_11"]["LFA_Pressed"]
- self.mads_enabled = False if not self.control_initialized else ret.cruiseState.available
-
if self.CP.spFlags & HyundaiFlagsSP.SP_NAV_MSG or self.CP.spFlags & HyundaiFlagsSP.SP_LKAS12:
self._update_traffic_signals(cp, cp_cam)
ret.cruiseState.speedLimit = self._calculate_speed_limit() * speed_conv
@@ -282,6 +282,7 @@ class CarState(CarStateBase):
ret.cruiseState.standstill = False
else:
cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp
+ ret.cruiseState.available = cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1
ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2)
ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1
ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor
@@ -298,6 +299,8 @@ class CarState(CarStateBase):
self.prev_main_buttons = self.main_buttons[-1]
self.cruise_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["CRUISE_BUTTONS"])
self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"])
+ if self.CP.openpilotLongitudinalControl:
+ ret.cruiseState.available = self.get_main_enabled(ret)
self.prev_mads_enabled = self.mads_enabled
self.prev_lfa_enabled = self.lfa_enabled
self.lfa_enabled = cp.vl[self.cruise_btns_msg_canfd]["LFA_BTN"]
diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py
index 3b4bdc3edf..05b916daf9 100644
--- a/selfdrive/car/hyundai/fingerprints.py
+++ b/selfdrive/car/hyundai/fingerprints.py
@@ -171,6 +171,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101',
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101',
+ b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0200 4DNAC102',
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC101',
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102',
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101',
@@ -291,6 +292,7 @@ FW_VERSIONS = {
b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102',
b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102',
b'\xf1\x00TM MDPS C 1.00 1.02 56310-GA000 4TSHA100',
+ b'\xf1\x00TM MDPS C 1.00 1.02 56310GA000\x00 4TSHA100',
b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105',
b'\xf1\x00TM MDPS R 1.00 1.06 57700-CL000 4TSHP106',
],
@@ -407,6 +409,7 @@ FW_VERSIONS = {
b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360',
b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320',
b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360',
+ b'\xf1\x00ON ESC \x0b 103$\x04\x08 58910-S9360',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103',
@@ -480,11 +483,13 @@ FW_VERSIONS = {
},
CAR.GENESIS_G80: {
(Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00DH__ SCC F-CU- 1.00 1.01 96400-B1110 ',
b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ',
b'\xf1\x00DH__ SCC F-CUP 1.00 1.02 96400-B1120 ',
b'\xf1\x00DH__ SCC FHCUP 1.00 1.01 96400-B1110 ',
],
(Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00DH LKAS AT EUR LHD 1.01 1.01 95895-B1500 161014',
b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.01 95895-B1500 161014',
b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.02 95895-B1500 170810',
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014',
@@ -585,6 +590,7 @@ FW_VERSIONS = {
b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7220 4DLHC102',
],
(Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.01 99210-L2000 191022',
b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309',
b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.04 99210-L2000 210527',
],
@@ -595,6 +601,7 @@ FW_VERSIONS = {
b'\xf1\x00OS IEB \x02 210 \x02\x14 58520-K4000',
b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000',
b'\xf1\x00OS IEB \x03 210 \x02\x14 58520-K4000',
+ b'\xf1\x00OS IEB \x03 211 \x04\x02 58520-K4000',
b'\xf1\x00OS IEB \x03 212 \x11\x13 58520-K4000',
b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000',
],
@@ -705,6 +712,7 @@ FW_VERSIONS = {
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ',
+ b'\xf1\x00DEhe SCC FHCUP 1.00 1.02 99110-G5100 ',
b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ',
],
},
@@ -757,10 +765,12 @@ FW_VERSIONS = {
b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4100 ',
],
(Ecu.abs, 0x7d1, None): [
+ b'\xf1\x00JF ESC \t 17 \x16\x06# 58920-D4180',
b'\xf1\x00JF ESC \x0f 16 \x16\x06\x17 58920-D5080',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00JFWGN LDWS AT USA LHD 1.00 1.02 95895-D4100 G21',
+ b'\xf1\x00JFWGN LKAS AT EUR LHD 1.00 1.01 95895-D4100 G20',
],
},
CAR.KIA_OPTIMA_G4_FL: {
diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py
index a2f44716ef..e54e450787 100644
--- a/selfdrive/car/hyundai/hyundaican.py
+++ b/selfdrive/car/hyundai/hyundaican.py
@@ -81,7 +81,7 @@ def create_lkas11(packer, frame, CP, apply_steer, steer_req,
# Genesis and Optima fault when forwarding while engaged
values["CF_Lkas_LdwsActivemode"] = 2
- dat = packer.make_can_msg("LKAS11", 0, values)[2]
+ dat = packer.make_can_msg("LKAS11", 0, values)[1]
if CP.flags & HyundaiFlags.CHECKSUM_CRC8:
# CRC Checksum as seen on 2019 Hyundai Santa Fe
@@ -166,7 +166,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
scc12_values["CF_VSM_DecCmdAct"] = CS.escc_aeb_dec_cmd_act
scc12_values["CR_VSM_DecCmd"] = CS.escc_aeb_dec_cmd
- scc12_dat = packer.make_can_msg("SCC12", 0, scc12_values)[2]
+ scc12_dat = packer.make_can_msg("SCC12", 0, scc12_values)[1]
scc12_values["CR_VSM_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in scc12_dat) % 0x10
commands.append(packer.make_can_msg("SCC12", 0, scc12_values))
@@ -197,7 +197,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
"FCA_DrvSetStatus": 1,
"FCA_Status": 1, # AEB disabled
}
- fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2]
+ fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[1]
fca11_values["CR_FCA_ChkSum"] = hyundai_checksum(fca11_dat[:7])
commands.append(packer.make_can_msg("FCA11", 0, fca11_values))
diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py
index 8225166bfa..12b84ad0f6 100644
--- a/selfdrive/car/hyundai/hyundaicanfd.py
+++ b/selfdrive/car/hyundai/hyundaicanfd.py
@@ -121,7 +121,7 @@ def create_lfahda_cluster(packer, CAN, enabled, lat_active, lateral_paused, blin
return packer.make_can_msg("LFAHDA_CLUSTER", CAN.ECAN, values)
-def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control):
+def create_acc_control(packer, CAN, CS, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control):
jerk = 5
jn = jerk / 50
if not enabled or gas_override:
@@ -132,7 +132,7 @@ def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_ov
values = {
"ACCMode": 0 if not enabled else (2 if gas_override else 1),
- "MainMode_ACC": 1,
+ "MainMode_ACC": 1 if CS.mainEnabled else 0,
"StopReq": 1 if stopping else 0,
"aReqValue": a_val,
"aReqRaw": a_raw,
diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py
index e801b47771..c2140d2925 100644
--- a/selfdrive/car/hyundai/interface.py
+++ b/selfdrive/car/hyundai/interface.py
@@ -7,7 +7,7 @@ from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, HyundaiFlagsSP,
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, NON_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, Buttons
from openpilot.selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR
-from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
+from openpilot.selfdrive.car import create_button_events, get_safety_config
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
from openpilot.selfdrive.car.disable_ecu import disable_ecu
@@ -203,35 +203,37 @@ class CarInterface(CarInterfaceBase):
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- self.sp_update_params()
- buttonEvents = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)
+ self.CS.button_events = [
+ *create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT),
+ *create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1}),
+ *create_button_events(self.CS.main_buttons[-1], self.CS.prev_main_buttons, {1: ButtonType.altButton3}),
+ ]
+
+ self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled,
- buttonEvents, c.vCruise)
-
- self.CS.mads_enabled = False if not self.mads_main_toggle else self.CS.mads_enabled
+ self.CS.button_events, c.vCruise)
if ret.cruiseState.available:
if not self.CP.pcmCruiseSpeed:
- if self.CS.prev_main_buttons == 1:
- if self.CS.main_buttons[-1] != 1:
- self.CS.accEnabled = True
- elif self.CS.prev_cruise_buttons == 4:
- if self.CS.cruise_buttons[-1] != 4:
- self.accEnabled = True
- if self.enable_mads:
- if not self.CS.prev_mads_enabled and self.CS.mads_enabled:
- self.CS.madsEnabled = True
- if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1:
- self.CS.madsEnabled = not self.CS.madsEnabled
- self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled)
- else:
+ if any(b.type in (ButtonType.altButton3, ButtonType.cancel) and not b.pressed for b in self.CS.button_events):
+ self.CS.accEnabled = True
+
+ if self.enable_mads:
+ if not self.CS.prev_mads_enabled and self.CS.mads_enabled and \
+ any(b.type == ButtonType.altButton3 for b in self.CS.button_events):
+ self.CS.madsEnabled = True
+ if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events):
+ self.CS.madsEnabled = not self.CS.madsEnabled
+ self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled)
+
+ if not ret.cruiseState.available and self.CS.out.cruiseState.available:
self.CS.madsEnabled = False
if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed:
if not self.CP.pcmCruise:
- if any(b.type == ButtonType.cancel for b in buttonEvents):
+ if any(b.type == ButtonType.cancel for b in self.CS.button_events):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
if not self.CP.pcmCruiseSpeed:
if not ret.cruiseState.enabled:
@@ -242,17 +244,10 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=(self.CS.cruise_buttons[-1] == 3))
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
# On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state
# To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py
index 9c535244b8..25e8b40a72 100644
--- a/selfdrive/car/interfaces.py
+++ b/selfdrive/car/interfaces.py
@@ -1,3 +1,4 @@
+import capnp
import json
import os
import numpy as np
@@ -6,6 +7,7 @@ import tomllib
from abc import abstractmethod, ABC
from difflib import SequenceMatcher
from enum import StrEnum
+from types import SimpleNamespace
from typing import Any, NamedTuple
from collections.abc import Callable
from functools import cache
@@ -16,13 +18,15 @@ from openpilot.common.conversions import Conversions as CV
from openpilot.common.simple_kalman import KF1D, get_kalman_gain
from openpilot.common.numpy_fast import clip
from openpilot.common.params import Params
-from openpilot.common.realtime import DT_CTRL
-from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG
+from openpilot.selfdrive.car import DT_CTRL, apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, get_friction, STD_CARGO_KG, \
+ ButtonEvents
+from openpilot.selfdrive.car.param_manager import ParamManager
from openpilot.selfdrive.car.values import PLATFORMS
from openpilot.selfdrive.controls.lib.desire_helper import get_min_lateral_speed
-from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, get_friction
+from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET
from openpilot.selfdrive.controls.lib.events import Events
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel
+from openpilot.selfdrive.pandad import can_capnp_to_list
ButtonType = car.CarState.ButtonEvent.Type
GearShifter = car.CarState.GearShifter
@@ -68,6 +72,7 @@ class LatControlInputs(NamedTuple):
aego: float
+SendCan = tuple[int, bytes, int]
TorqueFromLateralAccelCallbackType = Callable[[LatControlInputs, car.CarParams.LateralTorqueTuning, float, float, bool, bool], float]
@@ -237,22 +242,14 @@ class CarInterfaceBase(ABC):
self.mads_ndlob = self.enable_mads and not self.mads_disengage_lateral_on_brake
self.gear_warning = 0
self.cruise_cancelled_btn = True
- self.acc_mads_combo = self.param_s.get_bool("AccMadsCombo")
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause")
- self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8"))
self.prev_acc_mads_combo = False
self.mads_event_lock = True
self.gap_button_counter = 0
self.experimental_mode_hold = False
- self.experimental_mode = self.param_s.get_bool("ExperimentalMode")
- self._frame = 0
- self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam")
- self.mads_main_toggle = self.param_s.get_bool("MadsCruiseMain")
- self.lkas_toggle = self.param_s.get_bool("LkasToggle")
self.last_mads_init = 0.
self.madsEnabledInit = False
self.madsEnabledInitPrev = False
+ self.button_events = ButtonEvents()
self.lat_torque_nn_model = None
eps_firmware = str(next((fw.fwVersion for fw in CP.carFw if fw.ecu == "eps"), ""))
@@ -265,7 +262,7 @@ class CarInterfaceBase(ABC):
self.lat_torque_nn_model, _ = get_nn_model(_car, eps_firmware)
return self.lat_torque_nn_model is not None and self.param_s.get_bool("NNFF")
- def apply(self, c: car.CarControl, now_nanos: int) -> tuple[car.CarControl.Actuators, list[tuple[int, int, bytes, int]]]:
+ def apply(self, c: car.CarControl, now_nanos: int) -> tuple[car.CarControl.Actuators, list[SendCan]]:
return self.CC.update(c, self.CS, now_nanos)
@staticmethod
@@ -419,11 +416,15 @@ class CarInterfaceBase(ABC):
def _update(self, c: car.CarControl) -> car.CarState:
pass
- def update(self, c: car.CarControl, can_strings: list[bytes]) -> car.CarState:
+ def update(self, c: car.CarControl, can_strings: list[bytes], params_list: SimpleNamespace) -> car.CarState:
# parse can
+ can_list = can_capnp_to_list(can_strings)
for cp in self.can_parsers:
if cp is not None:
- cp.update_strings(can_strings)
+ cp.update_strings(can_list)
+
+ self.CS.button_events = []
+ self.CS.params_list = params_list
# get CarState
ret = self._update(c)
@@ -460,18 +461,19 @@ class CarInterfaceBase(ABC):
events.add(EventName.doorOpen)
if cs_out.seatbeltUnlatched and cs_out.gearShifter != GearShifter.park:
events.add(EventName.seatbeltNotLatched)
- if cs_out.gearShifter != GearShifter.drive and cs_out.gearShifter not in extra_gears and not \
- (cs_out.gearShifter == GearShifter.unknown and self.gear_warning < int(0.5/DT_CTRL)):
+ if cs_out.gearShifter != GearShifter.drive and (extra_gears is None or
+ cs_out.gearShifter not in extra_gears) and not (cs_out.gearShifter == GearShifter.unknown and
+ self.gear_warning < int(0.5/DT_CTRL)):
if cs_out.vEgo < 5:
events.add(EventName.silentWrongGear)
else:
events.add(EventName.wrongGear)
if cs_out.gearShifter == GearShifter.reverse:
- if not self.reverse_dm_cam and cs_out.vEgo < 5:
+ if not self.CS.params_list.reverse_dm_cam and cs_out.vEgo < 5:
events.add(EventName.spReverseGear)
elif cs_out.vEgo >= 5:
events.add(EventName.reverseGear)
- if not cs_out.cruiseState.available:
+ if not cs_out.cruiseState.available and cs_out.cruiseState.enabled:
events.add(EventName.wrongCarMode)
if cs_out.espDisabled:
events.add(EventName.espDisabled)
@@ -549,7 +551,7 @@ class CarInterfaceBase(ABC):
return v_cruise != V_CRUISE_UNSET
def get_acc_mads(self, cruiseState_enabled, acc_enabled, mads_enabled):
- if self.acc_mads_combo:
+ if self.CS.params_list.acc_mads_combo:
if not self.prev_acc_mads_combo and (cruiseState_enabled or acc_enabled):
mads_enabled = True
self.prev_acc_mads_combo = (cruiseState_enabled or acc_enabled)
@@ -589,7 +591,7 @@ class CarInterfaceBase(ABC):
def get_sp_cruise_main_state(self, cs_out, CS):
if not CS.control_initialized:
mads_enabled = False
- elif not self.mads_main_toggle:
+ elif not self.CS.params_list.mads_main_toggle:
mads_enabled = False
else:
mads_enabled = cs_out.cruiseState.available
@@ -601,7 +603,7 @@ class CarInterfaceBase(ABC):
self.madsEnabledInit = False
self.madsEnabledInitPrev = False
return False
- if not self.mads_main_toggle or self.prev_acc_mads_combo:
+ if not self.CS.params_list.mads_main_toggle or self.prev_acc_mads_combo:
return CS.madsEnabled
if not self.madsEnabledInit and CS.madsEnabled:
self.madsEnabledInit = True
@@ -616,9 +618,8 @@ class CarInterfaceBase(ABC):
else:
return CS.madsEnabled
- def get_sp_common_state(self, cs_out, CS, min_enable_speed_pcm=False, gear_allowed=True, gap_button=False):
- cs_out.cruiseState.enabled = CS.accEnabled if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed or min_enable_speed_pcm else \
- cs_out.cruiseState.enabled
+ def get_sp_common_state(self, cs_out, CS, gear_allowed=True, gap_button=False):
+ cs_out.cruiseState.enabled = CS.accEnabled if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed else cs_out.cruiseState.enabled
if not self.enable_mads:
if cs_out.cruiseState.enabled and not CS.out.cruiseState.enabled:
@@ -629,9 +630,9 @@ class CarInterfaceBase(ABC):
if self.CP.openpilotLongitudinalControl:
self.toggle_exp_mode(gap_button)
- lane_change_speed_min = get_min_lateral_speed(self.pause_lateral_speed, self.is_metric)
+ lane_change_speed_min = get_min_lateral_speed(self.CS.params_list.pause_lateral_speed, self.CS.params_list.is_metric)
- cs_out.belowLaneChangeSpeed = cs_out.vEgo < lane_change_speed_min and self.below_speed_pause
+ cs_out.belowLaneChangeSpeed = cs_out.vEgo < lane_change_speed_min and self.CS.params_list.below_speed_pause
if cs_out.gearShifter in [GearShifter.park, GearShifter.reverse] or cs_out.doorOpen or \
(cs_out.seatbeltUnlatched and cs_out.gearShifter != GearShifter.park):
@@ -664,7 +665,7 @@ class CarInterfaceBase(ABC):
if self.gap_button_counter > 50:
self.gap_button_counter = 0
self.experimental_mode_hold = True
- self.param_s.put_bool_nonblocking("ExperimentalMode", not self.experimental_mode)
+ self.param_s.put_bool_nonblocking("ExperimentalMode", not self.CS.params_list.experimental_mode)
else:
self.gap_button_counter = 0
self.experimental_mode_hold = False
@@ -730,19 +731,9 @@ class CarInterfaceBase(ABC):
return events, cs_out
- def sp_update_params(self):
- self.experimental_mode = self.param_s.get_bool("ExperimentalMode")
- self._frame += 1
- if self._frame % 100 == 0:
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause")
- self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8"))
- if self._frame % 300 == 0:
- self._frame = 0
- self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam")
-
class RadarInterfaceBase(ABC):
def __init__(self, CP):
+ self.CP = CP
self.rcp = None
self.pts = {}
self.delay = 0
@@ -771,14 +762,15 @@ class CarStateBase(ABC):
self.cluster_speed_hyst_gap = 0.0
self.cluster_min_speed = 0.0 # min speed before dropping to 0
- self.param_s = Params()
self.accEnabled = False
self.madsEnabled = False
self.disengageByBrake = False
self.mads_enabled = False
self.prev_mads_enabled = False
self.control_initialized = False
- self.pcm_cruise_enabled = False
+
+ self.button_events: list[capnp.lib.capnp._DynamicStructBuilder] = []
+ self.params_list: SimpleNamespace = ParamManager().get_params()
Q = [[0.0, 0.0], [0.0, 100.0]]
R = 0.3
@@ -879,12 +871,10 @@ class CarStateBase(ABC):
return None
-SendCan = tuple[int, int, bytes, int]
-
-
class CarControllerBase(ABC):
def __init__(self, dbc_name: str, CP, VM):
- pass
+ self.CP = CP
+ self.frame = 0
@abstractmethod
def update(self, CC: car.CarControl.Actuators, CS: car.CarState, now_nanos: int) -> tuple[car.CarControl.Actuators, list[SendCan]]:
diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py
index 447c7093c5..b5158f2126 100644
--- a/selfdrive/car/isotp_parallel_query.py
+++ b/selfdrive/car/isotp_parallel_query.py
@@ -27,7 +27,7 @@ class IsoTpParallelQuery:
assert tx_addr not in FUNCTIONAL_ADDRS, f"Functional address should be defined in functional_addrs: {hex(tx_addr)}"
self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in real_addrs}
- self.msg_buffer: dict[int, list[tuple[int, int, bytes, int]]] = defaultdict(list)
+ self.msg_buffer: dict[int, list[tuple[int, bytes, int]]] = defaultdict(list)
def rx(self):
"""Drain can socket and sort messages into buffers based on address"""
@@ -36,11 +36,11 @@ class IsoTpParallelQuery:
for packet in can_packets:
for msg in packet.can:
if msg.src == self.bus and msg.address in self.msg_addrs.values():
- self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src))
+ self.msg_buffer[msg.address].append((msg.address, msg.dat, msg.src))
def _can_tx(self, tx_addr, dat, bus):
"""Helper function to send single message"""
- msg = [tx_addr, 0, dat, bus]
+ msg = [tx_addr, dat, bus]
self.sendcan.send(can_list_to_can_capnp([msg], msgtype='sendcan'))
def _can_rx(self, addr, sub_addr=None):
@@ -53,7 +53,7 @@ class IsoTpParallelQuery:
# Filter based on subadress
msgs = []
for m in self.msg_buffer[addr]:
- first_byte = m[2][0]
+ first_byte = m[1][0]
if first_byte == sub_addr:
msgs.append(m)
else:
diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py
index 9832cebe46..4bbc9e1c67 100644
--- a/selfdrive/car/mazda/carcontroller.py
+++ b/selfdrive/car/mazda/carcontroller.py
@@ -11,23 +11,18 @@ from openpilot.selfdrive.car.mazda.values import CarControllerParams, Buttons
from openpilot.selfdrive.controls.lib.drive_helpers import MAZDA_V_CRUISE_MIN
VisualAlert = car.CarControl.HUDControl.VisualAlert
-
-BUTTONS_STATES = ["accelCruise", "decelCruise", "cancel", "resumeCruise"]
+ButtonType = car.CarState.ButtonEvent.Type
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.apply_steer_last = 0
self.packer = CANPacker(dbc_name)
self.brake_counter = 0
- self.frame = 0
self.sm = messaging.SubMaster(['longitudinalPlanSP'])
self.param_s = Params()
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.speed_limit_control_enabled = False
- self.last_speed_limit_sign_tap = False
self.last_speed_limit_sign_tap_prev = False
self.speed_limit = 0.
self.speed_limit_offset = 0
@@ -66,23 +61,19 @@ class CarController(CarControllerBase):
self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed
self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed
- if self.frame % 200 == 0:
- self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc")
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap")
- self.v_cruise_min = MAZDA_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1)
+ self.v_cruise_min = MAZDA_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)
can_sends = []
if not self.CP.pcmCruiseSpeed:
- if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap:
+ if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap:
self.sl_force_active_timer = self.frame
self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False)
- self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap
+ self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap
- sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
- sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
- sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
+ sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
+ sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
+ sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
slc_active = not sl_inactive and not sl_temp_inactive
self.slc_active_stock = slc_active
@@ -141,8 +132,10 @@ class CarController(CarControllerBase):
# multikyd methods, sunnyhaibin logic
def get_cruise_buttons_status(self, CS):
if not CS.out.cruiseState.enabled:
- if any(CS.buttonStates[button_state] for button_state in BUTTONS_STATES):
- self.timer = 40
+ for be in CS.out.buttonEvents:
+ if be.type in (ButtonType.accelCruise, ButtonType.resumeCruise,
+ ButtonType.decelCruise, ButtonType.setCruise) and be.pressed:
+ self.timer = 40
elif self.timer:
self.timer -= 1
else:
@@ -228,8 +221,8 @@ class CarController(CarControllerBase):
return min(target_speed_kph, curve_speed)
def get_button_control(self, CS, final_speed, v_cruise_kph_prev):
- self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1))
- self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH))
+ self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1))
+ self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH))
cruise_button = self.get_button_type(self.button_type)
return cruise_button
diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py
index dea768c03a..e5808238f8 100644
--- a/selfdrive/car/mazda/carstate.py
+++ b/selfdrive/car/mazda/carstate.py
@@ -3,7 +3,7 @@ from openpilot.common.conversions import Conversions as CV
from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
from openpilot.selfdrive.car.interfaces import CarStateBase
-from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, MazdaFlags, BUTTON_STATES
+from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, MazdaFlags, BUTTONS
class CarState(CarStateBase):
def __init__(self, CP):
@@ -24,8 +24,7 @@ class CarState(CarStateBase):
self.lkas_enabled = False
self.prev_lkas_enabled = False
- self.buttonStates = BUTTON_STATES.copy()
- self.buttonStatesPrev = BUTTON_STATES.copy()
+ self.button_states = {button.event_type: False for button in BUTTONS}
def update(self, cp, cp_cam):
@@ -36,7 +35,6 @@ class CarState(CarStateBase):
self.prev_mads_enabled = self.mads_enabled
self.prev_lkas_enabled = self.lkas_enabled
- self.buttonStatesPrev = self.buttonStates.copy()
ret.wheelSpeeds = self.get_wheel_speeds(
cp.vl["WHEEL_SPEEDS"]["FL"],
@@ -56,6 +54,16 @@ class CarState(CarStateBase):
can_gear = int(cp.vl["GEAR"]["GEAR"])
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None))
+ # Buttons
+ for button in BUTTONS:
+ state = (cp.vl[button.can_addr][button.can_msg] in button.values)
+ if self.button_states[button.event_type] != state:
+ event = car.CarState.ButtonEvent.new_message()
+ event.type = button.event_type
+ event.pressed = state
+ self.button_events.append(event)
+ self.button_states[button.event_type] = state
+
ret.genericToggle = bool(cp.vl["BLINK_INFO"]["HIGH_BEAMS"])
ret.leftBlindspot = cp.vl["BSM"]["LEFT_BS_STATUS"] != 0
ret.rightBlindspot = cp.vl["BSM"]["RIGHT_BS_STATUS"] != 0
@@ -114,11 +122,6 @@ class CarState(CarStateBase):
self.acc_active_last = ret.cruiseState.enabled
- self.buttonStates["accelCruise"] = bool(cp.vl["CRZ_BTNS"]["SET_P"])
- self.buttonStates["decelCruise"] = bool(cp.vl["CRZ_BTNS"]["SET_M"])
- self.buttonStates["cancel"] = bool(cp.vl["CRZ_BTNS"]["CAN_OFF"])
- self.buttonStates["resumeCruise"] = bool(cp.vl["CRZ_BTNS"]["RES"])
-
self.crz_btns_counter = cp.vl["CRZ_BTNS"]["CTR"]
# camera signals
diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py
index bb9a3f93a0..4c26a18a57 100755
--- a/selfdrive/car/mazda/interface.py
+++ b/selfdrive/car/mazda/interface.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
from cereal import car
from openpilot.common.conversions import Conversions as CV
-from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS, BUTTON_STATES
-from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
+from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS
+from openpilot.selfdrive.car import create_button_events, get_safety_config
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
ButtonType = car.CarState.ButtonEvent.Type
@@ -12,7 +12,6 @@ GearShifter = car.CarState.GearShifter
class CarInterface(CarInterfaceBase):
def __init__(self, CP, CarController, CarState):
super().__init__(CP, CarController, CarState)
- self.buttonStatesPrev = BUTTON_STATES.copy()
@staticmethod
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs):
@@ -38,35 +37,31 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- self.sp_update_params()
# TODO: add button types for inc and dec
- buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})
-
- for button in self.CS.buttonStates:
- if self.CS.buttonStates[button] != self.buttonStatesPrev[button]:
- be = car.CarState.ButtonEvent.new_message()
- be.type = button
- be.pressed = self.CS.buttonStates[button]
- buttonEvents.append(be)
+ self.CS.button_events = [
+ *self.CS.button_events,
+ *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}),
+ *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}),
+ ]
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled,
- buttonEvents, c.vCruise)
+ self.CS.button_events, c.vCruise)
if ret.cruiseState.available:
if self.enable_mads:
if not self.CS.prev_mads_enabled and self.CS.mads_enabled:
self.CS.madsEnabled = True
- if self.CS.prev_lkas_enabled != self.CS.lkas_enabled:
+ if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events):
self.CS.madsEnabled = not self.CS.madsEnabled
self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled)
else:
self.CS.madsEnabled = False
if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed:
- if any(b.type == ButtonType.cancel for b in buttonEvents):
+ if any(b.type == ButtonType.cancel for b in self.CS.button_events):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
if self.get_sp_pedal_disengage(ret):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
@@ -79,17 +74,10 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button))
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
# events
events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake],
@@ -108,6 +96,4 @@ class CarInterface(CarInterfaceBase):
ret.events = events.to_msg()
- self.buttonStatesPrev = self.CS.buttonStates.copy()
-
return ret
diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py
index 2aac951dbb..2889cbb7b2 100644
--- a/selfdrive/car/mazda/values.py
+++ b/selfdrive/car/mazda/values.py
@@ -1,3 +1,4 @@
+from collections import namedtuple
from dataclasses import dataclass, field
from enum import IntFlag
@@ -8,6 +9,7 @@ from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarPar
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
Ecu = car.CarParams.Ecu
+Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
# Steer torque limits
@@ -26,14 +28,6 @@ class CarControllerParams:
pass
-BUTTON_STATES = {
- "accelCruise": False,
- "decelCruise": False,
- "cancel": False,
- "resumeCruise": False,
-}
-
-
@dataclass
class MazdaCarDocs(CarDocs):
package: str = "All"
@@ -98,6 +92,14 @@ class Buttons:
CANCEL = 4
+BUTTONS = [
+ Button(car.CarState.ButtonEvent.Type.accelCruise, "CRZ_BTNS", "SET_P", [1]),
+ Button(car.CarState.ButtonEvent.Type.decelCruise, "CRZ_BTNS", "SET_M", [1]),
+ Button(car.CarState.ButtonEvent.Type.cancel, "CRZ_BTNS", "CAN_OFF", [1]),
+ Button(car.CarState.ButtonEvent.Type.resumeCruise, "CRZ_BTNS", "RES", [1]),
+]
+
+
FW_QUERY_CONFIG = FwQueryConfig(
requests=[
# TODO: check data to ensure ABS does not skip ISO-TP frames on bus 0
diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py
index 86263df786..ddaeb785ab 100644
--- a/selfdrive/car/nissan/carcontroller.py
+++ b/selfdrive/car/nissan/carcontroller.py
@@ -11,9 +11,8 @@ VisualAlert = car.CarControl.HUDControl.VisualAlert
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.car_fingerprint = CP.carFingerprint
- self.frame = 0
self.lkas_max_torque = 0
self.apply_angle_last = 0
diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py
index 3d744a41a0..661ac668a5 100644
--- a/selfdrive/car/nissan/interface.py
+++ b/selfdrive/car/nissan/interface.py
@@ -1,6 +1,6 @@
from cereal import car
from panda import Panda
-from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
+from openpilot.selfdrive.car import create_button_events, get_safety_config
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
from openpilot.selfdrive.car.nissan.values import CAR
@@ -32,9 +32,8 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_adas, self.cp_cam)
- self.sp_update_params()
- buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})
+ self.CS.button_events = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
@@ -53,24 +52,11 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button))
- # CANCEL
- if self.CS.out.cruiseState.enabled and not ret.cruiseState.enabled:
- be = car.CarState.ButtonEvent.new_message()
- be.pressed = True
- be.type = ButtonType.cancel
- buttonEvents.append(be)
-
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled),
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake],
pcm_enable=False)
diff --git a/selfdrive/car/nissan/nissancan.py b/selfdrive/car/nissan/nissancan.py
index c72c12ed45..7191488d65 100644
--- a/selfdrive/car/nissan/nissancan.py
+++ b/selfdrive/car/nissan/nissancan.py
@@ -15,7 +15,7 @@ def create_steering_control(packer, apply_steer, frame, steer_on, lkas_max_torqu
"LKA_ACTIVE": steer_on,
}
- dat = packer.make_can_msg("LKAS", 0, values)[2]
+ dat = packer.make_can_msg("LKAS", 0, values)[1]
values["CHECKSUM"] = nissan_checksum(dat[:7])
return packer.make_can_msg("LKAS", 0, values)
diff --git a/selfdrive/car/param_manager.py b/selfdrive/car/param_manager.py
new file mode 100644
index 0000000000..c7df87b9b7
--- /dev/null
+++ b/selfdrive/car/param_manager.py
@@ -0,0 +1,50 @@
+from types import SimpleNamespace
+
+from openpilot.common.params import Params
+
+
+class ParamManager:
+ def __init__(self):
+ self._params_list: SimpleNamespace = self._create_namespace({
+ "acc_mads_combo": False,
+ "below_speed_pause": False,
+ "experimental_mode": False,
+ "is_metric": False,
+ "last_speed_limit_sign_tap": False,
+ "mads_main_toggle": False,
+ "pause_lateral_speed": 0,
+ "reverse_acc_change": False,
+ "reverse_dm_cam": False,
+ "speed_limit_control_enabled": False,
+ "subaru_manual_parking_brake": False,
+ "toyota_auto_lock_by_speed": False,
+ "toyota_auto_unlock_by_shifter": False,
+ "toyota_lkas_toggle": False,
+ "toyota_sng_hack": False,
+ })
+
+ @staticmethod
+ def _create_namespace(data: dict) -> SimpleNamespace:
+ return SimpleNamespace(**data)
+
+ def get_params(self) -> SimpleNamespace:
+ return self._params_list
+
+ def update(self, params: Params) -> None:
+ self._params_list = self._create_namespace({
+ "acc_mads_combo": params.get_bool("AccMadsCombo"),
+ "below_speed_pause": params.get_bool("BelowSpeedPause"),
+ "experimental_mode": params.get_bool("ExperimentalMode"),
+ "is_metric": params.get_bool("IsMetric"),
+ "last_speed_limit_sign_tap": params.get_bool("LastSpeedLimitSignTap"),
+ "mads_main_toggle": params.get_bool("MadsCruiseMain"),
+ "pause_lateral_speed": int(params.get("PauseLateralSpeed", encoding="utf8")),
+ "reverse_acc_change": params.get_bool("ReverseAccChange"),
+ "reverse_dm_cam": params.get_bool("ReverseDmCam"),
+ "speed_limit_control_enabled": params.get_bool("EnableSlc"),
+ "subaru_manual_parking_brake": params.get_bool("SubaruManualParkingBrakeSng"),
+ "toyota_auto_lock_by_speed": params.get_bool("ToyotaAutoLockBySpeed"),
+ "toyota_auto_unlock_by_shifter": params.get_bool("ToyotaAutoUnlockByShifter"),
+ "toyota_lkas_toggle": params.get_bool("LkasToggle"),
+ "toyota_sng_hack": params.get_bool("ToyotaSnG"),
+ })
diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py
index 198b17ace6..36ff514b2c 100644
--- a/selfdrive/car/subaru/carcontroller.py
+++ b/selfdrive/car/subaru/carcontroller.py
@@ -1,8 +1,7 @@
from cereal import car
from openpilot.common.numpy_fast import clip, interp
-from openpilot.common.params import Params
from opendbc.can.packer import CANPacker
-from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance
+from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance, make_tester_present_msg
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.car.subaru import subarucan
from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags, SubaruFlagsSP
@@ -18,19 +17,15 @@ _SNG_ACC_MAX_DIST = 4.5
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.apply_steer_last = 0
- self.frame = 0
self.cruise_button_prev = 0
self.steer_rate_counter = 0
- self.param_s = Params()
-
self.subaru_sng = False
if CP.spFlags & SubaruFlagsSP.SP_SUBARU_SNG:
self.subaru_sng = True
- self.manual_parking_brake = self.param_s.get_bool("SubaruManualParkingBrakeSng")
self.prev_close_distance = 0
self.prev_standstill = False
self.standstill_start = 0
@@ -47,9 +42,6 @@ class CarController(CarControllerBase):
hud_control = CC.hudControl
pcm_cancel_cmd = CC.cruiseControl.cancel
- if self.frame % 250 == 0 and self.subaru_sng:
- self.manual_parking_brake = self.param_s.get_bool("SubaruManualParkingBrakeSng")
-
can_sends = []
# *** steering ***
@@ -158,7 +150,7 @@ class CarController(CarControllerBase):
if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT:
# Tester present (keeps eyesight disabled)
if self.frame % 100 == 0:
- can_sends.append([GLOBAL_ES_ADDR, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", CanBus.camera])
+ can_sends.append(make_tester_present_msg(GLOBAL_ES_ADDR, CanBus.camera, suppress_response=True))
# Create all of the other eyesight messages to keep the rest of the car happy when eyesight is disabled
if self.frame % 5 == 0:
@@ -191,7 +183,7 @@ class CarController(CarControllerBase):
and CS.close_distance > self.prev_close_distance): # distance with lead car is increasing
self.sng_acc_resume = True
elif not (self.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID)):
- if self.manual_parking_brake:
+ if CS.params_list.subaru_manual_parking_brake and self.subaru_sng:
# Send brake message with non-zero speed in standstill to avoid non-EPB ACC disengage
if (CC.enabled # ACC active
and CS.car_follow == 1 # lead car
diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py
index 744c017a96..185ee8e54d 100644
--- a/selfdrive/car/subaru/interface.py
+++ b/selfdrive/car/subaru/interface.py
@@ -1,6 +1,6 @@
from cereal import car
from panda import Panda
-from openpilot.selfdrive.car import get_safety_config, create_mads_event
+from openpilot.selfdrive.car import get_safety_config
from openpilot.selfdrive.car.disable_ecu import disable_ecu
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, SubaruFlags, SubaruFlagsSP
@@ -116,9 +116,6 @@ class CarInterface(CarInterfaceBase):
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_body)
- self.sp_update_params()
-
- buttonEvents = []
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
@@ -145,24 +142,11 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS)
- # CANCEL
- if self.CS.out.cruiseState.enabled and not ret.cruiseState.enabled:
- be = car.CarState.ButtonEvent.new_message()
- be.pressed = True
- be.type = ButtonType.cancel
- buttonEvents.append(be)
-
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled),
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low], pcm_enable=False)
diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py
index 3fdb58a837..ef62fc01da 100644
--- a/selfdrive/car/subaru/subarucan.py
+++ b/selfdrive/car/subaru/subarucan.py
@@ -284,7 +284,7 @@ def create_es_static_2(packer):
# *** Subaru Pre-global ***
def subaru_preglobal_checksum(packer, values, addr, checksum_byte=7):
- dat = packer.make_can_msg(addr, 0, values)[2]
+ dat = packer.make_can_msg(addr, 0, values)[1]
return (sum(dat[:checksum_byte]) + sum(dat[checksum_byte+1:])) % 256
diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py
index e460111e32..8ee2a2165e 100644
--- a/selfdrive/car/tesla/carcontroller.py
+++ b/selfdrive/car/tesla/carcontroller.py
@@ -8,8 +8,7 @@ from openpilot.selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParam
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
- self.frame = 0
+ super().__init__(dbc_name, CP, VM)
self.apply_angle_last = 0
self.packer = CANPacker(dbc_name)
self.pt_packer = CANPacker(DBC[CP.carFingerprint]['pt'])
diff --git a/selfdrive/car/tesla/radar_interface.py b/selfdrive/car/tesla/radar_interface.py
index ae5077824b..1684e42e7f 100755
--- a/selfdrive/car/tesla/radar_interface.py
+++ b/selfdrive/car/tesla/radar_interface.py
@@ -8,7 +8,6 @@ from openpilot.selfdrive.car.interfaces import RadarInterfaceBase
class RadarInterface(RadarInterfaceBase):
def __init__(self, CP):
super().__init__(CP)
- self.CP = CP
if CP.carFingerprint == CAR.TESLA_MODELS_RAVEN:
messages = [('RadarStatus', 16)]
diff --git a/selfdrive/car/tesla/teslacan.py b/selfdrive/car/tesla/teslacan.py
index 6bb27b995f..f8cd138e77 100644
--- a/selfdrive/car/tesla/teslacan.py
+++ b/selfdrive/car/tesla/teslacan.py
@@ -25,7 +25,7 @@ class TeslaCAN:
"DAS_steeringControlCounter": counter,
}
- data = self.packer.make_can_msg("DAS_steeringControl", CANBUS.chassis, values)[2]
+ data = self.packer.make_can_msg("DAS_steeringControl", CANBUS.chassis, values)[1]
values["DAS_steeringControlChecksum"] = self.checksum(0x488, data[:3])
return self.packer.make_can_msg("DAS_steeringControl", CANBUS.chassis, values)
@@ -69,7 +69,7 @@ class TeslaCAN:
values["SpdCtrlLvr_Stat"] = 1
values["MC_STW_ACTN_RQ"] = counter
- data = self.packer.make_can_msg("STW_ACTN_RQ", bus, values)[2]
+ data = self.packer.make_can_msg("STW_ACTN_RQ", bus, values)[1]
values["CRC_STW_ACTN_RQ"] = self.crc(data[:7])
return self.packer.make_can_msg("STW_ACTN_RQ", bus, values)
@@ -88,7 +88,7 @@ class TeslaCAN:
}
for packer, bus in [(self.packer, CANBUS.chassis), (self.pt_packer, CANBUS.powertrain)]:
- data = packer.make_can_msg("DAS_control", bus, values)[2]
+ data = packer.make_can_msg("DAS_control", bus, values)[1]
values["DAS_controlChecksum"] = self.checksum(0x2b9, data[:7])
messages.append(packer.make_can_msg("DAS_control", bus, values))
return messages
diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py
index 3ab4047c77..4cf32513c8 100755
--- a/selfdrive/car/tests/routes.py
+++ b/selfdrive/car/tests/routes.py
@@ -39,7 +39,7 @@ routes = [
CarTestRoute("0c94aa1e1296d7c6|2021-05-05--19-48-37", CHRYSLER.JEEP_GRAND_CHEROKEE),
CarTestRoute("91dfedae61d7bd75|2021-05-22--20-07-52", CHRYSLER.JEEP_GRAND_CHEROKEE_2019),
- CarTestRoute("420a8e183f1aed48|2020-03-05--07-15-29", CHRYSLER.CHRYSLER_PACIFICA_2017_HYBRID),
+ CarTestRoute("420a8e183f1aed48|2020-03-05--07-15-29", CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID), # 2017
CarTestRoute("43a685a66291579b|2021-05-27--19-47-29", CHRYSLER.CHRYSLER_PACIFICA_2018),
CarTestRoute("378472f830ee7395|2021-05-28--07-38-43", CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID),
CarTestRoute("8190c7275a24557b|2020-01-29--08-33-58", CHRYSLER.CHRYSLER_PACIFICA_2019_HYBRID),
diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py
index 5a1911045e..3aa2ea221e 100644
--- a/selfdrive/car/tests/test_car_interfaces.py
+++ b/selfdrive/car/tests/test_car_interfaces.py
@@ -6,8 +6,7 @@ import importlib
from parameterized import parameterized
from cereal import car, messaging
-from openpilot.common.realtime import DT_CTRL
-from openpilot.selfdrive.car import gen_empty_fingerprint
+from openpilot.selfdrive.car import DT_CTRL, gen_empty_fingerprint
from openpilot.selfdrive.car.car_helpers import interfaces
from openpilot.selfdrive.car.fingerprints import all_known_cars
from openpilot.selfdrive.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
@@ -16,6 +15,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
+from openpilot.selfdrive.pandad import can_capnp_to_list
from openpilot.selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator
ALL_ECUS = {ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}
@@ -133,7 +133,7 @@ class TestCarInterfaces:
# Test radar fault
if not car_params.radarUnavailable and radar_interface.rcp is not None:
- cans = [messaging.new_message('can', 1).to_bytes() for _ in range(5)]
+ cans = can_capnp_to_list([messaging.new_message('can', 1).to_bytes() for _ in range(5)])
rr = radar_interface.update(cans)
assert rr is None or len(rr.errors) > 0
diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py
index f872972542..f6f6ed86d7 100644
--- a/selfdrive/car/tests/test_fw_fingerprint.py
+++ b/selfdrive/car/tests/test_fw_fingerprint.py
@@ -5,11 +5,13 @@ from collections import defaultdict
from parameterized import parameterized
from cereal import car
+from openpilot.selfdrive.car import make_can_msg
from openpilot.selfdrive.car.car_helpers import interfaces
from openpilot.selfdrive.car.fingerprints import FW_VERSIONS
from openpilot.selfdrive.car.fw_versions import ESSENTIAL_ECUS, FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, \
match_fw_to_car, get_brand_ecu_matches, get_fw_versions, get_present_ecus
from openpilot.selfdrive.car.vin import get_vin
+from openpilot.selfdrive.pandad import can_list_to_can_capnp
CarFw = car.CarParams.CarFw
Ecu = car.CarParams.Ecu
@@ -19,7 +21,8 @@ ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
class FakeSocket:
def receive(self, non_blocking=False):
- pass
+ return (can_list_to_can_capnp([make_can_msg(random.randint(0x600, 0x800), b'\x00' * 8, 0)])
+ if random.uniform(0, 1) > 0.5 else None)
def send(self, msg):
pass
@@ -313,3 +316,18 @@ class TestFwFingerprintTiming:
total_time = round(total_times[num_pandas], 2)
self._assert_timing(total_time, total_ref_time[num_pandas])
print(f'all brands, total FW query time={total_time} seconds')
+
+ def test_get_fw_versions(self, subtests, mocker):
+ # some coverage on IsoTpParallelQuery and panda UDS library
+ # TODO: replace this with full fingerprint simulation testing
+ # https://github.com/commaai/panda/pull/1329
+
+ def fake_cloudlog_exception(*args, **kwargs):
+ raise
+
+ mocker.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", lambda *args: None)
+ mocker.patch("openpilot.common.swaglog.cloudlog.exception", fake_cloudlog_exception)
+ fake_socket = FakeSocket()
+ for brand in FW_QUERY_CONFIGS.keys():
+ with subtests.test(brand=brand):
+ get_fw_versions(fake_socket, fake_socket, brand, num_pandas=1)
diff --git a/selfdrive/car/tests/test_lateral_limits.py b/selfdrive/car/tests/test_lateral_limits.py
index e61d197f4b..24eac01fcd 100755
--- a/selfdrive/car/tests/test_lateral_limits.py
+++ b/selfdrive/car/tests/test_lateral_limits.py
@@ -5,7 +5,7 @@ from parameterized import parameterized_class
import pytest
import sys
-from openpilot.common.realtime import DT_CTRL
+from openpilot.selfdrive.car import DT_CTRL
from openpilot.selfdrive.car.car_helpers import interfaces
from openpilot.selfdrive.car.fingerprints import all_known_cars
from openpilot.selfdrive.car.interfaces import get_torque_params
diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py
index b5d75e665b..22a80f359a 100644
--- a/selfdrive/car/tests/test_models.py
+++ b/selfdrive/car/tests/test_models.py
@@ -12,14 +12,14 @@ from parameterized import parameterized_class
from cereal import messaging, log, car
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
-from openpilot.common.realtime import DT_CTRL
-from openpilot.selfdrive.car import gen_empty_fingerprint
-from openpilot.selfdrive.car.card import Car
+from openpilot.selfdrive.car import DT_CTRL, gen_empty_fingerprint
from openpilot.selfdrive.car.fingerprints import all_known_cars, MIGRATION
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces
from openpilot.selfdrive.car.honda.values import CAR as HONDA, HondaFlags
from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute
from openpilot.selfdrive.car.values import Platform
+from openpilot.selfdrive.car.card import Car
+from openpilot.selfdrive.pandad import can_capnp_to_list
from openpilot.selfdrive.test.helpers import read_segment_list
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
from openpilot.tools.lib.logreader import LogReader, internal_source, openpilotci_source
@@ -238,7 +238,7 @@ class TestCarModelBase(unittest.TestCase):
# start parsing CAN messages after we've left ELM mode and can expect CAN traffic
error_cnt = 0
for i, msg in enumerate(self.can_msgs[self.elm_frame:]):
- rr = RI.update((msg.as_builder().to_bytes(),))
+ rr = RI.update(can_capnp_to_list((msg.as_builder().to_bytes(),)))
if rr is not None and i > 50:
error_cnt += car.RadarData.Error.canError in rr.errors
self.assertEqual(error_cnt, 0)
@@ -298,7 +298,7 @@ class TestCarModelBase(unittest.TestCase):
now_nanos += DT_CTRL * 1e9
msgs_sent += len(sendcan)
- for addr, _, dat, bus in sendcan:
+ for addr, dat, bus in sendcan:
to_send = libpanda_py.make_CANPacket(addr, bus % 4, dat)
self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus))
diff --git a/selfdrive/car/torque_data/params.toml b/selfdrive/car/torque_data/params.toml
index b0ac39f392..f8884b1866 100644
--- a/selfdrive/car/torque_data/params.toml
+++ b/selfdrive/car/torque_data/params.toml
@@ -7,7 +7,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"CHEVROLET_VOLT" = [1.5961527626411784, 1.8422651988094612, 0.1572393918005158]
"CHRYSLER_PACIFICA_2018" = [2.07140, 1.3366521181047952, 0.13776367250652022]
"CHRYSLER_PACIFICA_2020" = [1.86206, 1.509076559398423, 0.14328246159386085]
-"CHRYSLER_PACIFICA_2017_HYBRID" = [1.79422, 1.06831764583744, 0.116237]
"CHRYSLER_PACIFICA_2018_HYBRID" = [2.08887, 1.2943025830995154, 0.114818]
"CHRYSLER_PACIFICA_2019_HYBRID" = [1.90120, 1.1958788168371808, 0.131520]
"GENESIS_G70" = [3.8520195946707947, 2.354697063349854, 0.06830285485626221]
diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py
index 210ae23fa1..b71fbe4f38 100644
--- a/selfdrive/car/toyota/carcontroller.py
+++ b/selfdrive/car/toyota/carcontroller.py
@@ -1,9 +1,8 @@
from cereal import car
from common.conversions import Conversions as CV
from openpilot.common.numpy_fast import clip, interp
-from openpilot.common.params import Params
-from openpilot.selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, \
- create_gas_interceptor_command, make_can_msg
+from openpilot.selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, make_can_msg, make_tester_present_msg, \
+ create_gas_interceptor_command
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.car.toyota import toyotacan
from openpilot.selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \
@@ -37,9 +36,8 @@ LOCK_CMD = b"\x40\x05\x30\x11\x00\x80\x00\x00"
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.params = CarControllerParams(self.CP)
- self.frame = 0
self.last_steer = 0
self.last_angle = 0
self.alert_active = False
@@ -52,33 +50,16 @@ class CarController(CarControllerBase):
self.gas = 0
self.accel = 0
- self.param_s = Params()
- self._is_metric = self.param_s.get_bool("IsMetric")
- self._reverse_acc_change = self.param_s.get_bool("ReverseAccChange")
- self._sng_hack = self.param_s.get_bool("ToyotaSnG")
-
self.left_blindspot_debug_enabled = False
self.right_blindspot_debug_enabled = False
self.last_blindspot_frame = 0
- if CP.spFlags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
- self.brake_hold_active: bool = False
- self._brake_hold_counter: int = 0
- self._brake_hold_reset: bool = False
- self._prev_brake_pressed: bool = False
-
- self._auto_lock_by_speed = self.param_s.get_bool("ToyotaAutoLockBySpeed")
- self._auto_unlock_by_shifter = self.param_s.get_bool("ToyotaAutoUnlockByShifter")
- self._auto_lock_speed = 10 * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS)
+ self._auto_lock_speed = 0.0
self._auto_lock_once = False
self._gear_prev = GearShifter.park
def update(self, CC, CS, now_nanos):
- if self.frame % 200 == 0:
- self._is_metric = self.param_s.get_bool("IsMetric")
- self._auto_lock_by_speed = self.param_s.get_bool("ToyotaAutoLockBySpeed")
- self._auto_unlock_by_shifter = self.param_s.get_bool("ToyotaAutoUnlockByShifter")
- self._auto_lock_speed = 10 * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS)
+ self._auto_lock_speed = 10 * (CV.KPH_TO_MS if CS.params_list.is_metric else CV.MPH_TO_MS)
actuators = CC.actuators
hud_control = CC.hudControl
@@ -94,11 +75,11 @@ class CarController(CarControllerBase):
gear = CS.out.gearShifter
if not CS.out.doorOpen:
if gear == GearShifter.park and self._gear_prev != gear:
- if self._auto_unlock_by_shifter:
+ if CS.params_list.toyota_auto_unlock_by_shifter:
can_sends.append(make_can_msg(0x750, UNLOCK_CMD, 0))
self._auto_lock_once = False
elif gear == GearShifter.drive and not self._auto_lock_once and CS.out.vEgo >= self._auto_lock_speed:
- if self._auto_lock_by_speed:
+ if CS.params_list.toyota_auto_lock_by_speed:
can_sends.append(make_can_msg(0x750, LOCK_CMD, 0))
self._auto_lock_once = True
self._gear_prev = gear
@@ -179,7 +160,7 @@ class CarController(CarControllerBase):
# on entering standstill, send standstill request
if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptorDEPRECATED) and \
- not self._sng_hack:
+ not CS.params_list.toyota_sng_hack:
self.standstill_req = True
if CS.pcm_acc_status != 8:
# pcm entered standstill or it's disabled
@@ -187,9 +168,6 @@ class CarController(CarControllerBase):
self.last_standstill = CS.out.standstill
- if self.CP.spFlags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
- can_sends.extend(self.create_auto_brake_hold_messages(CS))
-
# handle UI messages
fcw_alert = hud_control.visualAlert == VisualAlert.fcw
steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)
@@ -197,7 +175,7 @@ class CarController(CarControllerBase):
# we can spam can to cancel the system even if we are using lat only control
if (self.frame % 3 == 0 and self.CP.openpilotLongitudinalControl) or pcm_cancel_cmd:
lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged
- reverse_acc = 2 if self._reverse_acc_change else 1
+ reverse_acc = 2 if CS.params_list.reverse_acc_change else 1
# Press distance button until we are at the correct bar length. Only change while enabled to avoid skipping startup popup
if self.frame % 6 == 0 and self.CP.openpilotLongitudinalControl:
@@ -252,7 +230,7 @@ class CarController(CarControllerBase):
# keep radar disabled
if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value:
- can_sends.append([0x750, 0, b"\x0F\x02\x3E\x00\x00\x00\x00\x00", 0])
+ can_sends.append(make_tester_present_msg(0x750, 0, 0xF))
if self.CP.spFlags & ToyotaFlagsSP.SP_ENHANCED_BSM and self.frame > 200:
can_sends.extend(self.create_enhanced_bsm_messages(CS, 20, True))
@@ -308,25 +286,3 @@ class CarController(CarControllerBase):
# print("bsm poll right")
return can_sends
-
- # auto brake hold (https://github.com/AlexandreSato/)
- def create_auto_brake_hold_messages(self, CS: car.CarState, brake_hold_allowed_timer: int = 100):
- can_sends = []
- disallowed_gears = [GearShifter.park, GearShifter.reverse]
- brake_hold_allowed = CS.out.standstill and CS.out.cruiseState.available and not CS.out.gasPressed and \
- not CS.out.cruiseState.enabled and (CS.out.gearShifter not in disallowed_gears)
-
- if brake_hold_allowed:
- self._brake_hold_counter += 1
- self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer and not self._brake_hold_reset
- self._brake_hold_reset = not self._prev_brake_pressed and CS.out.brakePressed and not self._brake_hold_reset
- else:
- self._brake_hold_counter = 0
- self.brake_hold_active = False
- self._brake_hold_reset = False
- self._prev_brake_pressed = CS.out.brakePressed
-
- if self.frame % 2 == 0:
- can_sends.append(toyotacan.create_brake_hold_command(self.packer, self.frame, CS.pre_collision_2, self.brake_hold_active))
-
- return can_sends
diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py
index 456282982b..e83d05b73b 100644
--- a/selfdrive/car/toyota/carstate.py
+++ b/selfdrive/car/toyota/carstate.py
@@ -4,9 +4,9 @@ from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.common.numpy_fast import mean
from openpilot.common.filter_simple import FirstOrderFilter
-from openpilot.common.realtime import DT_CTRL
from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
+from openpilot.selfdrive.car import DT_CTRL
from openpilot.selfdrive.car.interfaces import CarStateBase
from openpilot.selfdrive.car.toyota.values import ToyotaFlags, ToyotaFlagsSP, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, \
TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR
@@ -79,9 +79,6 @@ class CarState(CarStateBase):
self._right_blindspot_d2 = 0
self._right_blindspot_counter = 0
- if CP.spFlags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
- self.pre_collision_2 = {}
-
self.frame = 0
def update(self, cp, cp_cam):
@@ -263,9 +260,6 @@ class CarState(CarStateBase):
if self.CP.spFlags & ToyotaFlagsSP.SP_ENHANCED_BSM and self.frame > 199:
ret.leftBlindspot, ret.rightBlindspot = self.sp_get_enhanced_bsm(cp)
- if self.CP.spFlags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
- self.pre_collision_2 = copy.copy(cp_cam.vl["PRE_COLLISION_2"])
-
self._update_traffic_signals(cp_cam)
ret.cruiseState.speedLimit = self._calculate_speed_limit()
self.frame += 1
@@ -469,7 +463,4 @@ class CarState(CarStateBase):
("PCS_HUD", 1),
]
- if CP.spFlags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
- messages.append(("PRE_COLLISION_2", 33))
-
return CANParser(DBC[CP.carFingerprint]["pt"], messages, 2)
diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py
index 5e746d1311..a7ea14f203 100644
--- a/selfdrive/car/toyota/fingerprints.py
+++ b/selfdrive/car/toyota/fingerprints.py
@@ -167,6 +167,7 @@ FW_VERSIONS = {
(Ecu.abs, 0x7b0, None): [
b'F152606210\x00\x00\x00\x00\x00\x00',
b'F152606230\x00\x00\x00\x00\x00\x00',
+ b'F152606260\x00\x00\x00\x00\x00\x00',
b'F152606270\x00\x00\x00\x00\x00\x00',
b'F152606290\x00\x00\x00\x00\x00\x00',
b'F152606410\x00\x00\x00\x00\x00\x00',
@@ -441,6 +442,7 @@ FW_VERSIONS = {
b'\x01896630ZU8000\x00\x00\x00\x00',
b'\x01896630ZU9000\x00\x00\x00\x00',
b'\x01896630ZX4000\x00\x00\x00\x00',
+ b'\x01896630ZX7100\x00\x00\x00\x00',
b'\x018966312L8000\x00\x00\x00\x00',
b'\x018966312M0000\x00\x00\x00\x00',
b'\x018966312M9000\x00\x00\x00\x00',
@@ -516,6 +518,7 @@ FW_VERSIONS = {
b'\x018965B1254000\x00\x00\x00\x00',
b'\x018965B1255000\x00\x00\x00\x00',
b'\x018965B1256000\x00\x00\x00\x00',
+ b'\x018965B1270000\x00\x00\x00\x00',
b'8965B12361\x00\x00\x00\x00\x00\x00',
b'8965B12451\x00\x00\x00\x00\x00\x00',
b'8965B16011\x00\x00\x00\x00\x00\x00',
@@ -647,6 +650,7 @@ FW_VERSIONS = {
(Ecu.dsu, 0x791, None): [
b'881510E01100\x00\x00\x00\x00',
b'881510E01200\x00\x00\x00\x00',
+ b'881510E02100\x00\x00\x00\x00',
b'881510E02200\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
@@ -1149,6 +1153,7 @@ FW_VERSIONS = {
},
CAR.TOYOTA_RAV4_TSS2_2023: {
(Ecu.abs, 0x7b0, None): [
+ b'\x01F15260R440\x00\x00\x00\x00\x00\x00',
b'\x01F15260R450\x00\x00\x00\x00\x00\x00',
b'\x01F15260R50000\x00\x00\x00\x00',
b'\x01F15260R51000\x00\x00\x00\x00',
@@ -1170,6 +1175,7 @@ FW_VERSIONS = {
b'\x01896634AE1001\x00\x00\x00\x00',
b'\x01896634AF0000\x00\x00\x00\x00',
b'\x01896634AJ2000\x00\x00\x00\x00',
+ b'\x01896634AJ3000\x00\x00\x00\x00',
b'\x01896634AL5000\x00\x00\x00\x00',
b'\x01896634AL6000\x00\x00\x00\x00',
b'\x01896634AL8000\x00\x00\x00\x00',
diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py
index 451265f9cd..527964cead 100644
--- a/selfdrive/car/toyota/interface.py
+++ b/selfdrive/car/toyota/interface.py
@@ -5,7 +5,7 @@ from panda import Panda
from panda.python import uds
from openpilot.selfdrive.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, ToyotaFlagsSP, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \
MIN_ACC_SPEED, EPS_SCALE, UNSUPPORTED_DSU_CAR, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR
-from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
+from openpilot.selfdrive.car import create_button_events, get_safety_config
from openpilot.selfdrive.car.disable_ecu import disable_ecu
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
@@ -195,9 +195,6 @@ class CarInterface(CarInterfaceBase):
if candidate == CAR.TOYOTA_PRIUS_TSS2:
ret.spFlags |= ToyotaFlagsSP.SP_NEED_DEBUG_BSM.value
- if Params().get_bool("ToyotaAutoHold") and candidate in (TSS2_CAR - RADAR_ACC_CAR):
- ret.spFlags |= ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD.value
-
return ret
@staticmethod
@@ -210,13 +207,11 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- self.sp_update_params()
- buttonEvents = []
distance_button = 0
if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) or (self.CP.flags & ToyotaFlags.SMART_DSU and not self.CP.flags & ToyotaFlags.RADAR_CAN_FILTER):
- buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})
+ self.CS.button_events = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})
distance_button = self.CS.distance_button
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
@@ -225,7 +220,7 @@ class CarInterface(CarInterfaceBase):
if self.enable_mads:
if not self.CS.prev_mads_enabled and self.CS.mads_enabled:
self.CS.madsEnabled = True
- if self.lkas_toggle:
+ if self.CS.params_list.toyota_lkas_toggle:
if self.CS.lta_status_active:
if (self.CS.prev_lkas_enabled == 16 and self.CS.lkas_enabled == 0) or \
(self.CS.prev_lkas_enabled == 0 and self.CS.lkas_enabled == 16):
@@ -245,24 +240,11 @@ class CarInterface(CarInterfaceBase):
ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(distance_button))
- # CANCEL
- if self.CS.out.cruiseState.enabled and not ret.cruiseState.enabled:
- be = car.CarState.ButtonEvent.new_message()
- be.pressed = True
- be.type = ButtonType.cancel
- buttonEvents.append(be)
-
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled),
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
# events
events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake],
@@ -289,11 +271,6 @@ class CarInterface(CarInterfaceBase):
# while in standstill, send a user alert
events.add(EventName.manualRestart)
- # auto brake hold
- if self.CP.spFlags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
- if self.CC.brake_hold_active and not ret.brakeHoldActive:
- events.add(EventName.spAutoBrakeHold)
-
ret.events = events.to_msg()
return ret
diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py
index 979879e368..abde32bc07 100644
--- a/selfdrive/car/toyota/toyotacan.py
+++ b/selfdrive/car/toyota/toyotacan.py
@@ -130,37 +130,3 @@ def create_set_bsm_debug_mode(lr_blindspot, enabled):
def create_bsm_polling_status(lr_blindspot):
return make_can_msg(0x750, lr_blindspot + b"\x02\x21\x69\x00\x00\x00\x00", 0)
-
-
-# auto brake hold
-def create_brake_hold_command(packer, frame, pre_collision_2, brake_hold_active):
- # forward PRE_COLLISION_2 when auto brake hold is not active
- values = {s: pre_collision_2[s] for s in [
- "DSS1GDRV",
- "DS1STAT2",
- "DS1STBK2",
- "PCSWAR",
- "PCSALM",
- "PCSOPR",
- "PCSABK",
- "PBATRGR",
- "PPTRGR",
- "IBTRGR",
- "CLEXTRGR",
- "IRLT_REQ",
- "BRKHLD",
- "AVSTRGR",
- "VGRSTRGR",
- "PREFILL",
- "PBRTRGR",
- "PCSDIS",
- "PBPREPMP",
- ]}
-
- if brake_hold_active:
- values = {
- "DSS1GDRV": 0x3FF,
- "PBRTRGR": frame % 730 < 727, # cut actuation for 3 frames
- }
-
- return packer.make_can_msg("PRE_COLLISION_2", 0, values)
diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py
index b4b277cc08..6952bb41bd 100644
--- a/selfdrive/car/toyota/values.py
+++ b/selfdrive/car/toyota/values.py
@@ -64,7 +64,6 @@ class ToyotaFlagsSP(IntFlag):
SP_ZSS = 1
SP_ENHANCED_BSM = 2
SP_NEED_DEBUG_BSM = 4
- SP_AUTO_BRAKE_HOLD = 8
class Footnote(Enum):
diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py
index d690b7963d..28c48283a7 100644
--- a/selfdrive/car/volkswagen/carcontroller.py
+++ b/selfdrive/car/volkswagen/carcontroller.py
@@ -4,8 +4,7 @@ from opendbc.can.packer import CANPacker
from openpilot.common.numpy_fast import clip
from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params
-from openpilot.common.realtime import DT_CTRL
-from openpilot.selfdrive.car import apply_driver_steer_torque_limits
+from openpilot.selfdrive.car import DT_CTRL, apply_driver_steer_torque_limits
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.car.volkswagen import mqbcan, pqcan
from openpilot.selfdrive.car.volkswagen.values import CANBUS, CarControllerParams, VolkswagenFlags
@@ -18,7 +17,7 @@ ButtonType = car.CarState.ButtonEvent.Type
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
- self.CP = CP
+ super().__init__(dbc_name, CP, VM)
self.CCP = CarControllerParams(CP)
self.CCS = pqcan if CP.flags & VolkswagenFlags.PQ else mqbcan
self.packer_pt = CANPacker(dbc_name)
@@ -26,7 +25,6 @@ class CarController(CarControllerBase):
self.apply_steer_last = 0
self.gra_acc_counter_last = None
- self.frame = 0
self.eps_timer_soft_disable_alert = False
self.hca_frame_timer_running = 0
self.hca_frame_same_torque = 0
@@ -34,9 +32,6 @@ class CarController(CarControllerBase):
self.sm = messaging.SubMaster(['longitudinalPlanSP'])
self.param_s = Params()
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.speed_limit_control_enabled = False
- self.last_speed_limit_sign_tap = False
self.last_speed_limit_sign_tap_prev = False
self.speed_limit = 0.
self.speed_limit_offset = 0
@@ -79,24 +74,20 @@ class CarController(CarControllerBase):
self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed
self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed
- if self.frame % 200 == 0:
- self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc")
- self.is_metric = self.param_s.get_bool("IsMetric")
- self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap")
- self.v_cruise_min = VOLKSWAGEN_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1)
+ self.v_cruise_min = VOLKSWAGEN_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)
actuators = CC.actuators
hud_control = CC.hudControl
can_sends = []
if not self.CP.pcmCruiseSpeed:
- if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap:
+ if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap:
self.sl_force_active_timer = self.frame
self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False)
- self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap
+ self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap
- sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
- sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
- sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
+ sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0))
+ sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False))
+ sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False))
slc_active = not sl_inactive and not sl_temp_inactive
self.slc_active_stock = slc_active
@@ -303,8 +294,8 @@ class CarController(CarControllerBase):
return min(target_speed_kph, curve_speed)
def get_button_control(self, CS, final_speed, v_cruise_kph_prev):
- self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1))
- self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH))
+ self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1))
+ self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH))
cruise_button = self.get_button_type(self.button_type)
return cruise_button
diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py
index 6ec27b8be0..1dc4c0e430 100644
--- a/selfdrive/car/volkswagen/carstate.py
+++ b/selfdrive/car/volkswagen/carstate.py
@@ -4,7 +4,7 @@ from openpilot.common.conversions import Conversions as CV
from openpilot.selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
from openpilot.selfdrive.car.volkswagen.values import DBC, CANBUS, NetworkLocation, TransmissionType, GearShifter, \
- CarControllerParams, VolkswagenFlags, BUTTON_STATES, VolkswagenFlagsSP
+ CarControllerParams, VolkswagenFlags, VolkswagenFlagsSP
class CarState(CarStateBase):
@@ -17,8 +17,6 @@ class CarState(CarStateBase):
self.esp_hold_confirmation = False
self.upscale_lead_car_signal = False
self.eps_stock_values = False
- self.buttonStates = BUTTON_STATES.copy()
- self.buttonStatesPrev = BUTTON_STATES.copy()
def create_button_events(self, pt_cp, buttons):
button_events = []
@@ -41,7 +39,6 @@ class CarState(CarStateBase):
ret = car.CarState.new_message()
self.prev_mads_enabled = self.mads_enabled
- self.buttonStatesPrev = self.buttonStates.copy()
# Update vehicle speed and acceleration from ABS wheel speeds.
ret.wheelSpeeds = self.get_wheel_speeds(
@@ -150,18 +147,10 @@ class CarState(CarStateBase):
if ret.cruiseState.speed > 90:
ret.cruiseState.speed = 0
- # Update control button states for turn signals and ACC controls.
- self.buttonStates["accelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Hoch"])
- self.buttonStates["decelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Runter"])
- self.buttonStates["cancel"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Abbrechen"])
- self.buttonStates["setCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Setzen"])
- self.buttonStates["resumeCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Wiederaufnahme"])
- self.buttonStates["gapAdjustCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Verstellung_Zeitluecke"])
-
# Update button states for turn signals and ACC controls, capture all ACC button state/config for passthrough
ret.leftBlinker = ret.leftBlinkerOn = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Left"])
ret.rightBlinker = ret.rightBlinkerOn = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Right"])
- ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS)
+ self.button_events = self.create_button_events(pt_cp, self.CCP.BUTTONS)
self.gra_stock_values = pt_cp.vl["GRA_ACC_01"]
# Additional safety checks performed in CarInterface.
@@ -177,7 +166,6 @@ class CarState(CarStateBase):
ret = car.CarState.new_message()
self.prev_mads_enabled = self.mads_enabled
- self.buttonStatesPrev = self.buttonStates.copy()
# Update vehicle speed and acceleration from ABS wheel speeds.
ret.wheelSpeeds = self.get_wheel_speeds(
@@ -265,18 +253,10 @@ class CarState(CarStateBase):
if ret.cruiseState.speed > 70: # 255 kph in m/s == no current setpoint
ret.cruiseState.speed = 0
- # Update control button states for turn signals and ACC controls.
- self.buttonStates["accelCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Up_kurz"])
- self.buttonStates["decelCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Down_kurz"])
- self.buttonStates["cancel"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Abbrechen"])
- self.buttonStates["setCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Neu_Setzen"])
- self.buttonStates["resumeCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Recall"])
- self.buttonStates["gapAdjustCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Zeitluecke"])
-
# Update button states for turn signals and ACC controls, capture all ACC button state/config for passthrough
ret.leftBlinker, ret.rightBlinker = ret.leftBlinkerOn, ret.rightBlinkerOn = self.update_blinker_from_stalk(300, pt_cp.vl["Gate_Komf_1"]["GK1_Blinker_li"],
pt_cp.vl["Gate_Komf_1"]["GK1_Blinker_re"])
- ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS)
+ self.button_events = self.create_button_events(pt_cp, self.CCP.BUTTONS)
self.gra_stock_values = pt_cp.vl["GRA_Neu"]
# Additional safety checks performed in CarInterface.
diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py
index 31dea25cbb..0d57539a1b 100644
--- a/selfdrive/car/volkswagen/interface.py
+++ b/selfdrive/car/volkswagen/interface.py
@@ -1,10 +1,10 @@
from cereal import car
from panda import Panda
from openpilot.common.params import Params
-from openpilot.selfdrive.car import get_safety_config, create_mads_event
+from openpilot.selfdrive.car import get_safety_config
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
from openpilot.selfdrive.car.volkswagen.values import CAR, CANBUS, CarControllerParams, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags, \
- BUTTON_STATES, VolkswagenFlagsSP
+ VolkswagenFlagsSP
ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
@@ -21,8 +21,6 @@ class CarInterface(CarInterfaceBase):
self.ext_bus = CANBUS.cam
self.cp_ext = self.cp_cam
- self.buttonStatesPrev = BUTTON_STATES.copy()
-
@staticmethod
def _get_params(ret, candidate: CAR, fingerprint, car_fw, experimental_long, docs):
ret.carName = "volkswagen"
@@ -115,23 +113,11 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState
def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_ext, self.CP.transmissionType)
- self.sp_update_params()
-
- buttonEvents = []
-
- # Check for and process state-change events (button press or release) from
- # the turn stalk switch or ACC steering wheel/control stalk buttons.
- for button in self.CS.buttonStates:
- if self.CS.buttonStates[button] != self.buttonStatesPrev[button]:
- be = car.CarState.ButtonEvent.new_message()
- be.type = button
- be.pressed = self.CS.buttonStates[button]
- buttonEvents.append(be)
self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS)
self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled,
- buttonEvents, c.vCruise,
+ self.CS.button_events, c.vCruise,
enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise))
if ret.cruiseState.available:
@@ -144,7 +130,7 @@ class CarInterface(CarInterfaceBase):
self.CS.madsEnabled = self.get_sp_started_mads(ret, self.CS)
if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed:
- if any(b.type == ButtonType.cancel for b in buttonEvents):
+ if any(b.type == ButtonType.cancel for b in self.CS.button_events):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
if self.get_sp_pedal_disengage(ret):
self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled)
@@ -155,19 +141,13 @@ class CarInterface(CarInterfaceBase):
self.CS.accEnabled = False
self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled
- ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.buttonStates["gapAdjustCruise"]))
+ ret, self.CS = self.get_sp_common_state(ret, self.CS,
+ gap_button=any(b.type == ButtonType.gapAdjustCruise and b.pressed for b in self.CS.button_events))
- # MADS BUTTON
- if self.CS.out.madsEnabled != self.CS.madsEnabled:
- if self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = False
- else:
- if not self.mads_event_lock:
- buttonEvents.append(create_mads_event(self.mads_event_lock))
- self.mads_event_lock = True
-
- ret.buttonEvents = buttonEvents
+ ret.buttonEvents = [
+ *self.CS.button_events,
+ *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON
+ ]
events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic],
pcm_enable=False,
@@ -199,8 +179,4 @@ class CarInterface(CarInterfaceBase):
ret.events = events.to_msg()
- # update previous car states
- self.buttonStatesPrev = self.CS.buttonStates.copy()
-
return ret
-
diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py
index 2996db602f..80b7ca0b0b 100644
--- a/selfdrive/car/volkswagen/values.py
+++ b/selfdrive/car/volkswagen/values.py
@@ -147,16 +147,6 @@ class VolkswagenFlagsSP(IntFlag):
SP_CC_ONLY_NO_RADAR = 2
-BUTTON_STATES = {
- "accelCruise": False,
- "decelCruise": False,
- "cancel": False,
- "setCruise": False,
- "resumeCruise": False,
- "gapAdjustCruise": False
-}
-
-
@dataclass
class VolkswagenMQBPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_mqb_2010', None))
diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py
index 2af32f8a0c..519fddaf6a 100755
--- a/selfdrive/controls/controlsd.py
+++ b/selfdrive/controls/controlsd.py
@@ -69,8 +69,7 @@ class Controls:
if CI is None:
cloudlog.info("controlsd is waiting for CarParams")
- with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg:
- self.CP = msg
+ self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
cloudlog.info("controlsd got CarParams")
# Uses car interface helper functions, altering state won't be considered by card for actuation
@@ -739,7 +738,9 @@ class Controls:
CC.angularVelocity = angular_rate_value
CC.cruiseControl.override = self.enabled_long and not CC.longActive and self.CP.openpilotLongitudinalControl
- CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled_long or (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)))
+ CC.cruiseControl.cancel = CS.cruiseState.enabled and \
+ (not self.enabled_long or (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or
+ (any(b.type == ButtonType.cancel for b in CS.buttonEvents) and self.CP.carName == "honda"))
if self.joystick_mode and self.sm.recv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]:
CC.cruiseControl.cancel = True
diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py
index 7ea0b17834..2f191b4a55 100644
--- a/selfdrive/controls/lib/drive_helpers.py
+++ b/selfdrive/controls/lib/drive_helpers.py
@@ -15,7 +15,7 @@ V_CRUISE_MAX = 145
V_CRUISE_UNSET = 255
V_CRUISE_INITIAL = 40
V_CRUISE_INITIAL_EXPERIMENTAL_MODE = 105
-IMPERIAL_INCREMENT = 1.6 # should be CV.MPH_TO_KPH, but this causes rounding errors
+IMPERIAL_INCREMENT = round(CV.MPH_TO_KPH, 1) # round here to avoid rounding errors incrementing set speed
MIN_SPEED = 1.0
CONTROL_N = 17
@@ -237,16 +237,6 @@ class VCruiseHelper:
self.is_metric_prev = is_metric
-def apply_center_deadzone(error, deadzone):
- if (error > - deadzone) and (error < deadzone):
- error = 0.
- return error
-
-
-def rate_limit(new_value, last_value, dw_step, up_step):
- return clip(new_value, last_value + dw_step, last_value + up_step)
-
-
def clip_curvature(v_ego, prev_curvature, new_curvature):
v_ego = max(MIN_SPEED, v_ego)
max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755
@@ -283,17 +273,6 @@ def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures):
return safe_desired_curvature
-def get_friction(lateral_accel_error: float, lateral_accel_deadzone: float, friction_threshold: float,
- torque_params: car.CarParams.LateralTorqueTuning, friction_compensation: bool) -> float:
- friction_interp = interp(
- apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone),
- [-friction_threshold, friction_threshold],
- [-torque_params.friction, torque_params.friction]
- )
- friction = float(friction_interp) if friction_compensation else 0.0
- return friction
-
-
def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float:
# ToDo: Try relative error, and absolute speed
if len(modelV2.temporalPose.trans):
diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py
index 0ef232f5fe..13cef37960 100755
--- a/selfdrive/controls/lib/events.py
+++ b/selfdrive/controls/lib/events.py
@@ -744,14 +744,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.NO_ENTRY: NoEntryAlert("Brake Hold Active"),
},
- EventName.spAutoBrakeHold: {
- ET.PERMANENT: Alert(
- "sunnypilot Brake Hold Active",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.prompt, 0.),
- },
-
EventName.parkBrake: {
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
ET.NO_ENTRY: NoEntryAlert("Parking Brake Engaged"),
@@ -998,7 +990,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
},
EventName.espActive: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Electronic Stability Control Active"),
+ ET.SOFT_DISABLE: soft_disable_alert("Electronic Stability Control Active"),
ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Active"),
},
diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript
index 73242cb8f9..2c03da06a6 100644
--- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript
+++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript
@@ -1,4 +1,4 @@
-Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'opendbc_python')
+Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'opendbc_python', 'np_version')
gen = "c_generated_code"
@@ -88,3 +88,4 @@ lenv2.Command(libacados_ocp_solver_c,
f' {acados_ocp_solver_pyx.get_labspath()}')
lib_cython = lenv2.Program(f'{gen}/acados_ocp_solver_pyx.so', [libacados_ocp_solver_c])
lenv2.Depends(lib_cython, lib_solver)
+lenv2.Depends(libacados_ocp_solver_c, np_version)
diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py
index a661c33361..e3cd4bb654 100644
--- a/selfdrive/controls/lib/longcontrol.py
+++ b/selfdrive/controls/lib/longcontrol.py
@@ -24,22 +24,26 @@ def long_control_state_trans(CP, active, long_control_state, v_ego,
long_control_state = LongCtrlState.off
else:
- if long_control_state in (LongCtrlState.off, LongCtrlState.pid):
- long_control_state = LongCtrlState.pid
- if stopping_condition:
+ if long_control_state == LongCtrlState.off:
+ if not starting_condition:
long_control_state = LongCtrlState.stopping
+ else:
+ if starting_condition and CP.startingState:
+ long_control_state = LongCtrlState.starting
+ else:
+ long_control_state = LongCtrlState.pid
+
elif long_control_state == LongCtrlState.stopping:
if starting_condition and CP.startingState:
long_control_state = LongCtrlState.starting
elif starting_condition:
long_control_state = LongCtrlState.pid
- elif long_control_state == LongCtrlState.starting:
+ elif long_control_state in [LongCtrlState.starting, LongCtrlState.pid]:
if stopping_condition:
long_control_state = LongCtrlState.stopping
elif started_condition:
long_control_state = LongCtrlState.pid
-
return long_control_state
class LongControl:
diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
index 592c1c2c2d..22342c0078 100644
--- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
+++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
@@ -1,4 +1,4 @@
-Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'opendbc_python')
+Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'opendbc_python', 'pandad_python', 'np_version')
gen = "c_generated_code"
@@ -66,7 +66,7 @@ lenv.Clean(generated_files, Dir(gen))
generated_long = lenv.Command(generated_files,
source_list,
f"cd {Dir('.').abspath} && python3 long_mpc.py")
-lenv.Depends(generated_long, [msgq_python, common_python, opendbc_python])
+lenv.Depends(generated_long, [msgq_python, common_python, opendbc_python, pandad_python])
lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES")
lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES")
@@ -94,3 +94,4 @@ lenv2.Command(libacados_ocp_solver_c,
f' {acados_ocp_solver_pyx.get_labspath()}')
lib_cython = lenv2.Program(f'{gen}/acados_ocp_solver_pyx.so', [libacados_ocp_solver_c])
lenv2.Depends(lib_cython, lib_solver)
+lenv2.Depends(libacados_ocp_solver_c, np_version)
diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py
index 62ed68a77e..bee5e5636a 100755
--- a/selfdrive/controls/lib/longitudinal_planner.py
+++ b/selfdrive/controls/lib/longitudinal_planner.py
@@ -57,22 +57,21 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
def get_accel_from_plan(CP, speeds, accels):
- if len(speeds) == CONTROL_N:
- v_target_now = interp(DT_MDL, CONTROL_N_T_IDX, speeds)
- a_target_now = interp(DT_MDL, CONTROL_N_T_IDX, accels)
+ if len(speeds) == CONTROL_N:
+ v_target_now = interp(DT_MDL, CONTROL_N_T_IDX, speeds)
+ a_target_now = interp(DT_MDL, CONTROL_N_T_IDX, accels)
- v_target = interp(CP.longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds)
- a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now
+ v_target = interp(CP.longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds)
+ a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now
- v_target_1sec = interp(CP.longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds)
- else:
- v_target = 0.0
- v_target_now = 0.0
- v_target_1sec = 0.0
- a_target = 0.0
- should_stop = (v_target < CP.vEgoStopping and
- v_target_1sec < CP.vEgoStopping)
- return a_target, should_stop
+ v_target_1sec = interp(CP.longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds)
+ else:
+ v_target = 0.0
+ v_target_1sec = 0.0
+ a_target = 0.0
+ should_stop = (v_target < CP.vEgoStopping and
+ v_target_1sec < CP.vEgoStopping)
+ return a_target, should_stop
class LongitudinalPlanner:
@@ -112,8 +111,8 @@ class LongitudinalPlanner:
@staticmethod
def parse_model(model_msg, model_error):
if (len(model_msg.position.x) == ModelConstants.IDX_N and
- len(model_msg.velocity.x) == ModelConstants.IDX_N and
- len(model_msg.acceleration.x) == ModelConstants.IDX_N):
+ len(model_msg.velocity.x) == ModelConstants.IDX_N and
+ len(model_msg.acceleration.x) == ModelConstants.IDX_N):
x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error
a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x)
diff --git a/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py b/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py
index d3e966bfb1..27b3de88e9 100644
--- a/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py
+++ b/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py
@@ -295,7 +295,7 @@ class SpeedLimitController:
def update(self, enabled, v_ego, a_ego, sm, v_cruise_setpoint, events=Events()):
_car_state = sm['carState']
- self._op_enabled = sm['controlsState'].enabled and _car_state.cruiseState.enabled and \
+ self._op_enabled = enabled and sm['controlsState'].enabled and _car_state.cruiseState.enabled and \
not (_car_state.brakePressed and (not self._brake_pressed_prev or not _car_state.standstill)) and \
not (events.contains(ET.OVERRIDE_LONGITUDINAL) and self._disengage_on_accelerator)
self._v_ego = v_ego
diff --git a/selfdrive/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py
index 4d0e41805d..f666f875a6 100644
--- a/selfdrive/controls/lib/tests/test_vehicle_model.py
+++ b/selfdrive/controls/lib/tests/test_vehicle_model.py
@@ -2,7 +2,6 @@ import pytest
import math
import numpy as np
-from control import StateSpace
from openpilot.selfdrive.car.honda.interface import CarInterface
from openpilot.selfdrive.car.honda.values import CAR
@@ -47,8 +46,12 @@ class TestVehicleModel:
A, B = create_dyn_state_matrices(u, self.VM)
# Convert to discrete time system
- ss = StateSpace(A, B, np.eye(2), np.zeros((2, 2)))
- ss = ss.sample(0.01)
+ dt = 0.01
+ top = np.hstack((A, B))
+ full = np.vstack((top, np.zeros_like(top))) * dt
+ Md = sum([np.linalg.matrix_power(full, k) / math.factorial(k) for k in range(25)])
+ Ad = Md[:A.shape[0], :A.shape[1]]
+ Bd = Md[:A.shape[0], A.shape[1]:]
for sa in np.linspace(math.radians(-20), math.radians(20), num=11):
inp = np.array([[sa], [roll]])
@@ -56,7 +59,7 @@ class TestVehicleModel:
# Simulate for 1 second
x1 = np.zeros((2, 1))
for _ in range(100):
- x1 = ss.A @ x1 + ss.B @ inp
+ x1 = Ad @ x1 + Bd @ inp
# Compute steady state solution directly
x2 = dyn_ss_sol(sa, u, roll, self.VM)
diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py
index 0d1eaeb075..6c51b38b13 100755
--- a/selfdrive/controls/plannerd.py
+++ b/selfdrive/controls/plannerd.py
@@ -17,8 +17,7 @@ def plannerd_thread():
cloudlog.info("plannerd is waiting for CarParams")
params = Params()
- with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg:
- CP = msg
+ CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("plannerd got CarParams: %s", CP.carName)
debug_mode = bool(int(os.getenv("DEBUG", "0")))
diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py
index c53c50cd7e..0c8c9ff179 100755
--- a/selfdrive/controls/radard.py
+++ b/selfdrive/controls/radard.py
@@ -10,9 +10,9 @@ from openpilot.common.numpy_fast import interp
from openpilot.common.params import Params
from openpilot.common.realtime import DT_CTRL, Ratekeeper, Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog
-from openpilot.selfdrive.car.hyundai.values import HyundaiFlagsSP
-
from openpilot.common.simple_kalman import KF1D
+from openpilot.selfdrive.car.hyundai.values import HyundaiFlagsSP
+from openpilot.selfdrive.pandad import can_capnp_to_list
# Default lead acceleration decay set to 50% at 1s
@@ -292,8 +292,7 @@ def main():
# wait for stats about the car to come in from controls
cloudlog.info("radard is waiting for CarParams")
- with car.CarParams.from_bytes(Params().get("CarParams", block=True)) as msg:
- CP = msg
+ CP = messaging.log_from_bytes(Params().get("CarParams", block=True), car.CarParams)
cloudlog.info("radard got CarParams")
# import the radar from the fingerprint
@@ -312,7 +311,7 @@ def main():
while 1:
can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True)
- rr = RI.update(can_strings)
+ rr = RI.update(can_capnp_to_list(can_strings))
sm.update(0)
if rr is None:
continue
diff --git a/selfdrive/controls/tests/test_longcontrol.py b/selfdrive/controls/tests/test_longcontrol.py
new file mode 100644
index 0000000000..ab50810d89
--- /dev/null
+++ b/selfdrive/controls/tests/test_longcontrol.py
@@ -0,0 +1,56 @@
+from cereal import car
+from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
+
+
+
+
+class TestLongControlStateTransition:
+
+ def test_stay_stopped(self):
+ CP = car.CarParams.new_message()
+ active = True
+ current_state = LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=True, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=True, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=True)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.pid
+ active = False
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.off
+
+def test_engage():
+ CP = car.CarParams.new_message()
+ active = True
+ current_state = LongCtrlState.off
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=True, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=True, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=True)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.pid
+
+def test_starting():
+ CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
+ active = True
+ current_state = LongCtrlState.starting
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.starting
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.pid
diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py
index 14b0788a3d..51b9887c96 100644
--- a/selfdrive/controls/tests/test_startup.py
+++ b/selfdrive/controls/tests/test_startup.py
@@ -90,7 +90,7 @@ def test_startup_alert(expected_event, car_model, fw_versions, brand):
managed_processes['card'].start()
assert pm.wait_for_readers_to_update('can', 5)
- pm.send('can', can_list_to_can_capnp([[0, 0, b"", 0]]))
+ pm.send('can', can_list_to_can_capnp([[0, b"", 0]]))
assert pm.wait_for_readers_to_update('pandaStates', 5)
msg = messaging.new_message('pandaStates', 1)
@@ -103,7 +103,7 @@ def test_startup_alert(expected_event, car_model, fw_versions, brand):
else:
finger = _FINGERPRINTS[car_model][0]
- msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()]
+ msgs = [[addr, b'\x00'*length, 0] for addr, length in finger.items()]
for _ in range(1000):
# card waits for pandad to echo back that it has changed the multiplexing mode
if not params.get_bool("ObdMultiplexingChanged"):
diff --git a/selfdrive/debug/check_can_parser_performance.py b/selfdrive/debug/check_can_parser_performance.py
index 604a1df124..a5155f0126 100755
--- a/selfdrive/debug/check_can_parser_performance.py
+++ b/selfdrive/debug/check_can_parser_performance.py
@@ -6,6 +6,7 @@ from tqdm import tqdm
from cereal import car
from openpilot.selfdrive.car.tests.routes import CarTestRoute
from openpilot.selfdrive.car.tests.test_models import TestCarModelBase
+from openpilot.selfdrive.pandad import can_capnp_to_list
from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE
N_RUNS = 10
@@ -25,12 +26,13 @@ if __name__ == '__main__':
CC = car.CarControl.new_message()
ets = []
for _ in tqdm(range(N_RUNS)):
- msgs = [(m.as_builder().to_bytes(),) for m in tm.can_msgs]
+ msgs = [m.as_builder().to_bytes() for m in tm.can_msgs]
start_t = time.process_time_ns()
for msg in msgs:
+ can_list = can_capnp_to_list([msg])
for cp in tm.CI.can_parsers:
if cp is not None:
- cp.update_strings(msg)
+ cp.update_strings(can_list)
ets.append((time.process_time_ns() - start_t) * 1e-6)
print(f'{len(tm.can_msgs)} CAN packets, {N_RUNS} runs')
diff --git a/selfdrive/debug/dump.py b/selfdrive/debug/dump.py
index 787e9bc738..78cf51e3db 100755
--- a/selfdrive/debug/dump.py
+++ b/selfdrive/debug/dump.py
@@ -4,7 +4,6 @@ import argparse
import json
import codecs
-from hexdump import hexdump
from cereal import log
from cereal.services import SERVICE_LIST
from openpilot.tools.lib.live_logreader import raw_live_logreader
@@ -12,6 +11,18 @@ from openpilot.tools.lib.live_logreader import raw_live_logreader
codecs.register_error("strict", codecs.backslashreplace_errors)
+def hexdump(msg):
+ m = str.upper(msg.hex())
+ m = [m[i:i+2] for i in range(0,len(m),2)]
+ m = [m[i:i+16] for i in range(0,len(m),16)]
+ for row,dump in enumerate(m):
+ addr = '%08X:' % (row*16)
+ raw = ' '.join(dump[:8]) + ' ' + ' '.join(dump[8:])
+ space = ' ' * (48 - len(raw))
+ asci = ''.join(chr(int(x,16)) if 0x20 <= int(x,16) <= 0x7E else '.' for x in dump)
+ print(f'{addr} {raw} {space} {asci}')
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Dump communication sockets. See cereal/services.py for a complete list of available sockets.')
diff --git a/selfdrive/debug/internal/measure_torque_time_to_max.py b/selfdrive/debug/internal/measure_torque_time_to_max.py
index ef3152b50c..7052dccf7d 100755
--- a/selfdrive/debug/internal/measure_torque_time_to_max.py
+++ b/selfdrive/debug/internal/measure_torque_time_to_max.py
@@ -18,7 +18,7 @@ if __name__ == "__main__":
if args.addr != "127.0.0.1":
os.environ["ZMQ"] = "1"
- messaging.context = messaging.Context()
+ messaging.reset_context()
poller = messaging.Poller()
messaging.sub_sock('can', poller, addr=args.addr)
diff --git a/selfdrive/debug/internal/qlog_size.py b/selfdrive/debug/internal/qlog_size.py
index 2d17a1ee28..11606c7589 100755
--- a/selfdrive/debug/internal/qlog_size.py
+++ b/selfdrive/debug/internal/qlog_size.py
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
import argparse
-import bz2
+import zstandard as zstd
from collections import defaultdict
import matplotlib.pyplot as plt
+from cereal.services import SERVICE_LIST
+from openpilot.system.loggerd.uploader import LOG_COMPRESSION_LEVEL
from openpilot.tools.lib.logreader import LogReader
from tqdm import tqdm
@@ -16,14 +18,14 @@ def make_pie(msgs, typ):
for m in msgs:
msgs_by_type[m.which()].append(m.as_builder().to_bytes())
- total = len(bz2.compress(b"".join([m.as_builder().to_bytes() for m in msgs])))
+ total = len(zstd.compress(b"".join([m.as_builder().to_bytes() for m in msgs]), LOG_COMPRESSION_LEVEL))
uncompressed_total = len(b"".join([m.as_builder().to_bytes() for m in msgs]))
length_by_type = {k: len(b"".join(v)) for k, v in msgs_by_type.items()}
# calculate compressed size by calculating diff when removed from the segment
compressed_length_by_type = {}
for k in tqdm(msgs_by_type.keys(), desc="Compressing"):
- compressed_length_by_type[k] = total - len(bz2.compress(b"".join([m.as_builder().to_bytes() for m in msgs if m.which() != k])))
+ compressed_length_by_type[k] = total - len(zstd.compress(b"".join([m.as_builder().to_bytes() for m in msgs if m.which() != k]), LOG_COMPRESSION_LEVEL))
sizes = sorted(compressed_length_by_type.items(), key=lambda kv: kv[1])
@@ -48,9 +50,29 @@ def make_pie(msgs, typ):
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='View log size breakdown by message type')
parser.add_argument('route', help='route to use')
+ parser.add_argument('--as-qlog', action='store_true', help='decimate rlog using latest decimation factors')
args = parser.parse_args()
msgs = list(LogReader(args.route))
+ if args.as_qlog:
+ new_msgs = []
+ msg_cnts: dict[str, int] = defaultdict(int)
+ for msg in msgs:
+ msg_which = msg.which()
+ if msg.which() in ("initData", "sentinel"):
+ new_msgs.append(msg)
+ continue
+
+ if msg_which not in SERVICE_LIST:
+ continue
+
+ decimation = SERVICE_LIST[msg_which].decimation
+ if decimation is not None and msg_cnts[msg_which] % decimation == 0:
+ new_msgs.append(msg)
+ msg_cnts[msg_which] += 1
+
+ msgs = new_msgs
+
make_pie(msgs, 'qlog')
plt.show()
diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py
index 90db14bc9a..c98763f3c9 100755
--- a/selfdrive/debug/run_process_on_route.py
+++ b/selfdrive/debug/run_process_on_route.py
@@ -3,8 +3,7 @@
import argparse
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process
-from openpilot.tools.lib.helpers import save_log
-from openpilot.tools.lib.logreader import LogReader
+from openpilot.tools.lib.logreader import LogReader, save_log
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run process on route and create new logs",
diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py
index 786bdbbfec..af8a3d3726 100644
--- a/selfdrive/locationd/helpers.py
+++ b/selfdrive/locationd/helpers.py
@@ -4,6 +4,14 @@ from typing import Any
from cereal import log
+def rotate_cov(rot_matrix, cov_in):
+ return rot_matrix @ cov_in @ rot_matrix.T
+
+
+def rotate_std(rot_matrix, std_in):
+ return np.sqrt(np.diag(rotate_cov(rot_matrix, np.diag(std_in**2))))
+
+
class NPQueue:
def __init__(self, maxlen: int, rowsize: int) -> None:
self.maxlen = maxlen
@@ -38,7 +46,7 @@ class PointBuckets:
def is_calculable(self) -> bool:
return all(len(v) > 0 for v in self.buckets.values())
- def add_point(self, x: float, y: float, bucket_val: float) -> None:
+ def add_point(self, x: float, y: float) -> None:
raise NotImplementedError
def get_points(self, num_points: int = None) -> Any:
diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc
index 2ac392a778..5607871f5f 100644
--- a/selfdrive/locationd/locationd.cc
+++ b/selfdrive/locationd/locationd.cc
@@ -41,14 +41,25 @@ const int GPS_ORIENTATION_ERROR_RESET_CNT = 3;
const bool DEBUG = getenv("DEBUG") != nullptr && std::string(getenv("DEBUG")) != "0";
-static VectorXd floatlist2vector(const capnp::List::Reader& floatlist) {
- VectorXd res(floatlist.size());
+template
+static Vector floatlist2vector(const ListType& floatlist) {
+ Vector res(floatlist.size());
for (int i = 0; i < floatlist.size(); i++) {
res[i] = floatlist[i];
}
return res;
}
+template
+static VectorXd float64list2vector(const ListType& floatlist) {
+ return floatlist2vector(floatlist);
+}
+
+template
+static VectorXf float32list2vector(const ListType& floatlist) {
+ return floatlist2vector(floatlist);
+}
+
static Vector4d quat2vector(const Quaterniond& quat) {
return Vector4d(quat.w(), quat.x(), quat.y(), quat.z());
}
@@ -63,6 +74,11 @@ static void init_measurement(cereal::LiveLocationKalman::Measurement::Builder me
meas.setValid(valid);
}
+static void init_xyz_measurement(cereal::LivePose::XYZMeasurement::Builder meas, const VectorXf& val, const VectorXf& std, bool valid) {
+ meas.setX(val[0]); meas.setY(val[1]); meas.setZ(val[2]);
+ meas.setXStd(std[0]); meas.setYStd(std[1]); meas.setZStd(std[2]);
+ meas.setValid(valid);
+}
static MatrixXdr rotate_cov(const MatrixXdr& rot_matrix, const MatrixXdr& cov_in) {
// To rotate a covariance matrix, the cov matrix needs to multiplied left and right by the transform matrix
@@ -91,6 +107,30 @@ Localizer::Localizer(LocalizerGnssSource gnss_source) {
this->configure_gnss_source(gnss_source);
}
+void Localizer::build_live_pose(cereal::LivePose::Builder& livePose, cereal::LiveLocationKalman::Reader& liveLocation) {
+ // Just copy the values from liveLocation to livePose for now
+ VectorXf orientation_ned = float32list2vector(liveLocation.getOrientationNED().getValue()), orientation_ned_std = float32list2vector(liveLocation.getOrientationNED().getStd());
+ init_xyz_measurement(livePose.initOrientationNED(), orientation_ned, orientation_ned_std, this->gps_mode);
+ VectorXf velocity_device = float32list2vector(liveLocation.getVelocityDevice().getValue()), velocity_device_std = float32list2vector(liveLocation.getVelocityDevice().getStd());
+ init_xyz_measurement(livePose.initVelocityDevice(), velocity_device, velocity_device_std, true);
+ VectorXf acceleration_device = float32list2vector(liveLocation.getAccelerationDevice().getValue()), acceleration_device_std = float32list2vector(liveLocation.getAccelerationDevice().getStd());
+ init_xyz_measurement(livePose.initAccelerationDevice(), acceleration_device, acceleration_device_std, true);
+ VectorXf ang_velocity_device = float32list2vector(liveLocation.getAngularVelocityDevice().getValue()), ang_velocity_device_std = float32list2vector(liveLocation.getAngularVelocityDevice().getStd());
+ init_xyz_measurement(livePose.initAngularVelocityDevice(), ang_velocity_device, ang_velocity_device_std, true);
+
+ if (DEBUG) {
+ VectorXd filter_state = float64list2vector(liveLocation.getFilterState().getValue()), filter_state_std = float64list2vector(liveLocation.getFilterState().getStd());
+ cereal::LivePose::FilterState::Builder filter_state_builder = livePose.initFilterState();
+ filter_state_builder.setValue(kj::arrayPtr(filter_state.data(), filter_state.size()));
+ filter_state_builder.setStd(kj::arrayPtr(filter_state_std.data(), filter_state_std.size()));
+ filter_state_builder.setValid(true);
+ }
+
+ livePose.setInputsOK(liveLocation.getInputsOK());
+ livePose.setPosenetOK(liveLocation.getPosenetOK());
+ livePose.setSensorsOK(liveLocation.getSensorsOK());
+}
+
void Localizer::build_live_location(cereal::LiveLocationKalman::Builder& fix) {
VectorXd predicted_state = this->kf->get_x();
MatrixXdr predicted_cov = this->kf->get_P();
@@ -279,7 +319,7 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData
// TODO: reduce false positives and re-enable this check
// check if device fell, estimate 10 for g
// 40m/s**2 is a good filter for falling detection, no false positives in 20k minutes of driving
- // this->device_fell |= (floatlist2vector(v) - Vector3d(10.0, 0.0, 0.0)).norm() > 40.0;
+ // this->device_fell |= (float64list2vector(v) - Vector3d(10.0, 0.0, 0.0)).norm() > 40.0;
auto meas = Vector3d(-v[2], -v[1], -v[0]);
if (meas.norm() < ACCEL_SANITY_CHECK) {
@@ -311,7 +351,7 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R
bool gps_unreasonable = (Vector2d(log.getHorizontalAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY);
bool gps_accuracy_insane = ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0));
bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK));
- bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK);
+ bool gps_vel_insane = (float64list2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK);
if (!log.getHasFix() || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) {
//this->gps_valid = false;
@@ -450,8 +490,8 @@ void Localizer::handle_car_state(double current_time, const cereal::CarState::Re
}
void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log) {
- VectorXd rot_device = this->device_from_calib * floatlist2vector(log.getRot());
- VectorXd trans_device = this->device_from_calib * floatlist2vector(log.getTrans());
+ VectorXd rot_device = this->device_from_calib * float64list2vector(log.getRot());
+ VectorXd trans_device = this->device_from_calib * float64list2vector(log.getTrans());
if (!this->is_timestamp_valid(current_time)) {
this->observation_timings_invalid = true;
@@ -463,8 +503,8 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry
return;
}
- VectorXd rot_calib_std = floatlist2vector(log.getRotStd());
- VectorXd trans_calib_std = floatlist2vector(log.getTransStd());
+ VectorXd rot_calib_std = float64list2vector(log.getRotStd());
+ VectorXd trans_calib_std = float64list2vector(log.getTransStd());
if ((rot_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK) || (trans_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK)) {
this->observation_values_invalid["cameraOdometry"] += 1.0;
@@ -499,7 +539,7 @@ void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibra
}
if (log.getRpyCalib().size() > 0) {
- auto live_calib = floatlist2vector(log.getRpyCalib());
+ auto live_calib = float64list2vector(log.getRpyCalib());
if ((live_calib.minCoeff() < -CALIB_RPY_SANITY_CHECK) || (live_calib.maxCoeff() > CALIB_RPY_SANITY_CHECK)) {
this->observation_values_invalid["liveCalibration"] += 1.0;
return;
@@ -611,8 +651,8 @@ void Localizer::handle_msg(const cereal::Event::Reader& log) {
this->update_reset_tracker();
}
-kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_builder, bool inputsOK,
- bool sensorsOK, bool gpsOK, bool msgValid) {
+void Localizer::build_location_message(
+ MessageBuilder& msg_builder, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid) {
cereal::Event::Builder evt = msg_builder.initEvent();
evt.setValid(msgValid);
cereal::LiveLocationKalman::Builder liveLoc = evt.initLiveLocationKalman();
@@ -620,7 +660,19 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build
liveLoc.setSensorsOK(sensorsOK);
liveLoc.setGpsOK(gpsOK);
liveLoc.setInputsOK(inputsOK);
- return msg_builder.toBytes();
+}
+
+void Localizer::build_pose_message(
+ MessageBuilder& msg_builder, MessageBuilder& location_msg_builder, bool inputsOK, bool sensorsOK, bool msgValid) {
+ cereal::Event::Builder evt = msg_builder.initEvent();
+ evt.setValid(msgValid);
+ cereal::LivePose::Builder livePose = evt.initLivePose();
+
+ cereal::LiveLocationKalman::Reader location_msg = location_msg_builder.getRoot().getLiveLocationKalman().asReader();
+ this->build_live_pose(livePose, location_msg);
+
+ livePose.setSensorsOK(sensorsOK);
+ livePose.setInputsOK(inputsOK);
}
bool Localizer::is_gps_ok() {
@@ -692,7 +744,7 @@ int Localizer::locationd_thread() {
"carState", "accelerometer", "gyroscope"};
SubMaster sm(service_list, {}, nullptr, {gps_location_socket});
- PubMaster pm({"liveLocationKalman"});
+ PubMaster pm({"liveLocationKalman", "livePose"});
uint64_t cnt = 0;
bool filterInitialized = false;
@@ -726,9 +778,15 @@ int Localizer::locationd_thread() {
this->ttff = std::max(1e-3, (sm[trigger_msg].getLogMonoTime() * 1e-9) - this->first_valid_log_time);
}
- MessageBuilder msg_builder;
- kj::ArrayPtr bytes = this->get_message_bytes(msg_builder, inputsOK, sensorsOK, gpsOK, filterInitialized);
- pm.send("liveLocationKalman", bytes.begin(), bytes.size());
+ MessageBuilder location_msg_builder, pose_msg_builder;
+ this->build_location_message(location_msg_builder, inputsOK, sensorsOK, gpsOK, filterInitialized);
+ this->build_pose_message(pose_msg_builder, location_msg_builder, inputsOK, sensorsOK, filterInitialized);
+
+ kj::ArrayPtr location_bytes = location_msg_builder.toBytes();
+ pm.send("liveLocationKalman", location_bytes.begin(), location_bytes.size());
+
+ kj::ArrayPtr pose_bytes = pose_msg_builder.toBytes();
+ pm.send("livePose", pose_bytes.begin(), pose_bytes.size());
if (cnt % 1200 == 0 && gpsOK) { // once a minute
VectorXd posGeo = this->get_position_geodetic();
diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h
index 47c8bf5627..615f31c9d0 100644
--- a/selfdrive/locationd/locationd.h
+++ b/selfdrive/locationd/locationd.h
@@ -45,9 +45,12 @@ public:
bool are_inputs_ok();
void observation_timings_invalid_reset();
- kj::ArrayPtr get_message_bytes(MessageBuilder& msg_builder,
- bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid);
+ void build_location_message(
+ MessageBuilder& msg_builder, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid);
+ void build_pose_message(
+ MessageBuilder& msg_builder, MessageBuilder& location_msg_builder, bool inputsOK, bool sensorsOK, bool msgValid);
void build_live_location(cereal::LiveLocationKalman::Builder& fix);
+ void build_live_pose(cereal::LivePose::Builder& livePose, cereal::LiveLocationKalman::Reader& liveLocation);
Eigen::VectorXd get_position_geodetic();
Eigen::VectorXd get_state();
diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py
index a6f51773a1..2447ffd9a8 100755
--- a/selfdrive/locationd/paramsd.py
+++ b/selfdrive/locationd/paramsd.py
@@ -5,14 +5,15 @@ import json
import numpy as np
import cereal.messaging as messaging
-from cereal import car
-from cereal import log
+from cereal import car, log
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.numpy_fast import clip
+from openpilot.common.transformations.orientation import rot_from_euler
from openpilot.selfdrive.car.chrysler.values import ChryslerFlagsSP
from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States
from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR
+from openpilot.selfdrive.locationd.helpers import rotate_std
from openpilot.common.swaglog import cloudlog
@@ -40,6 +41,9 @@ class ParamsLearner:
self.kf.filter.set_global("stiffness_rear", CP.tireStiffnessRear)
self.active = False
+ self.calibrated = False
+
+ self.calib_from_device = np.eye(3)
self.speed = 0.0
self.yaw_rate = 0.0
@@ -49,12 +53,16 @@ class ParamsLearner:
self.roll_valid = False
def handle_log(self, t, which, msg):
- if which == 'liveLocationKalman':
- self.yaw_rate = msg.angularVelocityCalibrated.value[2]
- self.yaw_rate_std = msg.angularVelocityCalibrated.std[2]
+ if which == 'livePose':
+ angular_velocity_device = np.array([msg.angularVelocityDevice.x, msg.angularVelocityDevice.y, msg.angularVelocityDevice.z])
+ angular_velocity_device_std = np.array([msg.angularVelocityDevice.xStd, msg.angularVelocityDevice.yStd, msg.angularVelocityDevice.zStd])
+ angular_velocity_calibrated = np.matmul(self.calib_from_device, angular_velocity_device)
+ angular_velocity_calibrated_std = rotate_std(self.calib_from_device, angular_velocity_device_std)
- localizer_roll = msg.orientationNED.value[0]
- localizer_roll_std = np.radians(1) if np.isnan(msg.orientationNED.std[0]) else msg.orientationNED.std[0]
+ self.yaw_rate, self.yaw_rate_std = angular_velocity_calibrated[2], angular_velocity_calibrated_std[2]
+
+ localizer_roll = msg.orientationNED.x
+ localizer_roll_std = np.radians(1) if np.isnan(msg.orientationNED.xStd) else msg.orientationNED.xStd
self.roll_valid = (localizer_roll_std < ROLL_STD_MAX) and (ROLL_MIN < localizer_roll < ROLL_MAX) and msg.sensorsOK
if self.roll_valid:
roll = localizer_roll
@@ -66,7 +74,7 @@ class ParamsLearner:
roll_std = np.radians(10.0)
self.roll = clip(roll, self.roll - ROLL_MAX_DELTA, self.roll + ROLL_MAX_DELTA)
- yaw_rate_valid = msg.angularVelocityCalibrated.valid
+ yaw_rate_valid = msg.angularVelocityDevice.valid and self.calibrated
yaw_rate_valid = yaw_rate_valid and 0 < self.yaw_rate_std < 10 # rad/s
yaw_rate_valid = yaw_rate_valid and abs(self.yaw_rate) < 1 # rad/s
@@ -93,6 +101,11 @@ class ParamsLearner:
self.kf.predict_and_observe(t, ObservationKind.STIFFNESS, np.array([[stiffness]]))
self.kf.predict_and_observe(t, ObservationKind.STEER_RATIO, np.array([[steer_ratio]]))
+ elif which == 'liveCalibration':
+ self.calibrated = msg.calStatus == log.LiveCalibrationData.Status.calibrated
+ device_from_calib = rot_from_euler(np.array(msg.rpyCalib))
+ self.calib_from_device = device_from_calib.T
+
elif which == 'carState':
self.steering_angle = msg.steeringAngleDeg
self.speed = msg.vEgo
@@ -125,13 +138,12 @@ def main():
REPLAY = bool(int(os.getenv("REPLAY", "0")))
pm = messaging.PubMaster(['liveParameters'])
- sm = messaging.SubMaster(['liveLocationKalman', 'carState'], poll='liveLocationKalman')
+ sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState'], poll='livePose')
params_reader = Params()
# wait for stats about the car to come in from controls
cloudlog.info("paramsd is waiting for CarParams")
- with car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) as msg:
- CP = msg
+ CP = messaging.log_from_bytes(params_reader.get("CarParams", block=True), car.CarParams)
cloudlog.info("paramsd got CarParams")
min_sr, max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio
@@ -191,7 +203,7 @@ def main():
t = sm.logMonoTime[which] * 1e-9
learner.handle_log(t, which, sm[which])
- if sm.updated['liveLocationKalman']:
+ if sm.updated['livePose']:
x = learner.kf.x
P = np.sqrt(learner.kf.P.diagonal())
if not all(map(math.isfinite, x)):
diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py
index 73a2efeec8..ce8a7935c8 100755
--- a/selfdrive/locationd/torqued.py
+++ b/selfdrive/locationd/torqued.py
@@ -8,6 +8,7 @@ from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.swaglog import cloudlog
+from openpilot.common.transformations.orientation import rot_from_euler
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator
@@ -36,8 +37,8 @@ ALLOWED_CARS = ['toyota', 'hyundai']
def slope2rot(slope):
- sin = np.sqrt(slope**2 / (slope**2 + 1))
- cos = np.sqrt(1 / (slope**2 + 1))
+ sin = np.sqrt(slope ** 2 / (slope ** 2 + 1))
+ cos = np.sqrt(1 / (slope ** 2 + 1))
return np.array([[cos, -sin], [sin, cos]])
@@ -50,9 +51,10 @@ class TorqueBuckets(PointBuckets):
class TorqueEstimator(ParameterEstimator):
- def __init__(self, CP, decimated=False):
+ def __init__(self, CP, decimated=False, track_all_points=False):
self.hist_len = int(HISTORY / DT_MDL)
- self.lag = CP.steerActuatorDelay + .2 # from controlsd
+ self.lag = CP.steerActuatorDelay + .2 # from controlsd
+ self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters
if decimated:
self.min_bucket_points = MIN_BUCKET_POINTS / 10
self.min_points_total = MIN_POINTS_TOTAL_QLOG
@@ -76,6 +78,8 @@ class TorqueEstimator(ParameterEstimator):
self.offline_friction = CP.lateralTuning.torque.friction
self.offline_latAccelFactor = CP.lateralTuning.torque.latAccelFactor
+ self.calib_from_device = np.eye(3)
+
params = Params()
if params.get_bool("EnforceTorqueLateral"):
if params.get_bool("CustomTorqueLateral"):
@@ -128,7 +132,8 @@ class TorqueEstimator(ParameterEstimator):
for param in initial_params:
self.filtered_params[param] = FirstOrderFilter(initial_params[param], self.decay, DT_MDL)
- def get_restore_key(self, CP, version):
+ @staticmethod
+ def get_restore_key(CP, version):
a, b = None, None
if CP.lateralTuning.which() == 'torque':
a = CP.lateralTuning.torque.friction
@@ -144,6 +149,7 @@ class TorqueEstimator(ParameterEstimator):
min_points_total=self.min_points_total,
points_per_bucket=POINTS_PER_BUCKET,
rowsize=3)
+ self.all_torque_points = []
def estimate_params(self):
points = self.filtered_points.get_points(self.fit_points)
@@ -168,25 +174,41 @@ class TorqueEstimator(ParameterEstimator):
def handle_log(self, t, which, msg):
if which == "carControl":
self.raw_points["carControl_t"].append(t + self.lag)
- self.raw_points["active"].append(msg.latActive)
+ self.raw_points["lat_active"].append(msg.latActive)
elif which == "carOutput":
self.raw_points["carOutput_t"].append(t + self.lag)
self.raw_points["steer_torque"].append(-msg.actuatorsOutput.steer)
elif which == "carState":
self.raw_points["carState_t"].append(t + self.lag)
+ # TODO: check if high aEgo affects resulting lateral accel
self.raw_points["vego"].append(msg.vEgo)
self.raw_points["steer_override"].append(msg.steeringPressed)
- elif which == "liveLocationKalman":
+ elif which == "liveCalibration":
+ device_from_calib = rot_from_euler(np.array(msg.rpyCalib))
+ self.calib_from_device = device_from_calib.T
+
+ # calculate lateral accel from past steering torque
+ elif which == "livePose":
if len(self.raw_points['steer_torque']) == self.hist_len:
- yaw_rate = msg.angularVelocityCalibrated.value[2]
- roll = msg.orientationNED.value[0]
- active = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t, DT_MDL), self.raw_points['carControl_t'], self.raw_points['active']).astype(bool)
- steer_override = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t, DT_MDL), self.raw_points['carState_t'], self.raw_points['steer_override']).astype(bool)
+ angular_velocity_device = np.array([msg.angularVelocityDevice.x, msg.angularVelocityDevice.y, msg.angularVelocityDevice.z])
+ angular_velocity_calibrated = np.matmul(self.calib_from_device, angular_velocity_device)
+
+ yaw_rate = angular_velocity_calibrated[2]
+ roll = msg.orientationNED.x
+ # check lat active up to now (without lag compensation)
+ lat_active = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t + self.lag, DT_MDL),
+ self.raw_points['carControl_t'], self.raw_points['lat_active']).astype(bool)
+ steer_override = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t + self.lag, DT_MDL),
+ self.raw_points['carState_t'], self.raw_points['steer_override']).astype(bool)
vego = np.interp(t, self.raw_points['carState_t'], self.raw_points['vego'])
- steer = np.interp(t, self.raw_points['carOutput_t'], self.raw_points['steer_torque'])
- lateral_acc = (vego * yaw_rate) - (np.sin(roll) * ACCELERATION_DUE_TO_GRAVITY)
- if all(active) and (not any(steer_override)) and (vego > MIN_VEL) and (abs(steer) > STEER_MIN_THRESHOLD) and (abs(lateral_acc) <= LAT_ACC_THRESHOLD):
- self.filtered_points.add_point(float(steer), float(lateral_acc))
+ steer = np.interp(t, self.raw_points['carOutput_t'], self.raw_points['steer_torque']).item()
+ lateral_acc = (vego * yaw_rate) - (np.sin(roll) * ACCELERATION_DUE_TO_GRAVITY).item()
+ if all(lat_active) and not any(steer_override) and (vego > MIN_VEL) and (abs(steer) > STEER_MIN_THRESHOLD):
+ if abs(lateral_acc) <= LAT_ACC_THRESHOLD:
+ self.filtered_points.add_point(steer, lateral_acc)
+
+ if self.track_all_points:
+ self.all_torque_points.append([steer, lateral_acc])
def get_msg(self, valid=True, with_points=False):
msg = messaging.new_message('liveTorqueParameters')
@@ -229,11 +251,10 @@ def main(demo=False):
config_realtime_process([0, 1, 2, 3], 5)
pm = messaging.PubMaster(['liveTorqueParameters'])
- sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveLocationKalman'], poll='liveLocationKalman')
+ sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose'], poll='livePose')
params = Params()
- with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP:
- estimator = TorqueEstimator(CP)
+ estimator = TorqueEstimator(messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams))
while True:
sm.update()
@@ -243,7 +264,7 @@ def main(demo=False):
t = sm.logMonoTime[which] * 1e-9
estimator.handle_log(t, which, sm[which])
- # 4Hz driven by liveLocationKalman
+ # 4Hz driven by livePose
if sm.frame % 5 == 0:
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
@@ -252,8 +273,10 @@ def main(demo=False):
msg = estimator.get_msg(valid=sm.all_checks(), with_points=True)
params.put_nonblocking("LiveTorqueParameters", msg.to_bytes())
+
if __name__ == "__main__":
import argparse
+
parser = argparse.ArgumentParser(description='Process the --demo argument.')
parser.add_argument('--demo', action='store_true', help='A boolean for demo mode.')
args = parser.parse_args()
diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py
index 360f7046ad..0469bd189c 100644
--- a/selfdrive/modeld/custom_model_metadata.py
+++ b/selfdrive/modeld/custom_model_metadata.py
@@ -42,14 +42,14 @@ class CustomModelMetadata:
self.params: Params = params
self.generation: ModelGeneration = self.read_model_generation_param()
- self.capabilities: int = self.get_model_capabilities()
+ self.capabilities: ModelCapabilities = self.get_model_capabilities()
self.valid: bool = self.params.get_bool("CustomDrivingModel") and not SIMULATION and \
self.capabilities != ModelCapabilities.Default
def read_model_generation_param(self) -> ModelGeneration:
return int(self.params.get('DrivingModelGeneration') or ModelGeneration.default)
- def get_model_capabilities(self) -> int:
+ def get_model_capabilities(self) -> ModelCapabilities:
"""Returns the model capabilities for a given generation."""
if self.generation == ModelGeneration.five:
return ModelCapabilities.DesiredCurvatureV2
diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py
index 39a161ed89..8d2bcc0ef4 100644
--- a/selfdrive/modeld/fill_model_msg.py
+++ b/selfdrive/modeld/fill_model_msg.py
@@ -68,7 +68,9 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
driving_model_data.frameDropPerc = frame_drop_perc
action = driving_model_data.action
- action.desiredCurvature = float(net_output_data['desired_curvature'][0,0])
+ model_use_lateral_planner = custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution
+ if not model_use_lateral_planner:
+ action.desiredCurvature = float(net_output_data['desired_curvature'][0,0])
modelV2 = extended_msg.modelV2
modelV2.frameId = vipc_frame_id
@@ -100,7 +102,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyz_poly(poly_path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T)
# lateral planning
- if custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution:
+ if model_use_lateral_planner:
solution = modelV2.lateralPlannerSolutionDEPRECATED
solution.x, solution.y, solution.yaw, solution.yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)]
solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = [net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)]
diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py
index ff3d9ec633..bb883c32cb 100755
--- a/selfdrive/modeld/modeld.py
+++ b/selfdrive/modeld/modeld.py
@@ -236,8 +236,8 @@ def main(demo=False):
if demo:
CP = get_demo_car_params()
else:
- with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg:
- CP = msg
+ CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
+
cloudlog.info("modeld got CarParams: %s", CP.carName)
# TODO this needs more thought, use .2s extra for now to estimate other delays
diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx
index e292bb0d2d..ecbe644e09 100644
--- a/selfdrive/modeld/models/commonmodel_pyx.pyx
+++ b/selfdrive/modeld/models/commonmodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
import numpy as np
cimport numpy as cnp
diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py
index af57e11d03..03d03c5e1c 100644
--- a/selfdrive/modeld/parse_model_outputs.py
+++ b/selfdrive/modeld/parse_model_outputs.py
@@ -42,7 +42,6 @@ class Parser:
raw = outs[name]
raw = raw.reshape((raw.shape[0], max(in_N, 1), -1))
- pred_mu = raw[:,:,:(raw.shape[2] - out_N)//2]
n_values = (raw.shape[2] - out_N)//2
pred_mu = raw[:,:,:n_values]
pred_std = np.exp(raw[:,:,n_values: 2*n_values])
diff --git a/selfdrive/modeld/runners/runmodel_pyx.pyx b/selfdrive/modeld/runners/runmodel_pyx.pyx
index e1b201a6a9..12b8ec10ff 100644
--- a/selfdrive/modeld/runners/runmodel_pyx.pyx
+++ b/selfdrive/modeld/runners/runmodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
from libcpp.string cimport string
diff --git a/selfdrive/modeld/runners/snpemodel_pyx.pyx b/selfdrive/modeld/runners/snpemodel_pyx.pyx
index c3b2b7e9bd..f83b7c8cff 100644
--- a/selfdrive/modeld/runners/snpemodel_pyx.pyx
+++ b/selfdrive/modeld/runners/snpemodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
import os
from libcpp cimport bool
diff --git a/selfdrive/modeld/runners/thneedmodel_pyx.pyx b/selfdrive/modeld/runners/thneedmodel_pyx.pyx
index 53487afa1b..6f8fdd255f 100644
--- a/selfdrive/modeld/runners/thneedmodel_pyx.pyx
+++ b/selfdrive/modeld/runners/thneedmodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
from libcpp cimport bool
from libcpp.string cimport string
diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript
index 1a8a3ee42b..eb726b1e5e 100644
--- a/selfdrive/navd/SConscript
+++ b/selfdrive/navd/SConscript
@@ -6,7 +6,7 @@ libs = ['qt_widgets', 'qt_util', 'QMapLibre', common, messaging, visionipc, tran
if arch == 'larch64':
libs.append(':libEGL_mesa.so.0')
-if arch in ['larch64', 'aarch64', 'x86_64'] and not UBUNTU_FOCAL:
+if arch in ['larch64', 'aarch64', 'x86_64']:
if arch == 'x86_64':
rpath = Dir(f"#third_party/maplibre-native-qt/{arch}/lib").srcnode().abspath
map_env["RPATH"] += [rpath, ]
diff --git a/selfdrive/navd/main.cc b/selfdrive/navd/main.cc
index 2e7b4d3b60..da188bb26b 100644
--- a/selfdrive/navd/main.cc
+++ b/selfdrive/navd/main.cc
@@ -6,7 +6,11 @@
#include "common/util.h"
#include "selfdrive/ui/qt/util.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
+#else
#include "selfdrive/ui/qt/maps/map_helpers.h"
+#endif
#include "selfdrive/navd/map_renderer.h"
#include "system/hardware/hw.h"
diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc
index d52ee162bd..f24e6bb876 100644
--- a/selfdrive/navd/map_renderer.cc
+++ b/selfdrive/navd/map_renderer.cc
@@ -8,7 +8,11 @@
#include "common/util.h"
#include "common/timing.h"
#include "common/swaglog.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
+#else
#include "selfdrive/ui/qt/maps/map_helpers.h"
+#endif
const float DEFAULT_ZOOM = 13.5; // Don't go below 13 or features will start to disappear
const int HEIGHT = 256, WIDTH = 256;
diff --git a/selfdrive/pandad/SConscript b/selfdrive/pandad/SConscript
index 63a2c1e650..dcc1f9811e 100644
--- a/selfdrive/pandad/SConscript
+++ b/selfdrive/pandad/SConscript
@@ -6,6 +6,8 @@ panda = env.Library('panda', ['panda.cc', 'panda_comms.cc', 'spi.cc'])
env.Program('pandad', ['main.cc', 'pandad.cc'], LIBS=[panda] + libs)
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc'])
-envCython.Program('pandad_api_impl.so', 'pandad_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"])
+pandad_python = envCython.Program('pandad_api_impl.so', 'pandad_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"])
+Export('pandad_python')
+
if GetOption('extras'):
env.Program('tests/test_pandad_usbprotocol', ['tests/test_pandad_usbprotocol.cc'], LIBS=[panda] + libs)
diff --git a/selfdrive/pandad/__init__.py b/selfdrive/pandad/__init__.py
index 8081a62dd0..b72c8ccb57 100644
--- a/selfdrive/pandad/__init__.py
+++ b/selfdrive/pandad/__init__.py
@@ -1,10 +1,11 @@
# Cython, now uses scons to build
-from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp
+from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp, can_capnp_to_list
assert can_list_to_can_capnp
+assert can_capnp_to_list
def can_capnp_to_can_list(can, src_filter=None):
ret = []
for msg in can:
if src_filter is None or msg.src in src_filter:
- ret.append((msg.address, msg.busTime, msg.dat, msg.src))
+ ret.append((msg.address, msg.dat, msg.src))
return ret
diff --git a/selfdrive/pandad/can_list_to_can_capnp.cc b/selfdrive/pandad/can_list_to_can_capnp.cc
index 9fc2648da2..ad2393b986 100644
--- a/selfdrive/pandad/can_list_to_can_capnp.cc
+++ b/selfdrive/pandad/can_list_to_can_capnp.cc
@@ -1,16 +1,16 @@
#include "cereal/messaging/messaging.h"
#include "selfdrive/pandad/panda.h"
+#include "opendbc/can/common.h"
-void can_list_to_can_capnp_cpp(const std::vector &can_list, std::string &out, bool sendCan, bool valid) {
+void can_list_to_can_capnp_cpp(const std::vector &can_list, std::string &out, bool sendcan, bool valid) {
MessageBuilder msg;
auto event = msg.initEvent(valid);
- auto canData = sendCan ? event.initSendcan(can_list.size()) : event.initCan(can_list.size());
+ auto canData = sendcan ? event.initSendcan(can_list.size()) : event.initCan(can_list.size());
int j = 0;
for (auto it = can_list.begin(); it != can_list.end(); it++, j++) {
auto c = canData[j];
c.setAddress(it->address);
- c.setBusTime(it->busTime);
c.setDat(kj::arrayPtr((uint8_t*)it->dat.data(), it->dat.size()));
c.setSrc(it->src);
}
@@ -19,3 +19,33 @@ void can_list_to_can_capnp_cpp(const std::vector &can_list, std::stri
kj::ArrayOutputStream output_stream(kj::ArrayPtr((unsigned char *)out.data(), msg_size));
capnp::writeMessage(output_stream, msg);
}
+
+// Converts a vector of Cap'n Proto serialized can strings into a vector of CanData structures.
+void can_capnp_to_can_list_cpp(const std::vector &strings, std::vector &can_list, bool sendcan) {
+ AlignedBuffer aligned_buf;
+ can_list.reserve(strings.size());
+
+ for (const auto &str : strings) {
+ // extract the messages
+ capnp::FlatArrayMessageReader reader(aligned_buf.align(str.data(), str.size()));
+ cereal::Event::Reader event = reader.getRoot();
+
+ auto frames = sendcan ? event.getSendcan() : event.getCan();
+
+ // Add new CanData entry
+ CanData &can_data = can_list.emplace_back();
+ can_data.nanos = event.getLogMonoTime();
+ can_data.frames.reserve(frames.size());
+
+ // Populate CAN frames
+ for (const auto &frame : frames) {
+ CanFrame &can_frame = can_data.frames.emplace_back();
+ can_frame.src = frame.getSrc();
+ can_frame.address = frame.getAddress();
+
+ // Copy CAN data
+ auto dat = frame.getDat();
+ can_frame.dat.assign(dat.begin(), dat.end());
+ }
+ }
+}
diff --git a/selfdrive/pandad/panda.cc b/selfdrive/pandad/panda.cc
index a404ad3880..ed5ee62501 100644
--- a/selfdrive/pandad/panda.cc
+++ b/selfdrive/pandad/panda.cc
@@ -26,8 +26,6 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
hw_type = get_hw_type();
can_reset_communications();
-
- return;
}
bool Panda::connected() {
@@ -259,7 +257,6 @@ bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector pandas) {
auto canData = evt.initCan(raw_can_data.size());
for (uint i = 0; i&f.dat[0])[:f.dat.size()], f.src] for f in d.frames]
+ result.append([d.nanos, frames])
+ preinc(it)
+ return result
diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py
index d12171b4a7..013cac3aca 100755
--- a/selfdrive/test/process_replay/model_replay.py
+++ b/selfdrive/test/process_replay/model_replay.py
@@ -10,8 +10,7 @@ from openpilot.tools.lib.openpilotci import BASE_URL, get_url
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff
from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process
from openpilot.tools.lib.framereader import FrameReader
-from openpilot.tools.lib.logreader import LogReader
-from openpilot.tools.lib.helpers import save_log
+from openpilot.tools.lib.logreader import LogReader, save_log
TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30"
SEGMENT = 6
diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py
index c7f57fd9bc..1731bf2e1e 100755
--- a/selfdrive/test/process_replay/process_replay.py
+++ b/selfdrive/test/process_replay/process_replay.py
@@ -390,7 +390,7 @@ def calibration_rcv_callback(msg, cfg, frame):
def torqued_rcv_callback(msg, cfg, frame):
# should_recv always true to increment frame
- return (frame - 1) == 0 or msg.which() == 'liveLocationKalman'
+ return (frame - 1) == 0 or msg.which() == 'livePose'
def dmonitoringmodeld_rcv_callback(msg, cfg, frame):
@@ -532,18 +532,18 @@ CONFIGS = [
"cameraOdometry", "accelerometer", "gyroscope", "gpsLocationExternal",
"liveCalibration", "carState", "gpsLocation"
],
- subs=["liveLocationKalman"],
+ subs=["liveLocationKalman", "livePose"],
ignore=["logMonoTime"],
config_callback=locationd_config_pubsub_callback,
tolerance=NUMPY_TOLERANCE,
),
ProcessConfig(
proc_name="paramsd",
- pubs=["liveLocationKalman", "carState"],
+ pubs=["livePose", "liveCalibration", "carState"],
subs=["liveParameters"],
ignore=["logMonoTime"],
init_callback=get_car_params_callback,
- should_recv_callback=FrequencyBasedRcvCallback("liveLocationKalman"),
+ should_recv_callback=FrequencyBasedRcvCallback("livePose"),
tolerance=NUMPY_TOLERANCE,
processing_time=0.004,
),
@@ -555,7 +555,7 @@ CONFIGS = [
),
ProcessConfig(
proc_name="torqued",
- pubs=["liveLocationKalman", "carState", "carControl", "carOutput"],
+ pubs=["livePose", "liveCalibration", "carState", "carControl", "carOutput"],
subs=["liveTorqueParameters"],
ignore=["logMonoTime"],
init_callback=get_car_params_callback,
@@ -819,11 +819,15 @@ def check_openpilot_enabled(msgs: LogIterable) -> bool:
def check_most_messages_valid(msgs: LogIterable, threshold: float = 0.9) -> bool:
+ relevant_services = {sock for cfg in CONFIGS for sock in cfg.subs}
msgs_counts = Counter(msg.which() for msg in msgs)
msgs_valid_counts = Counter(msg.which() for msg in msgs if msg.valid)
most_valid_for_service = {}
for msg_type in msgs_counts.keys():
+ if msg_type not in relevant_services:
+ continue
+
valid_share = msgs_valid_counts.get(msg_type, 0) / msgs_counts[msg_type]
ok = valid_share >= threshold
if not ok:
diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit
index 1635012f3e..a85e8919c4 100644
--- a/selfdrive/test/process_replay/ref_commit
+++ b/selfdrive/test/process_replay/ref_commit
@@ -1 +1 @@
-f6ff3601bd0496e78d8bc3b019d58bb7739f096b
\ No newline at end of file
+6ca7313b9c1337fad1334d9e087cb4984fdce74d
\ No newline at end of file
diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py
index bf7a4bfd97..e87b8347e1 100755
--- a/selfdrive/test/process_replay/regen.py
+++ b/selfdrive/test/process_replay/regen.py
@@ -14,8 +14,7 @@ from openpilot.selfdrive.test.process_replay.vision_meta import DRIVER_CAMERA_FR
from openpilot.selfdrive.test.update_ci_routes import upload_route
from openpilot.tools.lib.route import Route
from openpilot.tools.lib.framereader import FrameReader, BaseFrameReader, FrameType
-from openpilot.tools.lib.logreader import LogReader, LogIterable
-from openpilot.tools.lib.helpers import save_log
+from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log
class DummyFrameReader(BaseFrameReader):
@@ -75,7 +74,7 @@ def setup_data_readers(
assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera."
frs['driverCameraState'] = FrameReader(r.dcamera_paths()[sidx])
else:
- lr = LogReader(f"cd:/{route.replace('|', '/')}/{sidx}/rlog.bz2")
+ lr = LogReader(f"{route}/{sidx}/r")
frs = {}
if needs_road_cam:
frs['roadCameraState'] = FrameReader(f"cd:/{route.replace('|', '/')}/{sidx}/fcamera.hevc")
@@ -119,7 +118,7 @@ def regen_and_save(
log_dir = os.path.join(outdir, time.strftime("%Y-%m-%d--%H-%M-%S--0", time.gmtime()))
rel_log_dir = os.path.relpath(log_dir)
- rpath = os.path.join(log_dir, "rlog.bz2")
+ rpath = os.path.join(log_dir, "rlog.zst")
os.makedirs(log_dir)
save_log(rpath, output_logs, compress=True)
diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py
index 533ab125f9..84a19aa0a9 100755
--- a/selfdrive/test/process_replay/test_processes.py
+++ b/selfdrive/test/process_replay/test_processes.py
@@ -14,8 +14,7 @@ from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, f
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process, \
check_openpilot_enabled, check_most_messages_valid
from openpilot.tools.lib.filereader import FileReader
-from openpilot.tools.lib.logreader import LogReader
-from openpilot.tools.lib.helpers import save_log
+from openpilot.tools.lib.logreader import LogReader, save_log
source_segments = [
("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.COMMA_BODY
@@ -42,23 +41,23 @@ source_segments = [
]
segments = [
- ("BODY", "regen29FD9FF7760|2024-05-21--06-58-51--0"),
- ("HYUNDAI", "regen0B1B76A1C27|2024-05-21--06-57-53--0"),
- ("HYUNDAI2", "regen3BB55FA5E20|2024-05-21--06-59-03--0"),
- ("TOYOTA", "regenF6FB954C1E2|2024-05-21--06-57-53--0"),
- ("TOYOTA2", "regen0AC637CE7BA|2024-05-21--06-57-54--0"),
- ("TOYOTA3", "regenC7BE3FAE496|2024-05-21--06-59-01--0"),
- ("HONDA", "regen58E9F8B695A|2024-05-21--06-57-55--0"),
- ("HONDA2", "regen8695608EB15|2024-05-21--06-57-55--0"),
- ("CHRYSLER", "regenB0F8C25C902|2024-05-21--06-59-47--0"),
- ("RAM", "regenB3B2C7A105B|2024-05-21--07-00-47--0"),
- ("SUBARU", "regen860FD736DCC|2024-05-21--07-00-50--0"),
- ("GM", "regen8CB3048DEB9|2024-05-21--06-59-49--0"),
- ("GM2", "regen379D446541D|2024-05-21--07-00-51--0"),
- ("NISSAN", "regen24871108F80|2024-05-21--07-00-38--0"),
- ("VOLKSWAGEN", "regenF390392F275|2024-05-21--07-00-52--0"),
- ("MAZDA", "regenE5A36020581|2024-05-21--07-01-51--0"),
- ("FORD", "regenDC288ED0D78|2024-05-21--07-02-18--0"),
+ ("BODY", "regen34ECCE11CA1|2024-07-29--22-55-10--0"),
+ ("HYUNDAI", "regenC713CE6FA82|2024-07-29--22-56-31--0"),
+ ("HYUNDAI2", "regenD81F3A374A7|2024-07-29--22-58-45--0"),
+ ("TOYOTA", "regenE6D76723DC2|2024-07-29--23-00-08--0"),
+ ("TOYOTA2", "regen198859A572C|2024-07-29--23-01-31--0"),
+ ("TOYOTA3", "regenDF1EB621A66|2024-07-29--23-03-49--0"),
+ ("HONDA", "regen0FE7C4758B5|2024-07-29--23-05-14--0"),
+ ("HONDA2", "regen510A1F60E60|2024-07-29--23-06-39--0"),
+ ("CHRYSLER", "regenDACF082E83B|2024-07-29--23-08-01--0"),
+ ("RAM", "regen8BFB7E62F52|2024-07-29--23-10-14--0"),
+ ("SUBARU", "regen4EE2D45369E|2024-07-29--23-12-31--0"),
+ ("GM", "regenB38D92E6A4D|2024-07-29--23-13-54--0"),
+ ("GM2", "regenC5488470F1A|2024-07-29--23-16-09--0"),
+ ("NISSAN", "regenE5400EB4689|2024-07-29--23-17-30--0"),
+ ("VOLKSWAGEN", "regenD0B5635A8B9|2024-07-29--23-18-54--0"),
+ ("MAZDA", "regen57F8511F082|2024-07-29--23-21-09--0"),
+ ("FORD", "regen5708620AA2E|2024-07-29--23-23-20--0"),
]
# dashcamOnly makes don't need to be tested until a full port is done
@@ -112,7 +111,8 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non
if not check_most_messages_valid(log_msgs):
return f"Route did not have enough valid messages: {new_log_path}", log_msgs
- if cfg.proc_name != 'ubloxd' or segment != 'regen3BB55FA5E20|2024-05-21--06-59-03--0':
+ # skip this check if the segment is using qcom gps
+ if cfg.proc_name != 'ubloxd' or any(m.which() in cfg.pubs for m in lr):
seen_msgs = {m.which() for m in log_msgs}
expected_msgs = set(cfg.subs)
if seen_msgs != expected_msgs:
@@ -198,11 +198,11 @@ if __name__ == "__main__":
if cfg.proc_name not in tested_procs:
continue
- cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.bz2")
+ cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst")
if args.update_refs: # reference logs will not exist if routes were just regenerated
ref_log_path = get_url(*segment.rsplit("--", 1))
else:
- ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2")
+ ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst")
ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn)
dat = None if args.upload_only else log_data[segment]
diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py
index dd09ceeca3..7220b4086a 100644
--- a/selfdrive/test/test_onroad.py
+++ b/selfdrive/test/test_onroad.py
@@ -1,4 +1,3 @@
-import bz2
import math
import json
import os
@@ -9,6 +8,7 @@ import shutil
import subprocess
import time
import numpy as np
+import zstandard as zstd
from collections import Counter, defaultdict
from functools import cached_property
from pathlib import Path
@@ -20,9 +20,10 @@ from openpilot.common.basedir import BASEDIR
from openpilot.common.timeout import Timeout
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.events import EVENTS, ET
-from openpilot.system.hardware import HARDWARE
from openpilot.selfdrive.test.helpers import set_params_enabled, release_only
+from openpilot.system.hardware import HARDWARE
from openpilot.system.hardware.hw import Paths
+from openpilot.system.loggerd.uploader import LOG_COMPRESSION_LEVEL
from openpilot.tools.lib.logreader import LogReader
"""
@@ -56,7 +57,7 @@ PROCS = {
"system.logmessaged": 0.2,
"system.tombstoned": 0,
"logcatd": 0,
- "system.micd": 6.0,
+ "system.micd": 5.0,
"system.timed": 0,
"selfdrive.pandad.pandad": 0,
"system.statsd": 0.4,
@@ -167,10 +168,10 @@ class TestOnroad:
cls.log_sizes = {}
for f in cls.log_path.iterdir():
assert f.is_file()
- cls.log_sizes[f] = f.stat().st_size / 1e6
+ cls.log_sizes[f] = f.stat().st_size / 1e6
if f.name in ("qlog", "rlog"):
with open(f, 'rb') as ff:
- cls.log_sizes[f] = len(bz2.compress(ff.read())) / 1e6
+ cls.log_sizes[f] = len(zstd.compress(ff.read(), LOG_COMPRESSION_LEVEL)) / 1e6
@cached_property
@@ -207,7 +208,7 @@ class TestOnroad:
if f.name == "qcamera.ts":
assert 2.15 < sz < 2.35
elif f.name == "qlog":
- assert 0.6 < sz < 1.0
+ assert 0.4 < sz < 0.55
elif f.name == "rlog":
assert 5 < sz < 50
elif f.name.endswith('.hevc'):
diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript
index e1233b5cbc..3e238c17dc 100644
--- a/selfdrive/ui/SConscript
+++ b/selfdrive/ui/SConscript
@@ -1,6 +1,6 @@
import os
import json
-Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations', 'UBUNTU_FOCAL')
+Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations')
base_libs = [common, messaging, visionipc, transformations,
'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"]
@@ -17,44 +17,38 @@ if arch == "Darwin":
# FIXME: remove this once we're on 5.15 (24.04)
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
-qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs)
+sp_widgets_src = []
+sp_maps_widgets_src = []
+sp_qt_src = []
+sp_qt_util = []
+if not GetOption('stock_ui'):
+ SConscript(['sunnypilot/SConscript'])
+ Import('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src', "sp_qt_util")
+
+qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"] + sp_qt_util, LIBS=base_libs)
widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc",
"qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc",
"qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc",
"qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc",
- "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"]
-
-widgets_src += ["qt/offroad/sunnypilot/display_settings.cc", "qt/offroad/sunnypilot/sunnypilot_settings.cc",
- "qt/offroad/sunnypilot/vehicle_settings.cc", "qt/offroad/sunnypilot/visuals_settings.cc",
- "qt/offroad/sunnypilot/trips_settings.cc", "qt/offroad/sunnypilot/mads_settings.cc",
- "qt/offroad/sunnypilot/lane_change_settings.cc", "qt/offroad/sunnypilot/speed_limit_control_settings.cc",
- "qt/offroad/sunnypilot/monitoring_settings.cc", "qt/offroad/sunnypilot/osm_settings.cc",
- "qt/offroad/sunnypilot/custom_offsets_settings.cc", "qt/widgets/sunnypilot/drive_stats.cc",
- "qt/offroad/sunnypilot/software_settings_sp.cc", "qt/offroad/sunnypilot/models_fetcher.cc",
- "qt/offroad/sunnypilot/speed_limit_warning_settings.cc", "qt/offroad/sunnypilot/speed_limit_policy_settings.cc",
- "qt/offroad/sunnypilot/sunnylink_settings.cc"]
-
-widgets_src += ["qt/network/sunnylink/sunnylink_client.cc", "qt/network/sunnylink/services/base_device_service.cc",
- "qt/network/sunnylink/services/role_service.cc", "qt/network/sunnylink/services/user_service.cc"]
+ "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src
qt_env['CPPDEFINES'] = []
if maps:
base_libs += ['QMapLibre']
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc",
- "qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"]
+ "qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"] + sp_maps_widgets_src
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)
Export('widgets')
qt_libs = [widgets, qt_util] + base_libs
-qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc",
+qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc", "qt/offroad_home.cc",
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
"qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc",
"qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc",
"qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc",
- "qt/onroad/buttons.cc", "qt/onroad/alerts.cc",
- "qt/onroad_settings.cc", "qt/onroad_settings_panel.cc"]
+ "qt/onroad/buttons.cc", "qt/onroad/alerts.cc"] + sp_qt_src
# build translation files
with open(File("translations/languages.json").abspath) as f:
@@ -63,7 +57,7 @@ translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.v
translation_targets = [src.replace(".ts", ".qm") for src in translation_sources]
lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease'
-lupdate = qt_env.Command(translation_sources, qt_src + widgets_src, "selfdrive/ui/update_translations.py")
+lupdate = qt_env.Command(translation_sources + ["translations/alerts_generated.h"], qt_src + widgets_src, "selfdrive/ui/update_translations.py")
lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES")
qt_env.Depends(lrelease, lupdate)
qt_env.NoClean(translation_sources)
@@ -87,14 +81,15 @@ asset_obj = qt_env.Object("assets", assets)
qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs)
# spinner and text window
-qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs)
+text_cc_path = "qt/text.cc" if GetOption('stock_ui') else "sunnypilot/qt/text.cc"
+qt_env.Program("_text", [text_cc_path], LIBS=qt_libs)
qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs)
# build main UI
qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs)
if GetOption('extras'):
qt_src.remove("main.cc") # replaced by test_runner
- #qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs)
+ qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs)
qt_env.Program('tests/ui_snapshot', [asset_obj, "tests/ui_snapshot.cc"] + qt_src, LIBS=qt_libs)
@@ -132,7 +127,7 @@ if GetOption('extras') and arch in ['larch64']:
obj = senv.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d)
f = senv.Program(f"installer/installers/installer_{name}", [obj, cont], LIBS=qt_libs)
# keep installers small
- assert f[0].get_size() < 350*1e3
+ assert f[0].get_size() < 370*1e3
# build watch3
if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'):
diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc
index 4903a3db3d..baf09c79f1 100644
--- a/selfdrive/ui/main.cc
+++ b/selfdrive/ui/main.cc
@@ -6,7 +6,13 @@
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
+
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/window.h"
+#define MainWindow MainWindowSP
+#else
#include "selfdrive/ui/qt/window.h"
+#endif
int main(int argc, char *argv[]) {
setpriority(PRIO_PROCESS, 0, -20);
@@ -22,7 +28,6 @@ int main(int argc, char *argv[]) {
QApplication a(argc, argv);
a.installTranslator(&translator);
-
MainWindow w;
setMainWindow(&w);
a.installEventFilter(&w);
diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc
index f3314221b8..80019f406e 100644
--- a/selfdrive/ui/qt/api.cc
+++ b/selfdrive/ui/qt/api.cc
@@ -2,7 +2,6 @@
#include
#include
-#include
#include
#include
@@ -10,12 +9,10 @@
#include
#include
#include
-#include
#include
#include
-#include "common/swaglog.h"
#include "common/util.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h"
@@ -48,130 +45,11 @@ QByteArray rsa_sign(const QByteArray &data) {
return sig;
}
-void derive_aes_key_iv_from_rsa(EVP_PKEY *rsa_key, unsigned char *aes_key, unsigned char *aes_iv) {
- unsigned char hash[SHA256_DIGEST_LENGTH];
- size_t pub_len;
- unsigned char *pub_key;
-
- // Convert RSA key to public key in DER format for simplicity
- pub_len = i2d_PublicKey(rsa_key, NULL);
- pub_key = (unsigned char *)malloc(pub_len);
- unsigned char *tmp = pub_key;
- i2d_PublicKey(rsa_key, &tmp);
-
- // Hash the public key to derive bytes for AES key and IV
- SHA256(pub_key, pub_len, hash);
-
- // Assuming AES-256-CBC, we need 32 bytes for the key and 16 for the IV
- memcpy(aes_key, hash, 32); // First 32 bytes for AES key
- memcpy(aes_iv, hash + 32 - 16, 16); // Last 16 bytes for AES IV
-
- free(pub_key);
-}
-
-EVP_PKEY *load_public_key(const char *file) {
- EVP_PKEY *key = NULL;
- FILE *fp = fopen(file, "r");
- if (fp) {
- key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
- fclose(fp);
- }
- return key;
-}
-
-EVP_PKEY *load_private_key(const char *file) {
- EVP_PKEY *key = NULL;
- FILE *fp = fopen(file, "r");
- if (fp) {
- key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
- fclose(fp);
- }
- return key;
-}
-
-QByteArray rsa_encrypt(const QByteArray &data) {
- EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
- unsigned char aes_key[32], aes_iv[16];
- derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
-
- EVP_CIPHER_CTX *ctx;
- if (!(ctx = EVP_CIPHER_CTX_new())) {
- // Handle error: Allocate memory failed
- return {};
- }
-
- if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
- qDebug() << "Failed to initialize encryption";
- EVP_CIPHER_CTX_free(ctx);
- return {};
- }
-
- int ciphertext_len;
- int len = data.size();
- unsigned char out[len + AES_BLOCK_SIZE];
- auto *in = (unsigned char*) data.constData();
- if (EVP_EncryptUpdate(ctx, out, &ciphertext_len, in, len) != 1) {
- qDebug() << "Failed to update encryption";
- EVP_CIPHER_CTX_free(ctx);
- return {};
- }
-
- if (EVP_EncryptFinal_ex(ctx, out + ciphertext_len, &len) != 1) {
- // Handle error: Encryption finalize failed
- qDebug() << "Failed to finalize encryption";
- EVP_CIPHER_CTX_free(ctx);
- return {};
- }
-
- //qDebug() << "Encrypted data length: %d", ciphertext_len + len; // Print the length of encrypted data
- EVP_CIPHER_CTX_free(ctx);
- return {reinterpret_cast(out), ciphertext_len + len};
-}
-
-QByteArray rsa_decrypt(const QByteArray &data) {
- EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
- unsigned char aes_key[32], aes_iv[16];
- derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
-
- EVP_CIPHER_CTX *ctx;
- if (!(ctx = EVP_CIPHER_CTX_new())) {
- qDebug() << "Failed to allocate memory for EVP_CIPHER_CTX";
- return {};
- }
-
- if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
- qDebug() << "Failed to initialize EVP";
- EVP_CIPHER_CTX_free(ctx);
- return {};
- }
-
- int len = data.size();
- unsigned char out[len + AES_BLOCK_SIZE];
- auto *in = (unsigned char*) data.constData();
- if (EVP_DecryptUpdate(ctx, out, &len, in, len) != 1) {
- qDebug() << "Failed to update decryption";
- EVP_CIPHER_CTX_free(ctx);
- return {};
- }
-
- int final_len;
- if (EVP_DecryptFinal_ex(ctx, out + len, &final_len) != 1) {
- qDebug() << "Failed to finalize decryption";
- EVP_CIPHER_CTX_free(ctx);
- return {};
- }
- EVP_CIPHER_CTX_free(ctx);
-
- //qDebug() << "Decrypted data length: %d", len + final_len; // Print the length of decrypted data
- return {reinterpret_cast(out), len + final_len};
-}
-
-QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) {
+QString create_jwt(const QJsonObject &payloads, int expiry) {
QJsonObject header = {{"alg", "RS256"}};
auto t = QDateTime::currentSecsSinceEpoch();
- auto dongle_id = sunnylink ? getSunnylinkDongleId() : getDongleId();
- QJsonObject payload = {{"identity", dongle_id.value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}};
+ QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}};
for (auto it = payloads.begin(); it != payloads.end(); ++it) {
payload.insert(it.key(), it.value());
}
@@ -186,7 +64,7 @@ QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) {
} // namespace CommaApi
-HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout, const bool sunnylink) : create_jwt(create_jwt), sunnylink(sunnylink), QObject(parent) {
+HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) {
networkTimer = new QTimer(this);
networkTimer->setSingleShot(true);
networkTimer->setInterval(timeout);
@@ -201,40 +79,38 @@ bool HttpRequest::timeout() const {
return reply && reply->error() == QNetworkReply::OperationCanceledError;
}
-void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method, const QByteArray &payload) {
- LOGD("Requesting %s", qPrintable(requestURL));
- if (active()) {
- qDebug() << "HttpRequest is active";
- return;
- }
+QNetworkRequest HttpRequest::prepareRequest(const QString &requestURL)
+{
+ QNetworkRequest request;
QString token;
if (create_jwt) {
- token = CommaApi::create_jwt({}, 3600, sunnylink);
+ token = GetJwtToken();
} else {
QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json"));
QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8());
token = json_d["access_token"].toString();
}
- QNetworkRequest request;
request.setUrl(QUrl(requestURL));
- request.setRawHeader("User-Agent", getUserAgent(sunnylink).toUtf8());
- if (!payload.isEmpty()) {
- request.setRawHeader("Content-Type", "application/json");
- }
+ request.setRawHeader("User-Agent", GetUserAgent().toUtf8());
if (!token.isEmpty()) {
request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8());
}
+ return request;
+}
- if (method == HttpRequest::Method::GET) {
+void HttpRequest::sendRequest(const QString &requestURL, const Method method) {
+ if (active()) {
+ qDebug() << "HttpRequest is active";
+ return;
+ }
+
+ QNetworkRequest request = prepareRequest(requestURL);
+ if (method == Method::GET) {
reply = nam()->get(request);
- } else if (method == HttpRequest::Method::DELETE) {
+ } else if (method == Method::DELETE) {
reply = nam()->deleteResource(request);
- } else if (method == HttpRequest::Method::POST) {
- reply = nam()->post(request, payload);
- } else if (method == HttpRequest::Method::PUT) {
- reply = nam()->put(request, payload);
}
networkTimer->start();
diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h
index bbebc502e5..f0e21f56a8 100644
--- a/selfdrive/ui/qt/api.h
+++ b/selfdrive/ui/qt/api.h
@@ -6,14 +6,13 @@
#include
#include "common/util.h"
+#include "selfdrive/ui/qt/util.h"
namespace CommaApi {
const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str();
QByteArray rsa_sign(const QByteArray &data);
-QByteArray rsa_encrypt(const QByteArray &data);
-QByteArray rsa_decrypt(const QByteArray &data);
-QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600, bool sunnylink = false);
+QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600);
} // namespace CommaApi
@@ -27,8 +26,9 @@ class HttpRequest : public QObject {
public:
enum class Method {GET, DELETE, POST, PUT};
- explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000, const bool sunnylink = false);
- void sendRequest(const QString &requestURL, const Method method = Method::GET, const QByteArray &payload = {});
+ explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000);
+ virtual void sendRequest(const QString &requestURL, Method method);
+ void sendRequest(const QString &requestURL) { sendRequest(requestURL, Method::GET);}
bool active() const;
bool timeout() const;
@@ -37,14 +37,14 @@ signals:
protected:
QNetworkReply *reply = nullptr;
-
-private:
static QNetworkAccessManager *nam();
QTimer *networkTimer = nullptr;
bool create_jwt;
- bool sunnylink;
+ virtual QNetworkRequest prepareRequest(const QString& requestURL);
+ virtual QString GetJwtToken() const { return CommaApi::create_jwt(); }
+ virtual QString GetUserAgent() const { return getUserAgent(); }
-private slots:
+protected slots:
void requestTimeout();
void requestFinished();
};
diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h
index 567a54d49b..ac3cd2ce1c 100644
--- a/selfdrive/ui/qt/body.h
+++ b/selfdrive/ui/qt/body.h
@@ -5,7 +5,11 @@
#include
#include "common/util.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
class RecordButton : public QPushButton {
Q_OBJECT
diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc
index 48de10a79c..68ab992095 100644
--- a/selfdrive/ui/qt/home.cc
+++ b/selfdrive/ui/qt/home.cc
@@ -9,10 +9,6 @@
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/prime.h"
-#ifdef ENABLE_MAPS
-#include "selfdrive/ui/qt/maps/map_settings.h"
-#endif
-
// HomeWindow: the container for the offroad and onroad UIs
HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
@@ -32,8 +28,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
slayout->addWidget(home);
onroad = new OnroadWindow(this);
- QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); });
- QObject::connect(onroad, &OnroadWindow::onroadSettingsPanelRequested, this, [=] { sidebar->hide(); });
slayout->addWidget(onroad);
body = new BodyWindow(this);
@@ -54,10 +48,6 @@ void HomeWindow::showSidebar(bool show) {
sidebar->setVisible(show);
}
-void HomeWindow::showMapPanel(bool show) {
- onroad->showMapPanel(show);
-}
-
void HomeWindow::updateState(const UIState &s) {
const SubMaster &sm = *(s.sm);
@@ -66,9 +56,6 @@ void HomeWindow::updateState(const UIState &s) {
body->setEnabled(true);
slayout->setCurrentWidget(body);
}
-
- uiState()->scene.map_visible = onroad->isMapVisible();
- uiState()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible();
}
void HomeWindow::offroadTransition(bool offroad) {
@@ -92,23 +79,9 @@ void HomeWindow::showDriverView(bool show) {
}
void HomeWindow::mousePressEvent(QMouseEvent* e) {
- if (uiState()->scene.started) {
- if (uiState()->scene.onroadScreenOff != -2) {
- uiState()->scene.touched2 = true;
- QTimer::singleShot(500, []() { uiState()->scene.touched2 = false; });
- }
- if (uiState()->scene.button_auto_hide) {
- uiState()->scene.touch_to_wake = true;
- uiState()->scene.sleep_btn_fading_in = true;
- QTimer::singleShot(500, []() { uiState()->scene.touch_to_wake = false; });
- }
- }
-
// Handle sidebar collapsing
if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) {
- if (onroad->wakeScreenTimeout()) {
- sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible());
- }
+ sidebar->setVisible(!sidebar->isVisible());
}
}
@@ -124,146 +97,3 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
showSidebar(false);
}
}
-
-// OffroadHome: the offroad home page
-
-OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
- QVBoxLayout* main_layout = new QVBoxLayout(this);
- main_layout->setContentsMargins(40, 40, 40, 40);
-
- // top header
- QHBoxLayout* header_layout = new QHBoxLayout();
- header_layout->setContentsMargins(0, 0, 0, 0);
- header_layout->setSpacing(16);
-
- update_notif = new QPushButton(tr("UPDATE"));
- update_notif->setVisible(false);
- update_notif->setStyleSheet("background-color: #364DEF;");
- QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
- header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
-
- alert_notif = new QPushButton();
- alert_notif->setVisible(false);
- alert_notif->setStyleSheet("background-color: #E22C2C;");
- QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); });
- header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
-
- version = new ElidedLabel();
- header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight);
-
- main_layout->addLayout(header_layout);
-
- // main content
- main_layout->addSpacing(25);
- center_layout = new QStackedLayout();
-
- QWidget *home_widget = new QWidget(this);
- {
- QHBoxLayout *home_layout = new QHBoxLayout(home_widget);
- home_layout->setContentsMargins(0, 0, 0, 0);
- home_layout->setSpacing(30);
-
- // left: MapSettings/PrimeAdWidget
- QStackedWidget *left_widget = new QStackedWidget(this);
-#ifdef ENABLE_MAPS
- left_widget->addWidget(new MapSettings);
-#else
- left_widget->addWidget(new QWidget);
-#endif
- custom_mapbox = QString::fromStdString(params.get("CustomMapboxTokenSk")) != "";
- if (!custom_mapbox) {
- left_widget->addWidget(new PrimeAdWidget);
- }
- left_widget->setStyleSheet("border-radius: 10px;");
-
- left_widget->setCurrentIndex((uiState()->hasPrime() || custom_mapbox) ? 0 : 1);
- connect(uiState(), &UIState::primeChanged, [=](bool prime) {
- left_widget->setCurrentIndex((prime || custom_mapbox) ? 0 : 1);
- });
-
- home_layout->addWidget(left_widget, 1);
-
- // right: ExperimentalModeButton, SetupWidget
- QWidget* right_widget = new QWidget(this);
- QVBoxLayout* right_column = new QVBoxLayout(right_widget);
- right_column->setContentsMargins(0, 0, 0, 0);
- right_widget->setFixedWidth(750);
- right_column->setSpacing(30);
-
- ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this);
- QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings);
- right_column->addWidget(experimental_mode, 1);
-
- SetupWidget *setup_widget = new SetupWidget;
- QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings);
- right_column->addWidget(setup_widget, 1);
-
- home_layout->addWidget(right_widget, 1);
- }
- center_layout->addWidget(home_widget);
-
- // add update & alerts widgets
- update_widget = new UpdateAlert();
- QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
- center_layout->addWidget(update_widget);
- alerts_widget = new OffroadAlert();
- QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
- center_layout->addWidget(alerts_widget);
-
- main_layout->addLayout(center_layout, 1);
-
- // set up refresh timer
- timer = new QTimer(this);
- timer->callOnTimeout(this, &OffroadHome::refresh);
-
- setStyleSheet(R"(
- * {
- color: white;
- }
- OffroadHome {
- background-color: black;
- }
- OffroadHome > QPushButton {
- padding: 15px 30px;
- border-radius: 5px;
- font-size: 40px;
- font-weight: 500;
- }
- OffroadHome > QLabel {
- font-size: 55px;
- }
- )");
-}
-
-void OffroadHome::showEvent(QShowEvent *event) {
- refresh();
- timer->start(10 * 1000);
-}
-
-void OffroadHome::hideEvent(QHideEvent *event) {
- timer->stop();
-}
-
-void OffroadHome::refresh() {
- version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription")));
-
- bool updateAvailable = update_widget->refresh();
- int alerts = alerts_widget->refresh();
-
- // pop-up new notification
- int idx = center_layout->currentIndex();
- if (!updateAvailable && !alerts) {
- idx = 0;
- } else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) {
- idx = 1;
- } else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) {
- idx = 2;
- }
- center_layout->setCurrentIndex(idx);
-
- update_notif->setVisible(updateAvailable);
- alert_notif->setVisible(alerts);
- if (alerts) {
- alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT")));
- }
-}
diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h
index 7e0267a8a9..e903dad47d 100644
--- a/selfdrive/ui/qt/home.h
+++ b/selfdrive/ui/qt/home.h
@@ -12,35 +12,20 @@
#include "selfdrive/ui/qt/body.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#include "selfdrive/ui/qt/sidebar.h"
-#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
#include "selfdrive/ui/ui.h"
-class OffroadHome : public QFrame {
- Q_OBJECT
-
-public:
- explicit OffroadHome(QWidget* parent = 0);
-
-signals:
- void openSettings(int index = 0, const QString ¶m = "");
-
-private:
- void showEvent(QShowEvent *event) override;
- void hideEvent(QHideEvent *event) override;
- void refresh();
-
- Params params;
-
- QTimer* timer;
- ElidedLabel* version;
- QStackedLayout* center_layout;
- UpdateAlert *update_widget;
- OffroadAlert* alerts_widget;
- QPushButton* alert_notif;
- QPushButton* update_notif;
- bool custom_mapbox;
-};
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#include "selfdrive/ui/sunnypilot/qt/offroad_home.h"
+#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h"
+#define OnroadWindow OnroadWindowSP
+#define OffroadHome OffroadHomeSP
+#else
+#include "selfdrive/ui/qt/widgets/controls.h"
+#include "selfdrive/ui/qt/onroad/onroad_home.h"
+#include "selfdrive/ui/qt/offroad_home.h"
+#endif
class HomeWindow : public QWidget {
Q_OBJECT
@@ -56,13 +41,11 @@ public slots:
void offroadTransition(bool offroad);
void showDriverView(bool show);
void showSidebar(bool show);
- void showMapPanel(bool show);
protected:
void mousePressEvent(QMouseEvent* e) override;
void mouseDoubleClickEvent(QMouseEvent* e) override;
-private:
Sidebar *sidebar;
OffroadHome *home;
OnroadWindow *onroad;
@@ -70,6 +53,6 @@ private:
DriverViewWindow *driver_view;
QStackedLayout *slayout;
-private slots:
- void updateState(const UIState &s);
+protected slots:
+ virtual void updateState(const UIState &s);
};
diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc
index b5b731ef66..80cd82277e 100644
--- a/selfdrive/ui/qt/maps/map.cc
+++ b/selfdrive/ui/qt/maps/map.cc
@@ -6,9 +6,14 @@
#include
#include "common/swaglog.h"
-#include "selfdrive/ui/qt/maps/map_helpers.h"
#include "selfdrive/ui/qt/util.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
+#else
+#include "selfdrive/ui/qt/maps/map_helpers.h"
#include "selfdrive/ui/ui.h"
+#endif
const int INTERACTION_TIMEOUT = 100;
@@ -54,6 +59,7 @@ MapWindow::~MapWindow() {
}
void MapWindow::initLayers() {
+ RETURN_IF_SUNNYPILOT
// This doesn't work from initializeGL
if (!m_map->layerExists("modelPathLayer")) {
qDebug() << "Initializing modelPathLayer";
@@ -75,7 +81,7 @@ void MapWindow::initLayers() {
QVariantMap transition;
transition["duration"] = 400; // ms
- m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiState()->scene.navigate_on_openpilot_deprecated));
+ m_map->setPaintProperty("navLayer", "line-color", QColor("#31a1ee"));
m_map->setPaintProperty("navLayer", "line-color-transition", transition);
m_map->setPaintProperty("navLayer", "line-width", 7.5);
m_map->setLayoutProperty("navLayer", "line-cap", "round");
@@ -110,52 +116,10 @@ void MapWindow::initLayers() {
// TODO: remove, symbol-sort-key does not seem to matter outside of each layer
m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0);
}
- if ((!m_map->layerExists("buildingsLayer")) && uiState()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage
- qDebug() << "Initializing buildingsLayer";
- QVariantMap buildings;
- buildings["id"] = "buildingsLayer";
- buildings["source"] = "composite";
- buildings["source-layer"] = "building";
- buildings["type"] = "fill-extrusion";
- buildings["minzoom"] = 15;
- m_map->addLayer("buildingsLayer", buildings);
- m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"}));
-
- QVariantList fillExtrusionHeight = { // scale buildings as you zoom in
- "interpolate",
- QVariantList{"linear"},
- QVariantList{"zoom"},
- 15, 0,
- 15.05, QVariantList{"get", "height"}
- };
-
- QVariantList fillExtrusionBase = {
- "interpolate",
- QVariantList{"linear"},
- QVariantList{"zoom"},
- 15, 0,
- 15.05, QVariantList{"get", "min_height"}
- };
-
- QVariantList fillExtrusionOpacity = {
- "interpolate",
- QVariantList{"linear"},
- QVariantList{"zoom"},
- 15, 0, // transparent at zoom level 15
- 15.5, .6, // fade in
- 17, .6, // begin fading out
- 20, 0 // fade out when zoomed in
- };
-
- m_map->setPaintProperty("buildingsLayer", "fill-extrusion-color", QColor("grey"));
- m_map->setPaintProperty("buildingsLayer", "fill-extrusion-opacity", fillExtrusionOpacity);
- m_map->setPaintProperty("buildingsLayer", "fill-extrusion-height", fillExtrusionHeight);
- m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase);
- m_map->setLayoutProperty("buildingsLayer", "visibility", "visible");
- }
}
void MapWindow::updateState(const UIState &s) {
+ RETURN_IF_SUNNYPILOT
if (!uiState()->scene.started) {
return;
}
@@ -170,22 +134,6 @@ void MapWindow::updateState(const UIState &s) {
}
prev_time_valid = sm.valid("clocks");
- if (sm.updated("modelV2")) {
- // set path color on change, and show map on rising edge of navigate on openpilot
- auto car_control = sm["carControl"].getCarControl();
- bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() &&
- (sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive());
- if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) {
- if (loaded_once) {
- m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled));
- }
- if (nav_enabled) {
- emit requestVisible(true);
- }
- }
- uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled;
- }
-
if (sm.updated("liveLocationKalman")) {
auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman();
auto locationd_pos = locationd_location.getPositionGeodetic();
@@ -425,7 +373,6 @@ void MapWindow::pinchTriggered(QPinchGesture *gesture) {
void MapWindow::offroadTransition(bool offroad) {
if (offroad) {
clearRoute();
- uiState()->scene.navigate_on_openpilot_deprecated = false;
routing_problem = false;
} else {
auto dest = coordinate_from_param("NavDestination");
diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h
index 62871e79d2..00e8b6f78f 100644
--- a/selfdrive/ui/qt/maps/map.h
+++ b/selfdrive/ui/qt/maps/map.h
@@ -20,7 +20,11 @@
#include "cereal/messaging/messaging.h"
#include "common/params.h"
#include "common/util.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
#include "selfdrive/ui/qt/maps/map_eta.h"
#include "selfdrive/ui/qt/maps/map_instructions.h"
@@ -32,15 +36,17 @@ public:
~MapWindow();
private:
- void initializeGL() final;
void paintGL() final;
void resizeGL(int w, int h) override;
+protected:
+ void initializeGL() final;
QMapLibre::Settings m_settings;
QScopedPointer m_map;
void initLayers();
+protected:
void mousePressEvent(QMouseEvent *ev) final;
void mouseDoubleClickEvent(QMouseEvent *ev) final;
void mouseMoveEvent(QMouseEvent *ev) final;
@@ -50,9 +56,11 @@ private:
void pinchTriggered(QPinchGesture *gesture);
void setError(const QString &err_str);
+protected:
bool loaded_once = false;
bool prev_time_valid = true;
+protected:
// Panning
QPointF m_lastPos;
int interaction_counter = 0;
@@ -70,16 +78,11 @@ private:
MapInstructions* map_instructions;
MapETA* map_eta;
- // Blue with normal nav, green when nav is input into the model
- QColor getNavPathColor(bool nav_enabled) {
- return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee");
- }
-
void clearRoute();
void updateDestinationMarker();
uint64_t route_rcv_frame = 0;
-private slots:
+public slots:
void updateState(const UIState &s);
public slots:
diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc
index 0eb77e36ce..e153884576 100644
--- a/selfdrive/ui/qt/maps/map_eta.cc
+++ b/selfdrive/ui/qt/maps/map_eta.cc
@@ -3,8 +3,13 @@
#include
#include
-#include "selfdrive/ui/qt/maps/map_helpers.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
+#else
#include "selfdrive/ui/ui.h"
+#include "selfdrive/ui/qt/maps/map_helpers.h"
+#endif
const float MANEUVER_TRANSITION_THRESHOLD = 10;
diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc
index 50e1401164..6b7b05e786 100644
--- a/selfdrive/ui/qt/maps/map_helpers.cc
+++ b/selfdrive/ui/qt/maps/map_helpers.cc
@@ -1,4 +1,8 @@
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
+#else
#include "selfdrive/ui/qt/maps/map_helpers.h"
+#endif
#include
#include
diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h
index b9cb1f9469..0f4be674f0 100644
--- a/selfdrive/ui/qt/maps/map_helpers.h
+++ b/selfdrive/ui/qt/maps/map_helpers.h
@@ -12,9 +12,8 @@
#include "common/transformations/coordinates.hpp"
#include "common/transformations/orientation.hpp"
#include "cereal/messaging/messaging.h"
-#include "common/params.h"
-const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("CustomMapboxTokenSk")) != "" ? QString::fromStdString(Params().get("CustomMapboxTokenSk")) : util::getenv("MAPBOX_TOKEN").c_str();
+const QString MAPBOX_TOKEN = util::getenv("MAPBOX_TOKEN").c_str();
const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str();
const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db";
diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc
index ba8cb356bd..b631548fe7 100644
--- a/selfdrive/ui/qt/maps/map_instructions.cc
+++ b/selfdrive/ui/qt/maps/map_instructions.cc
@@ -3,8 +3,13 @@
#include
#include
-#include "selfdrive/ui/qt/maps/map_helpers.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
+#else
#include "selfdrive/ui/ui.h"
+#include "selfdrive/ui/qt/maps/map_helpers.h"
+#endif
const QString ICON_SUFFIX = ".png";
diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc
index c4cc20e21d..cd448483ff 100644
--- a/selfdrive/ui/qt/maps/map_panel.cc
+++ b/selfdrive/ui/qt/maps/map_panel.cc
@@ -3,10 +3,16 @@
#include
#include
-#include "selfdrive/ui/qt/maps/map.h"
#include "selfdrive/ui/qt/maps/map_settings.h"
#include "selfdrive/ui/qt/util.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/sunnypilot/qt/maps/map.h"
+#define MapWindow MapWindowSP
+#else
#include "selfdrive/ui/ui.h"
+#include "selfdrive/ui/qt/maps/map.h"
+#endif
MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) {
content_stack = new QStackedLayout(this);
diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h
index 0e151df4ad..40b0d35a6a 100644
--- a/selfdrive/ui/qt/maps/map_settings.h
+++ b/selfdrive/ui/qt/maps/map_settings.h
@@ -13,7 +13,11 @@
#include "common/params.h"
#include "selfdrive/ui/qt/util.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#else
#include "selfdrive/ui/qt/widgets/controls.h"
+#endif
const QString NAV_TYPE_FAVORITE = "favorite";
const QString NAV_TYPE_RECENT = "recent";
diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc
index 484536acf2..4f3d333ffd 100644
--- a/selfdrive/ui/qt/network/networking.cc
+++ b/selfdrive/ui/qt/network/networking.cc
@@ -6,11 +6,16 @@
#include
#include
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
+#include "selfdrive/ui/qt/home.h"
static const int ICON_WIDTH = 49;
@@ -26,29 +31,17 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
wifiScreen = new QWidget(this);
QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen);
vlayout->setContentsMargins(20, 20, 20, 20);
- QHBoxLayout* hlayout = new QHBoxLayout();
- QPushButton* scanButton = new QPushButton(tr("Scan"));
- scanButton->setObjectName("scan_btn");
- scanButton->setFixedSize(400, 100);
- connect(wifi, &WifiManager::refreshSignal, this, [=]() { scanButton->setText(tr("Scan")); scanButton->setEnabled(true); });
- connect(scanButton, &QPushButton::clicked, [=]() { scanButton->setText(tr("Scanning...")); scanButton->setEnabled(false); wifi->requestScan(); });
-
- hlayout->addWidget(scanButton);
- hlayout->addStretch(1); // Pushes the button all the way to the left
-
if (show_advanced) {
- hlayout->setSpacing(10);
-
QPushButton* advancedSettings = new QPushButton(tr("Advanced"));
advancedSettings->setObjectName("advanced_btn");
+ advancedSettings->setStyleSheet("margin-right: 30px;");
advancedSettings->setFixedSize(400, 100);
connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); });
- hlayout->addWidget(advancedSettings);
+ vlayout->addSpacing(10);
+ vlayout->addWidget(advancedSettings, 0, Qt::AlignRight);
+ vlayout->addSpacing(10);
}
- vlayout->addLayout(hlayout);
- vlayout->addSpacing(10);
-
wifiWidget = new WifiUI(this, wifi);
wifiWidget->setObjectName("wifiWidget");
connect(wifiWidget, &WifiUI::connectToNetwork, this, &Networking::connectToNetwork);
@@ -69,7 +62,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
setPalette(pal);
setStyleSheet(R"(
- #wifiWidget > QPushButton, #back_btn, #advanced_btn, #scan_btn{
+ #wifiWidget > QPushButton, #back_btn, #advanced_btn {
font-size: 50px;
margin: 0px;
padding: 15px;
@@ -78,7 +71,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
color: #dddddd;
background-color: #393939;
}
- #back_btn:pressed, #advanced_btn:pressed, #scan_btn:pressed {
+ #back_btn:pressed, #advanced_btn:pressed {
background-color: #4a4a4a;
}
)");
@@ -139,23 +132,10 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
ListWidget *list = new ListWidget(this);
// Enable tethering layout
- const bool set_hotspot_on_boot = params.getBool("HotspotOnBoot") && params.getBool("HotspotOnBootConfirmed");
- tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled() || set_hotspot_on_boot);
+ tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled());
list->addItem(tetheringToggle);
QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering);
- hotspotOnBootToggle = new ToggleControl(
- tr("Retain hotspot/tethering state"),
- tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."),
- "",
- params.getBool("HotspotOnBoot")
- );
- hotspotOnBootToggle->setEnabled(wifi->isTetheringEnabled() || set_hotspot_on_boot);
- QObject::connect(hotspotOnBootToggle, &ToggleControl::toggleFlipped, [=](bool state) {
- params.putBool("HotspotOnBoot", state);
- });
- list->addItem(hotspotOnBootToggle);
-
// Change tethering password
ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT"));
connect(editPasswordButton, &ButtonControl::clicked, [=]() {
@@ -226,19 +206,6 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
});
list->addItem(hiddenNetworkButton);
- // Ngrok
- QProcess process;
- process.start("sudo service ngrok status | grep running");
- process.waitForFinished();
- QString output = QString(process.readAllStandardOutput());
- bool ngrokRunning = !output.isEmpty();
- ToggleControl *ngrokToggle = new ToggleControl(tr("Ngrok Service"), "", "", ngrokRunning);
- connect(ngrokToggle, &ToggleControl::toggleFlipped, [=](bool state) {
- if (state) std::system("sudo ngrok service start");
- else std::system("sudo ngrok service stop");
- });
- list->addItem(ngrokToggle);
-
// Set initial config
wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered);
@@ -260,14 +227,8 @@ void AdvancedNetworking::refresh() {
}
void AdvancedNetworking::toggleTethering(bool enabled) {
- params.putBool("HotspotOnBootConfirmed", enabled);
wifi->setTetheringEnabled(enabled);
tetheringToggle->setEnabled(false);
-
- hotspotOnBootToggle->setEnabled(enabled);
- if (!enabled) {
- params.remove("HotspotOnBoot");
- }
}
// WifiUI functions
@@ -419,4 +380,4 @@ void WifiItem::setItem(const Network &n, const QPixmap &status_icon, bool show_f
iconLabel->setPixmap(status_icon);
strengthLabel->setPixmap(strength_icon);
-}
+}
\ No newline at end of file
diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h
index 7444e9c28d..5831d66dc9 100644
--- a/selfdrive/ui/qt/network/networking.h
+++ b/selfdrive/ui/qt/network/networking.h
@@ -6,6 +6,13 @@
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
#include "selfdrive/ui/qt/widgets/toggle.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#define LabelControl LabelControlSP
+#define ElidedLabel ElidedLabelSP
+#else
+#include "selfdrive/ui/qt/widgets/controls.h"
+#endif
class WifiItem : public QWidget {
Q_OBJECT
@@ -67,8 +74,6 @@ private:
WifiManager* wifi = nullptr;
Params params;
- ToggleControl* hotspotOnBootToggle;
-
signals:
void backPress();
void requestWifiScreen();
@@ -100,4 +105,4 @@ public slots:
private slots:
void connectToNetwork(const Network n);
void wrongPassword(const QString &ssid);
-};
+};
\ No newline at end of file
diff --git a/selfdrive/ui/qt/network/sunnylink/models/user_model.h b/selfdrive/ui/qt/network/sunnylink/models/user_model.h
deleted file mode 100644
index 72baa3983d..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/models/user_model.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef USER_MODEL_H
-#define USER_MODEL_H
-
-#include
-
-class UserModel {
-public:
- QString device_id;
- QString user_id;
- qint64 created_at;
- qint64 updated_at;
- QString token_hash;
-
- explicit UserModel(const QJsonObject &json) {
- device_id = json["device_id"].toString();
- user_id = json["user_id"].toString();
- created_at = json["created_at"].toInt();
- updated_at = json["updated_at"].toInt();
- token_hash = json["token_hash"].toString();
- }
-
- [[nodiscard]] QJsonObject toJson() const {
- QJsonObject json;
- json["device_id"] = device_id;
- json["user_id"] = user_id;
- json["created_at"] = created_at;
- json["updated_at"] = updated_at;
- json["token_hash"] = token_hash;
- return json;
- }
-};
-
-#endif
diff --git a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc b/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc
deleted file mode 100644
index 2f2561a49a..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-#include "base_device_service.h"
-
-#include "common/swaglog.h"
-#include "selfdrive/ui/qt/util.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h"
-
-BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent), initial_request(nullptr), repeater(nullptr) {
- param_watcher = new ParamWatcher(this);
- connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
- paramsRefresh();
- });
- param_watcher->addParam("SunnylinkEnabled");
-}
-
-void BaseDeviceService::paramsRefresh() {
-}
-
-void BaseDeviceService::loadDeviceData(const QString &url, bool poll) {
- if (!is_sunnylink_enabled()) {
- LOGW("Sunnylink is not enabled, refusing to load data.");
- return;
- }
-
- auto sl_dongle_id = getSunnylinkDongleId();
- if (!sl_dongle_id.has_value())
- return;
-
- QString fullUrl = SUNNYLINK_BASE_URL + "/device/" + *sl_dongle_id + url;
- if (poll && !isCurrentyPolling()) {
- LOGD("Polling %s", qPrintable(fullUrl));
- LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey())));
- repeater = new RequestRepeater(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true);
- connect(repeater, &RequestRepeater::requestDone, this, &BaseDeviceService::handleResponse);
- } else if(isCurrentyPolling()){
- repeater->ForceUpdate();
- } else {
- LOGD("Sending one-time %s", qPrintable(fullUrl));
- initial_request = new HttpRequest(this, true, 10000, true);
- connect(initial_request, &HttpRequest::requestDone, this, &BaseDeviceService::handleResponse);
- }
-}
-
-void BaseDeviceService::stopPolling() {
- if (repeater != nullptr) {
- repeater->deleteLater();
- repeater = nullptr;
- }
-}
diff --git a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.h b/selfdrive/ui/qt/network/sunnylink/services/base_device_service.h
deleted file mode 100644
index cd7aa9d9b9..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef BASESERVICE_H
-#define BASESERVICE_H
-
-
-#include "selfdrive/ui/qt/api.h"
-#include "selfdrive/ui/qt/request_repeater.h"
-#include "selfdrive/ui/qt/util.h"
-
-class BaseDeviceService : public QObject {
- Q_OBJECT
-
-protected:
- void paramsRefresh();
- void loadDeviceData(const QString &url, bool poll = false);
- virtual void handleResponse(const QString &response, bool success) = 0;
-
- static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");};
- ParamWatcher* param_watcher;
- HttpRequest* initial_request = nullptr;
- RequestRepeater* repeater = nullptr;
-
-public:
- explicit BaseDeviceService(QObject* parent = nullptr);
- virtual QString getCacheKey() const = 0;
- bool isCurrentyPolling() {return repeater != nullptr;}
- void stopPolling();
-};
-
-#endif
diff --git a/selfdrive/ui/qt/network/sunnylink/services/role_service.cc b/selfdrive/ui/qt/network/sunnylink/services/role_service.cc
deleted file mode 100644
index 8bd0904421..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/services/role_service.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "selfdrive/ui/qt/network/sunnylink/services/role_service.h"
-
-#include
-#include
-
-RoleService::RoleService(QObject* parent) : BaseDeviceService(parent) {}
-
-void RoleService::load() {
- loadDeviceData(url);
-}
-
-void RoleService::startPolling() {
- loadDeviceData(url, true);
-}
-
-void RoleService::handleResponse(const QString &response, bool success) {
- if (!success) return;
-
- QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
- QJsonArray jsonArray = doc.array();
-
- std::vector roles;
- for (const auto &value : jsonArray) {
- roles.emplace_back(value.toObject());
- }
-
- emit rolesReady(roles);
- uiState()->setSunnylinkRoles(roles);
-}
diff --git a/selfdrive/ui/qt/network/sunnylink/services/role_service.h b/selfdrive/ui/qt/network/sunnylink/services/role_service.h
deleted file mode 100644
index bc40342982..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/services/role_service.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef ROLESERVICE_H
-#define ROLESERVICE_H
-
-#include "selfdrive/ui/qt/network/sunnylink/services/base_device_service.h"
-#include "selfdrive/ui/qt/network/sunnylink/models/role_model.h"
-
-class RoleService : public BaseDeviceService {
- Q_OBJECT
-
-public:
- explicit RoleService(QObject* parent = nullptr);
- void load();
- void startPolling();
- [[nodiscard]] QString getCacheKey() const final { return "Roles"; };
-
-signals:
- void rolesReady(const std::vector &roles);
-
-protected:
- void handleResponse(const QString&response, bool success) override;
-
-private:
- QString url = "/roles";
-};
-
-#endif
diff --git a/selfdrive/ui/qt/network/sunnylink/services/user_service.cc b/selfdrive/ui/qt/network/sunnylink/services/user_service.cc
deleted file mode 100644
index 2d52421d69..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/services/user_service.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h"
-
-#include
-#include
-
-UserService::UserService(QObject* parent) : BaseDeviceService(parent) {
- url = "/users";
-}
-
-void UserService::load() {
- loadDeviceData(url);
-}
-
-void UserService::startPolling() {
- loadDeviceData(url, true);
-}
-
-void UserService::handleResponse(const QString &response, bool success) {
- if (!success) return;
-
- QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
- QJsonArray jsonArray = doc.array();
-
- std::vector users;
- for (const auto &value : jsonArray) {
- users.emplace_back(value.toObject());
- }
-
- emit usersReady(users);
- uiState()->setSunnylinkDeviceUsers(users);
-}
diff --git a/selfdrive/ui/qt/network/sunnylink/services/user_service.h b/selfdrive/ui/qt/network/sunnylink/services/user_service.h
deleted file mode 100644
index e4af93fa29..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/services/user_service.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef USERSERVICE_H
-#define USERSERVICE_H
-
-#include "selfdrive/ui/qt/network/sunnylink/services/base_device_service.h"
-#include "selfdrive/ui/qt/network/sunnylink/models/user_model.h"
-
-class UserService : public BaseDeviceService {
- Q_OBJECT
-
-public:
- explicit UserService(QObject* parent = nullptr);
- void load();
- void startPolling();
- [[nodiscard]] QString getCacheKey() const final { return "Users"; };
-
-signals:
- void usersReady(const std::vector&users);
-
-protected:
- void handleResponse(const QString&response, bool success) override;
-
-private:
- QString url = "/users";
-};
-
-#endif
diff --git a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc b/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc
deleted file mode 100644
index 2d1613c0eb..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc
+++ /dev/null
@@ -1,7 +0,0 @@
-#include "selfdrive/ui/qt/network/sunnylink/sunnylink_client.h"
-#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h"
-
-SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) {
- role_service = new RoleService(parent);
- user_service = new UserService(parent);
-}
diff --git a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h b/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h
deleted file mode 100644
index 872d80ccf7..0000000000
--- a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef SUNNYLINK_CLIENT_H
-#define SUNNYLINK_CLIENT_H
-
-#include
-
-#include "selfdrive/ui/qt/network/sunnylink/services/role_service.h"
-#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h"
-
-class SunnylinkClient : public QObject {
- Q_OBJECT
-
-public:
- explicit SunnylinkClient(QObject* parent);
- RoleService* role_service;
- UserService* user_service;
-};
-
-#endif
diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc
index 717da47096..b12207fab5 100644
--- a/selfdrive/ui/qt/network/wifi_manager.cc
+++ b/selfdrive/ui/qt/network/wifi_manager.cc
@@ -2,7 +2,11 @@
#include
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
#include "selfdrive/ui/qt/widgets/prime.h"
#include "common/params.h"
diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc
index b2c6f3323a..88fadd439b 100644
--- a/selfdrive/ui/qt/offroad/experimental_mode.cc
+++ b/selfdrive/ui/qt/offroad/experimental_mode.cc
@@ -7,13 +7,18 @@
#include
#include "selfdrive/ui/ui.h"
+#ifdef SUNNYPILOT
+#define TOGGLES_PANEL_INDEX 3
+#else
+#define TOGGLES_PANEL_INDEX 2
+#endif
ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) {
chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
// go to toggles and expand experimental mode description
- connect(this, &QPushButton::clicked, [=]() { emit openSettings(3, "ExperimentalMode"); });
+ connect(this, &QPushButton::clicked, [=]() { emit openSettings(TOGGLES_PANEL_INDEX, "ExperimentalMode"); });
setFixedHeight(125);
QHBoxLayout *main_layout = new QHBoxLayout;
diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc
index 014e9a6f05..8bf92aa06b 100644
--- a/selfdrive/ui/qt/offroad/onboarding.cc
+++ b/selfdrive/ui/qt/offroad/onboarding.cc
@@ -106,8 +106,7 @@ void TermsPage::showEvent(QShowEvent *event) {
text->setAttribute(Qt::WA_AlwaysStackOnTop);
text->setClearColor(QColor("#1B1B1B"));
- std::string tc_text = sunnypilot_tc ? "../assets/offroad/sp_tc.html" : "../assets/offroad/tc.html";
- QString text_view = util::read_file(tc_text).c_str();
+ QString text_view = util::read_file("../assets/offroad/tc.html").c_str();
text->rootContext()->setContextProperty("text_view", text_view);
text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml"));
@@ -186,8 +185,6 @@ void OnboardingWindow::updateActiveScreen() {
setCurrentIndex(0);
} else if (!training_done) {
setCurrentIndex(1);
- } else if (!accepted_terms_sp) {
- setCurrentIndex(3);
} else {
emit onboardingDone();
}
@@ -195,13 +192,11 @@ void OnboardingWindow::updateActiveScreen() {
OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
std::string current_terms_version = params.get("TermsVersion");
- std::string current_terms_version_sp = params.get("TermsVersionSunnypilot");
std::string current_training_version = params.get("TrainingVersion");
accepted_terms = params.get("HasAcceptedTerms") == current_terms_version;
- accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp;
training_done = params.get("CompletedTrainingVersion") == current_training_version;
- TermsPage* terms = new TermsPage(false, this);
+ TermsPage* terms = new TermsPage(this);
addWidget(terms);
connect(terms, &TermsPage::acceptedTerms, [=]() {
params.put("HasAcceptedTerms", current_terms_version);
@@ -222,15 +217,6 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
addWidget(declinePage);
connect(declinePage, &DeclinePage::getBack, [=]() { updateActiveScreen(); });
- TermsPage* terms_sp = new TermsPage(true, this);
- addWidget(terms_sp); // index = 3
- connect(terms_sp, &TermsPage::acceptedTerms, [=]() {
- params.put("HasAcceptedTermsSP", current_terms_version_sp);
- accepted_terms_sp = true;
- updateActiveScreen();
- });
- connect(terms_sp, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); });
-
setStyleSheet(R"(
* {
color: white;
@@ -245,4 +231,4 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
}
)");
updateActiveScreen();
-}
+}
\ No newline at end of file
diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h
index 008d86032c..098b0823a7 100644
--- a/selfdrive/ui/qt/offroad/onboarding.h
+++ b/selfdrive/ui/qt/offroad/onboarding.h
@@ -63,16 +63,17 @@ class TermsPage : public QFrame {
Q_OBJECT
public:
- explicit TermsPage(bool sunnypilot = false, QWidget *parent = 0) : QFrame(parent), sunnypilot_tc(sunnypilot) {}
+ explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {}
public slots:
void enableAccept();
+protected:
+ QPushButton *accept_btn;
+
private:
void showEvent(QShowEvent *event) override;
- QPushButton *accept_btn;
- bool sunnypilot_tc = false;
signals:
void acceptedTerms();
@@ -98,14 +99,14 @@ class OnboardingWindow : public QStackedWidget {
public:
explicit OnboardingWindow(QWidget *parent = 0);
inline void showTrainingGuide() { setCurrentIndex(1); }
- inline bool completed() const { return accepted_terms && accepted_terms_sp && training_done; }
+ virtual inline bool completed() const { return accepted_terms && training_done; }
-private:
- void updateActiveScreen();
+protected:
+ virtual void updateActiveScreen();
Params params;
- bool accepted_terms = false, accepted_terms_sp = false, training_done = false;
+ bool accepted_terms = false, training_done = false;
signals:
void onboardingDone();
-};
+};
\ No newline at end of file
diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc
index f9f852a3b4..040b074784 100644
--- a/selfdrive/ui/qt/offroad/settings.cc
+++ b/selfdrive/ui/qt/offroad/settings.cc
@@ -5,28 +5,29 @@
#include
#include
-#include
-#include
-#include
#include "common/watchdog.h"
#include "common/util.h"
#include "selfdrive/ui/qt/network/networking.h"
#include "selfdrive/ui/qt/offroad/settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot_main.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/widgets/prime.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/sunnypilot_main.h"
+#endif
TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
+ RETURN_IF_SUNNYPILOT
+
// param, title, desc, icon
std::vector> toggle_defs{
{
"OpenpilotEnabledToggle",
tr("Enable sunnypilot"),
tr("Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."),
- "../assets/offroad/icon_blank.png",
+ "../assets/offroad/icon_openpilot.png",
},
{
"ExperimentalLongitudinalEnabled",
@@ -35,105 +36,54 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
.arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
.arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. "
"Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")),
- "../assets/offroad/icon_blank.png",
- },
- {
- "CustomStockLong",
- tr("Custom Stock Longitudinal Control"),
- tr("When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses.\nThis feature must be used along with SLC, and/or V-TSC, and/or M-TSC."),
- "../assets/offroad/icon_blank.png",
+ "../assets/offroad/icon_speed_limit.png",
},
{
"ExperimentalMode",
tr("Experimental Mode"),
"",
- "../assets/offroad/icon_blank.png",
- },
- {
- "DynamicExperimentalControl",
- tr("Enable Dynamic Experimental Control"),
- tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."),
- "../assets/offroad/icon_blank.png",
- },
- {
- "DynamicPersonality",
- tr("Enable Dynamic Personality"),
- tr("Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your \"Driving Personality\" setting. "
- "Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car."),
- "../assets/offroad/icon_blank.png",
+ "../assets/img_experimental_white.svg",
},
{
"DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"),
tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
- "../assets/offroad/icon_blank.png",
+ "../assets/offroad/icon_disengage_on_accelerator.svg",
},
{
"IsLdwEnabled",
tr("Enable Lane Departure Warnings"),
tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."),
- "../assets/offroad/icon_blank.png",
+ "../assets/offroad/icon_warning.png",
},
{
"AlwaysOnDM",
tr("Always-On Driver Monitoring"),
tr("Enable driver monitoring even when openpilot is not engaged."),
- "../assets/offroad/icon_blank.png",
+ "../assets/offroad/icon_monitoring.png",
},
{
"RecordFront",
tr("Record and Upload Driver Camera"),
tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
- "../assets/offroad/icon_blank.png",
- },
- {
- "DisableOnroadUploads",
- tr("Disable Onroad Uploads"),
- tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."),
- "../assets/offroad/icon_blank.png",
+ "../assets/offroad/icon_monitoring.png",
},
{
"IsMetric",
tr("Use Metric System"),
tr("Display speed in km/h instead of mph."),
- "../assets/offroad/icon_blank.png",
+ "../assets/offroad/icon_metric.png",
},
-#ifdef ENABLE_MAPS
- {
- "NavSettingTime24h",
- tr("Show ETA in 24h Format"),
- tr("Use 24h format instead of am/pm"),
- "../assets/offroad/icon_blank.png",
- },
- {
- "NavSettingLeftSide",
- tr("Show Map on Left Side of UI"),
- tr("Show map on left side when in split screen view."),
- "../assets/offroad/icon_blank.png",
- },
-#endif
};
- std::vector longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")};
+ std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")};
long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"),
- tr("Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. "
+ tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. "
"In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with "
"your steering wheel distance button."),
- "../assets/offroad/icon_blank.png",
- longi_button_texts,
- 380);
- long_personality_setting->showDescription();
-
- // accel controller
- std::vector accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco"), tr("Stock")};
- accel_personality_setting = new ButtonParamControl("AccelPersonality", tr("Acceleration Personality"),
- tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. "
- "In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these "
- "acceleration personality within Onroad Settings on the driving screen."),
- "../assets/offroad/icon_blank.png",
- accel_personality_texts);
- accel_personality_setting->showDescription();
+ "../assets/offroad/icon_speed_limit.png",
+ longi_button_texts);
// set up uiState update for personality setting
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
@@ -150,28 +100,17 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// insert longitudinal personality after NDOG toggle
if (param == "DisengageOnAccelerator") {
addItem(long_personality_setting);
- addItem(accel_personality_setting);
}
}
// Toggles with confirmation dialogs
- //toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg");
+ toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg");
toggles["ExperimentalMode"]->setConfirmation(true, true);
toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false);
- toggles["CustomStockLong"]->setConfirmation(true, false);
connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() {
updateToggles();
});
- connect(toggles["CustomStockLong"], &ToggleControl::toggleFlipped, [=]() {
- updateToggles();
- });
-
- param_watcher = new ParamWatcher(this);
-
- QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
- updateToggles();
- });
}
void TogglesPanel::updateState(const UIState &s) {
@@ -184,14 +123,6 @@ void TogglesPanel::updateState(const UIState &s) {
}
uiState()->scene.personality = personality;
}
-
- if (sm.updated("controlsStateSP")) {
- auto accel_personality = sm["controlsStateSP"].getControlsStateSP().getAccelPersonality();
- if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) {
- accel_personality_setting->setCheckedButton(static_cast(accel_personality));
- }
- uiState()->scene.accel_personality = accel_personality;
- }
}
void TogglesPanel::expandToggleDescription(const QString ¶m) {
@@ -203,15 +134,8 @@ void TogglesPanel::showEvent(QShowEvent *event) {
}
void TogglesPanel::updateToggles() {
- param_watcher->addParam("LongitudinalPersonality");
-
- if (!isVisible()) return;
-
auto experimental_mode_toggle = toggles["ExperimentalMode"];
auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"];
- auto custom_stock_long_toggle = toggles["CustomStockLong"];
- auto dec_toggle = toggles["DynamicExperimentalControl"];
- auto dynamic_personality_toggle = toggles["DynamicPersonality"];
const QString e2e_description = QString("%1
"
"%2
"
"%3
"
@@ -236,35 +160,15 @@ void TogglesPanel::updateToggles() {
params.remove("ExperimentalLongitudinalEnabled");
}
op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release);
-
- if (!CP.getCustomStockLongAvailable()) {
- params.remove("CustomStockLong");
- }
- custom_stock_long_toggle->setVisible(CP.getCustomStockLongAvailable());
-
if (hasLongitudinalControl(CP)) {
// normal description and toggle
experimental_mode_toggle->setEnabled(true);
experimental_mode_toggle->setDescription(e2e_description);
long_personality_setting->setEnabled(true);
- accel_personality_setting->setEnabled(true);
- op_long_toggle->setEnabled(true);
- custom_stock_long_toggle->setEnabled(false);
- params.remove("CustomStockLong");
- dec_toggle->setEnabled(true);
- dynamic_personality_toggle->setEnabled(true);
- } else if (custom_stock_long_toggle->isToggled()) {
- op_long_toggle->setEnabled(false);
- experimental_mode_toggle->setEnabled(false);
- long_personality_setting->setEnabled(false);
- accel_personality_setting->setEnabled(false);
- params.remove("ExperimentalLongitudinalEnabled");
- params.remove("ExperimentalMode");
} else {
// no long for now
experimental_mode_toggle->setEnabled(false);
long_personality_setting->setEnabled(false);
- accel_personality_setting->setEnabled(false);
params.remove("ExperimentalMode");
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
@@ -279,26 +183,12 @@ void TogglesPanel::updateToggles() {
}
}
experimental_mode_toggle->setDescription("" + long_desc + "
" + e2e_description);
-
- op_long_toggle->setEnabled(CP.getExperimentalLongitudinalAvailable() && !is_release);
- custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable());
- dec_toggle->setEnabled(false);
- dynamic_personality_toggle->setEnabled(false);
- params.remove("DynamicExperimentalControl");
- params.remove("DynamicPersonality");
}
experimental_mode_toggle->refresh();
- op_long_toggle->refresh();
- custom_stock_long_toggle->refresh();
- dec_toggle->refresh();
- dynamic_personality_toggle->refresh();
} else {
experimental_mode_toggle->setDescription(e2e_description);
op_long_toggle->setVisible(false);
- custom_stock_long_toggle->setVisible(false);
- dec_toggle->setVisible(false);
- dynamic_personality_toggle->setVisible(false);
}
}
@@ -307,44 +197,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A"))));
addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str()));
- fleetManagerPin = new ButtonControl(
- pin_title + pin, tr("TOGGLE"),
- tr("Enable or disable PIN requirement for Fleet Manager access."));
- connect(fleetManagerPin, &ButtonControl::clicked, [=]() {
- if (params.getBool("FleetManagerPin")) {
- if (ConfirmationDialog::confirm(tr("Are you sure you want to turn off PIN requirement?"), tr("Turn Off"), this)) {
- params.remove("FleetManagerPin");
- refreshPin();
- }
- } else {
- params.putBool("FleetManagerPin", true);
- refreshPin();
- }
- });
- addItem(fleetManagerPin);
-
- fs_watch = new QFileSystemWatcher(this);
- connect(fs_watch, &QFileSystemWatcher::fileChanged, this, &DevicePanel::onPinFileChanged);
-
- QString pin_path = "/data/otp/otp.conf";
- QString pin_require = "/data/params/d/FleetManagerPin";
- fs_watch->addPath(pin_path);
- fs_watch->addPath(pin_require);
- refreshPin();
-
- // Error Troubleshoot
- auto errorBtn = new ButtonControl(
- tr("Error Troubleshoot"), tr("VIEW"),
- tr("Display error from the tmux session when an error has occurred from a system process."));
- QFileInfo file("/data/community/crashes/error.txt");
- QDateTime modifiedTime = file.lastModified();
- QString modified_time = modifiedTime.toString("yyyy-MM-dd hh:mm:ss ");
- connect(errorBtn, &ButtonControl::clicked, [=]() {
- const std::string txt = util::read_file("/data/community/crashes/error.txt");
- ConfirmationDialog::rich(modified_time + QString::fromStdString(txt), this);
- });
- addItem(errorBtn);
-
pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"),
tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
connect(pair_device, &ButtonControl::clicked, [=]() {
@@ -370,33 +222,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
});
addItem(resetCalibBtn);
- auto resetMapboxTokenBtn = new ButtonControl(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps."));
- connect(resetMapboxTokenBtn, &ButtonControl::clicked, [=]() {
- if (ConfirmationDialog::confirm(tr("Are you sure you want to reset access tokens for all map services?"), tr("Reset"), this)) {
- std::vector tokens = {
- "CustomMapboxTokenPk",
- "CustomMapboxTokenSk",
- "AmapKey1",
- "AmapKey2",
- "GmapKey"
- };
- for (const auto& token : tokens) {
- params.remove(token);
- }
- }
- });
- addItem(resetMapboxTokenBtn);
-
- auto resetParamsBtn = new ButtonControl(tr("Reset sunnypilot Settings"), tr("RESET"), "");
- connect(resetParamsBtn, &ButtonControl::clicked, [=]() {
- if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all sunnypilot settings?"), tr("Reset"), this)) {
- std::system("sudo rm -rf /data/params/d/*");
- Hardware::reboot();
- }
- });
- addItem(resetParamsBtn);
-
- auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of sunnypilot"));
+ auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot"));
connect(retrainingBtn, &ButtonControl::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) {
emit reviewTrainingGuide();
@@ -429,16 +255,19 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
QObject::connect(uiState(), &UIState::primeTypeChanged, [this] (PrimeType type) {
pair_device->setVisible(type == PrimeType::UNPAIRED);
});
+
+#ifndef SUNNYPILOT
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
for (auto btn : findChildren()) {
- if ((btn != pair_device) && (btn != errorBtn)) {
+ if (btn != pair_device) {
btn->setEnabled(offroad);
}
}
});
+#endif
// power buttons
- QHBoxLayout *power_layout = new QHBoxLayout();
+ power_layout = new QHBoxLayout();
power_layout->setSpacing(30);
QPushButton *reboot_btn = new QPushButton(tr("Reboot"));
@@ -455,46 +284,14 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible);
}
- offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad"));
- offroad_btn->setObjectName("offroad_btn");
- QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanel::forceoffroad);
-
- QVBoxLayout *buttons_layout = new QVBoxLayout();
- buttons_layout->setSpacing(24);
- buttons_layout->addLayout(power_layout);
- buttons_layout->addWidget(offroad_btn);
-
setStyleSheet(R"(
#reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; }
#reboot_btn:pressed { background-color: #4a4a4a; }
#poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; }
#poweroff_btn:pressed { background-color: #FF2424; }
)");
- addItem(buttons_layout);
-
- updateLabels();
-}
-
-void DevicePanel::onPinFileChanged(const QString &file_path) {
- if (file_path == "/data/params/d/FleetManagerPin") {
- refreshPin();
- } else if (file_path == "/data/otp/otp.conf") {
- refreshPin();
- }
-}
-
-void DevicePanel::refreshPin() {
- QFile f("/data/otp/otp.conf");
- QFile require("/data/params/d/FleetManagerPin");
- if (!require.exists()) {
- setSpacing(50);
- fleetManagerPin->setTitle(pin_title + tr("OFF"));
- } else if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
- pin = f.readAll();
- f.close();
- setSpacing(50);
- fleetManagerPin->setTitle(pin_title + pin);
- }
+ RETURN_IF_SUNNYPILOT
+ addItem(power_layout);
}
void DevicePanel::updateCalibDescription() {
@@ -547,51 +344,9 @@ void DevicePanel::poweroff() {
}
}
-void DevicePanel::forceoffroad() {
- if (!uiState()->engaged()) {
- if (params.getBool("ForceOffroad")) {
- if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) {
- // Check engaged again in case it changed while the dialog was open
- if (!uiState()->engaged()) {
- params.remove("ForceOffroad");
- }
- }
- } else {
- if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) {
- // Check engaged again in case it changed while the dialog was open
- if (!uiState()->engaged()) {
- params.putBool("ForceOffroad", true);
- }
- }
- }
- } else {
- ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this);
- }
-
- updateLabels();
-}
-
void DevicePanel::showEvent(QShowEvent *event) {
pair_device->setVisible(uiState()->primeType() == PrimeType::UNPAIRED);
ListWidget::showEvent(event);
- updateLabels();
-}
-
-void DevicePanel::updateLabels() {
- if (!isVisible()) {
- return;
- }
-
- bool force_offroad_param = params.getBool("ForceOffroad");
- QString offroad_btn_style = force_offroad_param ? "#393939" : "#E22C2C";
- QString offroad_btn_pressed_style = force_offroad_param ? "#4a4a4a" : "#FF2424";
- QString btn_common_style = QString("QPushButton { height: 120px; border-radius: 15px; background-color: %1; }"
- "QPushButton:pressed { background-color: %2; }")
- .arg(offroad_btn_style,
- offroad_btn_pressed_style);
-
- offroad_btn->setText(force_offroad_param ? tr("Unforce Offroad") : tr("Force Offroad"));
- offroad_btn->setStyleSheet(btn_common_style + offroad_btn_style + offroad_btn_pressed_style);
}
void SettingsWindow::showEvent(QShowEvent *event) {
@@ -607,23 +362,20 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) {
}
SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
+ RETURN_IF_SUNNYPILOT
// setup two main layouts
sidebar_widget = new QWidget;
QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget);
panel_widget = new QStackedWidget();
- // setup layout for close button
- QVBoxLayout *close_btn_layout = new QVBoxLayout;
- close_btn_layout->setContentsMargins(0, 0, 0, 20);
-
// close button
QPushButton *close_btn = new QPushButton(tr("×"));
close_btn->setStyleSheet(R"(
QPushButton {
font-size: 140px;
padding-bottom: 20px;
- border-radius: 76px;
+ border-radius: 100px;
background-color: #292929;
font-weight: 400;
}
@@ -631,16 +383,11 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
background-color: #3B3B3B;
}
)");
- close_btn->setFixedSize(152, 152);
- close_btn_layout->addWidget(close_btn, 0, Qt::AlignLeft);
+ close_btn->setFixedSize(200, 200);
+ sidebar_layout->addSpacing(45);
+ sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter);
QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings);
- // setup buttons widget
- QWidget *buttons_widget = new QWidget;
- QVBoxLayout *buttons_layout = new QVBoxLayout(buttons_widget);
- buttons_layout->setMargin(0);
- buttons_layout->addSpacing(10);
-
// setup panels
DevicePanel *device = new DevicePanel(this);
QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide);
@@ -649,43 +396,27 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
TogglesPanel *toggles = new TogglesPanel(this);
QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription);
- QList panels = {
- PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"),
- PanelInfo(" " + tr("Network"), new Networking(this), "../assets/offroad/icon_network.png"),
- PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"),
- PanelInfo(" " + tr("Toggles"), toggles, "../assets/offroad/icon_toggle.png"),
- PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../assets/offroad/icon_software.png"),
- PanelInfo(" " + tr("sunnypilot"), new SunnypilotPanel(this), "../assets/offroad/icon_openpilot.png"),
- PanelInfo(" " + tr("OSM"), new OsmPanel(this), "../assets/offroad/icon_map.png"),
- PanelInfo(" " + tr("Monitoring"), new MonitoringPanel(this), "../assets/offroad/icon_monitoring.png"),
- PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../assets/offroad/icon_visuals.png"),
- PanelInfo(" " + tr("Display"), new DisplayPanel(this), "../assets/offroad/icon_display.png"),
- PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../assets/offroad/icon_trips.png"),
- PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../assets/offroad/icon_vehicle.png"),
+ QList> panels = {
+ {tr("Device"), device},
+ {tr("Network"), new Networking(this)},
+ {tr("Toggles"), toggles},
+ {tr("Software"), new SoftwarePanel(this)},
};
nav_btns = new QButtonGroup(this);
- for (auto &[name, panel, icon] : panels) {
+ for (auto &[name, panel] : panels) {
QPushButton *btn = new QPushButton(name);
btn->setCheckable(true);
btn->setChecked(nav_btns->buttons().size() == 0);
- btn->setIcon(QIcon(QPixmap(icon)));
- btn->setIconSize(QSize(70, 70));
btn->setStyleSheet(R"(
QPushButton {
- border-radius: 20px;
- width: 400px;
- height: 98px;
- color: #bdbdbd;
+ color: grey;
border: none;
background: none;
- font-size: 50px;
+ font-size: 65px;
font-weight: 500;
- text-align: left;
- padding-left: 22px;
}
QPushButton:checked {
- background-color: #696868;
color: white;
}
QPushButton:pressed {
@@ -694,9 +425,9 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
)");
btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
nav_btns->addButton(btn);
- buttons_layout->addWidget(btn, 0, Qt::AlignLeft | Qt::AlignBottom);
+ sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
- const int lr_margin = (name != (" " + tr("Network")) || (name != (" " + tr("sunnypilot")))) ? 50 : 0; // Network and sunnypilot panel handles its own margins
+ const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins
panel->setContentsMargins(lr_margin, 25, lr_margin, 25);
ScrollView *panel_frame = new ScrollView(panel, this);
@@ -707,18 +438,11 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
panel_widget->setCurrentWidget(w);
});
}
- sidebar_layout->setContentsMargins(50, 50, 25, 50);
+ sidebar_layout->setContentsMargins(50, 50, 100, 50);
// main settings layout, sidebar + main panel
QHBoxLayout *main_layout = new QHBoxLayout(this);
- // add layout for close button
- sidebar_layout->addLayout(close_btn_layout);
-
- // add layout for buttons scrolling
- ScrollView *buttons_scrollview = new ScrollView(buttons_widget, this);
- sidebar_layout->addWidget(buttons_scrollview);
-
sidebar_widget->setFixedWidth(500);
main_layout->addWidget(sidebar_widget);
main_layout->addWidget(panel_widget);
@@ -732,7 +456,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
background-color: black;
}
QStackedWidget, ScrollView {
- background-color: black;
+ background-color: #292929;
border-radius: 30px;
}
)");
diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h
index 06fbe8bfb9..dcc07aa2f2 100644
--- a/selfdrive/ui/qt/offroad/settings.h
+++ b/selfdrive/ui/qt/offroad/settings.h
@@ -8,12 +8,23 @@
#include
#include
#include
-#include
#include
-#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
+
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#define ListWidget ListWidgetSP
+#define ParamControl ParamControlSP
+#define ButtonControl ButtonControlSP
+#define ButtonParamControl ButtonParamControlSP
+#define ToggleControl ToggleControlSP
+#define LabelControl LabelControlSP
+#else
+#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/widgets/controls.h"
+#endif
// ********** settings window + top-level panels **********
class SettingsWindow : public QFrame {
@@ -32,19 +43,11 @@ signals:
void showDriverView();
void expandToggleDescription(const QString ¶m);
-private:
+protected:
QPushButton *sidebar_alert_widget;
QWidget *sidebar_widget;
QButtonGroup *nav_btns;
QStackedWidget *panel_widget;
-
- struct PanelInfo {
- QString name;
- QWidget *widget;
- QString icon;
-
- PanelInfo(const QString &name, QWidget *widget, const QString &icon) : name(name), widget(widget), icon(icon) {}
- };
};
class DevicePanel : public ListWidget {
@@ -57,26 +60,15 @@ signals:
void reviewTrainingGuide();
void showDriverView();
-private slots:
+protected slots:
void poweroff();
void reboot();
void updateCalibDescription();
- void onPinFileChanged(const QString &file_path);
- void refreshPin();
- void forceoffroad();
- void updateLabels();
-
-private:
+protected:
Params params;
ButtonControl *pair_device;
-
- ButtonControl *fleetManagerPin;
- QString pin_title = tr("Fleet Manager PIN:") + " ";
- QString pin = "OFF";
- QFileSystemWatcher *fs_watch;
-
- QPushButton *offroad_btn;
+ QHBoxLayout *power_layout;
};
class TogglesPanel : public ListWidget {
@@ -87,18 +79,16 @@ public:
public slots:
void expandToggleDescription(const QString ¶m);
- void updateToggles();
-private slots:
- void updateState(const UIState &s);
+protected slots:
+ virtual void updateState(const UIState &s);
-private:
+protected:
Params params;
std::map toggles;
ButtonParamControl *long_personality_setting;
- ButtonParamControl *accel_personality_setting;
- ParamWatcher *param_watcher;
+ virtual void updateToggles();
};
class SoftwarePanel : public ListWidget {
@@ -114,7 +104,6 @@ protected:
bool is_onroad = false;
QLabel *onroadLbl;
- LabelControl *currentModelLbl;
LabelControl *versionLbl;
ButtonControl *installBtn;
ButtonControl *downloadBtn;
diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc
index 685ca3555d..47b4a22682 100644
--- a/selfdrive/ui/qt/offroad/software_settings.cc
+++ b/selfdrive/ui/qt/offroad/software_settings.cc
@@ -9,22 +9,27 @@
#include "common/params.h"
#include "common/util.h"
-#include "common/model.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#else
#include "selfdrive/ui/qt/widgets/controls.h"
+#endif
#include "selfdrive/ui/qt/widgets/input.h"
#include "system/hardware/hw.h"
+#ifdef SUNNYPILOT
+#define ListWidget ListWidgetSP
+#define ButtonControl ButtonControlSP
+#endif
+
void SoftwarePanel::checkForUpdates() {
std::system("pkill -SIGUSR1 -f system.updated.updated");
}
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
- currentModelLbl = new LabelControl(tr("Driving Model"), CURRENT_MODEL);
- addItem(currentModelLbl);
-
onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off."));
onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;");
addItem(onroadLbl);
@@ -74,7 +79,9 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
checkForUpdates();
}
});
- addItem(targetBranchBtn);
+ if (!params.getBool("IsTestedBranch")) {
+ addItem(targetBranchBtn);
+ }
// uninstall button
auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL"));
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h
deleted file mode 100644
index 63a5a817be..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/qt/widgets/controls.h"
-#include "selfdrive/ui/qt/widgets/scrollview.h"
-
-class CameraOffset : public SPOptionControl {
- Q_OBJECT
-
-public:
- CameraOffset();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class PathOffset : public SPOptionControl {
- Q_OBJECT
-
-public:
- PathOffset();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class CustomOffsetsSettings : public QWidget {
- Q_OBJECT
-
-public:
- explicit CustomOffsetsSettings(QWidget* parent = nullptr);
-
-signals:
- void backPress();
-
-private:
- Params params;
- std::map toggles;
-
- CameraOffset *camera_offset;
- PathOffset *path_offset;
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h
deleted file mode 100644
index b8083b53fc..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/qt/widgets/controls.h"
-
-class OnroadScreenOff : public SPOptionControl {
- Q_OBJECT
-
-public:
- OnroadScreenOff();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class OnroadScreenOffBrightness : public SPOptionControl {
- Q_OBJECT
-
-public:
- OnroadScreenOffBrightness();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class MaxTimeOffroad : public SPOptionControl {
- Q_OBJECT
-
-public:
- MaxTimeOffroad();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class BrightnessControl : public SPOptionControl {
- Q_OBJECT
-
-public:
- BrightnessControl();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class DisplayPanel : public ListWidget {
- Q_OBJECT
-
-public:
- explicit DisplayPanel(QWidget *parent = nullptr);
- void showEvent(QShowEvent *event) override;
-
-public slots:
- void updateToggles();
-
-private:
- Params params;
- std::map toggles;
-
- OnroadScreenOff *onroad_screen_off;
- OnroadScreenOffBrightness *onroad_screen_off_brightness;
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h b/selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h
deleted file mode 100644
index f82041e36e..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-#include
-#include
-#include
-#include
-
-class JsonFetcher
-{
-public:
- static QJsonObject getJsonFromURL(const QString &url)
- {
- const auto qurl = QUrl(url);
- QNetworkAccessManager manager;
- const QNetworkRequest request(qurl);
- QNetworkReply *reply = manager.get(request);
- QEventLoop loop;
-
- // Send GET request
-
- QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
- loop.exec();
-
- if (reply->error() != QNetworkReply::NoError) {
- qWarning() << "Failed to fetch data from URL: " << reply->errorString();
- return QJsonObject();
- }
-
- const QByteArray responseData = reply->readAll();
- const QJsonDocument doc = QJsonDocument::fromJson(responseData);
- QJsonObject json = doc.object();
-
- reply->deleteLater();
- return json;
- }
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h
deleted file mode 100644
index dd8e1748bd..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/ui.h"
-#include "selfdrive/ui/qt/widgets/controls.h"
-#include "selfdrive/ui/qt/widgets/scrollview.h"
-
-class AutoLaneChangeTimer : public SPOptionControl {
- Q_OBJECT
-
-public:
- AutoLaneChangeTimer();
-
- void refresh();
-
-signals:
- void toggleUpdated();
-
-private:
- Params params;
-};
-
-class PauseLateralSpeed : public SPOptionControl {
- Q_OBJECT
-
-public:
- PauseLateralSpeed();
-
- void refresh();
-
- signals:
- void ToggleUpdated();
-
-private:
- Params params;
-};
-
-
-class LaneChangeSettings : public QWidget {
- Q_OBJECT
-
-public:
- explicit LaneChangeSettings(QWidget* parent = nullptr);
- void showEvent(QShowEvent *event) override;
-
-signals:
- void backPress();
-
-public slots:
- void updateToggles();
-
-private:
- Params params;
- std::map toggles;
-
- AutoLaneChangeTimer *auto_lane_change_timer;
- PauseLateralSpeed *pause_lateral_speed;
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc
deleted file mode 100644
index e213cc19a7..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h"
-
-MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) {
- QVBoxLayout* main_layout = new QVBoxLayout(this);
- main_layout->setContentsMargins(50, 20, 50, 20);
- main_layout->setSpacing(20);
-
- // Back button
- PanelBackButton* back = new PanelBackButton();
- connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
- main_layout->addWidget(back, 0, Qt::AlignLeft);
-
- ListWidget *list = new ListWidget(this, false);
- // param, title, desc, icon
- std::vector> toggle_defs{
- {
- "AccMadsCombo",
- tr("Enable ACC+MADS with RES+/SET-"),
- QString("%1
"
- "%2
")
- .arg(tr("Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button."))
- .arg(tr("Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off.")),
- "../assets/offroad/icon_blank.png",
- },
- {
- "MadsCruiseMain",
- tr("Toggle M.A.D.S. with Cruise Main"),
- tr("Allows M.A.D.S. engagement/disengagement with \"Cruise Main\" cruise control button from the steering wheel."),
- "../assets/offroad/icon_blank.png",
- }
- };
-
- // Disengage Lateral on Brake (DLOB)
- std::vector dlob_settings_texts{tr("Remain Active"), tr("Pause Steering")};
- dlob_settings = new ButtonParamControl(
- "DisengageLateralOnBrake",
- tr("Steering Mode After Braking"),
- tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\nRemain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."),
- "../assets/offroad/icon_blank.png",
- dlob_settings_texts,
- 500
- );
- dlob_settings->showDescription();
- list->addItem(dlob_settings);
-
- for (auto &[param, title, desc, icon] : toggle_defs) {
- auto toggle = new ParamControl(param, title, desc, icon, this);
-
- list->addItem(toggle);
- toggles[param.toStdString()] = toggle;
-
- // trigger offroadTransition when going onroad/offroad
- connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled);
- }
-
- // trigger offroadTransition when going onroad/offroad
- connect(uiState(), &UIState::offroadTransition, dlob_settings, &ButtonParamControl::setEnabled);
-
- main_layout->addWidget(new ScrollView(list, this));
-}
-
-void MadsSettings::showEvent(QShowEvent *event) {
- updateToggles();
-}
-
-void MadsSettings::updateToggles() {
- if (!isVisible()) {
- return;
- }
-
- const bool is_offroad = !uiState()->scene.started;
- const bool enable_mads = params.getBool("EnableMads");
- const bool enabled = is_offroad && enable_mads;
-
- toggles["AccMadsCombo"]->setEnabled(enabled);
- toggles["MadsCruiseMain"]->setEnabled(enabled);
- dlob_settings->setEnabled(enabled);
-}
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h
deleted file mode 100644
index 2a949c97a4..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/ui.h"
-#include "selfdrive/ui/qt/widgets/controls.h"
-#include "selfdrive/ui/qt/widgets/scrollview.h"
-
-class MadsSettings : public QWidget {
- Q_OBJECT
-
-public:
- explicit MadsSettings(QWidget* parent = nullptr);
- void showEvent(QShowEvent *event) override;
-
-signals:
- void backPress();
-
-public slots:
- void updateToggles();
-
-private:
- Params params;
- std::map toggles;
-
- ButtonParamControl *dlob_settings;
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc
deleted file mode 100644
index e2a410f9ff..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h"
-
-MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) {
- main_layout = new QStackedLayout(this);
-
- ListWidget *list = new ListWidget(this, false);
- // param, title, desc, icon
- std::vector> toggle_defs{
- {
- "HandsOnWheelMonitoring",
- tr("Enable Hands on Wheel Monitoring"),
- tr("Monitor and alert when driver is not keeping the hands on the steering wheel."),
- "../assets/offroad/icon_blank.png",
- }
- };
-
- for (auto &[param, title, desc, icon] : toggle_defs) {
- auto toggle = new ParamControl(param, title, desc, icon, this);
-
- list->addItem(toggle);
- toggles[param.toStdString()] = toggle;
- }
-
- monitoringScreen = new QWidget(this);
- QVBoxLayout* vlayout = new QVBoxLayout(monitoringScreen);
- vlayout->setContentsMargins(50, 20, 50, 20);
-
- vlayout->addWidget(new ScrollView(list, this), 1);
- main_layout->addWidget(monitoringScreen);
-}
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h
deleted file mode 100644
index 214d2f8ac0..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/qt/widgets/controls.h"
-#include "selfdrive/ui/qt/widgets/scrollview.h"
-
-class MonitoringPanel : public QFrame {
- Q_OBJECT
-
-public:
- explicit MonitoringPanel(QWidget *parent = nullptr);
-
-private:
- QStackedLayout* main_layout = nullptr;
- QWidget* monitoringScreen = nullptr;
- Params params;
- std::map toggles;
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc
deleted file mode 100644
index 2de70e9b30..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h"
-
-SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(parent) {
- QVBoxLayout* main_layout = new QVBoxLayout(this);
- main_layout->setContentsMargins(50, 20, 50, 20);
- main_layout->setSpacing(20);
-
- // Back button
- PanelBackButton* back = new PanelBackButton();
- connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
- main_layout->addWidget(back, 0, Qt::AlignLeft);
-
- ListWidget *list = new ListWidget(this, false);
-
- speed_limit_policy = new ButtonParamControl(
- "SpeedLimitControlPolicy",
- tr("Speed Limit Source Policy"),
- "",
- "../assets/offroad/icon_blank.png",
- speed_limit_policy_texts,
- 250
- );
- speed_limit_policy->showDescription();
- connect(speed_limit_policy, &ButtonParamControl::buttonToggled, this, &SpeedLimitPolicySettings::updateToggles);
- list->addItem(speed_limit_policy);
-
- param_watcher = new ParamWatcher(this);
-
- QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
- updateToggles();
- });
-
- main_layout->addWidget(new ScrollView(list, this));
-}
-
-void SpeedLimitPolicySettings::showEvent(QShowEvent *event) {
- updateToggles();
-}
-
-void SpeedLimitPolicySettings::updateToggles() {
- param_watcher->addParam("SpeedLimitControlPolicy");
-
- if (!isVisible()) {
- return;
- }
-
- // TODO: SP: use upstream's setCheckedButton
- speed_limit_policy->setButton("SpeedLimitControlPolicy");
-
- speed_limit_policy->setDescription(speedLimitPolicyDescriptionBuilder("SpeedLimitControlPolicy", speed_limit_policy_descriptions));
-}
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h
deleted file mode 100644
index 8026cf7f1b..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h
+++ /dev/null
@@ -1,84 +0,0 @@
-#pragma once
-
-#include "common/model.h"
-#include "selfdrive/ui/ui.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h"
-#include "selfdrive/ui/qt/util.h"
-#include "selfdrive/ui/qt/widgets/controls.h"
-#include "selfdrive/ui/qt/widgets/scrollview.h"
-
-class TorqueFriction : public SPOptionControl {
- Q_OBJECT
-
-public:
- TorqueFriction();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class TorqueMaxLatAccel : public SPOptionControl {
- Q_OBJECT
-
-public:
- TorqueMaxLatAccel();
-
- void refresh();
-
-private:
- Params params;
-};
-
-class SunnypilotPanel : public QFrame {
- Q_OBJECT
-
-public:
- explicit SunnypilotPanel(QWidget *parent = nullptr);
- void showEvent(QShowEvent *event) override;
- void hideEvent(QHideEvent* event) override;
-
-public slots:
- void updateToggles();
-
-private:
- QStackedLayout* main_layout = nullptr;
- QWidget* sunnypilotScreen = nullptr;
- MadsSettings* mads_settings = nullptr;
- SubPanelButton *slcSettings = nullptr;
- SlcSettings* slc_settings = nullptr;
- SubPanelButton *slwSettings = nullptr;
- SpeedLimitWarningSettings* slw_settings = nullptr;
- SubPanelButton *slpSettings = nullptr;
- SpeedLimitPolicySettings* slp_settings = nullptr;
- LaneChangeSettings* lane_change_settings = nullptr;
- CustomOffsetsSettings* custom_offsets_settings = nullptr;
- Params params;
- std::map toggles;
- ParamWatcher *param_watcher;
-
- TorqueFriction *friction;
- TorqueMaxLatAccel *lat_accel_factor;
- ButtonParamControl *dlp_settings;
-
- ScrollView *scrollView = nullptr;
-
- const QString nnff_description = QString("%1
"
- "%2")
- .arg(tr("Formerly known as \"NNFF\", this replaces the lateral \"torque\" controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy."))
- .arg(tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: ") + "#tuning-nnlc");
-
- QString nnffDescriptionBuilder(const QString &custom_description) {
- QString description = "" + custom_description + "
" + nnff_description;
- return description;
- }
-
- const QString custom_offsets_description = QString(tr("Add custom offsets to Camera and Path in sunnypilot."));
- const QString dlp_description = QString(tr("Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions."));
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc
deleted file mode 100644
index e21dc675ae..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h"
-
-TripsPanel::TripsPanel(QWidget* parent) : QFrame(parent) {
- QVBoxLayout* main_layout = new QVBoxLayout(this);
- main_layout->setMargin(0);
-
- // main content
- main_layout->addSpacing(25);
- center_layout = new QStackedLayout();
-
- driveStatsWidget = new DriveStats;
- driveStatsWidget->setStyleSheet(R"(
- QLabel[type="title"] { font-size: 51px; font-weight: 500; }
- QLabel[type="number"] { font-size: 78px; font-weight: 500; }
- QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; }
- )");
- center_layout->addWidget(driveStatsWidget);
-
- main_layout->addLayout(center_layout, 1);
-
- setStyleSheet(R"(
- * {
- color: white;
- }
- TripsPanel > QLabel {
- font-size: 55px;
- }
- )");
-}
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h
deleted file mode 100644
index dc77acac75..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/qt/widgets/controls.h"
-#include "selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h"
-
-class TripsPanel : public QFrame {
- Q_OBJECT
-
-public:
- explicit TripsPanel(QWidget* parent = 0);
-
-private:
- Params params;
-
- QStackedLayout* center_layout;
- DriveStats *driveStatsWidget;
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h
deleted file mode 100644
index b210225f49..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h
+++ /dev/null
@@ -1,59 +0,0 @@
-#pragma once
-
-#include
-
-#include "common/watchdog.h"
-#include "selfdrive/ui/ui.h"
-#include "selfdrive/ui/qt/util.h"
-#include "selfdrive/ui/qt/widgets/controls.h"
-#include "selfdrive/ui/qt/widgets/scrollview.h"
-
-class VehiclePanel : public QWidget {
- Q_OBJECT
-
-public:
- explicit VehiclePanel(QWidget *parent = nullptr);
- void showEvent(QShowEvent *event) override;
-
-public slots:
- void updateToggles();
-
-private:
- Params params;
-
- QStackedLayout* main_layout = nullptr;
- QWidget* home = nullptr;
-
- QPushButton* setCarBtn;
- QString set;
-
- QWidget* home_widget;
- QString prompt_select = tr("Select your car");
-};
-
-class SPVehiclesTogglesPanel : public ListWidget {
- Q_OBJECT
-public:
- explicit SPVehiclesTogglesPanel(VehiclePanel *parent);
- void showEvent(QShowEvent *event) override;
-
-public slots:
- void updateToggles();
-
-private:
- Params params;
- bool is_onroad = false;
-
- ParamControl *stockLongToyota;
- ParamControl *toyotaEnhancedBsm;
-
- const QString toyotaEnhancedBsmDescription = QString("%1
"
- "%2")
- .arg(tr("sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects."))
- .arg(tr("Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2."));
-
- QString toyotaEnhancedBsmDesciptionBuilder(const QString &custom_description) {
- QString description = "" + custom_description + "
" + toyotaEnhancedBsmDescription;
- return description;
- }
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h
deleted file mode 100644
index 3ea5825f59..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/ui.h"
-#include "selfdrive/ui/qt/widgets/controls.h"
-
-class VisualsPanel : public ListWidget {
- Q_OBJECT
-
-public:
- explicit VisualsPanel(QWidget *parent = nullptr);
-
-private:
- Params params;
- std::map toggles;
-
- ButtonParamControl *dev_ui_settings;
- ButtonParamControl *chevron_info_settings;
- ButtonParamControl *sidebar_temp_setting;
-};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot_main.h b/selfdrive/ui/qt/offroad/sunnypilot_main.h
deleted file mode 100644
index fe4526db08..0000000000
--- a/selfdrive/ui/qt/offroad/sunnypilot_main.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h"
-#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h"
diff --git a/selfdrive/ui/qt/offroad_home.cc b/selfdrive/ui/qt/offroad_home.cc
new file mode 100644
index 0000000000..e9d54a4efb
--- /dev/null
+++ b/selfdrive/ui/qt/offroad_home.cc
@@ -0,0 +1,152 @@
+#include "selfdrive/ui/qt/offroad_home.h"
+
+#include
+#include
+
+#include "selfdrive/ui/qt/offroad/experimental_mode.h"
+#include "selfdrive/ui/qt/util.h"
+#include "selfdrive/ui/qt/widgets/prime.h"
+
+// OffroadHome: the offroad home page
+
+OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
+ QVBoxLayout* main_layout = new QVBoxLayout(this);
+ main_layout->setContentsMargins(40, 40, 40, 40);
+
+ // top header
+ QHBoxLayout* header_layout = new QHBoxLayout();
+ header_layout->setContentsMargins(0, 0, 0, 0);
+ header_layout->setSpacing(16);
+
+ update_notif = new QPushButton(tr("UPDATE"));
+ update_notif->setVisible(false);
+ update_notif->setStyleSheet("background-color: #364DEF;");
+ QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
+ header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
+
+ alert_notif = new QPushButton();
+ alert_notif->setVisible(false);
+ alert_notif->setStyleSheet("background-color: #E22C2C;");
+ QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); });
+ header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
+
+ version = new ElidedLabel();
+ header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight);
+
+ main_layout->addLayout(header_layout);
+
+ // main content
+ main_layout->addSpacing(25);
+ center_layout = new QStackedLayout();
+
+ home_widget = new QWidget(this);
+ {
+ home_layout = new QHBoxLayout(home_widget);
+ home_layout->setContentsMargins(0, 0, 0, 0);
+ home_layout->setSpacing(30);
+
+ // left: PrimeAdWidget
+ left_widget = new QStackedWidget(this);
+ QVBoxLayout *left_prime_layout = new QVBoxLayout();
+ QWidget *prime_user = new PrimeUserWidget();
+ prime_user->setStyleSheet(R"(
+ border-radius: 10px;
+ background-color: #333333;
+ )");
+ left_prime_layout->addWidget(prime_user);
+ left_prime_layout->addStretch();
+ left_widget->addWidget(new LayoutWidget(left_prime_layout));
+ left_widget->addWidget(new PrimeAdWidget);
+ left_widget->setStyleSheet("border-radius: 10px;");
+
+ left_widget->setCurrentIndex(uiState()->hasPrime() ? 0 : 1);
+ connect(uiState(), &UIState::primeChanged, [=](bool prime) {
+ left_widget->setCurrentIndex(prime ? 0 : 1);
+ });
+
+ home_layout->addWidget(left_widget, 1);
+
+ // right: ExperimentalModeButton, SetupWidget
+ QWidget* right_widget = new QWidget(this);
+ QVBoxLayout* right_column = new QVBoxLayout(right_widget);
+ right_column->setContentsMargins(0, 0, 0, 0);
+ right_widget->setFixedWidth(750);
+ right_column->setSpacing(30);
+
+ ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this);
+ QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings);
+ right_column->addWidget(experimental_mode, 1);
+
+ SetupWidget *setup_widget = new SetupWidget;
+ QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings);
+ right_column->addWidget(setup_widget, 1);
+
+ home_layout->addWidget(right_widget, 1);
+ }
+ center_layout->addWidget(home_widget);
+
+ // add update & alerts widgets
+ update_widget = new UpdateAlert();
+ QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
+ center_layout->addWidget(update_widget);
+ alerts_widget = new OffroadAlert();
+ QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
+ center_layout->addWidget(alerts_widget);
+
+ main_layout->addLayout(center_layout, 1);
+
+ // set up refresh timer
+ timer = new QTimer(this);
+ timer->callOnTimeout(this, &OffroadHome::refresh);
+
+ setStyleSheet(R"(
+ * {
+ color: white;
+ }
+ OffroadHome {
+ background-color: black;
+ }
+ OffroadHome > QPushButton {
+ padding: 15px 30px;
+ border-radius: 5px;
+ font-size: 40px;
+ font-weight: 500;
+ }
+ OffroadHome > QLabel {
+ font-size: 55px;
+ }
+ )");
+}
+
+void OffroadHome::showEvent(QShowEvent *event) {
+ refresh();
+ timer->start(10 * 1000);
+}
+
+void OffroadHome::hideEvent(QHideEvent *event) {
+ timer->stop();
+}
+
+void OffroadHome::refresh() {
+ version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription")));
+
+ bool updateAvailable = update_widget->refresh();
+ int alerts = alerts_widget->refresh();
+
+ // pop-up new notification
+ int idx = center_layout->currentIndex();
+ if (!updateAvailable && !alerts) {
+ idx = 0;
+ } else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) {
+ idx = 1;
+ } else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) {
+ idx = 2;
+ }
+ center_layout->setCurrentIndex(idx);
+
+ update_notif->setVisible(updateAvailable);
+ alert_notif->setVisible(alerts);
+ if (alerts) {
+ alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT")));
+ }
+}
diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h
new file mode 100644
index 0000000000..5796c60f52
--- /dev/null
+++ b/selfdrive/ui/qt/offroad_home.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include
+#include
+
+#include "common/params.h"
+#include "selfdrive/ui/qt/body.h"
+#include "selfdrive/ui/qt/onroad/onroad_home.h"
+#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
+
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h"
+#include "selfdrive/ui/sunnypilot/qt/sidebar.h"
+#define OnroadWindow OnroadWindowSP
+#define OffroadHomeImp OffroadHomeSP
+#define LayoutWidget LayoutWidgetSP
+#define Sidebar SidebarSP
+#define ElidedLabel ElidedLabelSP
+#else
+#include "selfdrive/ui/qt/widgets/controls.h"
+#include "selfdrive/ui/qt/onroad/onroad_home.h"
+#include "selfdrive/ui/qt/sidebar.h"
+#endif
+
+class OffroadHome : public QFrame {
+ Q_OBJECT
+
+public:
+ void do_work(QWidget*& home_widget);
+ explicit OffroadHome(QWidget* parent = 0);
+
+signals:
+ void openSettings(int index = 0, const QString ¶m = "");
+
+protected:
+ QStackedLayout* center_layout;
+ QWidget* home_widget;
+ QHBoxLayout *home_layout;
+ QStackedWidget *left_widget;
+
+private:
+ void showEvent(QShowEvent *event) override;
+ void hideEvent(QHideEvent *event) override;
+ void refresh();
+
+ Params params;
+
+ QTimer* timer;
+ ElidedLabel* version;
+ UpdateAlert *update_widget;
+ OffroadAlert* alerts_widget;
+ QPushButton* alert_notif;
+ QPushButton* update_notif;
+};
diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc
index 7e5629c4ef..484075b3e2 100644
--- a/selfdrive/ui/qt/onroad/annotated_camera.cc
+++ b/selfdrive/ui/qt/onroad/annotated_camera.cc
@@ -1,36 +1,17 @@
+
#include "selfdrive/ui/qt/onroad/annotated_camera.h"
+#include
#include
#include
-#include
-#include
#include "common/swaglog.h"
#include "selfdrive/ui/qt/onroad/buttons.h"
#include "selfdrive/ui/qt/util.h"
-static std::pair getFeatureStatus(int value, QStringList text_list, QStringList color_list,
- bool condition, QString off_text) {
-
- QString text("Error");
- QColor color("#ffffff");
-
- for (int i = 0; i < text_list.size() && i < color_list.size(); ++i) {
- if (value == i) {
- text = condition ? text_list[i] : off_text;
- color = condition ? QColor(color_list[i]) : QColor("#ffffff");
- break; // Exit the loop once a match is found
- }
- }
-
- return {text, color};
-}
-
-
// Window that shows camera view and variety of info drawn on top
AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) {
pm = std::make_unique>({"uiDebug"});
- e2e_state = std::make_unique>({"e2eLongStateSP"});
main_layout = new QVBoxLayout(this);
main_layout->setMargin(UI_BORDER_SIZE);
@@ -39,68 +20,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par
experimental_btn = new ExperimentalButton(this);
main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight);
- onroad_settings_btn = new OnroadSettingsButton(this);
-
- map_settings_btn = new MapSettingsButton(this);
-
dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5});
- map_img = loadPixmap("../assets/img_world_icon.png", {subsign_img_size, subsign_img_size});
- left_img = loadPixmap("../assets/img_turn_left_icon.png", {subsign_img_size, subsign_img_size});
- right_img = loadPixmap("../assets/img_turn_right_icon.png", {subsign_img_size, subsign_img_size});
-
- buttons_layout = new QHBoxLayout();
- buttons_layout->setContentsMargins(0, 0, 10, 20);
- main_layout->addLayout(buttons_layout);
- updateButtonsLayout(false);
-}
-
-void AnnotatedCameraWidget::mousePressEvent(QMouseEvent* e) {
- bool propagate_event = true;
-
- UIState *s = uiState();
- UIScene &scene = s->scene;
- const SubMaster &sm = *(s->sm);
- const auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
-
- if (longitudinal_plan_sp.getSpeedLimit() > 0.0 && sl_sign_rect.contains(e->x(), e->y())) {
- // If touching the speed limit sign area when visible
- scene.last_speed_limit_sign_tap = seconds_since_boot();
- params.putBool("LastSpeedLimitSignTap", true);
- scene.speed_limit_control_enabled = !scene.speed_limit_control_enabled;
- params.putBool("EnableSlc", scene.speed_limit_control_enabled);
- propagate_event = false;
- }
-
- if (propagate_event) {
- QWidget::mousePressEvent(e);
- }
-}
-
-void AnnotatedCameraWidget::updateButtonsLayout(bool is_rhd) {
- QLayoutItem *item;
- while ((item = buttons_layout->takeAt(0)) != nullptr) {
- delete item;
- }
-
- buttons_layout->setContentsMargins(0, 0, 10, rn_offset != 0 ? rn_offset + 10 : 20);
-
- if (is_rhd) {
- buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0);
- buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
-
- buttons_layout->addStretch(1);
-
- buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight);
- buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0);
- } else {
- buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0);
- buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
-
- buttons_layout->addStretch(1);
-
- buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight);
- buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); // Add spacing to the right
- }
}
void AnnotatedCameraWidget::updateState(const UIState &s) {
@@ -108,274 +28,38 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
const SubMaster &sm = *(s.sm);
const bool cs_alive = sm.alive("controlsState");
- const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid();
const auto cs = sm["controlsState"].getControlsState();
- const auto cs_sp = sm["controlsStateSP"].getControlsStateSP();
const auto car_state = sm["carState"].getCarState();
- const auto nav_instruction = sm["navInstruction"].getNavInstruction();
- 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 lateral_plan_sp = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED();
- car_params = sm["carParams"].getCarParams();
+
+ is_metric = s.scene.is_metric;
// Handle older routes where vCruiseCluster is not set
float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster();
setSpeed = cs_alive ? v_cruise : SET_SPEED_NA;
is_cruise_set = setSpeed > 0 && (int)setSpeed != SET_SPEED_NA;
- if (is_cruise_set && !s.scene.is_metric) {
+ if (is_cruise_set && !is_metric) {
setSpeed *= KM_TO_MILE;
}
// 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();
- v_ego = s.scene.true_vego_ui ? car_state.getVEgo() : v_ego;
speed = cs_alive ? std::max(0.0, v_ego) : 0.0;
- speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH;
+ speed *= is_metric ? MS_TO_KPH : MS_TO_MPH;
- auto speed_limit_sign = nav_instruction.getSpeedLimitSign();
- speedLimit = nav_alive ? nav_instruction.getSpeedLimit() : 0.0;
- speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
-
- has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD);
- has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA);
- is_metric = s.scene.is_metric;
- speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph");
+ speedUnit = is_metric ? tr("km/h") : tr("mph");
hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
status = s.status;
- // TODO: Add minimum speed?
- left_blindspot = cs_alive && car_state.getLeftBlindspot();
- right_blindspot = cs_alive && car_state.getRightBlindspot();
-
- steerOverride = car_state.getSteeringPressed();
- gasOverride = car_state.getGasPressed();
- latActive = car_control.getLatActive();
- madsEnabled = car_state.getMadsEnabled();
-
- brakeLights = car_state.getBrakeLightsDEPRECATED() && s.scene.visual_brake_lights;
-
- standStillTimer = s.scene.stand_still_timer;
- standStill = car_state.getStandstill();
- standstillElapsedTime = lateral_plan_sp.getStandstillElapsed();
-
- hideVEgoUi = s.scene.hide_vego_ui;
-
- splitPanelVisible = s.scene.map_visible || s.scene.onroad_settings_visible;
-
- // ############################## DEV UI START ##############################
- lead_d_rel = radar_state.getLeadOne().getDRel();
- lead_v_rel = radar_state.getLeadOne().getVRel();
- lead_status = radar_state.getLeadOne().getStatus();
- lateralState = QString::fromStdString(cs_sp.getLateralState());
- angleSteers = car_state.getSteeringAngleDeg();
- steerAngleDesired = cs.getLateralControlState().getPidState().getSteeringAngleDesiredDeg();
- curvature = cs.getCurvature();
- roll = sm["liveParameters"].getLiveParameters().getRoll();
- memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
- devUiInfo = s.scene.dev_ui_info;
- 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() || s.scene.live_torque_toggle) && !s.scene.torqued_override;
- latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
- frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
- liveValid = ltp.getLiveValid();
- // ############################## DEV UI END ##############################
-
- btnPerc = s.scene.sleep_btn_opacity * 0.05;
-
- left_blinker = car_state.getLeftBlinker();
- right_blinker = car_state.getRightBlinker();
- lane_change_edge_block = lateral_plan_sp.getLaneChangeEdgeBlockDEPRECATED();
-
// update engageability/experimental mode button
experimental_btn->updateState(s);
- // update onroad settings button state
- onroad_settings_btn->updateState(s);
-
// update DM icon
auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState();
dmActive = dm_state.getIsActiveMode();
rightHandDM = dm_state.getIsRHD();
// DM icon transition
dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0);
-
- // update buttons layout
- updateButtonsLayout(rightHandDM);
-
- // hide map settings button for alerts and flip for right hand DM
- if (map_settings_btn->isEnabled()) {
- map_settings_btn->setVisible(!hideBottomIcons);
- buttons_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom);
- }
-
- // hide onroad settings button for alerts and flip for right hand DM
- if (onroad_settings_btn->isEnabled()) {
- onroad_settings_btn->setVisible(!hideBottomIcons);
- buttons_layout->setAlignment(onroad_settings_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignBottom);
- }
-
- const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
- slcState = lp_sp.getSpeedLimitControlState();
-
- speedLimitControlToggle = s.scene.speed_limit_control_enabled;
-
- const auto vtcState = lp_sp.getVisionTurnControllerState();
- const float vtc_speed = lp_sp.getVisionTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
- const auto lpSoruce = lp_sp.getLongitudinalPlanSource();
- QColor vtc_color = tcs_colors[int(vtcState)];
- vtc_color.setAlpha(lpSoruce == cereal::LongitudinalPlanSP::LongitudinalPlanSource::TURN ? 255 : 100);
-
- showVTC = vtcState > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED;
- vtcSpeed = QString::number(std::nearbyint(vtc_speed));
- vtcColor = vtc_color;
- showDebugUI = s.scene.show_debug_ui;
-
- const auto lmd_sp = sm["liveMapDataSP"].getLiveMapDataSP();
-
- const auto data_type = int(lmd_sp.getDataType());
- const QString data_type_draw(data_type == 2 ? "🌐 " : "");
- roadName = QString::fromStdString(lmd_sp.getCurrentRoadName());
- roadName = !roadName.isEmpty() ? data_type_draw + roadName : "";
-
- float speed_limit_slc = lp_sp.getSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
- const float speed_limit_offset = lp_sp.getSpeedLimitOffset() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
- const bool sl_force_active = speedLimitControlToggle &&
- seconds_since_boot() < s.scene.last_speed_limit_sign_tap + 2.0;
- const bool sl_inactive = !sl_force_active && (!speedLimitControlToggle ||
- slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::INACTIVE);
- const bool sl_temp_inactive = !sl_force_active && (speedLimitControlToggle &&
- slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE);
- const bool sl_pre_active = !sl_force_active && (speedLimitControlToggle &&
- slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE);
- const int sl_distance = int(lp_sp.getDistToSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10;
- const QString sl_distance_str(QString::number(sl_distance) + (s.scene.is_metric ? "m" : "f"));
- const QString sl_offset_str(speed_limit_offset > 0.0 ? speed_limit_offset < 0.0 ?
- "-" + QString::number(std::nearbyint(std::abs(speed_limit_offset))) :
- "+" + QString::number(std::nearbyint(speed_limit_offset)) : "");
- const QString sl_inactive_str(sl_temp_inactive && s.scene.speed_limit_control_engage_type == 0 ? "TEMP" : "");
- const QString sl_substring(sl_inactive || sl_temp_inactive || sl_pre_active ? sl_inactive_str :
- sl_distance > 0 ? sl_distance_str : sl_offset_str);
-
- showSpeedLimit = speed_limit_slc > 0.0;
- speedLimitSLC = speed_limit_slc;
- speedLimitSLCOffset = speed_limit_offset;
- slcSubText = sl_substring;
- slcSubTextSize = sl_inactive || sl_temp_inactive || sl_distance > 0 ? 25.0 : 27.0;
- mapSourcedSpeedLimit = lp_sp.getIsMapSpeedLimit();
- slcActive = !sl_inactive && !sl_temp_inactive;
- overSpeedLimit = showSpeedLimit && s.scene.speed_limit_warning_type != 0 &&
- (std::nearbyint(speed_limit_slc + s.scene.speed_limit_warning_value_offset) < std::nearbyint(speed));
- plus_arrow_up_img = loadPixmap("../assets/img_plus_arrow_up", {105, 105});
- minus_arrow_down_img = loadPixmap("../assets/img_minus_arrow_down", {105, 105});
-
- const float tsc_speed = lp_sp.getTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
- const auto tscState = lp_sp.getTurnSpeedControlState();
- const int t_distance = int(lp_sp.getDistToTurn() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10;
- const QString t_distance_str(QString::number(t_distance) + (s.scene.is_metric ? "m" : "f"));
-
- showTurnSpeedLimit = tsc_speed > 0.0 && std::round(tsc_speed) < 224 && (tsc_speed < speed || s.scene.show_debug_ui);
- turnSpeedLimit = QString::number(std::nearbyint(tsc_speed));
- tscSubText = t_distance > 0 ? t_distance_str : QString("");
- tscActive = tscState > cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE;
- curveSign = lp_sp.getTurnSign();
-
- // TODO: Add toggle variables to cereal, and parse from cereal
- longitudinalPersonality = s.scene.longitudinal_personality;
- dynamicLaneProfile = s.scene.dynamic_lane_profile;
- mpcMode = QString::fromStdString(lp_sp.getE2eBlended());
- mpcMode = (mpcMode == "blended") ? mpcMode.replace(0, 1, mpcMode[0].toUpper()) : mpcMode.toUpper();
-
- 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;
-
- cruiseStateEnabled = car_state.getCruiseState().getEnabled();
-
- int e2eLStatus = 0;
- static bool chime_sent = false;
- static int chime_count = 0;
- int chime_prompt = 0;
- static float last_lead_distance = -1;
- const float lead_distance = radar_state.getLeadOne().getDRel();
-
- if (s.scene.e2eX[12] > 30 && car_state.getVEgo() < 1.0) {
- e2eLStatus = 2;
- } else if ((s.scene.e2eX[12] > 0 && s.scene.e2eX[12] < 80) || s.scene.e2eX[12] < 0) {
- e2eLStatus = 1;
- } else {
- e2eLStatus = 0;
- }
-
- if (!car_state.getStandstill()) {
- chime_prompt = 0;
- chime_sent = false;
- chime_count = 0;
-
- if (last_lead_distance != -1) {
- last_lead_distance = -1;
- }
- }
-
- if ((cruiseStateEnabled || car_state.getBrakeLightsDEPRECATED()) && !car_state.getGasPressed() && car_state.getStandstill()) {
- if (e2eLStatus == 2 && !radar_state.getLeadOne().getStatus()) {
- if (chime_sent) {
- chime_count = 0;
- } else {
- chime_count += 1;
- }
- if (s.scene.e2e_long_alert_light && chime_count >= 2 && !chime_sent) {
- chime_prompt = 1;
- chime_sent = true;
- } else {
- chime_prompt = 0;
- }
- } else if (radar_state.getLeadOne().getStatus()) {
- if ((last_lead_distance == -1) || (lead_distance < last_lead_distance)) {
- last_lead_distance = lead_distance;
- }
- if (s.scene.e2e_long_alert_lead && (lead_distance - last_lead_distance > 1.0) && !chime_sent) {
- chime_prompt = 2;
- chime_sent = true;
- } else {
- chime_prompt = 0;
- }
- } else {
- chime_prompt = 0;
- }
- } else {
- }
-
- e2eStatus = chime_prompt;
- e2eState = e2eLStatus;
- e2eLongAlertUi = s.scene.e2e_long_alert_ui;
- dynamicExperimentalControlToggle = s.scene.dynamic_experimental_control;
- speedLimitWarningFlash = s.scene.speed_limit_warning_flash;
- experimentalMode = cs.getExperimentalMode();
-
- featureStatusToggle = s.scene.feature_status_toggle;
-
- experimental_btn->setVisible(!(showDebugUI && showVTC));
- drivingModelGen = s.scene.driving_model_generation;
}
void AnnotatedCameraWidget::drawHud(QPainter &p) {
@@ -387,33 +71,18 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0));
p.fillRect(0, 0, width(), UI_HEADER_HEIGHT, bg);
- QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–";
- QString speedLimitStrSlc = showSpeedLimit ? QString::number(std::nearbyint(speedLimitSLC)) : "–";
QString speedStr = QString::number(std::nearbyint(speed));
QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–";
- const bool isNavSpeedLimit = has_us_speed_limit || has_eu_speed_limit;
-
- // Draw outer box + border to contain set speed and speed limit
- const int sign_margin = 12;
- const int us_sign_height = slcSubText == "" ? 186 : 216;
- const int eu_sign_size = 176;
+ // Draw outer box + border to contain set speed
const QSize default_size = {172, 204};
QSize set_speed_size = default_size;
- if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200;
- if ((mapSourcedSpeedLimit && !is_metric && speedLimitStrSlc.size() >= 3) ||
- (has_us_speed_limit && speedLimitStr.size() >= 3)) set_speed_size.rwidth() = 223;
-
- if ((mapSourcedSpeedLimit && !is_metric) || has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin;
- else if ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin;
-
- int top_radius = 32;
- int bottom_radius = ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) ? 100 : 32;
+ if (is_metric) set_speed_size.rwidth() = 200;
QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size);
p.setPen(QPen(whiteColor(75), 6));
p.setBrush(blackColor(166));
- drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius);
+ p.drawRoundedRect(set_speed_rect, 32, 32);
// Draw MAX
QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff);
@@ -421,20 +90,8 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
if (is_cruise_set) {
if (status == STATUS_DISENGAGED) {
max_color = whiteColor();
- } else if (status == STATUS_OVERRIDE && gasOverride) {
+ } else if (status == STATUS_OVERRIDE) {
max_color = QColor(0x91, 0x9b, 0x95, 0xff);
- } else if (speedLimitSLC > 0) {
- auto interp_color = [=](QColor c1, QColor c2, QColor c3) {
- return speedLimitSLC > 0 ? interpColor(setSpeed, {speedLimitSLC + 5, speedLimitSLC + 15, speedLimitSLC + 25}, {c1, c2, c3}) : c1;
- };
- max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf));
- set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00));
- } else if (speedLimit > 0) {
- auto interp_color = [=](QColor c1, QColor c2, QColor c3) {
- return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1;
- };
- max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf));
- set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00));
}
} else {
max_color = QColor(0xa6, 0xa6, 0xa6, 0xff);
@@ -447,116 +104,11 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
p.setPen(set_speed_color);
p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr);
- const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin);
- sl_sign_rect = sign_rect;
-
- speedLimitWarning(p, sign_rect, sign_margin);
-
- // US/Canada (MUTCD style) sign
- if (((mapSourcedSpeedLimit && !is_metric && !isNavSpeedLimit) || has_us_speed_limit) && slcShowSign) {
- p.setPen(Qt::NoPen);
- p.setBrush(whiteColor());
- p.drawRoundedRect(sign_rect, 24, 24);
- p.setPen(QPen(blackColor(), 6));
- p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16);
-
- p.setFont(InterFont(28, QFont::DemiBold));
- p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED"));
- p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT"));
- p.setFont(InterFont(70, QFont::Bold));
- if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255));
- else p.setPen(blackColor());
- p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStrSlc);
-
- // Speed limit offset value
- p.setFont(InterFont(32, QFont::Bold));
- p.setPen(blackColor());
- p.drawText(sign_rect.adjusted(0, 85 + 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText);
- }
-
- // EU (Vienna style) sign
- if (((mapSourcedSpeedLimit && is_metric && !isNavSpeedLimit) || has_eu_speed_limit) && slcShowSign) {
- p.setPen(Qt::NoPen);
- p.setBrush(whiteColor());
- p.drawEllipse(sign_rect);
- p.setPen(QPen(Qt::red, 20));
- p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16));
-
- p.setFont(InterFont((speedLimitStrSlc.size() >= 3) ? 60 : 70, QFont::Bold));
- if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255));
- else p.setPen(blackColor());
- p.drawText(sign_rect, Qt::AlignCenter, speedLimitStrSlc);
-
- // Speed limit offset value
- p.setFont(InterFont(slcSubTextSize, QFont::Bold));
- p.setPen(blackColor());
- p.drawText(sign_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText);
- }
-
// current speed
- if (!hideVEgoUi) {
- p.setFont(InterFont(176, QFont::Bold));
- drawColoredText(p, rect().center().x(), 210, speedStr, brakeLights ? QColor(0xff, 0, 0, 255) : QColor(0xff, 0xff, 0xff, 255));
- p.setFont(InterFont(66));
- drawText(p, rect().center().x(), 290, speedUnit, 200);
- }
-
- if (!reversing) {
- // ####### 1 ROW #######
- QRect bar_rect1(rect().left(), rect().bottom() - 60, rect().width(), 61);
- if (!splitPanelVisible && devUiInfo == 2) {
- p.setPen(Qt::NoPen);
- p.setBrush(QColor(0, 0, 0, 100));
- p.drawRect(bar_rect1);
- drawNewDevUi2(p, bar_rect1.left(), bar_rect1.center().y());
- }
-
- // ####### 1 COLUMN ########
- QRect rc2(rect().right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 152);
- if (devUiInfo != 0) {
- drawRightDevUi(p, rect().right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rc2.height());
- }
-
- int rn_btn = 0;
- rn_btn = !splitPanelVisible && devUiInfo == 2 ? 35 : 0;
- rn_offset = rn_btn;
-
- // Stand Still Timer
- if (standStillTimer && standStill && !splitPanelVisible) {
- drawStandstillTimer(p, rect().right() - 650, 30 + 160 + 250);
- }
-
- // V-TSC
- if (showDebugUI && showVTC) {
- drawVisionTurnControllerUI(p, rect().right() - 184 - (UI_BORDER_SIZE * 1.5), int(UI_BORDER_SIZE * 1.5), 184, vtcColor, vtcSpeed, 100);
- }
-
- // Bottom bar road name
- if (showDebugUI && !roadName.isEmpty()) {
- int font_size = splitPanelVisible ? 38 : 50;
- int h = splitPanelVisible ? 18 : 26;
- p.setFont(InterFont(font_size, QFont::Bold));
- drawRoadNameText(p, rect().center().x(), h, roadName, QColor(255, 255, 255, 255));
- }
-
- // Turn Speed Sign
- if (showTurnSpeedLimit) {
- QRect rc = sign_rect;
- rc.moveTop(sign_rect.bottom() + UI_BORDER_SIZE);
- drawTrunSpeedSign(p, rc, turnSpeedLimit, tscSubText, curveSign, tscActive);
- }
- }
-
- // E2E Status
- if (e2eLongAlertUi && e2eState != 0) {
- drawE2eStatus(p, UI_BORDER_SIZE * 2 + 190, 45, 150, 150, e2eState);
- }
-
- if (!hideBottomIcons && featureStatusToggle) {
- int x = UI_BORDER_SIZE * 2 + (rightHandDM ? 600 : 370);
- int feature_status_text_x = rightHandDM ? rect().right() - x : x;
- drawFeatureStatusText(p, feature_status_text_x, rect().bottom() - 160 - rn_offset);
- }
+ p.setFont(InterFont(176, QFont::Bold));
+ drawText(p, rect().center().x(), 210, speedStr);
+ p.setFont(InterFont(66));
+ drawText(p, rect().center().x(), 290, speedUnit, 200);
p.restore();
}
@@ -569,711 +121,6 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t
p.drawText(real_rect.x(), real_rect.bottom(), text);
}
-void AnnotatedCameraWidget::drawColoredText(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);
-}
-
-void AnnotatedCameraWidget::drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color) {
- QRect real_rect = p.fontMetrics().boundingRect(text);
- real_rect.moveCenter({x, y});
-
- p.setPen(color);
- p.drawText(real_rect, Qt::AlignCenter, text);
-}
-
-void AnnotatedCameraWidget::drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color) {
- QRect real_rect = p.fontMetrics().boundingRect(text);
- real_rect.moveCenter({x, y});
-
- QRect real_rect_adjusted(real_rect);
- real_rect_adjusted.adjust(-UI_ROAD_NAME_MARGIN_X, 5, UI_ROAD_NAME_MARGIN_X, 0);
- QPainterPath path;
- path.addRoundedRect(real_rect_adjusted, 10, 10);
- p.setPen(Qt::NoPen);
- p.setBrush(QColor(0, 0, 0, 100));
- p.drawPath(path);
-
- p.setPen(color);
- p.drawText(real_rect, Qt::AlignCenter, text);
-}
-
-void AnnotatedCameraWidget::drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color,
- const QString &vision_speed, int alpha) {
- QRect rvtc(x, y, size, size);
- p.setPen(QPen(color, 10));
- p.setBrush(QColor(0, 0, 0, alpha));
- p.drawRoundedRect(rvtc, 20, 20);
- p.setPen(Qt::NoPen);
-
- p.setFont(InterFont(56, QFont::DemiBold));
- drawCenteredText(p, rvtc.center().x(), rvtc.center().y(), vision_speed, color);
-}
-
-void AnnotatedCameraWidget::drawStandstillTimer(QPainter &p, int x, int y) {
- char lab_str[16];
- char val_str[16];
- int minute = (int)(standstillElapsedTime / 60);
- int second = (int)((standstillElapsedTime) - (minute * 60));
-
- if (standStill) {
- snprintf(lab_str, sizeof(lab_str), "STOP");
- snprintf(val_str, sizeof(val_str), "%01d:%02d", minute, second);
- }
-
- p.setFont(InterFont(125, QFont::DemiBold));
- drawColoredText(p, x, y, QString(lab_str), QColor(255, 175, 3, 240));
- p.setFont(InterFont(150, QFont::DemiBold));
- drawColoredText(p, x, y + 150, QString(val_str), QColor(255, 255, 255, 240));
-}
-
-void AnnotatedCameraWidget::drawCircle(QPainter &p, int x, int y, int r, QBrush bg) {
- p.setPen(Qt::NoPen);
- p.setBrush(bg);
- p.drawEllipse(x - r, y - r, 2 * r, 2 * r);
-}
-
-void AnnotatedCameraWidget::drawSpeedSign(QPainter &p, QRect rc, const QString &speed_limit, const QString &sub_text,
- int subtext_size, bool is_map_sourced, bool is_active) {
- const QColor ring_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50);
- const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85);
- const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85);
-
- const int x = rc.center().x();
- const int y = rc.center().y();
- const int r = rc.width() / 2.0f;
-
- drawCircle(p, x, y, r, ring_color);
- drawCircle(p, x, y, int(r * 0.8f), inner_color);
-
- p.setFont(InterFont(89, QFont::Bold));
- drawCenteredText(p, x, y, speed_limit, text_color);
- p.setFont(InterFont(subtext_size, QFont::Bold));
- drawCenteredText(p, x, y + 55, sub_text, text_color);
-
- if (is_map_sourced) {
- p.setPen(Qt::NoPen);
- p.setOpacity(is_active ? 1.0 : 0.3);
- p.drawPixmap(x - subsign_img_size / 2, y - 55 - subsign_img_size / 2, map_img);
- p.setOpacity(1.0);
- }
-}
-
-void AnnotatedCameraWidget::drawTrunSpeedSign(QPainter &p, QRect rc, const QString &turn_speed, const QString &sub_text,
- int curv_sign, bool is_active) {
- const QColor border_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50);
- const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85);
- const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85);
-
- const int x = rc.center().x();
- const int y = 184 * 2 + UI_BORDER_SIZE + 202;
- const int width = 184;
-
- const float stroke_w = 15.0;
- const float cS = stroke_w / 2.0 + 4.5; // half width of the stroke on the corners of the triangle
- const float R = width / 2.0 - stroke_w / 2.0;
- const float A = 0.73205;
- const float h2 = 2.0 * R / (1.0 + A);
- const float h1 = A * h2;
- const float L = 4.0 * R / sqrt(3.0);
-
- // Draw the internal triangle, compensate for stroke width. Needed to improve rendering when in inactive
- // state due to stroke transparency being different from inner transparency.
- QPainterPath path;
- path.moveTo(x, y - R + cS);
- path.lineTo(x - L / 2.0 + cS, y + h1 + h2 - R - stroke_w / 2.0);
- path.lineTo(x + L / 2.0 - cS, y + h1 + h2 - R - stroke_w / 2.0);
- path.lineTo(x, y - R + cS);
- p.setPen(Qt::NoPen);
- p.setBrush(inner_color);
- p.drawPath(path);
-
- // Draw the stroke
- QPainterPath stroke_path;
- stroke_path.moveTo(x, y - R);
- stroke_path.lineTo(x - L / 2.0, y + h1 + h2 - R);
- stroke_path.lineTo(x + L / 2.0, y + h1 + h2 - R);
- stroke_path.lineTo(x, y - R);
- p.setPen(QPen(border_color, stroke_w, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
- p.setBrush(Qt::NoBrush);
- p.drawPath(stroke_path);
-
- // Draw the turn sign
- if (curv_sign != 0) {
- p.setPen(Qt::NoPen);
- p.setOpacity(is_active ? 1.0 : 0.3);
- p.drawPixmap(int(x - (subsign_img_size / 2)), int(y - R + stroke_w + 30), curv_sign > 0 ? left_img : right_img);
- p.setOpacity(1.0);
- }
-
- // Draw the texts.
- p.setFont(InterFont(67, QFont::Bold));
- drawCenteredText(p, x, y + 25, turn_speed, text_color);
- p.setFont(InterFont(22, QFont::Bold));
- drawCenteredText(p, x, y + 65, sub_text, text_color);
-}
-
-// ############################## DEV UI START ##############################
-void AnnotatedCameraWidget::drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2) {
- QFontMetrics fm(p.font());
- QRect init_rect = fm.boundingRect(text1 + " ");
- QRect real_rect = fm.boundingRect(init_rect, 0, text1 + " ");
- real_rect.moveCenter({x, y});
-
- QRect init_rect3 = fm.boundingRect(text3);
- QRect real_rect3 = fm.boundingRect(init_rect3, 0, text3);
- real_rect3.moveTop(real_rect.top());
- real_rect3.moveLeft(real_rect.right() + 135);
-
- QRect init_rect2 = fm.boundingRect(text2);
- QRect real_rect2 = fm.boundingRect(init_rect2, 0, text2);
- real_rect2.moveTop(real_rect.top());
- real_rect2.moveRight(real_rect.right() + 125);
-
- p.setPen(color1);
- p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, text1);
-
- p.setPen(color2);
- p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, text2);
- p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, text3);
-}
-
-int AnnotatedCameraWidget::drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
- p.setFont(InterFont(30 * 2, QFont::Bold));
- drawColoredText(p, x + 92, y + 80, value, color);
-
- p.setFont(InterFont(28, QFont::Bold));
- drawText(p, x + 92, y + 80 + 42, label, 255);
-
- if (units.length() > 0) {
- p.save();
- p.translate(x + 54 + 30 - 3 + 92 + 30, y + 37 + 25);
- p.rotate(-90);
- drawText(p, 0, 0, units, 255);
- p.restore();
- }
-
- return 110;
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getDRel() {
- 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);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getVRel() {
- 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", speedUnit, color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringAngleDeg() {
- QString value = QString("%1%2%3").arg(QString::number(angleSteers, 'f', 1)).arg("°").arg("");
- QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
-
- // Red if large steering angle
- // Orange if moderate steering angle
- if (std::fabs(angleSteers) > 180) {
- color = QColor(255, 0, 0, 255);
- } else if (std::fabs(angleSteers) > 90) {
- color = QColor(255, 188, 0, 255);
- }
-
- return UiElement(value, "REAL STEER", "", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getActualLateralAccel() {
- float actualLateralAccel = (curvature * pow(vEgo, 2)) - (roll * 9.81);
-
- QString value = QString::number(actualLateralAccel, 'f', 2);
- QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
-
- return UiElement(value, "ACTUAL LAT", "m/s²", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringAngleDesiredDeg() {
- QString value = (madsEnabled && latActive) ? QString("%1%2%3").arg(QString::number(steerAngleDesired, 'f', 1)).arg("°").arg("") : "-";
- QColor color = QColor(255, 255, 255, 255);
-
- if (madsEnabled && latActive) {
- // Red if large steering angle
- // Orange if moderate steering angle
- if (std::fabs(angleSteers) > 180) {
- color = QColor(255, 0, 0, 255);
- } else if (std::fabs(angleSteers) > 90) {
- color = QColor(255, 188, 0, 255);
- } else {
- color = QColor(0, 255, 0, 255);
- }
- }
-
- return UiElement(value, "DESIRED STEER", "", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getMemoryUsagePercent() {
- QString value = QString("%1%2").arg(QString::number(memoryUsagePercent, 'd', 0)).arg("%");
- QColor color = (memoryUsagePercent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255);
-
- return UiElement(value, "RAM", "", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getAEgo() {
- QString value = QString::number(aEgo, 'f', 1);
- QColor color = QColor(255, 255, 255, 255);
-
- return UiElement(value, "ACC.", "m/s²", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getVEgoLead() {
- QString value = lead_status ? QString::number((lead_v_rel + vEgo) * (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.", speedUnit, color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getFrictionCoefficientFiltered() {
- QString value = QString::number(frictionCoefficientFiltered, 'f', 3);
- QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
-
- return UiElement(value, "FRIC.", "", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getLatAccelFactorFiltered() {
- QString value = QString::number(latAccelFactorFiltered, 'f', 3);
- QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
-
- return UiElement(value, "L.A.", "m/s²", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringTorqueEps() {
- QString value = QString::number(std::fabs(steeringTorqueEps), 'f', 1);
- QColor color = QColor(255, 255, 255, 255);
-
- return UiElement(value, "E.T.", "N·dm", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getBearingDeg() {
- QString value = (bearingAccuracyDeg != 180.00) ? QString("%1%2%3").arg(QString::number(bearingDeg, 'd', 0)).arg("°").arg("") : "-";
- QColor color = QColor(255, 255, 255, 255);
- QString dir_value;
-
- if (bearingAccuracyDeg != 180.00) {
- if (((bearingDeg >= 337.5) && (bearingDeg <= 360)) || ((bearingDeg >= 0) && (bearingDeg <= 22.5))) {
- dir_value = "N";
- } else if ((bearingDeg > 22.5) && (bearingDeg < 67.5)) {
- dir_value = "NE";
- } else if ((bearingDeg >= 67.5) && (bearingDeg <= 112.5)) {
- dir_value = "E";
- } else if ((bearingDeg > 112.5) && (bearingDeg < 157.5)) {
- dir_value = "SE";
- } else if ((bearingDeg >= 157.5) && (bearingDeg <= 202.5)) {
- dir_value = "S";
- } else if ((bearingDeg > 202.5) && (bearingDeg < 247.5)) {
- dir_value = "SW";
- } else if ((bearingDeg >= 247.5) && (bearingDeg <= 292.5)) {
- dir_value = "W";
- } else if ((bearingDeg > 292.5) && (bearingDeg < 337.5)) {
- dir_value = "NW";
- }
- } else {
- dir_value = "OFF";
- }
-
- return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color);
-}
-
-AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getAltitude() {
- QString value = (gpsAccuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-";
- QColor color = QColor(255, 255, 255, 255);
-
- return UiElement(value, "ALT.", "m", color);
-}
-
-void AnnotatedCameraWidget::drawRightDevUi(QPainter &p, int x, int y) {
- int rh = 5;
- int ry = y;
-
- // Add Relative Distance to Primary Lead Car
- // Unit: Meters
- UiElement dRelElement = getDRel();
- rh += drawDevUiElementRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color);
- ry = y + rh;
-
- // Add Relative Velocity vs Primary Lead Car
- // Unit: kph if metric, else mph
- UiElement vRelElement = getVRel();
- rh += drawDevUiElementRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color);
- ry = y + rh;
-
- // Add Real Steering Angle
- // Unit: Degrees
- UiElement steeringAngleDegElement = getSteeringAngleDeg();
- rh += drawDevUiElementRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color);
- ry = y + rh;
-
- if (lateralState == "torque") {
- // Add Actual Lateral Acceleration (roll compensated) when using Torque
- // Unit: m/s²
- UiElement actualLateralAccelElement = getActualLateralAccel();
- rh += drawDevUiElementRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color);
- } else {
- // Add Desired Steering Angle when using PID
- // Unit: Degrees
- UiElement steeringAngleDesiredDegElement = getSteeringAngleDesiredDeg();
- rh += drawDevUiElementRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color);
- }
- ry = y + rh;
-
- // Add Device Memory (RAM) Usage
- // Unit: Percent
- UiElement memoryUsagePercentElement = getMemoryUsagePercent();
- rh += drawDevUiElementRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color);
- ry = y + rh;
-
- rh += 25;
- p.setBrush(QColor(0, 0, 0, 0));
- QRect ldu(x, y, 184, rh);
-}
-
-int AnnotatedCameraWidget::drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
- p.setFont(InterFont(38, QFont::Bold));
- drawCenteredLeftText(p, x, y, label, whiteColor(), value, units, color);
-
- return 430;
-}
-
-void AnnotatedCameraWidget::drawNewDevUi2(QPainter &p, int x, int y) {
- int rw = 90;
-
- // Add Acceleration from Car
- // Unit: Meters per Second Squared
- UiElement aEgoElement = getAEgo();
- rw += drawNewDevUiElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color);
-
- // Add Velocity of Primary Lead Car
- // Unit: kph if metric, else mph
- UiElement vEgoLeadElement = getVEgoLead();
- rw += drawNewDevUiElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color);
-
- if (torquedUseParams) {
- // Add Friction Coefficient Raw from torqued
- // Unit: None
- UiElement frictionCoefficientFilteredElement = getFrictionCoefficientFiltered();
- rw += drawNewDevUiElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color);
-
- // Add Lateral Acceleration Factor Raw from torqued
- // Unit: m/s²
- UiElement latAccelFactorFilteredElement = getLatAccelFactorFiltered();
- rw += drawNewDevUiElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color);
- } else {
- // Add Steering Torque from Car EPS
- // Unit: Newton Meters
- UiElement steeringTorqueEpsElement = getSteeringTorqueEps();
- rw += drawNewDevUiElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color);
-
- // Add Bearing Degree and Direction from Car (Compass)
- // Unit: Meters
- UiElement bearingDegElement = getBearingDeg();
- rw += drawNewDevUiElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color);
- }
-
- // Add Altitude of Current Location
- // Unit: Meters
- UiElement altitudeElement = getAltitude();
- rw += drawNewDevUiElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color);
-}
-
-// ############################## DEV UI END ##############################
-
-void AnnotatedCameraWidget::drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status) {
- QColor status_color;
- QRect e2eStatusIcon(x, y, w, h);
- p.setPen(Qt::NoPen);
- p.setBrush(QBrush(blackColor(70)));
- p.drawEllipse(e2eStatusIcon);
- e2eStatusIcon -= QMargins(25, 25, 25, 25);
- p.setPen(Qt::NoPen);
- if (e2e_long_status == 2) {
- status_color = QColor::fromRgbF(0.0, 1.0, 0.0, 0.9);
- } else if (e2e_long_status == 1) {
- status_color = QColor::fromRgbF(1.0, 0.0, 0.0, 0.9);
- }
- p.setBrush(QBrush(status_color));
- p.drawEllipse(e2eStatusIcon);
-}
-
-void AnnotatedCameraWidget::drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) {
- painter.setRenderHint(QPainter::Antialiasing, true);
-
- QColor circle_color, circle_color_0, circle_color_1;
- QColor arrow_color, arrow_color_0, arrow_color_1;
- if ((left_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) {
- circle_color_0 = QColor(164, 0, 1);
- circle_color_1 = QColor(204, 0, 1);
- arrow_color_0 = QColor(72, 1, 1);
- arrow_color_1 = QColor(255, 255, 255);
- } else {
- circle_color_0 = QColor(22, 156, 69);
- circle_color_1 = QColor(30, 215, 96);
- arrow_color_0 = QColor(9, 56, 27);
- arrow_color_1 = QColor(255, 255, 255);
- }
-
- if (state == 1) {
- circle_color = circle_color_1;
- arrow_color = arrow_color_1;
- } else if (state == 0) {
- circle_color = circle_color_0;
- arrow_color = arrow_color_0;
- }
-
- // Draw the circle
- int circleX = x;
- int circleY = y;
- painter.setPen(Qt::NoPen);
- painter.setBrush(circle_color);
- painter.drawEllipse(circleX, circleY, circle_size, circle_size);
-
- // Draw the arrow
- int arrowSize = 50;
- int arrowX = circleX + (circle_size - arrowSize) / 4;
- int arrowY = circleY + (circle_size - arrowSize) / 2;
- painter.setPen(Qt::NoPen);
- painter.setBrush(arrow_color);
-
- // Draw the arrow shape
- QPolygon arrowPolygon;
- arrowPolygon << QPoint(arrowX + 10, arrowY + arrowSize / 2)
- << QPoint(arrowX + arrowSize - 3, arrowY)
- << QPoint(arrowX + arrowSize, arrowY)
- << QPoint(arrowX + arrowSize, arrowY + arrowSize)
- << QPoint(arrowX + arrowSize - 3, arrowY + arrowSize)
- << QPoint(arrowX + 10, arrowY + arrowSize / 2);
- painter.drawPolygon(arrowPolygon);
-
- // Draw the tail rectangle
- int tailWidth = arrowSize / 2.25;
- int tailHeight = arrowSize / 2;
- QRect tailRect(arrowX + arrowSize - 3, arrowY + arrowSize / 4, tailWidth, tailHeight);
- painter.fillRect(tailRect, arrow_color);
-}
-
-void AnnotatedCameraWidget::drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) {
- painter.setRenderHint(QPainter::Antialiasing, true);
-
- QColor circle_color, circle_color_0, circle_color_1;
- QColor arrow_color, arrow_color_0, arrow_color_1;
- if ((right_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) {
- circle_color_0 = QColor(164, 0, 1);
- circle_color_1 = QColor(204, 0, 1);
- arrow_color_0 = QColor(72, 1, 1);
- arrow_color_1 = QColor(255, 255, 255);
- } else {
- circle_color_0 = QColor(22, 156, 69);
- circle_color_1 = QColor(30, 215, 96);
- arrow_color_0 = QColor(9, 56, 27);
- arrow_color_1 = QColor(255, 255, 255);
- }
-
- if (state == 1) {
- circle_color = circle_color_1;
- arrow_color = arrow_color_1;
- } else if (state == 0) {
- circle_color = circle_color_0;
- arrow_color = arrow_color_0;
- }
-
-
- // Draw the circle
- int circleX = x;
- int circleY = y;
- painter.setPen(Qt::NoPen);
- painter.setBrush(circle_color);
- painter.drawEllipse(circleX, circleY, circle_size, circle_size);
-
- // Draw the arrow
- int arrowSize = 50;
- int arrowX = circleX + (circle_size - arrowSize) / 2 + (arrowSize / 2.5) - 3;
- int arrowY = circleY + (circle_size - arrowSize) / 2;
- painter.setPen(Qt::NoPen);
- painter.setBrush(arrow_color);
-
- // Draw the arrow shape
- QPolygon arrowPolygon;
- arrowPolygon << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2)
- << QPoint(arrowX + 3, arrowY)
- << QPoint(arrowX, arrowY)
- << QPoint(arrowX, arrowY + arrowSize)
- << QPoint(arrowX + 3, arrowY + arrowSize)
- << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2);
- painter.drawPolygon(arrowPolygon);
-
- // Draw the tail rectangle
- int tailWidth = arrowSize / 2.25;
- int tailHeight = arrowSize / 2;
- QRect tailRect(arrowX - tailWidth + 3, arrowY + arrowSize / 4, tailWidth, tailHeight);
- painter.fillRect(tailRect, arrow_color);
-}
-
-int AnnotatedCameraWidget::blinkerPulse(int frame) {
- if (frame % UI_FREQ < (UI_FREQ / 2)) {
- blinker_state = 1;
- } else {
- blinker_state = 0;
- }
-
- return blinker_state;
-}
-
-void AnnotatedCameraWidget::speedLimitSignPulse(int frame) {
- if (frame % UI_FREQ < (UI_FREQ / 2.5)) {
- slcShowSign = false;
- } else {
- slcShowSign = true;
- }
-}
-
-void AnnotatedCameraWidget::drawFeatureStatusText(QPainter &p, int x, int y) {
- const FeatureStatusText feature_text;
- const FeatureStatusColor feature_color;
- const QColor text_color = whiteColor();
- const QColor shadow_color = blackColor(38);
- const int text_height = 34;
- const int drop_shadow_size = 2;
- const int eclipse_x_offset = 25;
- const int eclipse_y_offset = 20;
- const int w = 16;
- const int h = 16;
-
- const bool longitudinal = hasLongitudinalControl(car_params);
-
- p.setFont(InterFont(32, QFont::Bold));
-
- // Define a function to draw a feature status button
- auto drawFeatureStatusElement = [&](int value, const QStringList& text_list, const QStringList& color_list, bool condition, const QString& off_text, const QString& label) {
- std::pair feature_status = getFeatureStatus(value, text_list, color_list, condition, off_text);
- QRect btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h);
- QRect btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h);
- p.setPen(Qt::NoPen);
- p.setBrush(shadow_color);
- p.drawEllipse(btn_shadow);
- p.setBrush(feature_status.second);
- p.drawEllipse(btn);
- QString status_text;
- status_text.sprintf("%s: %s", label.toStdString().c_str(), (feature_status.first).toStdString().c_str());
- p.setPen(QPen(shadow_color, 2));
- p.drawText(x + drop_shadow_size, y + drop_shadow_size, status_text);
- p.setPen(QPen(text_color, 2));
- p.drawText(x, y, status_text);
- y += text_height;
- };
-
- // Driving Personality / Gap Adjust Cruise
- if (longitudinal) {
- drawFeatureStatusElement(longitudinalPersonality, feature_text.gac_list_text, feature_color.gac_list_color, longitudinal, "N/A", "GAP");
- }
-
- // Dynamic Lane Profile
- if (drivingModelGen == cereal::ModelGeneration::ONE) {
- drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP");
- }
-
- // TODO: Add toggle variables to cereal, and parse from cereal
- if (longitudinal) {
- QColor dec_color((cruiseStateEnabled && dynamicExperimentalControlToggle) ? "#4bff66" : "#ffffff");
- QRect dec_btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h);
- QRect dec_btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h);
- p.setPen(Qt::NoPen);
- p.setBrush(shadow_color);
- p.drawEllipse(dec_btn_shadow);
- p.setBrush(dec_color);
- p.drawEllipse(dec_btn);
- QString dec_status_text;
- dec_status_text.sprintf("DEC: %s\n", dynamicExperimentalControlToggle ? (experimentalMode ? QString(mpcMode).toStdString().c_str() : QString("Inactive").toStdString().c_str()) : "OFF");
- p.setPen(QPen(shadow_color, 2));
- p.drawText(x + drop_shadow_size, y + drop_shadow_size, dec_status_text);
- p.setPen(QPen(text_color, 2));
- p.drawText(x, y, dec_status_text);
- y += text_height;
- }
-
- // TODO: Add toggle variables to cereal, and parse from cereal
- // Speed Limit Control
- if (longitudinal || !car_params.getPcmCruiseSpeed()) {
- drawFeatureStatusElement(int(slcState), feature_text.slc_list_text, feature_color.slc_list_color, speedLimitControlToggle, "OFF", "SLC");
- }
-}
-
-void AnnotatedCameraWidget::speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin) {
- // PRE ACTIVE
- if (slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE) {
- int set_speed = std::nearbyint(setSpeed);
- int speed_limit_offsetted = std::nearbyint(speedLimitSLC + speedLimitSLCOffset);
-
- // Calculate the vertical offset using a sinusoidal function for smooth bouncing
- double bounce_frequency = 2.0 * M_PI / 20.0; // 20 frames for one full oscillation
- int bounce_offset = 20 * sin(speed_limit_frame * bounce_frequency); // Adjust the amplitude (20 pixels) as needed
-
- if (set_speed < speed_limit_offsetted) {
- QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - plus_arrow_up_img.height() / 2 + bounce_offset);
- p.drawPixmap(iconPosition, plus_arrow_up_img);
- } else if (set_speed > speed_limit_offsetted) {
- QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - minus_arrow_down_img.height() / 2 - bounce_offset);
- p.drawPixmap(iconPosition, minus_arrow_down_img);
- }
-
- speed_limit_frame++;
- speedLimitSignPulse(speed_limit_frame);
- }
-
- // current speed over speed limit
- else if (overSpeedLimit && speedLimitWarningFlash) {
- speed_limit_frame++;
- speedLimitSignPulse(speed_limit_frame);
- }
-
- else {
- speed_limit_frame = 0;
- slcShowSign = true;
- }
-}
-
-
void AnnotatedCameraWidget::initializeGL() {
CameraWidget::initializeGL();
qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION));
@@ -1309,29 +156,12 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
const UIScene &scene = s->scene;
SubMaster &sm = *(s->sm);
- const auto car_state = sm["carState"].getCarState();
-
- // Shane's colored lanelines
+ // lanelines
for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) {
- if (i == 1 || i == 2) {
- // TODO: can we just use the projected vertices somehow?
- const cereal::XYZTData::Reader &line = (*s->sm)["modelV2"].getModelV2().getLaneLines()[i];
- const float default_pos = 1.4; // when lane poly isn't available
- const float lane_pos = line.getY().size() > 0 ? std::abs(line.getY()[5]) : default_pos; // get redder when line is closer to car
- float hue = 332.5 * lane_pos - 332.5; // equivalent to {1.4, 1.0}: {133, 0} (green to red)
- hue = std::fmin(133, fmax(0, hue)) / 360.; // clip and normalize
- painter.setBrush(QColor::fromHslF(hue, 1.0, 0.50, std::clamp(scene.lane_line_probs[i], 0.0, 0.7)));
- } else {
- painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7)));
- }
+ painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7)));
painter.drawPolygon(scene.lane_line_vertices[i]);
}
- // TODO: Fix empty spaces when curiving back on itself
- painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.2));
- if (left_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[0]);
- if (right_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[1]);
-
// road edges
for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) {
painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0)));
@@ -1340,70 +170,42 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
// paint path
QLinearGradient bg(0, height(), 0, 0);
- if (madsEnabled || car_state.getCruiseState().getEnabled()) {
- if (steerOverride && latActive) {
- bg.setColorAt(0.0, QColor::fromHslF(20 / 360., 0.94, 0.51, 0.17));
- bg.setColorAt(0.5, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.17));
- bg.setColorAt(1.0, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.0));
- } else if (!(latActive || car_state.getCruiseState().getEnabled())) {
- bg.setColorAt(0, whiteColor());
- bg.setColorAt(1, whiteColor(0));
- } else if (sm["controlsState"].getControlsState().getExperimentalMode()) {
- // The first half of track_vertices are the points for the right side of the path
- const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX();
- const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size());
+ if (sm["controlsState"].getControlsState().getExperimentalMode()) {
+ // The first half of track_vertices are the points for the right side of the path
+ const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX();
+ const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size());
- for (int i = 0; i < max_len; ++i) {
- // Some points are out of frame
- if (scene.track_vertices[i].y() < 0 || scene.track_vertices[i].y() > height()) continue;
+ for (int i = 0; i < max_len; ++i) {
+ // Some points are out of frame
+ int track_idx = (scene.track_vertices.length() / 2) - i; // flip idx to start from top
+ if (scene.track_vertices[track_idx].y() < 0 || scene.track_vertices[track_idx].y() > height()) continue;
- // Flip so 0 is bottom of frame
- float lin_grad_point = (height() - scene.track_vertices[i].y()) / height();
+ // Flip so 0 is bottom of frame
+ float lin_grad_point = (height() - scene.track_vertices[track_idx].y()) / height();
- // speed up: 120, slow down: 0
- float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
- // FIXME: painter.drawPolygon can be slow if hue is not rounded
- path_hue = int(path_hue * 100 + 0.5) / 100;
+ // speed up: 120, slow down: 0
+ float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
+ // FIXME: painter.drawPolygon can be slow if hue is not rounded
+ path_hue = int(path_hue * 100 + 0.5) / 100;
- float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
- float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
- float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
- bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
+ float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
+ float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
+ float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
+ bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
- // Skip a point, unless next is last
- i += (i + 2) < max_len ? 1 : 0;
- }
-
- } else {
- bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4));
- bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35));
- bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0));
+ // Skip a point, unless next is last
+ i += (i + 2) < max_len ? 1 : 0;
}
+
} else {
- bg.setColorAt(0.0, whiteColor(102));
- bg.setColorAt(0.5, whiteColor(89));
- bg.setColorAt(1.0, whiteColor(0));
+ bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4));
+ bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35));
+ bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0));
}
painter.setBrush(bg);
painter.drawPolygon(scene.track_vertices);
- // create path combining track vertices and track edge vertices
- QPainterPath path;
- path.addPolygon(scene.track_vertices);
- path.addPolygon(scene.track_edge_vertices);
-
- // paint path edges
- QLinearGradient pe(0, height(), 0, height() / 4);
- if (!scene.dynamic_lane_profile_status) {
- pe.setColorAt(0.0, QColor::fromHslF(240 / 360., 0.94, 0.51, 1.0));
- pe.setColorAt(0.5, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.5));
- pe.setColorAt(1.0, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.0));
-
- painter.setBrush(pe);
- painter.drawPath(path);
- }
-
painter.restore();
}
@@ -1415,7 +217,7 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s)
// base icon
int offset = UI_BORDER_SIZE + btn_size / 2;
int x = rightHandDM ? width() - offset : offset;
- int y = height() - offset - rn_offset;
+ int y = height() - offset;
float opacity = dmActive ? 0.65 : 0.2;
drawIcon(painter, QPoint(x, y), dm_img, blackColor(70), opacity);
@@ -1448,49 +250,7 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s)
painter.restore();
}
-void AnnotatedCameraWidget::rocketFuel(QPainter &p) {
-
- static const int ct_n = 1;
- static float ct;
-
- int rect_w = rect().width();
- int rect_h = rect().height();
-
- const int n = 15 + 1; //Add one off screen due to timing issues
- static float t[n];
- int dim_n = (sin(ct / 5) + 1) * (n - 0.01);
- t[dim_n] = 1.0;
- t[(int)(ct/ct_n)] = 1.0;
-
- UIState *s = uiState();
- float vc_accel0 = (*s->sm)["carState"].getCarState().getAEgo();
- static float vc_accel;
- vc_accel = vc_accel + (vc_accel0 - vc_accel) / 5;
- float hha = 0;
- if (vc_accel > 0) {
- hha = 0.85 - 0.1 / vc_accel; // only extend up to 85%
- p.setBrush(QColor(0, 245, 0, 200));
- }
- if (vc_accel < 0) {
- hha = 0.85 + 0.1 / vc_accel; // only extend up to 85%
- p.setBrush(QColor(245, 0, 0, 200));
- }
- if (hha < 0) {
- hha = 0;
- }
- hha = hha * rect_h;
- float wp = 28;
- if (vc_accel > 0) {
- QRect ra = QRect(rect_w - wp, rect_h / 2 - hha / 2, wp, hha / 2);
- p.drawRect(ra);
- } else {
- QRect ra = QRect(rect_w - wp, rect_h / 2, wp, hha / 2);
- p.drawRect(ra);
- }
-}
-
-void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd,
- int num, float radar_d_rel, float v_ego, float radar_v_rel, int chevron_data, bool isMetric) {
+void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) {
painter.save();
const float speedBuff = 10.;
@@ -1523,47 +283,15 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState
painter.setBrush(redColor(fillAlpha));
painter.drawPolygon(chevron, std::size(chevron));
- if (num == 0) { // Display metrics to the 0th lead car
- QStringList chevron_text[2];
- if (chevron_data == 1 || chevron_data == 3) {
- chevron_text[0].append(QString::number(radar_d_rel,'f', 0) + " " + "m");
- }
- if (chevron_data == 2 || chevron_data == 3) {
- chevron_text[chevron_data - 2].append(QString::number((radar_v_rel + v_ego) * (isMetric ? MS_TO_KPH : MS_TO_MPH),'f', 0) + " " + (isMetric ? "km/h" : "mph"));
- }
-
- int str_w = 200; // Width of the text box, might need adjustment
- int str_h = 50; // Height of the text box, adjust as necessary
- painter.setFont(InterFont(45, QFont::Bold));
- // Calculate the center of the chevron and adjust the text box position
- float text_y = y + sz + 12; // Position the text at the bottom of the chevron
- QRect textRect(x - str_w / 2, text_y, str_w, str_h); // Adjust the rectangle to center the text horizontally at the chevron's bottom
- QPoint shadow_offset(2, 2);
- for (int i = 0; i < 2; ++i) {
- if (!chevron_text[i].isEmpty()) {
- painter.setPen(QColor(0x0, 0x0, 0x0, 200)); // Draw shadow
- painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y() + i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0));
- painter.setPen(QColor(0xff, 0xff, 0xff)); // Draw text
- painter.drawText(textRect.translated(0, i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0));
- painter.setPen(Qt::NoPen); // Reset pen to default
- }
- }
- }
-
painter.restore();
}
void AnnotatedCameraWidget::paintGL() {
-}
-
-void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
UIState *s = uiState();
SubMaster &sm = *(s->sm);
const double start_draw_t = millis_since_boot();
const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2();
- QPainter painter(this);
-
// draw camera frame
{
std::lock_guard lk(frame_lock);
@@ -1585,10 +313,9 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD);
if (has_wide_cam) {
float v_ego = sm["carState"].getCarState().getVEgo();
- float steer_angle = sm["carState"].getCarState().getSteeringAngleDeg();
- if ((v_ego < 10) || available_streams.size() == 1 || (std::fabs(steer_angle) > 45)) {
+ if ((v_ego < 10) || available_streams.size() == 1) {
wide_cam_requested = true;
- } else if ((v_ego > 15) && (std::fabs(steer_angle) < 30)) {
+ } else if (v_ego > 15) {
wide_cam_requested = false;
}
wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode();
@@ -1597,10 +324,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
}
CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD);
- if (reversing && s->scene.reverse_dm_cam) {
- CameraWidget::setStreamType(VISION_STREAM_DRIVER, s->scene.reverse_dm_cam);
- }
-
s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD;
if (s->scene.calibration_valid) {
auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib;
@@ -1608,12 +331,11 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
} else {
CameraWidget::updateCalibration(DEFAULT_CALIBRATION);
}
- painter.beginNativePainting();
CameraWidget::setFrameId(model.getFrameId());
CameraWidget::paintGL();
- painter.endNativePainting();
}
+ QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
@@ -1626,17 +348,12 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
update_leads(s, radar_state, model.getPosition());
auto lead_one = radar_state.getLeadOne();
auto lead_two = radar_state.getLeadTwo();
- float v_ego = sm["carState"].getCarState().getVEgo();
- float radar_d_rel = radar_state.getLeadOne().getDRel();
- float radar_v_rel = radar_state.getLeadOne().getVRel();
if (lead_one.getStatus()) {
- drawLead(painter, lead_one, s->scene.lead_vertices[0], 0, radar_d_rel, v_ego, radar_v_rel, s->scene.chevron_data, s->scene.is_metric);
+ drawLead(painter, lead_one, s->scene.lead_vertices[0]);
}
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
- drawLead(painter, lead_two, s->scene.lead_vertices[1], 1, radar_d_rel, v_ego, radar_v_rel, s->scene.chevron_data, s->scene.is_metric);
+ drawLead(painter, lead_two, s->scene.lead_vertices[1]);
}
-
- rocketFuel(painter);
}
}
@@ -1648,21 +365,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
drawHud(painter);
- if (left_blinker || right_blinker) {
- blinker_frame++;
- int state = blinkerPulse(blinker_frame);
- int blinker_x = splitPanelVisible ? 150 : 180;
- int blinker_y = splitPanelVisible ? 220 : 90;
- if (left_blinker) {
- drawLeftTurnSignal(painter, rect().center().x() - (blinker_x + blinker_size), blinker_y, blinker_size, state);
- }
- if (right_blinker) {
- drawRightTurnSignal(painter, rect().center().x() + blinker_x, blinker_y, blinker_size, state);
- }
- } else {
- blinker_frame = 0;
- }
-
double cur_draw_t = millis_since_boot();
double dt = cur_draw_t - prev_draw_t;
double fps = fps_filter.update(1. / dt * 1000);
@@ -1676,11 +378,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
auto m = msg.initEvent().initUiDebug();
m.setDrawTimeMillis(cur_draw_t - start_draw_t);
pm->send("uiDebug", msg);
-
- MessageBuilder e2e_long_msg;
- auto e2eLongStatus = e2e_long_msg.initEvent().initE2eLongStateSP();
- e2eLongStatus.setStatus(e2eStatus);
- e2e_state->send("e2eLongStateSP", e2e_long_msg);
}
void AnnotatedCameraWidget::showEvent(QShowEvent *event) {
diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h
index 94bd12c8c0..465ba23e04 100644
--- a/selfdrive/ui/qt/onroad/annotated_camera.h
+++ b/selfdrive/ui/qt/onroad/annotated_camera.h
@@ -2,14 +2,10 @@
#include
#include
-#include
#include "selfdrive/ui/qt/onroad/buttons.h"
#include "selfdrive/ui/qt/widgets/cameraview.h"
-const int subsign_img_size = 35;
-const int blinker_size = 120;
-
class AnnotatedCameraWidget : public CameraWidget {
Q_OBJECT
@@ -17,68 +13,8 @@ public:
explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0);
void updateState(const UIState &s);
- MapSettingsButton *map_settings_btn;
- OnroadSettingsButton *onroad_settings_btn;
-
private:
void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255);
- void drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color);
- void drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, const QString &speed,
- int alpha);
- void drawCircle(QPainter &p, int x, int y, int r, QBrush bg);
- void drawSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int subtext_size,
- bool is_map_sourced, bool is_active);
- void drawTrunSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int curv_sign,
- bool is_active);
-
- void drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color);
- void drawStandstillTimer(QPainter &p, int x, int y);
-
- // ############################## DEV UI START ##############################
- void drawRightDevUi(QPainter &p, int x, int y);
- int drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
- int drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
- void drawNewDevUi2(QPainter &p, int x, int y);
- void drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2);
- struct UiElement {
- QString value;
- QString label;
- QString units;
- QColor color;
-
- 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) {}
- };
-
- UiElement getDRel();
- UiElement getVRel();
- UiElement getSteeringAngleDeg();
- UiElement getActualLateralAccel();
- UiElement getSteeringAngleDesiredDeg();
- UiElement getMemoryUsagePercent();
-
- UiElement getAEgo();
- UiElement getVEgoLead();
- UiElement getFrictionCoefficientFiltered();
- UiElement getLatAccelFactorFiltered();
- UiElement getSteeringTorqueEps();
- UiElement getBearingDeg();
- UiElement getAltitude();
-
- // ############################## DEV UI END ##############################
-
- void drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status);
-
- void drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state);
- void drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state);
- int blinkerPulse(int frame);
- void updateButtonsLayout(bool is_rhd);
-
- void drawFeatureStatusText(QPainter &p, int x, int y);
- void speedLimitSignPulse(int frame);
- void speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin);
- void mousePressEvent(QMouseEvent* e) override;
- void drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color);
QVBoxLayout *main_layout;
ExperimentalButton *experimental_btn;
@@ -86,15 +22,12 @@ private:
float speed;
QString speedUnit;
float setSpeed;
- float speedLimit;
bool is_cruise_set = false;
bool is_metric = false;
bool dmActive = false;
bool hideBottomIcons = false;
bool rightHandDM = false;
float dm_fade_state = 1.0;
- bool has_us_speed_limit = false;
- bool has_eu_speed_limit = false;
bool v_ego_cluster_seen = false;
int status = STATUS_DISENGAGED;
std::unique_ptr pm;
@@ -102,125 +35,19 @@ private:
int skip_frame_count = 0;
bool wide_cam_requested = false;
- Params params;
- QHBoxLayout *buttons_layout;
- QPixmap map_img;
- QPixmap left_img;
- QPixmap right_img;
- bool left_blindspot = false;
- bool right_blindspot = false;
- std::unique_ptr e2e_state;
-
- bool steerOverride = false;
- bool gasOverride = false;
- bool latActive = false;
- bool madsEnabled = false;
-
- bool brakeLights;
-
- bool standStillTimer;
- bool standStill;
- float standstillElapsedTime;
-
- bool showVTC = false;
- QString vtcSpeed;
- QColor vtcColor;
-
- bool showDebugUI = false;
-
- QString roadName = "";
-
- bool showSpeedLimit = false;
- float speedLimitSLC;
- float speedLimitSLCOffset;
- float speedLimitWarningOffset;
- QString slcSubText;
- float slcSubTextSize = 0.0;
- bool overSpeedLimit;
- bool mapSourcedSpeedLimit = false;
- bool slcActive = false;
-
- bool showTurnSpeedLimit = false;
- QString turnSpeedLimit;
- QString tscSubText;
- bool tscActive = false;
- int curveSign = 0;
-
- bool hideVEgoUi;
-
- bool splitPanelVisible;
-
- // ############################## DEV UI START ##############################
- bool lead_status;
- float lead_d_rel = 0;
- float lead_v_rel = 0;
- QString lateralState;
- float angleSteers = 0;
- float steerAngleDesired = 0;
- 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;
- // ############################## DEV UI END ##############################
-
- float btnPerc;
-
- bool reversing;
-
- int e2eState;
- int e2eStatus;
-
- bool left_blinker, right_blinker, lane_change_edge_block;
- int blinker_frame;
- int blinker_state = 0;
-
- cereal::LongitudinalPlanSP::SpeedLimitControlState slcState;
- int longitudinalPersonality;
- int dynamicLaneProfile;
- QString mpcMode;
-
- int speed_limit_frame;
- bool slcShowSign = true;
- QPixmap plus_arrow_up_img;
- QPixmap minus_arrow_down_img;
- QRect sl_sign_rect;
- int rn_offset = 0;
- bool e2eLongAlertUi, dynamicExperimentalControlToggle, speedLimitControlToggle, speedLimitWarningFlash;
- cereal::CarParams::Reader car_params;
- bool cruiseStateEnabled;
- bool experimentalMode;
-
- bool featureStatusToggle;
-
- cereal::ModelGeneration drivingModelGen;
-
protected:
void paintGL() override;
void initializeGL() override;
void showEvent(QShowEvent *event) override;
void updateFrameMat() override;
void drawLaneLines(QPainter &painter, const UIState *s);
- void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, int num, float radar_d_rel, float v_ego, float radar_v_rel, int chevron_data, bool isMetric);
+ void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd);
void drawHud(QPainter &p);
void drawDriverState(QPainter &painter, const UIState *s);
inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); }
inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); }
inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); }
- void paintEvent(QPaintEvent *event) override;
- void rocketFuel(QPainter &p);
-
double prev_draw_t = 0;
FirstOrderFilter fps_filter;
};
diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc
index fbca42da7b..92bcea11b5 100644
--- a/selfdrive/ui/qt/onroad/buttons.cc
+++ b/selfdrive/ui/qt/onroad/buttons.cc
@@ -15,18 +15,6 @@ void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrus
p.setOpacity(1.0);
}
-static void drawCustomButtonIcon(QPainter &p, const int btn_size_x, const int btn_size_y, const QPixmap &img, const QBrush &bg, float opacity) {
- QPoint center(btn_size_x / 2, btn_size_y / 2);
- p.setRenderHint(QPainter::Antialiasing);
- p.setOpacity(1.0); // bg dictates opacity of ellipse
- p.setPen(Qt::NoPen);
- p.setBrush(bg);
- p.drawEllipse(center, btn_size_x / 2, btn_size_y / 2);
- p.setOpacity(opacity);
- p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img);
- p.setOpacity(1.0);
-}
-
// ExperimentalButton
ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) {
setFixedSize(btn_size, btn_size);
@@ -46,8 +34,7 @@ void ExperimentalButton::changeMode() {
void ExperimentalButton::updateState(const UIState &s) {
const auto cs = (*s.sm)["controlsState"].getControlsState();
- const auto lp_sp = (*s.sm)["longitudinalPlanSP"].getLongitudinalPlanSP();
- bool eng = (cs.getEngageable() || cs.getEnabled()) && !(lp_sp.getVisionTurnControllerState() > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED);
+ bool eng = cs.getEngageable() || cs.getEnabled();
if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) {
engageable = eng;
experimental_mode = cs.getExperimentalMode();
@@ -60,48 +47,3 @@ void ExperimentalButton::paintEvent(QPaintEvent *event) {
QPixmap img = experimental_mode ? experimental_img : engage_img;
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0);
}
-
-
-// MapSettingsButton
-MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) {
- // btn_size: 192 * 80% ~= 152
- // img_size: (152 / 4) * 3 = 114
- setFixedSize(152, 152);
- settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {114, 114});
-
- // hidden by default, made visible if map is created (has prime or mapbox token)
- setVisible(false);
- setEnabled(false);
-}
-
-void MapSettingsButton::paintEvent(QPaintEvent *event) {
- QPainter p(this);
- drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0);
-}
-
-
-// OnroadSettingsButton
-OnroadSettingsButton::OnroadSettingsButton(QWidget *parent) : QPushButton(parent) {
- // btn_size: 192 * 80% ~= 152
- // img_size: (152 / 4) * 3 = 114
- setFixedSize(152, 152);
- settings_img = loadPixmap("../assets/navigation/icon_settings.svg", {114, 114});
-
- // hidden by default, made visible if Driving Personality / GAC, DLP, DEC, or SLC is enabled
- setVisible(false);
- setEnabled(false);
-}
-
-void OnroadSettingsButton::paintEvent(QPaintEvent *event) {
- QPainter p(this);
- drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0);
-}
-
-void OnroadSettingsButton::updateState(const UIState &s) {
- const auto cp = (*s.sm)["carParams"].getCarParams();
- auto dlp_enabled = s.scene.driving_model_generation == cereal::ModelGeneration::ONE;
- bool allow_btn = s.scene.onroad_settings_toggle && (dlp_enabled || hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed());
-
- setVisible(allow_btn);
- setEnabled(allow_btn);
-}
diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h
index feee1ba639..e999480d5c 100644
--- a/selfdrive/ui/qt/onroad/buttons.h
+++ b/selfdrive/ui/qt/onroad/buttons.h
@@ -2,7 +2,11 @@
#include
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
const int btn_size = 192;
const int img_size = (btn_size / 4) * 3;
@@ -14,6 +18,10 @@ public:
explicit ExperimentalButton(QWidget *parent = 0);
void updateState(const UIState &s);
+protected:
+ bool experimental_mode;
+ bool engageable;
+
private:
void paintEvent(QPaintEvent *event) override;
void changeMode();
@@ -21,35 +29,6 @@ private:
Params params;
QPixmap engage_img;
QPixmap experimental_img;
- bool experimental_mode;
- bool engageable;
-};
-
-
-class MapSettingsButton : public QPushButton {
- Q_OBJECT
-
-public:
- explicit MapSettingsButton(QWidget *parent = 0);
-
-private:
- void paintEvent(QPaintEvent *event) override;
-
- QPixmap settings_img;
-};
-
-
-class OnroadSettingsButton : public QPushButton {
- Q_OBJECT
-
-public:
- explicit OnroadSettingsButton(QWidget *parent = 0);
- void updateState(const UIState &s);
-
-private:
- void paintEvent(QPaintEvent *event) override;
-
- QPixmap settings_img;
};
void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity);
diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc
index 7413cdff7e..e93ded6cc2 100644
--- a/selfdrive/ui/qt/onroad/onroad_home.cc
+++ b/selfdrive/ui/qt/onroad/onroad_home.cc
@@ -3,12 +3,6 @@
#include
#include
-#ifdef ENABLE_MAPS
-#include "selfdrive/ui/qt/maps/map_helpers.h"
-#include "selfdrive/ui/qt/maps/map_panel.h"
-#endif
-#include "selfdrive/ui/qt/onroad_settings_panel.h"
-
#include "selfdrive/ui/qt/util.h"
OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
@@ -31,11 +25,6 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
split->insertWidget(0, arCam);
}
- if (getenv("MAP_RENDER_VIEW")) {
- CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this);
- split->insertWidget(0, map_render);
- }
-
stacked_layout->addWidget(split_wrapper);
alerts = new OnroadAlerts(this);
@@ -46,9 +35,12 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
alerts->raise();
setAttribute(Qt::WA_OpaquePaintEvent);
+
+ // We handle the connection of the signals on the derived class
+#ifndef SUNNYPILOT
QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState);
QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition);
- QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindow::primeChanged);
+#endif
}
void OnroadWindow::updateState(const UIState &s) {
@@ -56,12 +48,6 @@ void OnroadWindow::updateState(const UIState &s) {
return;
}
- if (s.scene.map_on_left || s.scene.mapbox_fullscreen) {
- split->setDirection(QBoxLayout::LeftToRight);
- } else {
- split->setDirection(QBoxLayout::RightToLeft);
- }
-
alerts->updateState(s);
nvg->updateState(s);
@@ -73,99 +59,10 @@ void OnroadWindow::updateState(const UIState &s) {
}
}
-
-void OnroadWindow::mousePressEvent(QMouseEvent* e) {
-#ifdef ENABLE_MAPS
- UIState *s = uiState();
- UIScene &scene = s->scene;
- if (map != nullptr && !isOnroadSettingsVisible()) {
- if (wakeScreenTimeout()) {
- // Switch between map and sidebar when using navigate on openpilot
- bool sidebarVisible = geometry().x() > 0;
- bool show_map = uiState()->scene.navigate_on_openpilot_deprecated ? sidebarVisible : !sidebarVisible;
- updateMapSize(scene);
- map->setVisible(show_map && !map->isVisible());
- }
- }
-#endif
- if (onroad_settings != nullptr && !isMapVisible()) {
- if (wakeScreenTimeout()) {
- onroad_settings->setVisible(false);
- }
- }
- // propagation event to parent(HomeWindow)
- QWidget::mousePressEvent(e);
-}
-
-void OnroadWindow::createMapWidget() {
-#ifdef ENABLE_MAPS
- auto m = new MapPanel(get_mapbox_settings());
- map = m;
-
- QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested);
- QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::onroadSettingsPanelNotRequested);
- QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings);
- nvg->map_settings_btn->setEnabled(true);
-
- m->setFixedWidth(uiState()->scene.mapbox_fullscreen ? topWidget(this)->width() :
- topWidget(this)->width() / 2 - UI_BORDER_SIZE);
- split->insertWidget(0, m);
-
- // hidden by default, made visible when navRoute is published
- m->setVisible(false);
-#endif
-}
-
-void OnroadWindow::createOnroadSettingsWidget() {
- auto os = new OnroadSettingsPanel(this);
- onroad_settings = os;
-
- QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindow::onroadSettingsPanelRequested);
- QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindow::mapPanelNotRequested);
- QObject::connect(nvg->onroad_settings_btn, &OnroadSettingsButton::clicked, os, &OnroadSettingsPanel::toggleOnroadSettings);
- nvg->onroad_settings_btn->setEnabled(true);
-
- os->setFixedWidth(topWidget(this)->width() / 2.6 - UI_BORDER_SIZE);
- split->insertWidget(0, os);
-
- // hidden by default
- os->setVisible(false);
-}
-
void OnroadWindow::offroadTransition(bool offroad) {
- if (!offroad) {
-#ifdef ENABLE_MAPS
- if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) {
- createMapWidget();
- }
-#endif
- if (onroad_settings == nullptr) {
- createOnroadSettingsWidget();
- }
- }
-
alerts->clear();
}
-void OnroadWindow::updateMapSize(const UIScene &scene) {
- map->setFixedWidth(scene.mapbox_fullscreen ? topWidget(this)->width() :
- topWidget(this)->width() / 2 - UI_BORDER_SIZE);
- split->insertWidget(0, map);
-}
-
-void OnroadWindow::primeChanged(bool prime) {
-#ifdef ENABLE_MAPS
- if (map && (!prime && MAPBOX_TOKEN.isEmpty())) {
- nvg->map_settings_btn->setEnabled(false);
- nvg->map_settings_btn->setVisible(false);
- map->deleteLater();
- map = nullptr;
- } else if (!map && (prime || !MAPBOX_TOKEN.isEmpty())) {
- createMapWidget();
- }
-#endif
-}
-
void OnroadWindow::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255));
diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h
index 1beadcd13b..4ac37678fa 100644
--- a/selfdrive/ui/qt/onroad/onroad_home.h
+++ b/selfdrive/ui/qt/onroad/onroad_home.h
@@ -1,51 +1,29 @@
#pragma once
-#include "common/params.h"
#include "selfdrive/ui/qt/onroad/alerts.h"
+
+#if SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h"
+#define AnnotatedCameraWidget AnnotatedCameraWidgetSP
+#define UIState UIStateSP
+#else
#include "selfdrive/ui/qt/onroad/annotated_camera.h"
+#endif
class OnroadWindow : public QWidget {
Q_OBJECT
public:
OnroadWindow(QWidget* parent = 0);
- bool isMapVisible() const { return map && map->isVisible(); }
- void showMapPanel(bool show) { if (map) map->setVisible(show); }
- bool isOnroadSettingsVisible() const { return onroad_settings && onroad_settings->isVisible(); }
- bool isMapAvailable() const { return map; }
- void mapPanelNotRequested() { if (map) map->setVisible(false); }
- void onroadSettingsPanelNotRequested() { if (onroad_settings) onroad_settings->setVisible(false); }
-
- bool wakeScreenTimeout() {
- if ((uiState()->scene.sleep_btn != 0 && uiState()->scene.sleep_btn_opacity != 0) ||
- (uiState()->scene.sleep_time != 0 && uiState()->scene.onroadScreenOff != -2)) {
- return true;
- }
- return false;
- }
-
-signals:
- void mapPanelRequested();
- void onroadSettingsPanelRequested();
-
-private:
- void createMapWidget();
- void createOnroadSettingsWidget();
- void paintEvent(QPaintEvent *event);
- void mousePressEvent(QMouseEvent* e) override;
+protected:
+ void paintEvent(QPaintEvent *event) override;
OnroadAlerts *alerts;
AnnotatedCameraWidget *nvg;
QColor bg = bg_colors[STATUS_DISENGAGED];
- QWidget *map = nullptr;
- QWidget *onroad_settings = nullptr;
QHBoxLayout* split;
- Params params;
-
-private slots:
- void offroadTransition(bool offroad);
- void primeChanged(bool prime);
- void updateState(const UIState &s);
- void updateMapSize(const UIScene &scene);
+protected slots:
+ virtual void offroadTransition(bool offroad);
+ virtual void updateState(const UIState &s);
};
diff --git a/selfdrive/ui/qt/onroad_settings_panel.cc b/selfdrive/ui/qt/onroad_settings_panel.cc
deleted file mode 100644
index 163f0fb31f..0000000000
--- a/selfdrive/ui/qt/onroad_settings_panel.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "selfdrive/ui/qt/onroad_settings_panel.h"
-
-#include
-#include
-
-#include "selfdrive/ui/qt/onroad_settings.h"
-#include "selfdrive/ui/qt/util.h"
-#include "selfdrive/ui/ui.h"
-
-OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) {
- content_stack = new QStackedLayout(this);
- content_stack->setContentsMargins(0, 0, 0, 0);
-
- auto onroad_settings = new OnroadSettings(true, parent);
- QObject::connect(onroad_settings, &OnroadSettings::closeSettings, this, &OnroadSettings::hide);
- content_stack->addWidget(onroad_settings);
-}
-
-void OnroadSettingsPanel::toggleOnroadSettings() {
- if (isVisible()) {
- hide();
- } else {
- emit onroadSettingsPanelRequested();
- show();
- }
-}
diff --git a/selfdrive/ui/qt/onroad_settings_panel.h b/selfdrive/ui/qt/onroad_settings_panel.h
deleted file mode 100644
index 8b81aef25a..0000000000
--- a/selfdrive/ui/qt/onroad_settings_panel.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include
-#ifdef ENABLE_MAPS
-#include
-#endif
-#include
-
-class OnroadSettingsPanel : public QFrame {
- Q_OBJECT
-
-public:
- explicit OnroadSettingsPanel(QWidget *parent = nullptr);
-
-signals:
- void onroadSettingsPanelRequested();
-
-public slots:
- void toggleOnroadSettings();
-
-private:
- QStackedLayout *content_stack;
-};
diff --git a/selfdrive/ui/qt/qt_window.cc b/selfdrive/ui/qt/qt_window.cc
index f71cea04e9..8d3d7cf72e 100644
--- a/selfdrive/ui/qt/qt_window.cc
+++ b/selfdrive/ui/qt/qt_window.cc
@@ -18,7 +18,9 @@ void setMainWindow(QWidget *w) {
wl_surface *s = reinterpret_cast(native->nativeResourceForWindow("surface", w->windowHandle()));
wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270);
wl_surface_commit(s);
- w->showFullScreen();
+
+ w->setWindowState(Qt::WindowFullScreen);
+ w->setVisible(true);
// ensure we have a valid eglDisplay, otherwise the ui will silently fail
void *egl = native->nativeResourceForWindow("egldisplay", w->windowHandle());
diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc
index 8212d3db94..d215fe42ca 100644
--- a/selfdrive/ui/qt/request_repeater.cc
+++ b/selfdrive/ui/qt/request_repeater.cc
@@ -1,36 +1,36 @@
#include "selfdrive/ui/qt/request_repeater.h"
-#include "common/swaglog.h"
-
RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey,
- int period, bool whileOnroad, bool sunnylink) : HttpRequest(parent, true, 20000, sunnylink) {
- request_url = requestURL;
- while_onroad = whileOnroad;
+ int period, bool while_onroad) : HttpRequest(parent) {
timer = new QTimer(this);
timer->setTimerType(Qt::VeryCoarseTimer);
- connect(timer, &QTimer::timeout, [=]() { this->timerTick(); });
+
+ connectTimer(requestURL, while_onroad);
+
timer->start(period * 1000);
+ setupCacheProcess(cacheKey);
+}
+
+void RequestRepeater::connectTimer(const QString &requestURL, bool while_onroad) {
+ QObject::connect(timer, &QTimer::timeout, [=]() {
+ if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) {
+ sendRequest(requestURL);
+ }
+ });
+}
+
+void RequestRepeater::setupCacheProcess(const QString &cacheKey) {
if (!cacheKey.isEmpty()) {
prevResp = QString::fromStdString(params.get(cacheKey.toStdString()));
if (!prevResp.isEmpty()) {
QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true, QNetworkReply::NoError); });
}
- connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) {
+ QObject::connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) {
if (success && resp != prevResp) {
params.put(cacheKey.toStdString(), resp.toStdString());
prevResp = resp;
}
});
}
-
- // Don't wait for the timer to fire to send the first request
- ForceUpdate();
-}
-
-void RequestRepeater::timerTick() {
- if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) {
- LOGD("Sending request for %s", qPrintable(request_url));
- sendRequest(request_url);
- }
}
diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h
index 45cb643b13..32e714b1cb 100644
--- a/selfdrive/ui/qt/request_repeater.h
+++ b/selfdrive/ui/qt/request_repeater.h
@@ -1,24 +1,23 @@
#pragma once
-#include "common/swaglog.h"
#include "common/util.h"
-#include "selfdrive/ui/qt/api.h"
+
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/sunnypilot/qt/api.h"
+#else
#include "selfdrive/ui/ui.h"
+#include "selfdrive/ui/qt/api.h"
+#endif
class RequestRepeater : public HttpRequest {
+public:
+ void connectTimer(const QString& requestURL, bool while_onroad);
+ void setupCacheProcess(const QString& cacheKey);
+ RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool while_onroad=false);
private:
Params params;
QTimer *timer;
QString prevResp;
- QString request_url;
- bool while_onroad;
- void timerTick();
-
-public:
- RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool whileOnroad=false, bool sunnylink = false);
- void ForceUpdate() {
- LOGD("Forcing update for %s", qPrintable(request_url));
- timerTick();
- }
};
diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc
index 8d45e62ce4..615276be00 100644
--- a/selfdrive/ui/qt/sidebar.cc
+++ b/selfdrive/ui/qt/sidebar.cc
@@ -1,11 +1,8 @@
#include "selfdrive/ui/qt/sidebar.h"
-#include
-
#include
#include "selfdrive/ui/qt/util.h"
-#include "common/params.h"
void Sidebar::drawMetric(QPainter &p, const QPair &label, QColor c, int y) {
const QRect rect = {30, y, 240, 126};
@@ -93,58 +90,16 @@ void Sidebar::updateState(const UIState &s) {
}
setProperty("connectStatus", QVariant::fromValue(connectStatus));
- if (sm.frame % UI_FREQ == 0) { // Update every 1 Hz
- switch (s.scene.sidebar_temp_options) {
- case 0:
- break;
- case 1:
- sidebar_temp = QString::number((int)deviceState.getMemoryTempC());
- break;
- case 2: {
- const auto& cpu_temp_list = deviceState.getCpuTempC();
- float max_cpu_temp = std::numeric_limits::lowest();
-
- for (const float& temp : cpu_temp_list) {
- max_cpu_temp = std::max(max_cpu_temp, temp);
- }
-
- if (max_cpu_temp >= 0) {
- sidebar_temp = QString::number(std::nearbyint(max_cpu_temp));
- }
- break;
- }
- case 3: {
- const auto& gpu_temp_list = deviceState.getGpuTempC();
- float max_gpu_temp = std::numeric_limits::lowest();
-
- for (const float& temp : gpu_temp_list) {
- max_gpu_temp = std::max(max_gpu_temp, temp);
- }
-
- if (max_gpu_temp >= 0) {
- sidebar_temp = QString::number(std::nearbyint(max_gpu_temp));
- }
- break;
- }
- case 4:
- sidebar_temp = QString::number((int)deviceState.getMaxTempC());
- break;
- default:
- break;
- }
-
- setProperty("sidebarTemp", sidebar_temp + "°C");
- }
-
- bool show_sidebar_temp = s.scene.sidebar_temp_options != 0;
- ItemStatus tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("HIGH")}, danger_color};
+#ifndef SUNNYPILOT
+ ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color};
auto ts = deviceState.getThermalStatus();
if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
- tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("GOOD")}, good_color};
+ tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color};
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
- tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("OK")}, warning_color};
+ tempStatus = {{tr("TEMP"), tr("OK")}, warning_color};
}
setProperty("tempStatus", QVariant::fromValue(tempStatus));
+#endif
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color};
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
@@ -153,32 +108,14 @@ void Sidebar::updateState(const UIState &s) {
pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color};
}
setProperty("pandaStatus", QVariant::fromValue(pandaStatus));
-
- ItemStatus sunnylinkStatus;
- auto sl_dongle_id = getSunnylinkDongleId();
- auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime");
- auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str);
- auto elapsed_sunnylink_ping = nanos_since_boot() - last_sunnylink_ping;
- auto sunnylink_enabled = params.getBool("SunnylinkEnabled");
-
- QString status = tr("DISABLED");
- QColor color = disabled_color;
-
- if (sunnylink_enabled && last_sunnylink_ping == 0) {
- // If sunnylink is enabled, but we don't have a dongle id, and we haven't received a ping yet, we are registering
- status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGIST...");
- color = sl_dongle_id.has_value() ? warning_color : progress_color;
- } else if (sunnylink_enabled) {
- // If sunnylink is enabled, we are considered online if we have received a ping in the last 80 seconds, else error.
- status = elapsed_sunnylink_ping < 80000000000ULL ? tr("ONLINE") : tr("ERROR");
- color = elapsed_sunnylink_ping < 80000000000ULL ? good_color : danger_color;
- }
- sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), status}, color };
- setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus));
}
void Sidebar::paintEvent(QPaintEvent *event) {
QPainter p(this);
+ DrawSidebar(p); // Because derived classes implement this. Otherwise QPainter gets terminated before time.
+}
+
+void Sidebar::DrawSidebar(QPainter &p){
p.setPen(Qt::NoPen);
p.setRenderHint(QPainter::Antialiasing);
@@ -204,10 +141,10 @@ void Sidebar::paintEvent(QPaintEvent *event) {
p.setPen(QColor(0xff, 0xff, 0xff));
const QRect r = QRect(50, 247, 100, 50);
p.drawText(r, Qt::AlignCenter, net_type);
+ RETURN_IF_SUNNYPILOT // Because we draw ourselves
// metrics
- drawMetric(p, temp_status.first, temp_status.second, 310);
- drawMetric(p, panda_status.first, panda_status.second, 440);
- drawMetric(p, connect_status.first, connect_status.second, 570);
- drawMetric(p, sunnylink_status.first, sunnylink_status.second, 700);
+ drawMetric(p, temp_status.first, temp_status.second, 338);
+ drawMetric(p, panda_status.first, panda_status.second, 496);
+ drawMetric(p, connect_status.first, connect_status.second, 654);
}
diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h
index 9012f25135..62a5486809 100644
--- a/selfdrive/ui/qt/sidebar.h
+++ b/selfdrive/ui/qt/sidebar.h
@@ -4,8 +4,13 @@
#include
#include
+#include "cereal/messaging/messaging.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
typedef QPair, QColor> ItemStatus;
Q_DECLARE_METATYPE(ItemStatus);
@@ -13,12 +18,10 @@ Q_DECLARE_METATYPE(ItemStatus);
class Sidebar : public QFrame {
Q_OBJECT
Q_PROPERTY(ItemStatus connectStatus MEMBER connect_status NOTIFY valueChanged);
- Q_PROPERTY(ItemStatus sunnylinkStatus MEMBER sunnylink_status NOTIFY valueChanged);
Q_PROPERTY(ItemStatus pandaStatus MEMBER panda_status NOTIFY valueChanged);
Q_PROPERTY(ItemStatus tempStatus MEMBER temp_status NOTIFY valueChanged);
Q_PROPERTY(QString netType MEMBER net_type NOTIFY valueChanged);
Q_PROPERTY(int netStrength MEMBER net_strength NOTIFY valueChanged);
- Q_PROPERTY(QString sidebarTemp MEMBER sidebar_temp_str NOTIFY valueChanged);
public:
explicit Sidebar(QWidget* parent = 0);
@@ -36,6 +39,7 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void drawMetric(QPainter &p, const QPair &label, QColor c, int y);
+ virtual void DrawSidebar(QPainter &p);
QPixmap home_img, flag_img, settings_img;
bool onroad, flag_pressed, settings_pressed;
@@ -52,19 +56,13 @@ protected:
const QRect home_btn = QRect(60, 860, 180, 180);
const QRect settings_btn = QRect(50, 35, 200, 117);
const QColor good_color = QColor(255, 255, 255);
- const QColor progress_color = QColor(3, 132, 252);
const QColor warning_color = QColor(218, 202, 37);
const QColor danger_color = QColor(201, 34, 49);
- const QColor disabled_color = QColor(128, 128, 128);
- ItemStatus connect_status, panda_status, temp_status, sunnylink_status;
+ ItemStatus connect_status, panda_status, temp_status;
QString net_type;
int net_strength = 0;
private:
- Params params;
std::unique_ptr pm;
-
- QString sidebar_temp = "0";
- QString sidebar_temp_str = "0";
};
diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc
index 4b5f8ee21e..21ec5eedcf 100644
--- a/selfdrive/ui/qt/text.cc
+++ b/selfdrive/ui/qt/text.cc
@@ -4,41 +4,12 @@
#include
#include
#include
-#include
-#include
-#include
-#include "common/params.h"
-#include "common/swaglog.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
-std::string executeCommand(const char* cmd) {
- std::array buffer{};
- std::ostringstream result;
- std::unique_ptr pipe(popen(cmd, "r"), pclose);
- if (!pipe) {
- LOGW("Failed to open pipe");
- throw std::runtime_error("popen() failed!");
- }
- while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
- result << buffer.data();
- }
- return result.str();
-}
-
-// The format is intentional!
-QString text = QString("%1\n%2\n%3\n%4\n%5\n%6\n%7")
- .arg(" ________________________________________")
- .arg("| |")
- .arg("| " + QObject::tr("Update downloaded. Ready to reboot.") + " |")
- .arg("| |")
- .arg("| " + QObject::tr("Update: Check and Download Update") + " |")
- .arg("| " + QObject::tr("Reboot: Reboot Device") + " |")
- .arg("|________________________________________|");
-
int main(int argc, char *argv[]) {
initApp(argc, argv);
QApplication a(argc, argv);
@@ -51,7 +22,6 @@ int main(int argc, char *argv[]) {
QLabel *label = new QLabel(argv[1]);
label->setWordWrap(true);
label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
- label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ScrollView *scroll = new ScrollView(label);
scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
main_layout->addWidget(scroll, 0, 0, Qt::AlignTop);
@@ -62,54 +32,16 @@ int main(int argc, char *argv[]) {
});
QPushButton *btn = new QPushButton();
- QPushButton *update_btn = new QPushButton();
- update_btn->setText(QObject::tr("Update"));
#ifdef __aarch64__
btn->setText(QObject::tr("Reboot"));
- QFutureWatcher watcher;
QObject::connect(btn, &QPushButton::clicked, [=]() {
Hardware::reboot();
});
- QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() {
- btn->setEnabled(false);
- update_btn->setEnabled(false);
- update_btn->setText(QObject::tr("Updating..."));
- const std::string git_branch = Params().get("GitBranch");
- const std::string git_remote = Params().get("GitRemote");
- const std::string to_home_dir = "cd /data/openpilot";
- const std::string check_remote = "git remote | grep origin-update";
- const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote;
- const std::string add_remote = "git remote add origin-update " + git_remote;
- const std::string fetch_remote = "git fetch origin-update " + git_branch;
- const std::string reset_branch = "git reset --hard origin-update/" + git_branch + " 2>&1";
-
- std::string remote_cmd = add_remote;
- if (!std::system((to_home_dir + " && " + check_remote).c_str())) {
- remote_cmd = reset_remote;
- }
- const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch;
-
- QFuture future = QtConcurrent::run([=]() {
- label->clear();
- std::string output = executeCommand(cmd.c_str());
- //LOGW("CHECK OUTPUT PLS\n%s", output.c_str());
- QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection,
- Q_ARG(QString, QString::fromStdString(output) + text));
- });
- QObject::connect(&watcher, &QFutureWatcher::finished, [=]() {
- btn->setEnabled(true);
- update_btn->setEnabled(true);
- update_btn->setText(QObject::tr("Update"));
- });
- watcher.setFuture(future);
- });
#else
- update_btn->setEnabled(false);
btn->setText(QObject::tr("Exit"));
QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit);
#endif
main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom);
- main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom);
window.setStyleSheet(R"(
* {
@@ -126,10 +58,6 @@ int main(int argc, char *argv[]) {
border-radius: 20px;
margin-right: 40px;
}
- QPushButton:disabled {
- color: #33FFFFFF;
- border: 2px solid #33FFFFFF;
- }
)");
return a.exec();
diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc
index 173d9ba970..886402b157 100644
--- a/selfdrive/ui/qt/util.cc
+++ b/selfdrive/ui/qt/util.cc
@@ -29,54 +29,38 @@ QString getBrand() {
return QObject::tr("sunnypilot");
}
-QString getUserAgent(bool sunnylink) {
- return (sunnylink ? "sunnypilot-" : "openpilot-") + getVersion();
+QString getUserAgent() {
+ return "openpilot-" + getVersion();
+}
+
+std::optional getParamIgnoringDefault(const std::string ¶m_name, const std::string &default_value) {
+ std::string value = Params().get(param_name);
+
+ if (!value.empty() && value != default_value)
+ return QString::fromStdString(value);
+
+ return {};
}
std::optional getDongleId() {
- std::string id = Params().get("DongleId");
-
- if (!id.empty() && (id != "UnregisteredDevice")) {
- return QString::fromStdString(id);
- } else {
- return {};
- }
+ return getParamIgnoringDefault("DongleId", "UnregisteredDevice");
}
-std::optional getSunnylinkDongleId() {
- std::string id = Params().get("SunnylinkDongleId");
+QMap getFromJsonFile(const QString &path) {
+ QFile f(path);
+ f.open(QIODevice::ReadOnly | QIODevice::Text);
+ QString val = f.readAll();
- if (!id.empty() && (id != "UnregisteredDevice")) {
- return QString::fromStdString(id);
- } else {
- return {};
+ QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object();
+ QMap map;
+ for (auto key : obj.keys()) {
+ map[key] = obj[key].toString();
}
+ return map;
}
QMap getSupportedLanguages() {
- QFile f(":/languages.json");
- f.open(QIODevice::ReadOnly | QIODevice::Text);
- QString val = f.readAll();
-
- QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object();
- QMap map;
- for (auto key : obj.keys()) {
- map[key] = obj[key].toString();
- }
- return map;
-}
-
-QMap getCarNames() {
- QFile f("/data/openpilot/selfdrive/car/sunnypilot_carname.json");
- f.open(QIODevice::ReadOnly | QIODevice::Text);
- QString val = f.readAll();
-
- QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object();
- QMap map;
- for (auto key : obj.keys()) {
- map[key] = obj[key].toString();
- }
- return map;
+ return getFromJsonFile(":/languages.json");
}
QString timeAgo(const QDateTime &date) {
@@ -179,60 +163,6 @@ QPixmap loadPixmap(const QString &fileName, const QSize &size, Qt::AspectRatioMo
}
}
-void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom){
- qreal w_2 = rect.width() / 2;
- qreal h_2 = rect.height() / 2;
-
- xRadiusTop = 100 * qMin(xRadiusTop, w_2) / w_2;
- yRadiusTop = 100 * qMin(yRadiusTop, h_2) / h_2;
-
- xRadiusBottom = 100 * qMin(xRadiusBottom, w_2) / w_2;
- yRadiusBottom = 100 * qMin(yRadiusBottom, h_2) / h_2;
-
- qreal x = rect.x();
- qreal y = rect.y();
- qreal w = rect.width();
- qreal h = rect.height();
-
- qreal rxx2Top = w*xRadiusTop/100;
- qreal ryy2Top = h*yRadiusTop/100;
-
- qreal rxx2Bottom = w*xRadiusBottom/100;
- qreal ryy2Bottom = h*yRadiusBottom/100;
-
- QPainterPath path;
- path.arcMoveTo(x, y, rxx2Top, ryy2Top, 180);
- path.arcTo(x, y, rxx2Top, ryy2Top, 180, -90);
- path.arcTo(x+w-rxx2Top, y, rxx2Top, ryy2Top, 90, -90);
- path.arcTo(x+w-rxx2Bottom, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 0, -90);
- path.arcTo(x, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 270, -90);
- path.closeSubpath();
-
- painter.drawPath(path);
-}
-
-QColor interpColor(float xv, std::vector xp, std::vector fp) {
- assert(xp.size() == fp.size());
-
- int N = xp.size();
- int hi = 0;
-
- while (hi < N and xv > xp[hi]) hi++;
- int low = hi - 1;
-
- if (hi == N && xv > xp[low]) {
- return fp[fp.size() - 1];
- } else if (hi == 0){
- return fp[0];
- } else {
- return QColor(
- (xv - xp[low]) * (fp[hi].red() - fp[low].red()) / (xp[hi] - xp[low]) + fp[low].red(),
- (xv - xp[low]) * (fp[hi].green() - fp[low].green()) / (xp[hi] - xp[low]) + fp[low].green(),
- (xv - xp[low]) * (fp[hi].blue() - fp[low].blue()) / (xp[hi] - xp[low]) + fp[low].blue(),
- (xv - xp[low]) * (fp[hi].alpha() - fp[low].alpha()) / (xp[hi] - xp[low]) + fp[low].alpha());
- }
-}
-
static QHash load_bootstrap_icons() {
QHash icons;
@@ -297,4 +227,4 @@ void ParamWatcher::fileChanged(const QString &path) {
void ParamWatcher::addParam(const QString ¶m_name) {
watcher->addPath(QString::fromStdString(params.getParamPath(param_name.toStdString())));
-}
+}
\ No newline at end of file
diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h
index 438b9677e7..20443db468 100644
--- a/selfdrive/ui/qt/util.h
+++ b/selfdrive/ui/qt/util.h
@@ -13,13 +13,19 @@
#include "cereal/gen/cpp/car.capnp.h"
#include "common/params.h"
+#ifdef SUNNYPILOT
+#define RETURN_IF_SUNNYPILOT return;
+#else
+#define RETURN_IF_SUNNYPILOT // Do nothing
+#endif
+
QString getVersion();
QString getBrand();
-QString getUserAgent(bool sunnylink = false);
+QString getUserAgent();
+std::optional getParamIgnoringDefault(const std::string ¶m_name, const std::string &default_value);
std::optional getDongleId();
-std::optional getSunnylinkDongleId();
+QMap getFromJsonFile(const QString &path);
QMap getSupportedLanguages();
-QMap getCarNames();
void setQtSurfaceFormat();
void sigTermHandler(int s);
QString timeAgo(const QDateTime &date);
@@ -28,9 +34,6 @@ void initApp(int argc, char *argv[], bool disable_hidpi = true);
QWidget* topWidget(QWidget* widget);
QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio);
QPixmap bootstrapPixmap(const QString &id);
-
-void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom);
-QColor interpColor(float xv, std::vector xp, std::vector fp);
bool hasLongitudinalControl(const cereal::CarParams::Reader &car_params);
struct InterFont : public QFont {
diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h
index 526c17e77c..62999b2aa4 100644
--- a/selfdrive/ui/qt/widgets/cameraview.h
+++ b/selfdrive/ui/qt/widgets/cameraview.h
@@ -24,7 +24,11 @@
#include "msgq/visionipc/visionipc_client.h"
#include "system/camerad/cameras/camera_common.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
const int FRAME_BUFFER_SIZE = 5;
static_assert(FRAME_BUFFER_SIZE <= YUV_BUFFER_COUNT);
diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc
index 238051cf5e..8af48185aa 100644
--- a/selfdrive/ui/qt/widgets/controls.cc
+++ b/selfdrive/ui/qt/widgets/controls.cc
@@ -2,20 +2,10 @@
#include
#include
-
-QFrame *horizontal_line(QWidget *parent) {
- QFrame *line = new QFrame(parent);
- line->setFrameShape(QFrame::StyledPanel);
- line->setStyleSheet(R"(
- border-width: 2px;
- border-bottom-style: solid;
- border-color: gray;
- )");
- line->setFixedHeight(10);
- return line;
-}
+#include
AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) {
+ RETURN_IF_SUNNYPILOT
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setMargin(0);
@@ -36,7 +26,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons
// title
title_label = new QPushButton(title);
title_label->setFixedHeight(120);
- title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;");
+ title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; border: none;");
hlayout->addWidget(title_label, 1);
// value next to control button
@@ -74,51 +64,6 @@ void AbstractControl::hideEvent(QHideEvent *e) {
}
}
-SPAbstractControl::SPAbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) {
- QVBoxLayout *main_layout = new QVBoxLayout(this);
- main_layout->setMargin(0);
-
- hlayout = new QHBoxLayout;
- hlayout->setMargin(0);
- hlayout->setSpacing(0);
-
- // title
- if (!title.isEmpty()) {
- title_label = new QPushButton(title);
- title_label->setFixedHeight(120);
- title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;");
- main_layout->addWidget(title_label, 1);
-
- connect(title_label, &QPushButton::clicked, [=]() {
- if (!description->isVisible()) {
- emit showDescriptionEvent();
- }
-
- if (!description->text().isEmpty()) {
- description->setVisible(!description->isVisible());
- }
- });
- } else {
- main_layout->addSpacing(20);
- }
-
- main_layout->addLayout(hlayout);
- main_layout->addSpacing(2);
-
- // description
- description = new QLabel(desc);
- description->setContentsMargins(0, 20, 40, 20);
- description->setStyleSheet("font-size: 40px; color: grey");
- description->setWordWrap(true);
- description->setVisible(false);
- main_layout->addWidget(description);
-
- main_layout->addStretch();
-}
-
-void SPAbstractControl::hideEvent(QHideEvent *e) {
-}
-
// controls
ButtonControl::ButtonControl(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControl(title, desc, "", parent) {
@@ -178,15 +123,6 @@ ParamControl::ParamControl(const QString ¶m, const QString &title, const QSt
: ToggleControl(title, desc, icon, false, parent) {
key = param.toStdString();
QObject::connect(this, &ParamControl::toggleFlipped, this, &ParamControl::toggleClicked);
-
- hlayout->removeWidget(&toggle);
- hlayout->insertWidget(0, &toggle);
-
- hlayout->removeWidget(this->icon_label);
- this->icon_pixmap = QPixmap(icon).scaledToWidth(20, Qt::SmoothTransformation);
- this->icon_label->setPixmap(this->icon_pixmap);
- this->icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
- hlayout->insertWidget(1, this->icon_label);
}
void ParamControl::toggleClicked(bool state) {
diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h
index 3466b67f5e..aa304e0df6 100644
--- a/selfdrive/ui/qt/widgets/controls.h
+++ b/selfdrive/ui/qt/widgets/controls.h
@@ -1,6 +1,5 @@
#pragma once
-#include
#include
#include
@@ -15,8 +14,6 @@
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/qt/widgets/toggle.h"
-QFrame *horizontal_line(QWidget *parent = nullptr);
-
class ElidedLabel : public QLabel {
Q_OBJECT
@@ -24,10 +21,6 @@ public:
explicit ElidedLabel(QWidget *parent = 0);
explicit ElidedLabel(const QString &text, QWidget *parent = 0);
- void setColor(const QString &color) {
- setStyleSheet("QLabel { color : " + color + "; }");
- }
-
signals:
void clicked();
@@ -55,11 +48,8 @@ public:
title_label->setText(title);
}
- void setValue(const QString &val, std::optional color = std::nullopt) {
+ void setValue(const QString &val) {
value->setText(val);
- if (color.has_value()) {
- value->setColor(color.value());
- }
}
const QString getDescription() {
@@ -74,10 +64,6 @@ public slots:
description->setVisible(true);
}
- void hideDescription() {
- description->setVisible(false);
- }
-
signals:
void showDescriptionEvent();
@@ -117,7 +103,6 @@ public:
ButtonControl(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr);
inline void setText(const QString &text) { btn.setText(text); }
inline QString text() const { return btn.text(); }
- inline void click() { btn.click(); }
signals:
void clicked();
@@ -181,8 +166,6 @@ public:
refresh();
}
- bool isToggled() { return params.getBool(key); }
-
private:
void toggleClicked(bool state);
void setIcon(bool state) {
@@ -200,71 +183,29 @@ private:
bool store_confirm = false;
};
-class SPAbstractControl : public QFrame {
- Q_OBJECT
-
-public:
- void setDescription(const QString &desc) {
- if (description) description->setText(desc);
- }
-
- void setTitle(const QString &title) {
- title_label->setText(title);
- }
-
- const QString getDescription() {
- return description->text();
- }
-
-public slots:
- void showDescription() {
- description->setVisible(true);
- }
-
- void hideDescription() {
- description->setVisible(false);
- }
-
-signals:
- void showDescriptionEvent();
-
-protected:
- SPAbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
- void hideEvent(QHideEvent *e) override;
-
- QHBoxLayout *hlayout;
- QPushButton *title_label;
-
-private:
- QLabel *description = nullptr;
-};
-
-class ButtonParamControl : public SPAbstractControl {
+class ButtonParamControl : public AbstractControl {
Q_OBJECT
public:
ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
- const std::vector &button_texts, const int minimum_button_width = 300) : SPAbstractControl(title, desc, icon), button_texts(button_texts) {
+ const std::vector &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) {
const QString style = R"(
QPushButton {
- border-radius: 20px;
- font-size: 50px;
- font-weight: 450;
- height:150px;
+ border-radius: 50px;
+ font-size: 40px;
+ font-weight: 500;
+ height:100px;
padding: 0 25 0 25;
- color: #FFFFFF;
+ color: #E4E4E4;
+ background-color: #393939;
}
QPushButton:pressed {
background-color: #4a4a4a;
}
QPushButton:checked:enabled {
- background-color: #696868;
+ background-color: #33Ab4C;
}
QPushButton:disabled {
- color: #33FFFFFF;
- }
- QPushButton:checked:disabled {
- background-color: #121212;
- color: #33FFFFFF;
+ color: #33E4E4E4;
}
)";
key = param.toStdString();
@@ -278,16 +219,12 @@ public:
button->setChecked(i == value);
button->setStyleSheet(style);
button->setMinimumWidth(minimum_button_width);
- if (i == 0) hlayout->addSpacing(2);
hlayout->addWidget(button);
button_group->addButton(button, i);
}
- hlayout->setAlignment(Qt::AlignLeft);
-
QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) {
params.put(key, std::to_string(id));
- emit buttonToggled(id);
});
}
@@ -295,9 +232,6 @@ public:
for (auto btn : button_group->buttons()) {
btn->setEnabled(enable);
}
- button_group_enabled = enable;
-
- update();
}
void setCheckedButton(int id) {
@@ -306,14 +240,6 @@ public:
void refresh() {
int value = atoi(params.get(key).c_str());
-
- if (value >= button_texts.size()) {
- value = button_texts.size() - 1;
- }
- if (value < 0) {
- value = 0;
- }
-
button_group->button(value)->setChecked(true);
}
@@ -321,59 +247,16 @@ public:
refresh();
}
- void setButton(QString param) {
- key = param.toStdString();
- int value = atoi(params.get(key).c_str());
- for (int i = 0; i < button_group->buttons().size(); i++) {
- button_group->buttons()[i]->setChecked(i == value);
- }
- }
-
- void setDisabledSelectedButton(std::string val) {
- int value = atoi(val.c_str());
- for (int i = 0; i < button_group->buttons().size(); i++) {
- button_group->buttons()[i]->setEnabled(i != value);
- }
- }
-
-protected:
- void paintEvent(QPaintEvent *event) override {
- QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing);
-
- // Calculate the total width and height for the background rectangle
- int w = 0;
- int h = 150;
-
- for (int i = 0; i < hlayout->count(); ++i) {
- QPushButton *button = qobject_cast(hlayout->itemAt(i)->widget());
- if (button) {
- w += button->width();
- }
- }
-
- // Draw the rectangle
- QRect rect(0 + 2, h - 24, w, h);
- p.setPen(QPen(QColor(button_group_enabled ? "#696868" : "#121212"), 3));
- p.drawRoundedRect(rect, 20, 20);
- }
-
-signals:
- void buttonToggled(int btn_id);
-
private:
std::string key;
Params params;
QButtonGroup *button_group;
- std::vector button_texts;
-
- bool button_group_enabled = true;
};
class ListWidget : public QWidget {
Q_OBJECT
public:
- explicit ListWidget(QWidget *parent = 0, const bool split_line = true) : QWidget(parent), _split_line(split_line), outer_layout(this) {
+ explicit ListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) {
outer_layout.setMargin(0);
outer_layout.setSpacing(0);
outer_layout.addLayout(&inner_layout);
@@ -385,30 +268,13 @@ class ListWidget : public QWidget {
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); }
- inline void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); }
- inline void RemoveWidgetAt(const int index) {
- if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) {
- if(item->widget()) delete item->widget();
- delete item;
- }
- }
-
- inline void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) {
- if (const int index = inner_layout.indexOf(old_widget); index != -1) {
- RemoveWidgetAt(index);
- AddWidgetAt(index, new_widget);
- } else {
- addItem(new_widget);
- }
- }
-
private:
void paintEvent(QPaintEvent *) override {
QPainter p(this);
p.setPen(Qt::gray);
for (int i = 0; i < inner_layout.count() - 1; ++i) {
QWidget *widget = inner_layout.itemAt(i)->widget();
- if ((widget == nullptr || widget->isVisible()) && _split_line) {
+ if (widget == nullptr || widget->isVisible()) {
QRect r = inner_layout.itemAt(i)->geometry();
int bottom = r.bottom() + inner_layout.spacing() / 2;
p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom);
@@ -417,8 +283,6 @@ private:
}
QVBoxLayout outer_layout;
QVBoxLayout inner_layout;
-
- bool _split_line;
};
// convenience class for wrapping layouts
@@ -430,178 +294,3 @@ public:
setLayout(l);
}
};
-
-class SPOptionControl : public SPAbstractControl {
- Q_OBJECT
-
-private:
- struct MinMaxValue {
- int min_value;
- int max_value;
- };
-
-public:
- SPOptionControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
- const MinMaxValue &range, const int per_value_change = 1) : _title(title), SPAbstractControl(title, desc, icon) {
- const QString style = R"(
- QPushButton {
- border-radius: 20px;
- font-size: 60px;
- font-weight: 500;
- width: 150px;
- height: 150px;
- padding: -3 25 3 25;
- color: #FFFFFF;
- font-weight: bold;
- }
- QPushButton:pressed {
- color: #5C5C5C;
- }
- QPushButton:disabled {
- color: #5C5C5C;
- }
- )";
-
- label.setStyleSheet(label_enabled_style);
- label.setFixedWidth(300);
- label.setAlignment(Qt::AlignCenter);
-
- const std::vector button_texts{"-", "+"};
-
- key = param.toStdString();
- value = atoi(params.get(key).c_str());
-
- button_group = new QButtonGroup(this);
- button_group->setExclusive(true);
- for (int i = 0; i < button_texts.size(); i++) {
- QPushButton *button = new QPushButton(button_texts[i], this);
- button->setStyleSheet(style + ((i == 0) ? "QPushButton { text-align: left; }" :
- "QPushButton { text-align: right; }"));
- hlayout->addWidget(button, 0, ((i == 0) ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter);
- if (i == 0) {
- hlayout->addWidget(&label, 0, Qt::AlignCenter);
- }
- button_group->addButton(button, i);
-
- QObject::connect(button, &QPushButton::clicked, [=]() {
- int change_value = (i == 0) ? -per_value_change : per_value_change;
- key = param.toStdString();
- value = atoi(params.get(key).c_str());
- value += change_value;
- value = std::clamp(value, range.min_value, range.max_value);
- params.put(key, QString::number(value).toStdString());
-
- button_group->button(0)->setEnabled(!(value <= range.min_value));
- button_group->button(1)->setEnabled(!(value >= range.max_value));
-
- updateLabels();
-
- if (request_update) {
- emit updateOtherToggles();
- }
- });
- }
-
- hlayout->setAlignment(Qt::AlignLeft);
- }
-
- void setUpdateOtherToggles(bool _update) {
- request_update = _update;
- }
-
- inline void setLabel(const QString &text) { label.setText(text); }
-
- void setEnabled(bool enabled) {
- for (auto btn : button_group->buttons()) {
- btn->setEnabled(enabled);
- }
- label.setEnabled(enabled);
- label.setStyleSheet(enabled ? label_enabled_style : label_disabled_style);
- button_enabled = enabled;
-
- update();
- }
-
-protected:
- void paintEvent(QPaintEvent *event) override {
- QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing);
-
- // Calculate the total width and height for the background rectangle
- int w = 0;
- int h = 150;
-
- for (int i = 0; i < hlayout->count(); ++i) {
- QWidget *widget = qobject_cast(hlayout->itemAt(i)->widget());
- if (widget) {
- w += widget->width();
- }
- }
-
- // Draw the rectangle
- QRect rect(0, !_title.isEmpty() ? (h - 24) : 20, w, h);
- p.setBrush(QColor(button_enabled ? "#b24a4a4a" : "#121212")); // Background color
- p.setPen(QPen(Qt::NoPen));
- p.drawRoundedRect(rect, 20, 20);
- }
-
-signals:
- void updateLabels();
- void updateOtherToggles();
-
-private:
- std::string key;
- int value;
- QButtonGroup *button_group;
- QLabel label;
- Params params;
- std::map option_label = {};
- bool request_update = false;
- QString _title = "";
-
- const QString label_enabled_style = "font-size: 50px; font-weight: 450; color: #FFFFFF;";
- const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;";
-
- bool button_enabled = true;
-};
-
-class SubPanelButton : public QPushButton {
- Q_OBJECT
-
-public:
- SubPanelButton(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) {
- const QString buttonStyle = R"(
- QPushButton {
- border-radius: 20px;
- font-size: 50px;
- font-weight: 450;
- height: 150px;
- padding: 0 25px 0 25px;
- color: #FFFFFF;
- }
- QPushButton:enabled {
- background-color: #393939;
- }
- QPushButton:pressed {
- background-color: #4A4A4A;
- }
- QPushButton:disabled {
- background-color: #121212;
- color: #5C5C5C;
- }
- )";
-
- setStyleSheet(buttonStyle);
- setFixedWidth(minimum_button_width);
- }
-};
-
-class PanelBackButton : public QPushButton {
- Q_OBJECT
-
-public:
- PanelBackButton(const QString &label = "Back", QWidget *parent = nullptr) : QPushButton(label, parent) {
- setObjectName("back_btn");
- setFixedSize(400, 100);
- }
-};
diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc
index 7f6a5ff1cc..978bf83a63 100644
--- a/selfdrive/ui/qt/widgets/scrollview.cc
+++ b/selfdrive/ui/qt/widgets/scrollview.cc
@@ -47,11 +47,3 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) {
void ScrollView::hideEvent(QHideEvent *e) {
verticalScrollBar()->setValue(0);
}
-
-void ScrollView::setLastScrollPosition() {
- lastScrollPosition = verticalScrollBar()->value();
-}
-
-void ScrollView::restoreScrollPosition() {
- verticalScrollBar()->setValue(lastScrollPosition);
-}
diff --git a/selfdrive/ui/qt/widgets/scrollview.h b/selfdrive/ui/qt/widgets/scrollview.h
index 7e4084412c..024331aa39 100644
--- a/selfdrive/ui/qt/widgets/scrollview.h
+++ b/selfdrive/ui/qt/widgets/scrollview.h
@@ -9,11 +9,4 @@ public:
explicit ScrollView(QWidget *w = nullptr, QWidget *parent = nullptr);
protected:
void hideEvent(QHideEvent *e) override;
-
-public slots:
- void setLastScrollPosition();
- void restoreScrollPosition();
-
-private:
- int lastScrollPosition = 0;
};
diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h
index 920bd651e2..2834164702 100644
--- a/selfdrive/ui/qt/widgets/ssh_keys.h
+++ b/selfdrive/ui/qt/widgets/ssh_keys.h
@@ -3,7 +3,13 @@
#include
#include "system/hardware/hw.h"
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#define ToggleControl ToggleControlSP
+#define ButtonControl ButtonControlSP
+#else
#include "selfdrive/ui/qt/widgets/controls.h"
+#endif
// SSH enable toggle
class SshToggle : public ToggleControl {
diff --git a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h b/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h
deleted file mode 100644
index 5e2d96b240..0000000000
--- a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma once
-
-#include
-#include
-
-class DriveStats : public QFrame {
- Q_OBJECT
-
-public:
- explicit DriveStats(QWidget* parent = 0);
-
-private:
- void showEvent(QShowEvent *event) override;
- void updateStats();
- inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); }
-
- bool metric_;
- QJsonDocument stats_;
- struct StatsLabels {
- QLabel *routes, *distance, *distance_unit, *hours;
- } all_, week_;
-
-private slots:
- void parseResponse(const QString &response, bool success);
-};
diff --git a/selfdrive/ui/qt/widgets/toggle.cc b/selfdrive/ui/qt/widgets/toggle.cc
index c8f12bc272..82302ad5bc 100644
--- a/selfdrive/ui/qt/widgets/toggle.cc
+++ b/selfdrive/ui/qt/widgets/toggle.cc
@@ -75,9 +75,9 @@ void Toggle::setEnabled(bool value) {
enabled = value;
if (value) {
circleColor.setRgb(0xfafafa);
- green.setRgb(0x1e79e8);
+ green.setRgb(0x33ab4c);
} else {
circleColor.setRgb(0x888888);
- green.setRgb(0x125db8);
+ green.setRgb(0x227722);
}
}
diff --git a/selfdrive/ui/qt/widgets/toggle.h b/selfdrive/ui/qt/widgets/toggle.h
index e7263a008f..a0fa434a4c 100644
--- a/selfdrive/ui/qt/widgets/toggle.h
+++ b/selfdrive/ui/qt/widgets/toggle.h
@@ -23,14 +23,13 @@ public:
update();
}
bool getEnabled();
- void setEnabled(bool value);
+ virtual void setEnabled(bool value);
protected:
void paintEvent(QPaintEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void enterEvent(QEvent*) override;
-private:
QColor circleColor;
QColor green;
bool enabled = true;
diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h
index 60c865f2b8..3010abc6ff 100644
--- a/selfdrive/ui/qt/widgets/wifi.h
+++ b/selfdrive/ui/qt/widgets/wifi.h
@@ -4,7 +4,11 @@
#include
#include
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/ui.h"
+#else
#include "selfdrive/ui/ui.h"
+#endif
class WiFiPromptWidget : public QFrame {
Q_OBJECT
diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc
index 6b6f939af4..d9ff51763b 100644
--- a/selfdrive/ui/qt/window.cc
+++ b/selfdrive/ui/qt/window.cc
@@ -4,16 +4,18 @@
#include "system/hardware/hw.h"
-MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
+MainWindow::MainWindow(QWidget* parent, HomeWindow* hw, SettingsWindow* sw, OnboardingWindow* ow)
+ : QWidget(parent),
+ homeWindow(hw ? hw : new HomeWindow(this)),
+ settingsWindow(sw ? sw : new SettingsWindow(this)),
+ onboardingWindow(ow ? ow : new OnboardingWindow(this)) {
main_layout = new QStackedLayout(this);
main_layout->setMargin(0);
- homeWindow = new HomeWindow(this);
main_layout->addWidget(homeWindow);
QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings);
QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings);
- settingsWindow = new SettingsWindow(this);
main_layout->addWidget(settingsWindow);
QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings);
QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() {
@@ -24,7 +26,6 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
homeWindow->showDriverView(true);
});
- onboardingWindow = new OnboardingWindow(this);
main_layout->addWidget(onboardingWindow);
QObject::connect(onboardingWindow, &OnboardingWindow::onboardingDone, [=]() {
main_layout->setCurrentWidget(homeWindow);
@@ -75,10 +76,6 @@ void MainWindow::closeSettings() {
if (uiState()->scene.started) {
homeWindow->showSidebar(false);
- // Map is always shown when using navigate on openpilot
- if (uiState()->scene.navigate_on_openpilot_deprecated) {
- homeWindow->showMapPanel(true);
- }
}
}
diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h
index 05b61e1f76..fa0c43486e 100644
--- a/selfdrive/ui/qt/window.h
+++ b/selfdrive/ui/qt/window.h
@@ -11,15 +11,18 @@ class MainWindow : public QWidget {
Q_OBJECT
public:
- explicit MainWindow(QWidget *parent = 0);
+ explicit MainWindow(QWidget *parent = nullptr) : MainWindow(parent, nullptr, nullptr, nullptr) {}
+
+protected:
+ explicit MainWindow(QWidget* parent, HomeWindow* hw = nullptr, SettingsWindow* sw = nullptr, OnboardingWindow* ow = nullptr);
+ HomeWindow *homeWindow;
+ SettingsWindow *settingsWindow;
+ OnboardingWindow *onboardingWindow;
+ virtual void closeSettings();
private:
bool eventFilter(QObject *obj, QEvent *event) override;
void openSettings(int index = 0, const QString ¶m = "");
- void closeSettings();
QStackedLayout *main_layout;
- HomeWindow *homeWindow;
- SettingsWindow *settingsWindow;
- OnboardingWindow *onboardingWindow;
};
diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript
new file mode 100644
index 0000000000..945c9ccdb2
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/SConscript
@@ -0,0 +1,63 @@
+widgets_src = [
+ "sunnypilot/ui.cc",
+ "sunnypilot/qt/window.cc",
+ "sunnypilot/qt/request_repeater.cc",
+ "sunnypilot/qt/network/networking.cc",
+ "sunnypilot/qt/offroad/settings/display_settings.cc",
+ "sunnypilot/qt/offroad/settings/sunnypilot_settings.cc",
+ "sunnypilot/qt/offroad/settings/vehicle_settings.cc",
+ "sunnypilot/qt/offroad/settings/visuals_settings.cc",
+ "sunnypilot/qt/offroad/settings/trips_settings.cc",
+ "sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc",
+ "sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc",
+ "sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc",
+ "sunnypilot/qt/offroad/settings/monitoring_settings.cc",
+ "sunnypilot/qt/offroad/settings/osm_settings.cc",
+ "sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc",
+ "sunnypilot/qt/widgets/drive_stats.cc",
+ "sunnypilot/qt/offroad/settings/software_settings.cc",
+ "sunnypilot/qt/offroad/settings/osm/models_fetcher.cc",
+ "sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc",
+ "sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc",
+ "sunnypilot/qt/offroad/settings/sunnylink_settings.cc",
+ "sunnypilot/qt/widgets/controls.cc",
+ "sunnypilot/qt/widgets/scrollview.cc",
+ "sunnypilot/qt/widgets/toggle.cc"
+]
+
+sp_maps_widgets_src = [
+ "sunnypilot/qt/maps/map.cc"
+]
+
+sp_qt_util = [
+ # "#selfdrive/ui/sunnypilot/qt/api.cc",
+ "#selfdrive/ui/sunnypilot/qt/util.cc",
+]
+
+network_src = [
+ "sunnypilot/qt/network/sunnylink/sunnylink_client.cc",
+ "sunnypilot/qt/network/sunnylink/services/base_device_service.cc",
+ "sunnypilot/qt/network/sunnylink/services/role_service.cc",
+ "sunnypilot/qt/network/sunnylink/services/user_service.cc"
+]
+
+qt_src = [
+ "sunnypilot/qt/api.cc",
+ "sunnypilot/qt/home.cc",
+ "sunnypilot/qt/offroad_home.cc",
+ "sunnypilot/qt/sidebar.cc",
+ "sunnypilot/qt/offroad/settings/onboarding.cc",
+ "sunnypilot/qt/offroad/settings/device_panel.cc",
+ "sunnypilot/qt/offroad/settings/settings.cc",
+ "sunnypilot/qt/onroad/buttons.cc",
+ "sunnypilot/qt/onroad/onroad_home.cc",
+ "sunnypilot/qt/onroad/onroad_settings.cc",
+ "sunnypilot/qt/onroad/annotated_camera.cc",
+ "sunnypilot/qt/onroad/onroad_settings_panel.cc",
+ "sunnypilot/qt/onroad/developer_ui/developer_ui.cc",
+]
+
+sp_widgets_src = widgets_src + network_src
+sp_qt_src = qt_src
+
+Export('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src', "sp_qt_util")
diff --git a/selfdrive/ui/sunnypilot/qt/api.cc b/selfdrive/ui/sunnypilot/qt/api.cc
new file mode 100644
index 0000000000..fcf925fd1a
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/api.cc
@@ -0,0 +1,212 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/api.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include "util.h"
+#include "common/util.h"
+#include "system/hardware/hw.h"
+#include "selfdrive/ui/qt/util.h"
+
+namespace SunnylinkApi {
+
+QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) {
+ QJsonObject header = {{"alg", "RS256"}};
+
+ auto t = QDateTime::currentSecsSinceEpoch();
+ auto dongle_id = sunnylink ? getSunnylinkDongleId() : getDongleId();
+ QJsonObject payload = {{"identity", dongle_id.value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}};
+ for (auto it = payloads.begin(); it != payloads.end(); ++it) {
+ payload.insert(it.key(), it.value());
+ }
+
+ auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals;
+ QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' +
+ QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts);
+
+ auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256);
+ return jwt + "." + CommaApi::rsa_sign(hash).toBase64(b64_opts);
+}
+
+ void derive_aes_key_iv_from_rsa(EVP_PKEY *rsa_key, unsigned char *aes_key, unsigned char *aes_iv) {
+ unsigned char hash[SHA256_DIGEST_LENGTH];
+ size_t pub_len;
+ unsigned char *pub_key;
+
+ // Convert RSA key to public key in DER format for simplicity
+ pub_len = i2d_PublicKey(rsa_key, NULL);
+ pub_key = (unsigned char *)malloc(pub_len);
+ unsigned char *tmp = pub_key;
+ i2d_PublicKey(rsa_key, &tmp);
+
+ // Hash the public key to derive bytes for AES key and IV
+ SHA256(pub_key, pub_len, hash);
+
+ // Assuming AES-256-CBC, we need 32 bytes for the key and 16 for the IV
+ memcpy(aes_key, hash, 32); // First 32 bytes for AES key
+ memcpy(aes_iv, hash + 32 - 16, 16); // Last 16 bytes for AES IV
+
+ free(pub_key);
+}
+
+EVP_PKEY *load_public_key(const char *file) {
+ EVP_PKEY *key = NULL;
+ FILE *fp = fopen(file, "r");
+ if (fp) {
+ key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+ return key;
+}
+
+EVP_PKEY *load_private_key(const char *file) {
+ EVP_PKEY *key = NULL;
+ FILE *fp = fopen(file, "r");
+ if (fp) {
+ key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+ return key;
+}
+
+QByteArray rsa_encrypt(const QByteArray &data) {
+ EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
+ unsigned char aes_key[32], aes_iv[16];
+ derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
+
+ EVP_CIPHER_CTX *ctx;
+ if (!(ctx = EVP_CIPHER_CTX_new())) {
+ // Handle error: Allocate memory failed
+ return {};
+ }
+
+ if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
+ qDebug() << "Failed to initialize encryption";
+ EVP_CIPHER_CTX_free(ctx);
+ return {};
+ }
+
+ int ciphertext_len;
+ int len = data.size();
+ unsigned char out[len + AES_BLOCK_SIZE];
+ auto *in = (unsigned char*) data.constData();
+ if (EVP_EncryptUpdate(ctx, out, &ciphertext_len, in, len) != 1) {
+ qDebug() << "Failed to update encryption";
+ EVP_CIPHER_CTX_free(ctx);
+ return {};
+ }
+
+ if (EVP_EncryptFinal_ex(ctx, out + ciphertext_len, &len) != 1) {
+ // Handle error: Encryption finalize failed
+ qDebug() << "Failed to finalize encryption";
+ EVP_CIPHER_CTX_free(ctx);
+ return {};
+ }
+
+ //qDebug() << "Encrypted data length: %d", ciphertext_len + len; // Print the length of encrypted data
+ EVP_CIPHER_CTX_free(ctx);
+ return {reinterpret_cast(out), ciphertext_len + len};
+}
+
+QByteArray rsa_decrypt(const QByteArray &data) {
+ EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
+ unsigned char aes_key[32], aes_iv[16];
+ derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
+
+ EVP_CIPHER_CTX *ctx;
+ if (!(ctx = EVP_CIPHER_CTX_new())) {
+ qDebug() << "Failed to allocate memory for EVP_CIPHER_CTX";
+ return {};
+ }
+
+ if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
+ qDebug() << "Failed to initialize EVP";
+ EVP_CIPHER_CTX_free(ctx);
+ return {};
+ }
+
+ int len = data.size();
+ unsigned char out[len + AES_BLOCK_SIZE];
+ auto *in = (unsigned char*) data.constData();
+ if (EVP_DecryptUpdate(ctx, out, &len, in, len) != 1) {
+ qDebug() << "Failed to update decryption";
+ EVP_CIPHER_CTX_free(ctx);
+ return {};
+ }
+
+ int final_len;
+ if (EVP_DecryptFinal_ex(ctx, out + len, &final_len) != 1) {
+ qDebug() << "Failed to finalize decryption";
+ EVP_CIPHER_CTX_free(ctx);
+ return {};
+ }
+ EVP_CIPHER_CTX_free(ctx);
+
+ //qDebug() << "Decrypted data length: %d", len + final_len; // Print the length of decrypted data
+ return {reinterpret_cast(out), len + final_len};
+}
+
+
+} // namespace SunnylinkApi
+
+void HttpRequestSP::sendRequest(const QString& requestURL, Method method, const QByteArray& payload) {
+ if (active()) {
+ return;
+ }
+ QNetworkRequest request = prepareRequest(requestURL);
+
+ if(!payload.isEmpty() && (method == Method::POST || method == Method::PUT)) {
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ }
+
+ switch (method) {
+ case Method::GET:
+ reply = nam()->get(request);
+ break;
+ case Method::DELETE:
+ reply = nam()->deleteResource(request);
+ break;
+ case Method::POST:
+ reply = nam()->post(request, payload);
+ break;
+ case Method::PUT:
+ reply = nam()->put(request, payload);
+ break;
+ }
+
+ networkTimer->start();
+ connect(reply, &QNetworkReply::finished, this, &HttpRequestSP::requestFinished);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/api.h b/selfdrive/ui/sunnypilot/qt/api.h
new file mode 100644
index 0000000000..b8e835dc91
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/api.h
@@ -0,0 +1,55 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include "selfdrive/ui/qt/api.h"
+#include "selfdrive/ui/sunnypilot/qt/util.h"
+#include "common/util.h"
+
+namespace SunnylinkApi {
+ QByteArray rsa_encrypt(const QByteArray& data);
+ QByteArray rsa_decrypt(const QByteArray& data);
+ QString create_jwt(const QJsonObject& payloads = {}, int expiry = 3600, bool sunnylink = false);
+}
+
+class HttpRequestSP : public HttpRequest {
+ Q_OBJECT
+
+public:
+ explicit HttpRequestSP(QObject* parent, bool create_jwt = true, int timeout = 20000, bool sunnylink = false) :
+ HttpRequest(parent, create_jwt, timeout), sunnylink(sunnylink) {}
+
+ using HttpRequest::sendRequest;
+ void sendRequest(const QString& requestURL, Method method, const QByteArray& payload);
+
+private:
+ bool sunnylink;
+
+protected:
+ [[nodiscard]] QString GetJwtToken() const override { return SunnylinkApi::create_jwt({}, 3600, sunnylink); }
+ [[nodiscard]] QString GetUserAgent() const override { return getUserAgent(sunnylink); }
+};
diff --git a/selfdrive/ui/sunnypilot/qt/common/json_fetcher.h b/selfdrive/ui/sunnypilot/qt/common/json_fetcher.h
new file mode 100644
index 0000000000..2002e72aa0
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/common/json_fetcher.h
@@ -0,0 +1,60 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class JsonFetcher {
+public:
+ static QJsonObject getJsonFromURL(const QString &url) {
+ const auto qurl = QUrl(url);
+ QNetworkAccessManager manager;
+ const QNetworkRequest request(qurl);
+ QNetworkReply *reply = manager.get(request);
+ QEventLoop loop;
+
+ // Send GET request
+
+ QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+ loop.exec();
+
+ if (reply->error() != QNetworkReply::NoError) {
+ qWarning() << "Failed to fetch data from URL: " << reply->errorString();
+ return QJsonObject();
+ }
+
+ const QByteArray responseData = reply->readAll();
+ const QJsonDocument doc = QJsonDocument::fromJson(responseData);
+ QJsonObject json = doc.object();
+
+ reply->deleteLater();
+ return json;
+ }
+};
diff --git a/selfdrive/ui/sunnypilot/qt/home.cc b/selfdrive/ui/sunnypilot/qt/home.cc
new file mode 100644
index 0000000000..61abbff7dc
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/home.cc
@@ -0,0 +1,80 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/home.h"
+
+#include
+#include
+#include
+
+#include "selfdrive/ui/qt/offroad/experimental_mode.h"
+#include "selfdrive/ui/qt/util.h"
+
+// HomeWindowSP: the container for the offroad and onroad UIs
+HomeWindowSP::HomeWindowSP(QWidget* parent) : HomeWindow(parent){
+ QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); });
+ QObject::connect(onroad, &OnroadWindow::onroadSettingsPanelRequested, this, [=] { sidebar->hide(); });
+}
+
+void HomeWindowSP::showMapPanel(bool show) {
+ onroad->showMapPanel(show);
+}
+
+void HomeWindowSP::updateState(const UIState &s) { //OVERRIDE
+ HomeWindow::updateState(s);
+
+ uiStateSP()->scene.map_visible = onroad->isMapVisible();
+ uiStateSP()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible();
+}
+
+void HomeWindowSP::mousePressEvent(QMouseEvent* e) {
+ // We are not calling the parent for the time being because it only handles sidebar, and the sidebar code conflicts
+ // with ours as we turn off the sidebar after it was turned on by the parent when the tap happens beyond the 300px of the left.
+ // HomeWindow::mousePressEvent(e);
+
+ if (uiStateSP()->scene.started) {
+ if (uiStateSP()->scene.onroadScreenOff != -2) {
+ uiStateSP()->scene.touched2 = true;
+ QTimer::singleShot(500, []() { uiStateSP()->scene.touched2 = false; });
+ }
+ if (uiStateSP()->scene.button_auto_hide) {
+ uiStateSP()->scene.touch_to_wake = true;
+ uiStateSP()->scene.sleep_btn_fading_in = true;
+ QTimer::singleShot(500, []() { uiStateSP()->scene.touch_to_wake = false; });
+ }
+ }
+
+ // TODO: a very similar, but not identical call is made by parent. Which made me question if I should override it here...
+ // Will have to revisit later if this is not behaving as expected.
+ // Handle sidebar collapsing
+ if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) {
+ LOGD("HomeWindowSP sidebar->isVisible() [%d] | e->x() [%d] | sidebar->width() [%d]", sidebar->isVisible(), e->x(), sidebar->width());
+ if (onroad->wakeScreenTimeout()) {
+ LOGD("HomeWindowSP wakeScreenTimeout() [%d]", onroad->wakeScreenTimeout());
+ sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible());
+ }
+ }
+}
diff --git a/selfdrive/ui/sunnypilot/qt/home.h b/selfdrive/ui/sunnypilot/qt/home.h
new file mode 100644
index 0000000000..8bb44d81b4
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/home.h
@@ -0,0 +1,59 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include
+#include
+
+#include "common/params.h"
+#include "selfdrive/ui/qt/body.h"
+#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/qt/home.h"
+
+#ifdef SUNNYPILOT
+#include "selfdrive/ui/sunnypilot/qt/sidebar.h"
+#define OnroadWindow OnroadWindowSP
+#else
+#include "selfdrive/ui/qt/sidebar.h"
+#include "selfdrive/ui/qt/onroad/onroad_home.h"
+#endif
+
+class HomeWindowSP : public HomeWindow {
+ Q_OBJECT
+
+public:
+ explicit HomeWindowSP(QWidget* parent = 0);
+
+public slots:
+ void showMapPanel(bool show);
+
+protected:
+ void mousePressEvent(QMouseEvent* e) override;
+private slots:
+ void updateState(const UIState &s) override;
+};
diff --git a/selfdrive/ui/sunnypilot/qt/maps/map.cc b/selfdrive/ui/sunnypilot/qt/maps/map.cc
new file mode 100644
index 0000000000..f1d649980a
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/maps/map.cc
@@ -0,0 +1,293 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/maps/map.h"
+
+#include "common/swaglog.h"
+#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
+
+const float MAX_ZOOM = 17;
+const float MIN_ZOOM = 14;
+const float MAX_PITCH = 50;
+
+MapWindowSP::MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settings) {
+ QObject::disconnect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState);
+ QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &MapWindowSP::updateState);
+}
+
+MapWindowSP::~MapWindowSP() {
+ makeCurrent();
+}
+
+void MapWindowSP::initLayers() {
+ // This doesn't work from initializeGL
+ if (!m_map->layerExists("modelPathLayer")) {
+ qDebug() << "Initializing modelPathLayer";
+ QVariantMap modelPath;
+ //modelPath["id"] = "modelPathLayer";
+ modelPath["type"] = "line";
+ modelPath["source"] = "modelPathSource";
+ m_map->addLayer("modelPathLayer", modelPath);
+ m_map->setPaintProperty("modelPathLayer", "line-color", QColor("red"));
+ m_map->setPaintProperty("modelPathLayer", "line-width", 5.0);
+ m_map->setLayoutProperty("modelPathLayer", "line-cap", "round");
+ }
+ if (!m_map->layerExists("navLayer")) {
+ qDebug() << "Initializing navLayer";
+ QVariantMap nav;
+ nav["type"] = "line";
+ nav["source"] = "navSource";
+ m_map->addLayer("navLayer", nav, "road-intersection");
+
+ QVariantMap transition;
+ transition["duration"] = 400; // ms
+ m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiStateSP()->scene.navigate_on_openpilot_deprecated));
+ m_map->setPaintProperty("navLayer", "line-color-transition", transition);
+ m_map->setPaintProperty("navLayer", "line-width", 7.5);
+ m_map->setLayoutProperty("navLayer", "line-cap", "round");
+ }
+ if (!m_map->layerExists("pinLayer")) {
+ qDebug() << "Initializing pinLayer";
+ m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg"));
+ QVariantMap pin;
+ pin["type"] = "symbol";
+ pin["source"] = "pinSource";
+ m_map->addLayer("pinLayer", pin);
+ m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport");
+ m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker");
+ m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true);
+ m_map->setLayoutProperty("pinLayer", "icon-allow-overlap", true);
+ m_map->setLayoutProperty("pinLayer", "symbol-sort-key", 0);
+ m_map->setLayoutProperty("pinLayer", "icon-anchor", "bottom");
+ }
+ if (!m_map->layerExists("carPosLayer")) {
+ qDebug() << "Initializing carPosLayer";
+ m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg"));
+
+ QVariantMap carPos;
+ carPos["type"] = "symbol";
+ carPos["source"] = "carPosSource";
+ m_map->addLayer("carPosLayer", carPos);
+ m_map->setLayoutProperty("carPosLayer", "icon-pitch-alignment", "map");
+ m_map->setLayoutProperty("carPosLayer", "icon-image", "label-arrow");
+ m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5);
+ m_map->setLayoutProperty("carPosLayer", "icon-ignore-placement", true);
+ m_map->setLayoutProperty("carPosLayer", "icon-allow-overlap", true);
+ // TODO: remove, symbol-sort-key does not seem to matter outside of each layer
+ m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0);
+ }
+ if ((!m_map->layerExists("buildingsLayer")) && uiStateSP()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage
+ qDebug() << "Initializing buildingsLayer";
+ QVariantMap buildings;
+ buildings["id"] = "buildingsLayer";
+ buildings["source"] = "composite";
+ buildings["source-layer"] = "building";
+ buildings["type"] = "fill-extrusion";
+ buildings["minzoom"] = 15;
+ m_map->addLayer("buildingsLayer", buildings);
+ m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"}));
+
+ QVariantList fillExtrusionHeight = { // scale buildings as you zoom in
+ "interpolate",
+ QVariantList{"linear"},
+ QVariantList{"zoom"},
+ 15, 0,
+ 15.05, QVariantList{"get", "height"}
+ };
+
+ QVariantList fillExtrusionBase = {
+ "interpolate",
+ QVariantList{"linear"},
+ QVariantList{"zoom"},
+ 15, 0,
+ 15.05, QVariantList{"get", "min_height"}
+ };
+
+ QVariantList fillExtrusionOpacity = {
+ "interpolate",
+ QVariantList{"linear"},
+ QVariantList{"zoom"},
+ 15, 0, // transparent at zoom level 15
+ 15.5, .6, // fade in
+ 17, .6, // begin fading out
+ 20, 0 // fade out when zoomed in
+ };
+
+ m_map->setPaintProperty("buildingsLayer", "fill-extrusion-color", QColor("grey"));
+ m_map->setPaintProperty("buildingsLayer", "fill-extrusion-opacity", fillExtrusionOpacity);
+ m_map->setPaintProperty("buildingsLayer", "fill-extrusion-height", fillExtrusionHeight);
+ m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase);
+ m_map->setLayoutProperty("buildingsLayer", "visibility", "visible");
+ }
+}
+
+void MapWindowSP::updateState(const UIStateSP &s) {
+ if (!uiState()->scene.started) {
+ return;
+ }
+ const SubMaster &sm = *(s.sm);
+ update();
+
+ // on rising edge of a valid system time, reinitialize the map to set a new token
+ if (sm.valid("clocks") && !prev_time_valid) {
+ LOGW("Time is now valid, reinitializing map");
+ m_settings.setApiKey(get_mapbox_token());
+ initializeGL();
+ }
+ prev_time_valid = sm.valid("clocks");
+
+ if (sm.updated("modelV2")) {
+ // set path color on change, and show map on rising edge of navigate on openpilot
+ auto car_control = sm["carControl"].getCarControl();
+ bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() &&
+ (sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive());
+ if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) {
+ if (loaded_once) {
+ m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled));
+ }
+ if (nav_enabled) {
+ emit requestVisible(true);
+ }
+ }
+ uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled;
+ }
+
+ if (sm.updated("liveLocationKalman")) {
+ auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman();
+ auto locationd_pos = locationd_location.getPositionGeodetic();
+ auto locationd_orientation = locationd_location.getCalibratedOrientationNED();
+ auto locationd_velocity = locationd_location.getVelocityCalibrated();
+ auto locationd_ecef = locationd_location.getPositionECEF();
+
+ locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && locationd_ecef.getValid());
+ if (locationd_valid) {
+ // Check std norm
+ auto pos_ecef_std = locationd_ecef.getStd();
+ bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100;
+ locationd_valid = pos_accurate_enough;
+ }
+
+ if (locationd_valid) {
+ last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]);
+ last_bearing = RAD2DEG(locationd_orientation.getValue()[2]);
+ velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0]));
+ }
+ }
+
+ if (sm.updated("navRoute") && sm["navRoute"].getNavRoute().getCoordinates().size()) {
+ auto nav_dest = coordinate_from_param("NavDestination");
+ bool allow_open = std::exchange(last_valid_nav_dest, nav_dest) != nav_dest &&
+ nav_dest && !isVisible();
+ qWarning() << "Got new navRoute from navd. Opening map:" << allow_open;
+
+ // Show map on destination set/change
+ if (allow_open) {
+ emit requestSettings(false);
+ emit requestVisible(true);
+ }
+ }
+
+ loaded_once = loaded_once || (m_map && m_map->isFullyLoaded());
+ if (!loaded_once) {
+ setError(tr("Map Loading"));
+ return;
+ }
+ initLayers();
+
+ if (!locationd_valid) {
+ setError(tr("Waiting for GPS"));
+ } else if (routing_problem) {
+ setError(tr("Waiting for route"));
+ } else {
+ setError("");
+ }
+
+ if (locationd_valid) {
+ // Update current location marker
+ auto point = coordinate_to_collection(*last_position);
+ QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {});
+ QVariantMap carPosSource;
+ carPosSource["type"] = "geojson";
+ carPosSource["data"] = QVariant::fromValue(feature1);
+ m_map->updateSource("carPosSource", carPosSource);
+
+ // Map bearing isn't updated when interacting, keep location marker up to date
+ if (last_bearing) {
+ m_map->setLayoutProperty("carPosLayer", "icon-rotate", *last_bearing - m_map->bearing());
+ }
+ }
+
+ if (interaction_counter == 0) {
+ if (last_position) m_map->setCoordinate(*last_position);
+ if (last_bearing) m_map->setBearing(*last_bearing);
+ m_map->setZoom(util::map_val(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM));
+ } else {
+ interaction_counter--;
+ }
+
+ if (sm.updated("navInstruction")) {
+ // an invalid navInstruction packet with a nav destination is only possible if:
+ // - API exception/no internet
+ // - route response is empty
+ // - any time navd is waiting for recompute_countdown
+ routing_problem = !sm.valid("navInstruction") && coordinate_from_param("NavDestination").has_value();
+
+ if (sm.valid("navInstruction")) {
+ auto i = sm["navInstruction"].getNavInstruction();
+ map_eta->updateETA(i.getTimeRemaining(), i.getTimeRemainingTypical(), i.getDistanceRemaining());
+
+ if (locationd_valid) {
+ m_map->setPitch(MAX_PITCH); // TODO: smooth pitching based on maneuver distance
+ map_instructions->updateInstructions(i);
+ }
+ } else {
+ clearRoute();
+ }
+ }
+
+ if (sm.rcv_frame("navRoute") != route_rcv_frame) {
+ qWarning() << "Updating navLayer with new route";
+ auto route = sm["navRoute"].getNavRoute();
+ auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates());
+ QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {});
+ QVariantMap navSource;
+ navSource["type"] = "geojson";
+ navSource["data"] = QVariant::fromValue(feature);
+ m_map->updateSource("navSource", navSource);
+ m_map->setLayoutProperty("navLayer", "visibility", "visible");
+
+ route_rcv_frame = sm.rcv_frame("navRoute");
+ updateDestinationMarker();
+ }
+}
+
+void MapWindowSP::offroadTransition(bool offroad) {
+ if (offroad) {
+ uiStateSP()->scene.navigate_on_openpilot_deprecated = false;
+ }
+
+ MapWindow::offroadTransition(offroad);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/maps/map.h b/selfdrive/ui/sunnypilot/qt/maps/map.h
new file mode 100644
index 0000000000..00c20e2ae7
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/maps/map.h
@@ -0,0 +1,53 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include "selfdrive/ui/qt/maps/map.h"
+#include "selfdrive/ui/sunnypilot/ui.h"
+
+#include
+
+class MapWindowSP : public MapWindow {
+ Q_OBJECT
+
+public:
+ explicit MapWindowSP(const QMapLibre::Settings &);
+ ~MapWindowSP();
+
+private:
+ void initLayers();
+ // Blue with normal nav, green when nav is input into the model
+ QColor getNavPathColor(bool nav_enabled) {
+ return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee");
+ }
+
+protected slots:
+ void updateState(const UIStateSP &s);
+
+public slots:
+ void offroadTransition(bool offroad);
+};
diff --git a/selfdrive/ui/sunnypilot/qt/maps/map_helpers.h b/selfdrive/ui/sunnypilot/qt/maps/map_helpers.h
new file mode 100644
index 0000000000..464b35995c
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/maps/map_helpers.h
@@ -0,0 +1,38 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include "selfdrive/ui/qt/maps/map_helpers.h"
+
+#define MAPBOX_TOKEN MAPBOX_TOKEN_SP
+#define MAPS_HOST MAPS_HOST_SP
+
+#include "common/params.h"
+
+const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("CustomMapboxTokenSk")) != "" ?
+ QString::fromStdString(Params().get("CustomMapboxTokenSk")) : util::getenv("MAPBOX_TOKEN").c_str();
+const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str();
diff --git a/selfdrive/ui/sunnypilot/qt/network/networking.cc b/selfdrive/ui/sunnypilot/qt/network/networking.cc
new file mode 100644
index 0000000000..0b26c2f8ba
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/networking.cc
@@ -0,0 +1,452 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/network/networking.h"
+
+#include
+
+#include
+#include
+#include
+#include "selfdrive/ui/qt/widgets/ssh_keys.h"
+
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/qt/qt_window.h"
+#include "selfdrive/ui/qt/util.h"
+#include "selfdrive/ui/qt/widgets/scrollview.h"
+
+static const int ICON_WIDTH = 49;
+
+// Networking functions
+
+NetworkingSP::NetworkingSP(QWidget* parent, bool show_advanced) : QFrame(parent) {
+ main_layout = new QStackedLayout(this);
+
+ wifi = new WifiManager(this);
+ connect(wifi, &WifiManager::refreshSignal, this, &NetworkingSP::refresh);
+ connect(wifi, &WifiManager::wrongPassword, this, &NetworkingSP::wrongPassword);
+
+ wifiScreen = new QWidget(this);
+ QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen);
+ vlayout->setContentsMargins(20, 20, 20, 20);
+ QHBoxLayout* hlayout = new QHBoxLayout();
+ QPushButton* scanButton = new QPushButton(tr("Scan"));
+ scanButton->setObjectName("scan_btn");
+ scanButton->setFixedSize(400, 100);
+ connect(wifi, &WifiManager::refreshSignal, this, [=]() { scanButton->setText(tr("Scan")); scanButton->setEnabled(true); });
+ connect(scanButton, &QPushButton::clicked, [=]() { scanButton->setText(tr("Scanning...")); scanButton->setEnabled(false); wifi->requestScan(); });
+
+ hlayout->addWidget(scanButton);
+ hlayout->addStretch(1); // Pushes the button all the way to the left
+
+ if (show_advanced) {
+ hlayout->setSpacing(10);
+
+ QPushButton* advancedSettings = new QPushButton(tr("Advanced"));
+ advancedSettings->setObjectName("advanced_btn");
+ advancedSettings->setFixedSize(400, 100);
+ connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); });
+ hlayout->addWidget(advancedSettings);
+ }
+
+ vlayout->addLayout(hlayout);
+ vlayout->addSpacing(10);
+
+ wifiWidget = new WifiUISP(this, wifi);
+ wifiWidget->setObjectName("wifiWidget");
+ connect(wifiWidget, &WifiUISP::connectToNetwork, this, &NetworkingSP::connectToNetwork);
+
+ ScrollView *wifiScroller = new ScrollView(wifiWidget, this);
+ wifiScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ vlayout->addWidget(wifiScroller, 1);
+ main_layout->addWidget(wifiScreen);
+
+ an = new AdvancedNetworkingSP(this, wifi);
+ connect(an, &AdvancedNetworkingSP::backPress, [=]() { main_layout->setCurrentWidget(wifiScreen); });
+ connect(an, &AdvancedNetworkingSP::requestWifiScreen, [=]() { main_layout->setCurrentWidget(wifiScreen); });
+ main_layout->addWidget(an);
+
+ QPalette pal = palette();
+ pal.setColor(QPalette::Window, QColor(0x29, 0x29, 0x29));
+ setAutoFillBackground(true);
+ setPalette(pal);
+
+ setStyleSheet(R"(
+ #wifiWidget > QPushButton, #back_btn, #advanced_btn, #scan_btn{
+ font-size: 50px;
+ margin: 0px;
+ padding: 15px;
+ border-width: 0;
+ border-radius: 30px;
+ color: #dddddd;
+ background-color: #393939;
+ }
+ #back_btn:pressed, #advanced_btn:pressed, #scan_btn:pressed {
+ background-color: #4a4a4a;
+ }
+ #scan_btn:disabled {
+ background-color: #121212;
+ color: #33FFFFFF;
+ }
+ )");
+ main_layout->setCurrentWidget(wifiScreen);
+}
+
+void NetworkingSP::refresh() {
+ wifiWidget->refresh();
+ an->refresh();
+}
+
+void NetworkingSP::connectToNetwork(const Network n) {
+ if (wifi->isKnownConnection(n.ssid)) {
+ wifi->activateWifiConnection(n.ssid);
+ } else if (n.security_type == SecurityType::OPEN) {
+ wifi->connect(n, false);
+ } else if (n.security_type == SecurityType::WPA) {
+ QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8);
+ if (!pass.isEmpty()) {
+ wifi->connect(n, false, pass);
+ }
+ }
+}
+
+void NetworkingSP::wrongPassword(const QString &ssid) {
+ if (wifi->seenNetworks.contains(ssid)) {
+ const Network &n = wifi->seenNetworks.value(ssid);
+ QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8);
+ if (!pass.isEmpty()) {
+ wifi->connect(n, false, pass);
+ }
+ }
+}
+
+void NetworkingSP::showEvent(QShowEvent *event) {
+ wifi->start();
+}
+
+void NetworkingSP::hideEvent(QHideEvent *event) {
+ main_layout->setCurrentWidget(wifiScreen);
+ wifi->stop();
+}
+
+// AdvancedNetworkingSP functions
+
+AdvancedNetworkingSP::AdvancedNetworkingSP(QWidget* parent, WifiManager* wifi): QWidget(parent), wifi(wifi) {
+
+ QVBoxLayout* main_layout = new QVBoxLayout(this);
+ main_layout->setMargin(40);
+ main_layout->setSpacing(20);
+
+ // Back button
+ QPushButton* back = new QPushButton(tr("Back"));
+ back->setObjectName("back_btn");
+ back->setFixedSize(400, 100);
+ connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
+ main_layout->addWidget(back, 0, Qt::AlignLeft);
+
+ ListWidgetSP *list = new ListWidgetSP(this);
+ // Enable tethering layout
+ const bool set_hotspot_on_boot = params.getBool("HotspotOnBoot") && params.getBool("HotspotOnBootConfirmed");
+ tetheringToggle = new ToggleControlSP(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled() || set_hotspot_on_boot);
+ list->addItem(tetheringToggle);
+ QObject::connect(tetheringToggle, &ToggleControlSP::toggleFlipped, this, &AdvancedNetworkingSP::toggleTethering);
+
+ hotspotOnBootToggle = new ToggleControlSP(
+ tr("Retain hotspot/tethering state"),
+ tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."),
+ "",
+ params.getBool("HotspotOnBoot")
+ );
+ hotspotOnBootToggle->setEnabled(wifi->isTetheringEnabled() || set_hotspot_on_boot);
+ QObject::connect(hotspotOnBootToggle, &ToggleControlSP::toggleFlipped, [=](bool state) {
+ params.putBool("HotspotOnBoot", state);
+ });
+ list->addItem(hotspotOnBootToggle);
+
+ // Change tethering password
+ ButtonControlSP *editPasswordButton = new ButtonControlSP(tr("Tethering Password"), tr("EDIT"));
+ connect(editPasswordButton, &ButtonControlSP::clicked, [=]() {
+ QString pass = InputDialog::getText(tr("Enter new tethering password"), this, "", true, 8, wifi->getTetheringPassword());
+ if (!pass.isEmpty()) {
+ wifi->changeTetheringPassword(pass);
+ }
+ });
+ list->addItem(editPasswordButton);
+
+ // IP address
+ ipLabel = new LabelControlSP(tr("IP Address"), wifi->ipv4_address);
+ list->addItem(ipLabel);
+
+ // SSH keys
+ list->addItem(new SshToggle());
+ list->addItem(new SshControl());
+
+ // Roaming toggle
+ const bool roamingEnabled = params.getBool("GsmRoaming");
+ roamingToggle = new ToggleControlSP(tr("Enable Roaming"), "", "", roamingEnabled);
+ QObject::connect(roamingToggle, &ToggleControlSP::toggleFlipped, [=](bool state) {
+ params.putBool("GsmRoaming", state);
+ wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")), params.getBool("GsmMetered"));
+ });
+ list->addItem(roamingToggle);
+
+ // APN settings
+ editApnButton = new ButtonControlSP(tr("APN Setting"), tr("EDIT"));
+ connect(editApnButton, &ButtonControlSP::clicked, [=]() {
+ const QString cur_apn = QString::fromStdString(params.get("GsmApn"));
+ QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed();
+
+ if (apn.isEmpty()) {
+ params.remove("GsmApn");
+ } else {
+ params.put("GsmApn", apn.toStdString());
+ }
+ wifi->updateGsmSettings(params.getBool("GsmRoaming"), apn, params.getBool("GsmMetered"));
+ });
+ list->addItem(editApnButton);
+
+ // Metered toggle
+ const bool metered = params.getBool("GsmMetered");
+ meteredToggle = new ToggleControlSP(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered);
+ QObject::connect(meteredToggle, &SshToggle::toggleFlipped, [=](bool state) {
+ params.putBool("GsmMetered", state);
+ wifi->updateGsmSettings(params.getBool("GsmRoaming"), QString::fromStdString(params.get("GsmApn")), state);
+ });
+ list->addItem(meteredToggle);
+
+ // Hidden Network
+ hiddenNetworkButton = new ButtonControlSP(tr("Hidden Network"), tr("CONNECT"));
+ connect(hiddenNetworkButton, &ButtonControlSP::clicked, [=]() {
+ QString ssid = InputDialog::getText(tr("Enter SSID"), this, "", false, 1);
+ if (!ssid.isEmpty()) {
+ QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(ssid), true, -1);
+ Network hidden_network;
+ hidden_network.ssid = ssid.toUtf8();
+ if (!pass.isEmpty()) {
+ hidden_network.security_type = SecurityType::WPA;
+ wifi->connect(hidden_network, true, pass);
+ } else {
+ wifi->connect(hidden_network, true);
+ }
+ emit requestWifiScreen();
+ }
+ });
+ list->addItem(hiddenNetworkButton);
+
+ // Ngrok
+ QProcess process;
+ process.start("sudo service ngrok status | grep running");
+ process.waitForFinished();
+ QString output = QString(process.readAllStandardOutput());
+ bool ngrokRunning = !output.isEmpty();
+ ToggleControlSP *ngrokToggle = new ToggleControlSP(tr("Ngrok Service"), "", "", ngrokRunning);
+ connect(ngrokToggle, &ToggleControlSP::toggleFlipped, [=](bool state) {
+ if (state) std::system("sudo ngrok service start");
+ else std::system("sudo ngrok service stop");
+ });
+ list->addItem(ngrokToggle);
+
+ // Set initial config
+ wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered);
+
+ connect(uiState(), &UIState::primeTypeChanged, this, [=](PrimeType prime_type) {
+ bool gsmVisible = prime_type == PrimeType::NONE || prime_type == PrimeType::LITE;
+ roamingToggle->setVisible(gsmVisible);
+ editApnButton->setVisible(gsmVisible);
+ meteredToggle->setVisible(gsmVisible);
+ });
+
+ main_layout->addWidget(new ScrollView(list, this));
+ main_layout->addStretch(1);
+}
+
+void AdvancedNetworkingSP::refresh() {
+ ipLabel->setText(wifi->ipv4_address);
+ tetheringToggle->setEnabled(true);
+ update();
+}
+
+void AdvancedNetworkingSP::toggleTethering(bool enabled) {
+ params.putBool("HotspotOnBootConfirmed", enabled);
+ wifi->setTetheringEnabled(enabled);
+ tetheringToggle->setEnabled(false);
+
+ hotspotOnBootToggle->setEnabled(enabled);
+ if (!enabled) {
+ params.remove("HotspotOnBoot");
+ }
+}
+
+// WifiUISP functions
+
+WifiUISP::WifiUISP(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) {
+ QVBoxLayout *main_layout = new QVBoxLayout(this);
+ main_layout->setContentsMargins(0, 0, 0, 0);
+ main_layout->setSpacing(0);
+
+ // load imgs
+ for (const auto &s : {"low", "medium", "high", "full"}) {
+ QPixmap pix(ASSET_PATH + "/offroad/icon_wifi_strength_" + s + ".svg");
+ strengths.push_back(pix.scaledToHeight(68, Qt::SmoothTransformation));
+ }
+ lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
+ checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
+ circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
+
+ scanningLabel = new QLabel(tr("Scanning for networks..."));
+ scanningLabel->setStyleSheet("font-size: 65px;");
+ main_layout->addWidget(scanningLabel, 0, Qt::AlignCenter);
+
+ wifi_list_widget = new ListWidgetSP(this);
+ wifi_list_widget->setVisible(false);
+ main_layout->addWidget(wifi_list_widget);
+
+ setStyleSheet(R"(
+ QScrollBar::handle:vertical {
+ min-height: 0px;
+ border-radius: 4px;
+ background-color: #8A8A8A;
+ }
+ #forgetBtn {
+ font-size: 32px;
+ font-weight: 600;
+ color: #292929;
+ background-color: #BDBDBD;
+ border-width: 1px solid #828282;
+ border-radius: 5px;
+ padding: 40px;
+ padding-bottom: 16px;
+ padding-top: 16px;
+ }
+ #forgetBtn:pressed {
+ background-color: #828282;
+ }
+ #connecting {
+ font-size: 32px;
+ font-weight: 600;
+ color: white;
+ border-radius: 0;
+ padding: 27px;
+ padding-left: 43px;
+ padding-right: 43px;
+ background-color: black;
+ }
+ #ssidLabel {
+ text-align: left;
+ border: none;
+ padding-top: 50px;
+ padding-bottom: 50px;
+ }
+ #ssidLabel:disabled {
+ color: #696969;
+ }
+ )");
+}
+
+void WifiUISP::refresh() {
+ bool is_empty = wifi->seenNetworks.isEmpty();
+ scanningLabel->setVisible(is_empty);
+ wifi_list_widget->setVisible(!is_empty);
+ if (is_empty) return;
+
+ setUpdatesEnabled(false);
+
+ const bool is_tethering_enabled = wifi->isTetheringEnabled();
+ QList sortedNetworks = wifi->seenNetworks.values();
+ std::sort(sortedNetworks.begin(), sortedNetworks.end(), compare_by_strength);
+
+ int n = 0;
+ for (Network &network : sortedNetworks) {
+ QPixmap status_icon;
+ if (network.connected == ConnectedType::CONNECTED) {
+ status_icon = checkmark;
+ } else if (network.security_type == SecurityType::UNSUPPORTED) {
+ status_icon = circled_slash;
+ } else if (network.security_type == SecurityType::WPA) {
+ status_icon = lock;
+ }
+ bool show_forget_btn = wifi->isKnownConnection(network.ssid) && !is_tethering_enabled;
+ QPixmap strength = strengths[strengthLevel(network.strength)];
+
+ auto item = getItem(n++);
+ item->setItem(network, status_icon, show_forget_btn, strength);
+ item->setVisible(true);
+ }
+ for (; n < wifi_items.size(); ++n) wifi_items[n]->setVisible(false);
+
+ setUpdatesEnabled(true);
+}
+
+WifiItemSP *WifiUISP::getItem(int n) {
+ auto item = n < wifi_items.size() ? wifi_items[n] : wifi_items.emplace_back(new WifiItemSP(tr("CONNECTING..."), tr("FORGET")));
+ if (!item->parentWidget()) {
+ QObject::connect(item, &WifiItemSP::connectToNetwork, this, &WifiUISP::connectToNetwork);
+ QObject::connect(item, &WifiItemSP::forgotNetwork, [this](const Network n) {
+ if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(n.ssid)), tr("Forget"), this))
+ wifi->forgetConnection(n.ssid);
+ });
+ wifi_list_widget->addItem(item);
+ }
+ return item;
+}
+
+// WifiItemSP
+
+WifiItemSP::WifiItemSP(const QString &connecting_text, const QString &forget_text, QWidget *parent) : QWidget(parent) {
+ QHBoxLayout *hlayout = new QHBoxLayout(this);
+ hlayout->setContentsMargins(44, 0, 73, 0);
+ hlayout->setSpacing(50);
+
+ hlayout->addWidget(ssidLabel = new ElidedLabelSP());
+ ssidLabel->setObjectName("ssidLabel");
+ ssidLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ hlayout->addWidget(connecting = new QPushButton(connecting_text), 0, Qt::AlignRight);
+ connecting->setObjectName("connecting");
+ hlayout->addWidget(forgetBtn = new QPushButton(forget_text), 0, Qt::AlignRight);
+ forgetBtn->setObjectName("forgetBtn");
+ hlayout->addWidget(iconLabel = new QLabel(), 0, Qt::AlignRight);
+ hlayout->addWidget(strengthLabel = new QLabel(), 0, Qt::AlignRight);
+
+ iconLabel->setFixedWidth(ICON_WIDTH);
+ QObject::connect(forgetBtn, &QPushButton::clicked, [this]() { emit forgotNetwork(network); });
+ QObject::connect(ssidLabel, &ElidedLabelSP::clicked, [this]() {
+ if (network.connected == ConnectedType::DISCONNECTED) emit connectToNetwork(network);
+ });
+}
+
+void WifiItemSP::setItem(const Network &n, const QPixmap &status_icon, bool show_forget_btn, const QPixmap &strength_icon) {
+ network = n;
+
+ ssidLabel->setText(n.ssid);
+ ssidLabel->setEnabled(n.security_type != SecurityType::UNSUPPORTED);
+ ssidLabel->setFont(InterFont(55, network.connected == ConnectedType::DISCONNECTED ? QFont::Normal : QFont::Bold));
+
+ connecting->setVisible(n.connected == ConnectedType::CONNECTING);
+ forgetBtn->setVisible(show_forget_btn);
+
+ iconLabel->setPixmap(status_icon);
+ strengthLabel->setPixmap(strength_icon);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/network/networking.h b/selfdrive/ui/sunnypilot/qt/network/networking.h
new file mode 100644
index 0000000000..931fe3443e
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/networking.h
@@ -0,0 +1,128 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include
+#include
+
+#include "selfdrive/ui/qt/network/wifi_manager.h"
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+
+class WifiItemSP : public QWidget {
+ Q_OBJECT
+public:
+ explicit WifiItemSP(const QString &connecting_text, const QString &forget_text, QWidget* parent = nullptr);
+ void setItem(const Network& n, const QPixmap &icon, bool show_forget_btn, const QPixmap &strength);
+
+signals:
+ // Cannot pass Network by reference. it may change after the signal is sent.
+ void connectToNetwork(const Network n);
+ void forgotNetwork(const Network n);
+
+protected:
+ ElidedLabelSP* ssidLabel;
+ QPushButton* connecting;
+ QPushButton* forgetBtn;
+ QLabel* iconLabel;
+ QLabel* strengthLabel;
+ Network network;
+};
+
+class WifiUISP : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit WifiUISP(QWidget *parent = 0, WifiManager* wifi = 0);
+
+private:
+ WifiItemSP *getItem(int n);
+
+ WifiManager *wifi = nullptr;
+ QLabel *scanningLabel = nullptr;
+ QPixmap lock;
+ QPixmap checkmark;
+ QPixmap circled_slash;
+ QVector strengths;
+ ListWidgetSP *wifi_list_widget = nullptr;
+ std::vector wifi_items;
+
+signals:
+ void connectToNetwork(const Network n);
+
+public slots:
+ void refresh();
+};
+
+class AdvancedNetworkingSP : public QWidget {
+ Q_OBJECT
+public:
+ explicit AdvancedNetworkingSP(QWidget* parent = 0, WifiManager* wifi = 0);
+
+private:
+ LabelControlSP* ipLabel;
+ ToggleControlSP* tetheringToggle;
+ ToggleControlSP* roamingToggle;
+ ButtonControlSP* editApnButton;
+ ButtonControlSP* hiddenNetworkButton;
+ ToggleControlSP* meteredToggle;
+ WifiManager* wifi = nullptr;
+ Params params;
+
+ ToggleControlSP* hotspotOnBootToggle;
+
+signals:
+ void backPress();
+ void requestWifiScreen();
+
+public slots:
+ void toggleTethering(bool enabled);
+ void refresh();
+};
+
+class NetworkingSP : public QFrame {
+ Q_OBJECT
+
+public:
+ explicit NetworkingSP(QWidget* parent = 0, bool show_advanced = true);
+ WifiManager* wifi = nullptr;
+
+private:
+ QStackedLayout* main_layout = nullptr;
+ QWidget* wifiScreen = nullptr;
+ AdvancedNetworkingSP* an = nullptr;
+ WifiUISP* wifiWidget;
+
+ void showEvent(QShowEvent* event) override;
+ void hideEvent(QHideEvent* event) override;
+
+public slots:
+ void refresh();
+
+private slots:
+ void connectToNetwork(const Network n);
+ void wrongPassword(const QString &ssid);
+};
diff --git a/selfdrive/ui/qt/network/sunnylink/models/role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h
similarity index 53%
rename from selfdrive/ui/qt/network/sunnylink/models/role_model.h
rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h
index d7f3a98281..c1e28a5f9a 100644
--- a/selfdrive/ui/qt/network/sunnylink/models/role_model.h
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h
@@ -1,5 +1,30 @@
-#ifndef ROLE_MODEL_H
-#define ROLE_MODEL_H
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
#include
@@ -56,5 +81,3 @@ public:
return T(m_raw_json_object);
}
};
-
-#endif
diff --git a/selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h
similarity index 67%
rename from selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h
rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h
index 64aad1ca46..6b6470f0cd 100644
--- a/selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h
@@ -1,5 +1,30 @@
-#ifndef SPONSORROLE_MODEL_H
-#define SPONSORROLE_MODEL_H
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
#include
@@ -24,7 +49,7 @@ public:
QJsonObject json = RoleModel::toJson();
json["role_tier"] = sponsorTierToString(roleTier);
return json;
- };
+ }
static SponsorTier stringToSponsorTier(const QString &sponsorTierString) {
const auto sponsorTierStringLower = sponsorTierString.toLower();
@@ -80,5 +105,3 @@ public:
}
[[nodiscard]] auto getSponsorTierColor() const { return sponsorTierToColor(roleTier); }
};
-
-#endif
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h
new file mode 100644
index 0000000000..259d4b39b9
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h
@@ -0,0 +1,56 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include
+
+class UserModel {
+public:
+ QString device_id;
+ QString user_id;
+ qint64 created_at;
+ qint64 updated_at;
+ QString token_hash;
+
+ explicit UserModel(const QJsonObject &json) {
+ device_id = json["device_id"].toString();
+ user_id = json["user_id"].toString();
+ created_at = json["created_at"].toInt();
+ updated_at = json["updated_at"].toInt();
+ token_hash = json["token_hash"].toString();
+ }
+
+ [[nodiscard]] QJsonObject toJson() const {
+ QJsonObject json;
+ json["device_id"] = device_id;
+ json["user_id"] = user_id;
+ json["created_at"] = created_at;
+ json["updated_at"] = updated_at;
+ json["token_hash"] = token_hash;
+ return json;
+ }
+};
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc
new file mode 100644
index 0000000000..13764c5749
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc
@@ -0,0 +1,76 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink//services/base_device_service.h"
+
+#include "selfdrive/ui/sunnypilot/qt/request_repeater.h"
+
+#include "common/swaglog.h"
+#include "selfdrive/ui/qt/util.h"
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h"
+
+BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent), initial_request(nullptr), repeater(nullptr) {
+ param_watcher = new ParamWatcher(this);
+ connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
+ paramsRefresh();
+ });
+ param_watcher->addParam("SunnylinkEnabled");
+}
+
+void BaseDeviceService::paramsRefresh() {
+}
+
+void BaseDeviceService::loadDeviceData(const QString &url, bool poll) {
+ if (!is_sunnylink_enabled()) {
+ LOGW("Sunnylink is not enabled, refusing to load data.");
+ return;
+ }
+
+ auto sl_dongle_id = getSunnylinkDongleId();
+ if (!sl_dongle_id.has_value())
+ return;
+
+ QString fullUrl = SUNNYLINK_BASE_URL + "/device/" + *sl_dongle_id + url;
+ if (poll && !isCurrentyPolling()) {
+ LOGD("Polling %s", qPrintable(fullUrl));
+ LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey())));
+ repeater = new RequestRepeaterSP(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true);
+ connect(repeater, &RequestRepeaterSP::requestDone, this, &BaseDeviceService::handleResponse);
+ } else if (isCurrentyPolling()) {
+ repeater->ForceUpdate();
+ } else {
+ LOGD("Sending one-time %s", qPrintable(fullUrl));
+ initial_request = new HttpRequestSP(this, true, 10000, true);
+ connect(initial_request, &HttpRequestSP::requestDone, this, &BaseDeviceService::handleResponse);
+ }
+}
+
+void BaseDeviceService::stopPolling() {
+ if (repeater != nullptr) {
+ repeater->deleteLater();
+ repeater = nullptr;
+ }
+}
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h
new file mode 100644
index 0000000000..dced4edb52
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h
@@ -0,0 +1,50 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include "selfdrive/ui/sunnypilot/qt/request_repeater.h"
+#include "selfdrive/ui/qt/util.h"
+
+class BaseDeviceService : public QObject {
+ Q_OBJECT
+
+protected:
+ void paramsRefresh();
+ void loadDeviceData(const QString &url, bool poll = false);
+ virtual void handleResponse(const QString &response, bool success) = 0;
+
+ static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");}
+ ParamWatcher* param_watcher;
+ HttpRequestSP* initial_request = nullptr;
+ RequestRepeaterSP* repeater = nullptr;
+
+public:
+ explicit BaseDeviceService(QObject* parent = nullptr);
+ virtual QString getCacheKey() const = 0;
+ bool isCurrentyPolling() {return repeater != nullptr;}
+ void stopPolling();
+};
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc
new file mode 100644
index 0000000000..c835bf6f33
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc
@@ -0,0 +1,55 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h"
+
+#include
+#include
+
+RoleService::RoleService(QObject* parent) : BaseDeviceService(parent) {}
+
+void RoleService::load() {
+ loadDeviceData(url);
+}
+
+void RoleService::startPolling() {
+ loadDeviceData(url, true);
+}
+
+void RoleService::handleResponse(const QString &response, bool success) {
+ if (!success) return;
+
+ QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
+ QJsonArray jsonArray = doc.array();
+
+ std::vector roles;
+ for (const auto &value : jsonArray) {
+ roles.emplace_back(value.toObject());
+ }
+
+ emit rolesReady(roles);
+ uiStateSP()->setSunnylinkRoles(roles);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h
new file mode 100644
index 0000000000..91d59fe6a9
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h
@@ -0,0 +1,51 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include
+
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
+
+class RoleService : public BaseDeviceService {
+ Q_OBJECT
+
+public:
+ explicit RoleService(QObject* parent = nullptr);
+ void load();
+ void startPolling();
+ [[nodiscard]] QString getCacheKey() const final { return "Roles"; }
+
+signals:
+ void rolesReady(const std::vector &roles);
+
+protected:
+ void handleResponse(const QString&response, bool success) override;
+
+private:
+ QString url = "/roles";
+};
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc
new file mode 100644
index 0000000000..c0ce22ac89
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc
@@ -0,0 +1,57 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
+
+#include
+#include
+
+UserService::UserService(QObject* parent) : BaseDeviceService(parent) {
+ url = "/users";
+}
+
+void UserService::load() {
+ loadDeviceData(url);
+}
+
+void UserService::startPolling() {
+ loadDeviceData(url, true);
+}
+
+void UserService::handleResponse(const QString &response, bool success) {
+ if (!success) return;
+
+ QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
+ QJsonArray jsonArray = doc.array();
+
+ std::vector users;
+ for (const auto &value : jsonArray) {
+ users.emplace_back(value.toObject());
+ }
+
+ emit usersReady(users);
+ uiStateSP()->setSunnylinkDeviceUsers(users);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h
new file mode 100644
index 0000000000..aaac6c5b37
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h
@@ -0,0 +1,51 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include
+
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h"
+
+class UserService : public BaseDeviceService {
+ Q_OBJECT
+
+public:
+ explicit UserService(QObject* parent = nullptr);
+ void load();
+ void startPolling();
+ [[nodiscard]] QString getCacheKey() const final { return "Users"; };
+
+signals:
+ void usersReady(const std::vector&users);
+
+protected:
+ void handleResponse(const QString&response, bool success) override;
+
+private:
+ QString url = "/users";
+};
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc
new file mode 100644
index 0000000000..3217e44dbd
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc
@@ -0,0 +1,33 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h"
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
+
+SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) {
+ role_service = new RoleService(parent);
+ user_service = new UserService(parent);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h
new file mode 100644
index 0000000000..625f99f1f4
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h
@@ -0,0 +1,41 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include
+
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h"
+#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
+
+class SunnylinkClient : public QObject {
+ Q_OBJECT
+
+public:
+ explicit SunnylinkClient(QObject* parent);
+ RoleService* role_service;
+ UserService* user_service;
+};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc
new file mode 100644
index 0000000000..99964c4681
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc
@@ -0,0 +1,189 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
+
+#include
+#include
+#include
+
+#include "selfdrive/ui/qt/qt_window.h"
+#include "selfdrive/ui/qt/widgets/prime.h"
+
+DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) {
+ fleetManagerPin = new ButtonControlSP(
+ pin_title + pin, tr("TOGGLE"),
+ tr("Enable or disable PIN requirement for Fleet Manager access."));
+ connect(fleetManagerPin, &ButtonControlSP::clicked, [=]() {
+ if (params.getBool("FleetManagerPin")) {
+ if (ConfirmationDialog::confirm(tr("Are you sure you want to turn off PIN requirement?"), tr("Turn Off"), this)) {
+ params.remove("FleetManagerPin");
+ refreshPin();
+ }
+ } else {
+ params.putBool("FleetManagerPin", true);
+ refreshPin();
+ }
+ });
+ AddWidgetAt(2, fleetManagerPin);
+
+ fs_watch = new QFileSystemWatcher(this);
+ connect(fs_watch, &QFileSystemWatcher::fileChanged, this, &DevicePanelSP::onPinFileChanged);
+
+ QString pin_path = "/data/otp/otp.conf";
+ QString pin_require = "/data/params/d/FleetManagerPin";
+ fs_watch->addPath(pin_path);
+ fs_watch->addPath(pin_require);
+ refreshPin();
+
+ // Error Troubleshoot
+ auto errorBtn = new ButtonControlSP(
+ tr("Error Troubleshoot"), tr("VIEW"),
+ tr("Display error from the tmux session when an error has occurred from a system process."));
+ QFileInfo file("/data/community/crashes/error.txt");
+ QDateTime modifiedTime = file.lastModified();
+ QString modified_time = modifiedTime.toString("yyyy-MM-dd hh:mm:ss ");
+ connect(errorBtn, &ButtonControlSP::clicked, [=]() {
+ const std::string txt = util::read_file("/data/community/crashes/error.txt");
+ ConfirmationDialog::rich(modified_time + QString::fromStdString(txt), this);
+ });
+ AddWidgetAt(3, errorBtn);
+
+
+ auto resetMapboxTokenBtn = new ButtonControlSP(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps."));
+ connect(resetMapboxTokenBtn, &ButtonControlSP::clicked, [=]() {
+ if (ConfirmationDialog::confirm(tr("Are you sure you want to reset access tokens for all map services?"), tr("Reset"), this)) {
+ std::vector tokens = {
+ "CustomMapboxTokenPk",
+ "CustomMapboxTokenSk",
+ "AmapKey1",
+ "AmapKey2",
+ "GmapKey"
+ };
+ for (const auto& token : tokens) {
+ params.remove(token);
+ }
+ }
+ });
+ AddWidgetAt(6, resetMapboxTokenBtn);
+
+ auto resetParamsBtn = new ButtonControlSP(tr("Reset sunnypilot Settings"), tr("RESET"), "");
+ connect(resetParamsBtn, &ButtonControlSP::clicked, [=]() {
+ if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all sunnypilot settings?"), tr("Reset"), this)) {
+ std::system("sudo rm -rf /data/params/d/*");
+ Hardware::reboot();
+ }
+ });
+ AddWidgetAt(6, resetParamsBtn);
+
+ QObject::connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) {
+ for (auto btn : findChildren()) {
+ if (btn != pair_device && btn != errorBtn) {
+ btn->setEnabled(offroad);
+ }
+ }
+ });
+
+ offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad"));
+ offroad_btn->setObjectName("offroad_btn");
+ QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanelSP::forceoffroad);
+
+ QVBoxLayout *buttons_layout = new QVBoxLayout();
+ buttons_layout->setSpacing(24);
+ buttons_layout->addLayout(power_layout);
+ buttons_layout->addWidget(offroad_btn);
+ addItem(buttons_layout);
+
+ updateLabels();
+}
+
+void DevicePanelSP::onPinFileChanged(const QString &file_path) {
+ if (file_path == "/data/params/d/FleetManagerPin") {
+ refreshPin();
+ } else if (file_path == "/data/otp/otp.conf") {
+ refreshPin();
+ }
+}
+
+void DevicePanelSP::refreshPin() {
+ QFile f("/data/otp/otp.conf");
+ QFile require("/data/params/d/FleetManagerPin");
+ if (!require.exists()) {
+ setSpacing(50);
+ fleetManagerPin->setTitle(pin_title + tr("OFF"));
+ } else if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ pin = f.readAll();
+ f.close();
+ setSpacing(50);
+ fleetManagerPin->setTitle(pin_title + pin);
+ }
+}
+
+void DevicePanelSP::forceoffroad() {
+ if (!uiStateSP()->engaged()) {
+ if (params.getBool("ForceOffroad")) {
+ if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) {
+ // Check engaged again in case it changed while the dialog was open
+ if (!uiStateSP()->engaged()) {
+ params.remove("ForceOffroad");
+ }
+ }
+ } else {
+ if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) {
+ // Check engaged again in case it changed while the dialog was open
+ if (!uiStateSP()->engaged()) {
+ params.putBool("ForceOffroad", true);
+ }
+ }
+ }
+ } else {
+ ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this);
+ }
+
+ updateLabels();
+}
+
+void DevicePanelSP::showEvent(QShowEvent *event) {
+ DevicePanel::showEvent(event);
+ updateLabels();
+}
+
+void DevicePanelSP::updateLabels() {
+ if (!isVisible()) {
+ return;
+ }
+
+ bool force_offroad_param = params.getBool("ForceOffroad");
+ QString offroad_btn_style = force_offroad_param ? "#393939" : "#E22C2C";
+ QString offroad_btn_pressed_style = force_offroad_param ? "#4a4a4a" : "#FF2424";
+ QString btn_common_style = QString("QPushButton { height: 120px; border-radius: 15px; background-color: %1; }"
+ "QPushButton:pressed { background-color: %2; }")
+ .arg(offroad_btn_style,
+ offroad_btn_pressed_style);
+
+ offroad_btn->setText(force_offroad_param ? tr("Unforce Offroad") : tr("Force Offroad"));
+ offroad_btn->setStyleSheet(btn_common_style + offroad_btn_style + offroad_btn_pressed_style);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h
new file mode 100644
index 0000000000..14319612b2
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h
@@ -0,0 +1,52 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
+
+class DevicePanelSP : public DevicePanel {
+ Q_OBJECT
+
+public:
+ explicit DevicePanelSP(SettingsWindow *parent);
+ void showEvent(QShowEvent *event) override;
+
+private slots:
+ void onPinFileChanged(const QString &file_path);
+ void refreshPin();
+ void forceoffroad();
+
+ void updateLabels();
+
+private:
+ ButtonControlSP *fleetManagerPin;
+ QString pin_title = tr("Fleet Manager PIN:") + " ";
+ QString pin = "OFF";
+ QFileSystemWatcher *fs_watch;
+
+ QPushButton *offroad_btn;
+};
diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc
similarity index 71%
rename from selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc
rename to selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc
index 5d11c8526c..9000c87910 100644
--- a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc
@@ -1,6 +1,35 @@
-#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h"
+/**
+The MIT License
-DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) {
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h"
+
+#include
+#include
+
+DisplayPanel::DisplayPanel(QWidget *parent) : ListWidgetSP(parent, false) {
// param, title, desc, icon
std::vector> toggle_defs{
{
@@ -18,27 +47,27 @@ DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) {
// General: Max Time Offroad (Shutdown timer)
auto max_time_offroad = new MaxTimeOffroad();
- connect(max_time_offroad, &SPOptionControl::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh);
+ connect(max_time_offroad, &OptionControlSP::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh);
addItem(max_time_offroad);
// General: Onroad Screen Off (Auto Onroad Screen Timer)
onroad_screen_off = new OnroadScreenOff();
onroad_screen_off->setUpdateOtherToggles(true);
- connect(onroad_screen_off, &SPOptionControl::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh);
- connect(onroad_screen_off, &SPOptionControl::updateOtherToggles, this, &DisplayPanel::updateToggles);
+ connect(onroad_screen_off, &OptionControlSP::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh);
+ connect(onroad_screen_off, &OptionControlSP::updateOtherToggles, this, &DisplayPanel::updateToggles);
addItem(onroad_screen_off);
// General: Onroad Screen Off Brightness
onroad_screen_off_brightness = new OnroadScreenOffBrightness();
- connect(onroad_screen_off_brightness, &SPOptionControl::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh);
+ connect(onroad_screen_off_brightness, &OptionControlSP::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh);
addItem(onroad_screen_off_brightness);
// General: Brightness Control (Global)
auto brightness_control = new BrightnessControl();
- connect(brightness_control, &SPOptionControl::updateLabels, brightness_control, &BrightnessControl::refresh);
+ connect(brightness_control, &OptionControlSP::updateLabels, brightness_control, &BrightnessControl::refresh);
for (auto &[param, title, desc, icon] : toggle_defs) {
- auto toggle = new ParamControl(param, title, desc, icon, this);
+ auto toggle = new ParamControlSP(param, title, desc, icon, this);
addItem(toggle);
toggles[param.toStdString()] = toggle;
@@ -65,7 +94,7 @@ void DisplayPanel::updateToggles() {
}
// Max Time Offroad (Shutdown timer)
-MaxTimeOffroad::MaxTimeOffroad() : SPOptionControl (
+MaxTimeOffroad::MaxTimeOffroad() : OptionControlSP(
"MaxTimeOffroad",
tr("Max Time Offroad"),
tr("Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road)."),
@@ -110,7 +139,7 @@ void MaxTimeOffroad::refresh() {
}
// Onroad Screen Off (Auto Onroad Screen Timer)
-OnroadScreenOff::OnroadScreenOff() : SPOptionControl (
+OnroadScreenOff::OnroadScreenOff() : OptionControlSP(
"OnroadScreenOff",
tr("Driving Screen Off Timer"),
tr("Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs."),
@@ -136,7 +165,7 @@ void OnroadScreenOff::refresh() {
}
// Onroad Screen Off Brightness
-OnroadScreenOffBrightness::OnroadScreenOffBrightness() : SPOptionControl (
+OnroadScreenOffBrightness::OnroadScreenOffBrightness() : OptionControlSP(
"OnroadScreenOffBrightness",
tr("Driving Screen Off Brightness (%)"),
tr("When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio."),
@@ -157,7 +186,7 @@ void OnroadScreenOffBrightness::refresh() {
}
// Brightness Control (Global)
-BrightnessControl::BrightnessControl() : SPOptionControl (
+BrightnessControl::BrightnessControl() : OptionControlSP(
"BrightnessControl",
tr("Brightness"),
tr("Manually adjusts the global brightness of the screen."),
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h
new file mode 100644
index 0000000000..5298b66fd6
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h
@@ -0,0 +1,98 @@
+/**
+The MIT License
+
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Last updated: July 29, 2024
+***/
+
+#pragma once
+
+#include