mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-23 17:33:50 +08:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c590c39362 | |||
| 1b0ae07a0c | |||
| a564824c85 | |||
| 78635eee88 | |||
| 97c7eacbf4 | |||
| 4a204b8a22 | |||
| ae658b82d7 | |||
| 11aeebf452 | |||
| f6be0110b8 | |||
| 8981993def | |||
| 209cbaed01 | |||
| 028c3691f6 | |||
| de64b99740 | |||
| 0e1de37281 | |||
| 5a309daee6 | |||
| 520649b893 | |||
| 18abe218d9 | |||
| cca3be3a96 | |||
| 282a8b093d | |||
| 1e7fc15a04 | |||
| e999839a57 | |||
| 1bfecbc9c2 | |||
| dcd382ffb8 | |||
| 09d165a85b | |||
| 4c4964a740 | |||
| 225ce45d31 | |||
| 92214b69d8 | |||
| f3ed577870 | |||
| 49e58a2532 | |||
| ae901d1562 | |||
| 85d2653fda | |||
| 0f4828df82 | |||
| dc0fd4ca96 | |||
| 90adc18032 | |||
| db65937fc7 | |||
| 8ebe9b69af | |||
| 4c40be8b1f | |||
| 082ea8119b | |||
| 1465e38c7b | |||
| ecee67dd64 | |||
| ea6178e53e | |||
| 01a0ad496d | |||
| b64d5a0fa4 | |||
| 005c6aed95 | |||
| 2fa66d6f4d | |||
| d5a873ed86 | |||
| 563ae65443 | |||
| 2efe78a4ef | |||
| 569a9216db | |||
| 629cfd845f | |||
| 2892dc05c8 | |||
| 632b416f2a | |||
| 5f3821c1f9 | |||
| 55b7529ca4 | |||
| c248f307f8 | |||
| bdb83b6be1 | |||
| c55f40e77d | |||
| ddf63701e8 | |||
| 28098bb7c4 | |||
| 60e056cc0a | |||
| fb743d313e | |||
| 4441671227 | |||
| 1f8941367d | |||
| 784e1d6658 | |||
| cb94d3b055 | |||
| 94f93a9f26 | |||
| 747460363f | |||
| b32c6dafee | |||
| bffb2fb6fa | |||
| 688b694266 | |||
| ec8f036850 | |||
| 4f44d6e643 | |||
| 1be13fdc55 | |||
| 810a2d9448 | |||
| c9dbf97649 | |||
| 1bb4ca2547 | |||
| 2c04a27a2a | |||
| b7f8dd11a5 | |||
| 70c0592e84 | |||
| 572c03dbac | |||
| fa498221da | |||
| 67238d5045 | |||
| 994170ddb5 | |||
| 3c28188d7a | |||
| 4ccd17903b | |||
| 0e1b573f89 | |||
| 10580aca92 | |||
| 6b13175338 | |||
| d0171084b5 | |||
| 2bfdd0d61d | |||
| ea53111afc | |||
| 0739d4ac2d | |||
| 698e0ca00f | |||
| 8dca43881a | |||
| a885111c0c | |||
| bd73664f4c | |||
| 608c16007e | |||
| 275abc1eb5 | |||
| ff34b8af76 | |||
| ff4d1923f0 | |||
| 3a91ae08a9 | |||
| b161764b1e | |||
| 03e9777c3f | |||
| 1033d3d80e | |||
| 7057c57419 | |||
| 1f1efec4c9 | |||
| 7d4df73ea5 | |||
| 29fe152bd3 | |||
| 31918c067a | |||
| daf5ea2783 | |||
| f0f04d4b5b | |||
| 2b7707ecf6 | |||
| ef870d5533 | |||
| fd7295c980 | |||
| 220cfff04d | |||
| ee0fb6bf8e | |||
| 0cd2bbf6c0 | |||
| 0871abcf55 | |||
| 8ccb777192 | |||
| 0593667601 | |||
| a5044302a2 | |||
| 6a4f685d04 | |||
| 355499a8de | |||
| 288a5e14da | |||
| 9447aa0e3d | |||
| 67fd6c80dd | |||
| 43c12ae7b3 | |||
| 1894a312d3 | |||
| 3e9545670b | |||
| 45ee58b1f6 | |||
| 3f3c293559 | |||
| c990515eaf | |||
| 76e91da3ad | |||
| 9fcac06297 | |||
| 3e2549f2b8 | |||
| 63961dec45 | |||
| 7a19a11001 | |||
| 93f7925c4d | |||
| a2c5fca787 | |||
| bb06468ead | |||
| 1d8dc8a69a | |||
| a254a05df0 | |||
| 2aa7648bb8 | |||
| b309bf4173 | |||
| a3fcde2ae8 | |||
| f8ff156869 | |||
| 375dfe16a8 | |||
| b976135d2f | |||
| f40f7f9ece | |||
| ea6677c464 | |||
| 8258257658 | |||
| f5d67b5eee | |||
| 8ee3c7b485 | |||
| 8450f9f333 | |||
| 4cd76f4966 | |||
| ec254074d1 | |||
| 8059106cae | |||
| 23b4aaf2a5 | |||
| 5359f6d354 | |||
| a70e4c3074 | |||
| 7a2f2ddf32 | |||
| 2dc0f97c93 | |||
| 15fcbf24f1 | |||
| e89c6b3b88 | |||
| c4a7f25b62 | |||
| 1d74a97ba6 | |||
| aea467ff02 | |||
| b501ad4d51 |
@@ -40,10 +40,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
|
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
|
||||||
env:
|
env:
|
||||||
PR_LABEL: 'dev-c3'
|
PR_LABEL: 'dev'
|
||||||
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
|
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
|
||||||
steps:
|
steps:
|
||||||
- name: Check if PR has dev-c3 label
|
- name: Check if PR has dev label
|
||||||
id: check-labels
|
id: check-labels
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
@@ -62,11 +62,11 @@ jobs:
|
|||||||
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
||||||
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
|
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
|
||||||
|
|
||||||
core.setOutput('has-dev-c3', hasDevC3Label ? 'true' : 'false');
|
core.setOutput('has-dev', hasDevC3Label ? 'true' : 'false');
|
||||||
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
|
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
|
||||||
|
|
||||||
- name: Remove trust-fork-pr label if present
|
- name: Remove trust-fork-pr label if present
|
||||||
if: steps.check-labels.outputs.has-dev-c3 == 'true' && steps.check-labels.outputs.has-trust == 'true'
|
if: steps.check-labels.outputs.has-dev == 'true' && steps.check-labels.outputs.has-trust == 'true'
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: uv lock
|
- name: uv lock
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
run: |
|
run: |
|
||||||
python3 -m ensurepip --upgrade
|
python3 -m ensurepip --upgrade
|
||||||
pip3 install uv
|
pip3 install uv
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ jobs:
|
|||||||
|
|
||||||
build_mac:
|
build_mac:
|
||||||
name: build macOS
|
name: build macOS
|
||||||
if: false # temp disable since homebrew install is getting stuck
|
|
||||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -116,7 +115,9 @@ jobs:
|
|||||||
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||||
- name: Homebrew cache
|
- name: Homebrew cache
|
||||||
uses: ./.github/workflows/auto-cache
|
uses: ./.github/workflows/auto-cache
|
||||||
|
if: false # disabling the cache for now because it is breaking macos builds...
|
||||||
with:
|
with:
|
||||||
|
save: false # No need save here if we manually save it later conditionally
|
||||||
path: ~/Library/Caches/Homebrew
|
path: ~/Library/Caches/Homebrew
|
||||||
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
@@ -125,8 +126,8 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: ./tools/mac_setup.sh
|
run: ./tools/mac_setup.sh
|
||||||
env:
|
env:
|
||||||
# package install has DeprecationWarnings
|
PYTHONWARNINGS: default # package install has DeprecationWarnings
|
||||||
PYTHONWARNINGS: default
|
HOMEBREW_DISPLAY_INSTALL_TIMES: 1
|
||||||
- name: Save Homebrew cache
|
- name: Save Homebrew cache
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
@@ -137,6 +138,7 @@ jobs:
|
|||||||
- name: Getting scons cache
|
- name: Getting scons cache
|
||||||
uses: ./.github/workflows/auto-cache
|
uses: ./.github/workflows/auto-cache
|
||||||
with:
|
with:
|
||||||
|
save: false # No need save here if we manually save it later conditionally
|
||||||
path: /tmp/scons_cache
|
path: /tmp/scons_cache
|
||||||
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ env:
|
|||||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||||
|
|
||||||
# Branch configurations
|
# Branch configurations
|
||||||
STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master' }} # vars are set on repo settings.
|
STAGING_SOURCE_BRANCH: 'master'
|
||||||
|
|
||||||
# Runtime configuration
|
# Runtime configuration
|
||||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master, master-dev-c3-new ]
|
branches: [ master, master-dev ]
|
||||||
tags: [ 'release/*' ]
|
tags: [ 'release/*' ]
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [ labeled ]
|
types: [ labeled ]
|
||||||
@@ -138,7 +138,7 @@ jobs:
|
|||||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_C3_SOURCE_BRANCH }}
|
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_SOURCE_BRANCH }}
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}
|
scons-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
|
|||||||
+6
-6
@@ -1,9 +1,9 @@
|
|||||||
name: Build dev-c3-new
|
name: Build dev
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DEFAULT_SOURCE_BRANCH: "master"
|
DEFAULT_SOURCE_BRANCH: "master"
|
||||||
DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
|
DEFAULT_TARGET_BRANCH: "master-dev"
|
||||||
PR_LABEL: "dev-c3"
|
PR_LABEL: "dev"
|
||||||
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
|
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
|
||||||
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ on:
|
|||||||
target_branch:
|
target_branch:
|
||||||
description: 'Target branch to reset and squash into'
|
description: 'Target branch to reset and squash into'
|
||||||
required: true
|
required: true
|
||||||
default: 'master-dev-c3-new'
|
default: 'master-dev'
|
||||||
type: string
|
type: string
|
||||||
cancel_in_progress:
|
cancel_in_progress:
|
||||||
description: 'Cancel any in-progress runs of this workflow'
|
description: 'Cancel any in-progress runs of this workflow'
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
if: (
|
if: (
|
||||||
(github.event_name == 'workflow_dispatch')
|
(github.event_name == 'workflow_dispatch')
|
||||||
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||||
)
|
)
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||||
if: (
|
if: (
|
||||||
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||||
)
|
)
|
||||||
with:
|
with:
|
||||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||||
@@ -50,7 +50,6 @@ cereal/services.h
|
|||||||
cereal/gen
|
cereal/gen
|
||||||
cereal/messaging/bridge
|
cereal/messaging/bridge
|
||||||
selfdrive/mapd/default_speeds_by_region.json
|
selfdrive/mapd/default_speeds_by_region.json
|
||||||
system/proclogd/proclogd
|
|
||||||
selfdrive/ui/translations/tmp
|
selfdrive/ui/translations/tmp
|
||||||
selfdrive/test/longitudinal_maneuvers/out
|
selfdrive/test/longitudinal_maneuvers/out
|
||||||
selfdrive/car/tests/cars_dump
|
selfdrive/car/tests/cars_dump
|
||||||
|
|||||||
Vendored
+40
-1
@@ -23,6 +23,11 @@
|
|||||||
"id": "args",
|
"id": "args",
|
||||||
"description": "Arguments to pass to the process",
|
"description": "Arguments to pass to the process",
|
||||||
"type": "promptString"
|
"type": "promptString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "replayArg",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "Enter route or segment to replay."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configurations": [
|
"configurations": [
|
||||||
@@ -40,7 +45,41 @@
|
|||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/${input:cpp_process}",
|
"program": "${workspaceFolder}/${input:cpp_process}",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach LLDB to Replay drive",
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "attach",
|
||||||
|
"pid": "${command:pickMyProcess}",
|
||||||
|
"initCommands": [
|
||||||
|
"script import time; time.sleep(3)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Replay drive",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/opendbc/safety/tests/safety_replay/replay_drive.py",
|
||||||
|
"args": [
|
||||||
|
"${input:replayArg}"
|
||||||
|
],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": false,
|
||||||
|
"env": {
|
||||||
|
"PYTHONPATH": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"subProcess": true,
|
||||||
|
"stopOnEntry": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Replay drive + Safety LLDB",
|
||||||
|
"configurations": [
|
||||||
|
"Replay drive",
|
||||||
|
"Attach LLDB to Replay drive"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Vendored
+9
-23
@@ -178,7 +178,7 @@ node {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (env.BRANCH_NAME == 'devel-staging') {
|
if (env.BRANCH_NAME == 'devel-staging') {
|
||||||
deviceStage("build release3-staging", "tici-needs-can", [], [
|
deviceStage("build release3-staging", "tizi-needs-can", [], [
|
||||||
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
|
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -186,12 +186,12 @@ node {
|
|||||||
if (env.BRANCH_NAME == '__nightly') {
|
if (env.BRANCH_NAME == '__nightly') {
|
||||||
parallel (
|
parallel (
|
||||||
'nightly': {
|
'nightly': {
|
||||||
deviceStage("build nightly", "tici-needs-can", [], [
|
deviceStage("build nightly", "tizi-needs-can", [], [
|
||||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'nightly-dev': {
|
'nightly-dev': {
|
||||||
deviceStage("build nightly-dev", "tici-needs-can", [], [
|
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
||||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@@ -200,39 +200,30 @@ node {
|
|||||||
|
|
||||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||||
parallel (
|
parallel (
|
||||||
// tici tests
|
|
||||||
'onroad tests': {
|
'onroad tests': {
|
||||||
deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
|
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("check dirty", "release/check-dirty.sh"),
|
step("check dirty", "release/check-dirty.sh"),
|
||||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'HW + Unit Tests': {
|
'HW + Unit Tests': {
|
||||||
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||||
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
|
|
||||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'loopback': {
|
'loopback': {
|
||||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'camerad AR0231': {
|
|
||||||
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
|
||||||
step("build", "cd system/manager && ./build.py"),
|
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
|
||||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
'camerad OX03C10': {
|
'camerad OX03C10': {
|
||||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||||
@@ -246,17 +237,13 @@ node {
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
'sensord': {
|
'sensord': {
|
||||||
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
|
||||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
|
||||||
])
|
|
||||||
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
|
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'replay': {
|
'replay': {
|
||||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
])
|
])
|
||||||
@@ -266,7 +253,6 @@ node {
|
|||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
|
||||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||||
// TODO: enable once new AGNOS is available
|
// TODO: enable once new AGNOS is available
|
||||||
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
||||||
|
|||||||
@@ -22,34 +22,24 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t
|
|||||||
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `release-c3` branch.
|
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging-c3-new` branch.
|
||||||
|
|
||||||
|
### If you want to use our newest branches (our rewrite)
|
||||||
|
> [!TIP]
|
||||||
|
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
|
||||||
|
|
||||||
* sunnypilot not installed or you installed a version before 0.8.17?
|
* sunnypilot not installed or you installed a version before 0.8.17?
|
||||||
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
|
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.
|
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: ```release-c3.sunnypilot.ai```.
|
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```https://staging-c3-new.sunnypilot.ai```.
|
||||||
4. Complete the rest of the installation following the onscreen instructions.
|
4. Complete the rest of the installation following the onscreen instructions.
|
||||||
|
|
||||||
* sunnypilot already installed and you installed a version after 0.8.17?
|
* sunnypilot already installed and you installed a version after 0.8.17?
|
||||||
1. On the comma three, go to `Settings` ▶️ `Software`.
|
1. On the comma three, go to `Settings` ▶️ `Software`.
|
||||||
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
|
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
|
||||||
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
|
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
|
||||||
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `release-c3`
|
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging-c3-new`
|
||||||
|
|
||||||
| Branch | Installation URL |
|
|
||||||
|:------------:|:--------------------------------:|
|
|
||||||
| `release-c3` | https://release-c3.sunnypilot.ai |
|
|
||||||
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
|
||||||
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
|
||||||
|
|
||||||
### If you want to use our newest branches (our rewrite)
|
|
||||||
> [!TIP]
|
|
||||||
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
|
|
||||||
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
|
|
||||||
> You can still restore the latest sunnylink backup made on the old branches.
|
|
||||||
|
|
||||||
| Branch | Installation URL |
|
| Branch | Installation URL |
|
||||||
|:----------------:|:---------------------------------------------:|
|
|:----------------:|:---------------------------------------------:|
|
||||||
@@ -59,8 +49,31 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
|
|||||||
| `release-c3-new` | **Not yet available**. |
|
| `release-c3-new` | **Not yet available**. |
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
|
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
|
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Older legacy branches</summary>
|
||||||
|
|
||||||
|
### If you want to use our older legacy branches (*not recommended*)
|
||||||
|
|
||||||
|
> [**IMPORTANT**]
|
||||||
|
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
|
||||||
|
> You can still restore the latest sunnylink backup made on the old branches.
|
||||||
|
|
||||||
|
| Branch | Installation URL |
|
||||||
|
|:------------:|:--------------------------------:|
|
||||||
|
| `release-c3` | https://release-c3.sunnypilot.ai |
|
||||||
|
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
||||||
|
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 🎆 Pull Requests
|
## 🎆 Pull Requests
|
||||||
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -1,7 +1,13 @@
|
|||||||
Version 0.10.1 (2025-09-08)
|
Version 0.10.1 (2025-09-08)
|
||||||
========================
|
========================
|
||||||
* Record driving feedback using LKAS button
|
* New driving model
|
||||||
* Honda City 2023 support thanks to drFritz!
|
* World Model: removed global localization inputs
|
||||||
|
* World Model: 2x the number of parameters
|
||||||
|
* World Model: trained on 4x the number of segments
|
||||||
|
* Driving Vision Model: trained on 4x the number of segments
|
||||||
|
* Honda City 2023 support thanks to vanillagorillaa and drFritz!
|
||||||
|
* Honda N-Box 2018 support thanks to miettal!
|
||||||
|
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
|
||||||
|
|
||||||
Version 0.10.0 (2025-08-05)
|
Version 0.10.0 (2025-08-05)
|
||||||
========================
|
========================
|
||||||
|
|||||||
@@ -359,11 +359,6 @@ SConscript([
|
|||||||
'system/ubloxd/SConscript',
|
'system/ubloxd/SConscript',
|
||||||
'system/loggerd/SConscript',
|
'system/loggerd/SConscript',
|
||||||
])
|
])
|
||||||
if arch != "Darwin":
|
|
||||||
SConscript([
|
|
||||||
'system/logcatd/SConscript',
|
|
||||||
'system/proclogd/SConscript',
|
|
||||||
])
|
|
||||||
|
|
||||||
if arch == "larch64":
|
if arch == "larch64":
|
||||||
SConscript(['system/camerad/SConscript'])
|
SConscript(['system/camerad/SConscript'])
|
||||||
|
|||||||
+153
-2
@@ -25,6 +25,26 @@ struct ModularAssistiveDrivingSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct IntelligentCruiseButtonManagement {
|
||||||
|
state @0 :IntelligentCruiseButtonManagementState;
|
||||||
|
sendButton @1 :SendButtonState;
|
||||||
|
vTarget @2 :Float32;
|
||||||
|
|
||||||
|
enum IntelligentCruiseButtonManagementState {
|
||||||
|
inactive @0; # No button press or default state
|
||||||
|
preActive @1; # Pre-active state before transitioning to increasing or decreasing
|
||||||
|
increasing @2; # Increasing speed
|
||||||
|
decreasing @3; # Decreasing speed
|
||||||
|
holding @4; # Holding steady speed
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SendButtonState {
|
||||||
|
none @0;
|
||||||
|
increase @1;
|
||||||
|
decrease @2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Same struct as Log.RadarState.LeadData
|
# Same struct as Log.RadarState.LeadData
|
||||||
struct LeadData {
|
struct LeadData {
|
||||||
dRel @0 :Float32;
|
dRel @0 :Float32;
|
||||||
@@ -48,6 +68,7 @@ struct LeadData {
|
|||||||
|
|
||||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||||
mads @0 :ModularAssistiveDrivingSystem;
|
mads @0 :ModularAssistiveDrivingSystem;
|
||||||
|
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ModelManagerSP @0xaedffd8f31e7b55d {
|
struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||||
@@ -122,6 +143,13 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
|
|
||||||
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||||
dec @0 :DynamicExperimentalControl;
|
dec @0 :DynamicExperimentalControl;
|
||||||
|
longitudinalPlanSource @1 :LongitudinalPlanSource;
|
||||||
|
smartCruiseControl @2 :SmartCruiseControl;
|
||||||
|
speedLimit @3 :SpeedLimit;
|
||||||
|
vTarget @4 :Float32;
|
||||||
|
aTarget @5 :Float32;
|
||||||
|
events @6 :List(OnroadEventSP.Event);
|
||||||
|
e2eAlerts @7 :E2eAlerts;
|
||||||
|
|
||||||
struct DynamicExperimentalControl {
|
struct DynamicExperimentalControl {
|
||||||
state @0 :DynamicExperimentalControlState;
|
state @0 :DynamicExperimentalControlState;
|
||||||
@@ -133,6 +161,97 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
|||||||
blended @1;
|
blended @1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SmartCruiseControl {
|
||||||
|
vision @0 :Vision;
|
||||||
|
map @1 :Map;
|
||||||
|
|
||||||
|
struct Vision {
|
||||||
|
state @0 :VisionState;
|
||||||
|
vTarget @1 :Float32;
|
||||||
|
aTarget @2 :Float32;
|
||||||
|
currentLateralAccel @3 :Float32;
|
||||||
|
maxPredictedLateralAccel @4 :Float32;
|
||||||
|
enabled @5 :Bool;
|
||||||
|
active @6 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Map {
|
||||||
|
state @0 :MapState;
|
||||||
|
vTarget @1 :Float32;
|
||||||
|
aTarget @2 :Float32;
|
||||||
|
enabled @3 :Bool;
|
||||||
|
active @4 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VisionState {
|
||||||
|
disabled @0; # System disabled or inactive.
|
||||||
|
enabled @1; # No predicted substantial turn on vision range.
|
||||||
|
entering @2; # A substantial turn is predicted ahead, adapting speed to turn comfort levels.
|
||||||
|
turning @3; # Actively turning. Managing acceleration to provide a roll on turn feeling.
|
||||||
|
leaving @4; # Road ahead straightens. Start to allow positive acceleration.
|
||||||
|
overriding @5; # System overriding with manual control.
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MapState {
|
||||||
|
disabled @0; # System disabled or inactive.
|
||||||
|
enabled @1; # No predicted substantial turn on map range.
|
||||||
|
turning @2; # Actively turning. Managing acceleration to provide a roll on turn feeling.
|
||||||
|
overriding @3; # System overriding with manual control.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpeedLimit {
|
||||||
|
resolver @0 :Resolver;
|
||||||
|
assist @1 :Assist;
|
||||||
|
|
||||||
|
struct Resolver {
|
||||||
|
speedLimit @0 :Float32;
|
||||||
|
distToSpeedLimit @1 :Float32;
|
||||||
|
source @2 :Source;
|
||||||
|
speedLimitOffset @3 :Float32;
|
||||||
|
speedLimitLast @4 :Float32;
|
||||||
|
speedLimitFinal @5 :Float32;
|
||||||
|
speedLimitFinalLast @6 :Float32;
|
||||||
|
speedLimitValid @7 :Bool;
|
||||||
|
speedLimitLastValid @8 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Assist {
|
||||||
|
state @0 :AssistState;
|
||||||
|
enabled @1 :Bool;
|
||||||
|
active @2 :Bool;
|
||||||
|
vTarget @3 :Float32;
|
||||||
|
aTarget @4 :Float32;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Source {
|
||||||
|
none @0;
|
||||||
|
car @1;
|
||||||
|
map @2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AssistState {
|
||||||
|
disabled @0;
|
||||||
|
inactive @1; # No speed limit set or not enabled by parameter.
|
||||||
|
preActive @2;
|
||||||
|
pending @3; # Awaiting new speed limit.
|
||||||
|
adapting @4; # Reducing speed to match new speed limit.
|
||||||
|
active @5; # Cruising at speed limit.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LongitudinalPlanSource {
|
||||||
|
cruise @0;
|
||||||
|
sccVision @1;
|
||||||
|
sccMap @2;
|
||||||
|
speedLimitAssist @3;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct E2eAlerts {
|
||||||
|
greenLightAlert @0 :Bool;
|
||||||
|
leadDepartAlert @1 :Bool;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OnroadEventSP @0xda96579883444c35 {
|
struct OnroadEventSP @0xda96579883444c35 {
|
||||||
@@ -172,12 +291,22 @@ struct OnroadEventSP @0xda96579883444c35 {
|
|||||||
experimentalModeSwitched @14;
|
experimentalModeSwitched @14;
|
||||||
wrongCarModeAlertOnly @15;
|
wrongCarModeAlertOnly @15;
|
||||||
pedalPressedAlertOnly @16;
|
pedalPressedAlertOnly @16;
|
||||||
|
laneTurnLeft @17;
|
||||||
|
laneTurnRight @18;
|
||||||
|
speedLimitPreActive @19;
|
||||||
|
speedLimitActive @20;
|
||||||
|
speedLimitChanged @21;
|
||||||
|
speedLimitPending @22;
|
||||||
|
e2eChime @23;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CarParamsSP @0x80ae746ee2596b11 {
|
struct CarParamsSP @0x80ae746ee2596b11 {
|
||||||
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
||||||
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
||||||
|
pcmCruiseSpeed @3 :Bool;
|
||||||
|
intelligentCruiseButtonManagementAvailable @4 :Bool;
|
||||||
|
enableGasInterceptor @5 :Bool;
|
||||||
|
|
||||||
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
||||||
|
|
||||||
@@ -197,10 +326,24 @@ struct CarControlSP @0xa5cd762cd951a455 {
|
|||||||
params @1 :List(Param);
|
params @1 :List(Param);
|
||||||
leadOne @2 :LeadData;
|
leadOne @2 :LeadData;
|
||||||
leadTwo @3 :LeadData;
|
leadTwo @3 :LeadData;
|
||||||
|
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
|
||||||
|
|
||||||
struct Param {
|
struct Param {
|
||||||
key @0 :Text;
|
key @0 :Text;
|
||||||
value @1 :Text;
|
type @2 :ParamType;
|
||||||
|
value @3 :Data;
|
||||||
|
|
||||||
|
valueDEPRECATED @1 :Text; # The data type change may cause issues with backwards compatibility.
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ParamType {
|
||||||
|
string @0;
|
||||||
|
bool @1;
|
||||||
|
int @2;
|
||||||
|
float @3;
|
||||||
|
time @4;
|
||||||
|
json @5;
|
||||||
|
bytes @6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +390,7 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CarStateSP @0xb86e6369214c01c8 {
|
struct CarStateSP @0xb86e6369214c01c8 {
|
||||||
|
speedLimit @0 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||||
@@ -258,9 +402,16 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
|
|||||||
roadName @5 :Text;
|
roadName @5 :Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved9 @0xa1680744031fdb2d {
|
struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||||
|
laneTurnDirection @0 :TurnDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TurnDirection {
|
||||||
|
none @0;
|
||||||
|
turnLeft @1;
|
||||||
|
turnRight @2;
|
||||||
|
}
|
||||||
|
|
||||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -585,7 +585,6 @@ struct PandaState @0xa7649e2575e4591e {
|
|||||||
heartbeatLost @22 :Bool;
|
heartbeatLost @22 :Bool;
|
||||||
interruptLoad @25 :Float32;
|
interruptLoad @25 :Float32;
|
||||||
fanPower @28 :UInt8;
|
fanPower @28 :UInt8;
|
||||||
fanStallCount @34 :UInt8;
|
|
||||||
|
|
||||||
spiErrorCount @33 :UInt16;
|
spiErrorCount @33 :UInt16;
|
||||||
|
|
||||||
@@ -714,6 +713,7 @@ struct PandaState @0xa7649e2575e4591e {
|
|||||||
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
||||||
safetyParamDEPRECATED @20 :Int16;
|
safetyParamDEPRECATED @20 :Int16;
|
||||||
safetyParam2DEPRECATED @26 :UInt32;
|
safetyParam2DEPRECATED @26 :UInt32;
|
||||||
|
fanStallCountDEPRECATED @34 :UInt8;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PeripheralState {
|
struct PeripheralState {
|
||||||
@@ -2631,7 +2631,7 @@ struct Event {
|
|||||||
backupManagerSP @113 :Custom.BackupManagerSP;
|
backupManagerSP @113 :Custom.BackupManagerSP;
|
||||||
carStateSP @114 :Custom.CarStateSP;
|
carStateSP @114 :Custom.CarStateSP;
|
||||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||||
customReserved9 @116 :Custom.CustomReserved9;
|
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||||
customReserved10 @136 :Custom.CustomReserved10;
|
customReserved10 @136 :Custom.CustomReserved10;
|
||||||
customReserved11 @137 :Custom.CustomReserved11;
|
customReserved11 @137 :Custom.CustomReserved11;
|
||||||
customReserved12 @138 :Custom.CustomReserved12;
|
customReserved12 @138 :Custom.CustomReserved12;
|
||||||
@@ -2684,7 +2684,7 @@ struct Event {
|
|||||||
lateralPlanDEPRECATED @64 :LateralPlan;
|
lateralPlanDEPRECATED @64 :LateralPlan;
|
||||||
navModelDEPRECATED @104 :NavModelData;
|
navModelDEPRECATED @104 :NavModelData;
|
||||||
uiPlanDEPRECATED @106 :UiPlan;
|
uiPlanDEPRECATED @106 :UiPlan;
|
||||||
liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
|
liveLocationKalman @72 :LiveLocationKalman;
|
||||||
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
||||||
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ MessageContext message_context;
|
|||||||
struct SubMaster::SubMessage {
|
struct SubMaster::SubMessage {
|
||||||
std::string name;
|
std::string name;
|
||||||
SubSocket *socket = nullptr;
|
SubSocket *socket = nullptr;
|
||||||
int freq = 0;
|
float freq = 0.0f;
|
||||||
bool updated = false, alive = false, valid = false, ignore_alive;
|
bool updated = false, alive = false, valid = false, ignore_alive;
|
||||||
uint64_t rcv_time = 0, rcv_frame = 0;
|
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||||
void *allocated_msg_reader = nullptr;
|
void *allocated_msg_reader = nullptr;
|
||||||
|
|||||||
+4
-2
@@ -88,6 +88,8 @@ _services: dict[str, tuple] = {
|
|||||||
"carControlSP": (True, 100., 10),
|
"carControlSP": (True, 100., 10),
|
||||||
"carStateSP": (True, 100., 10),
|
"carStateSP": (True, 100., 10),
|
||||||
"liveMapDataSP": (True, 1., 1),
|
"liveMapDataSP": (True, 1., 1),
|
||||||
|
"modelDataV2SP": (True, 20.),
|
||||||
|
"liveLocationKalman": (True, 20.),
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
"uiDebug": (True, 0., 1),
|
"uiDebug": (True, 0., 1),
|
||||||
@@ -120,12 +122,12 @@ def build_header():
|
|||||||
h += "#include <map>\n"
|
h += "#include <map>\n"
|
||||||
h += "#include <string>\n"
|
h += "#include <string>\n"
|
||||||
|
|
||||||
h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n"
|
h += "struct service { std::string name; bool should_log; float frequency; int decimation; };\n"
|
||||||
h += "static std::map<std::string, service> services = {\n"
|
h += "static std::map<std::string, service> services = {\n"
|
||||||
for k, v in SERVICE_LIST.items():
|
for k, v in SERVICE_LIST.items():
|
||||||
should_log = "true" if v.should_log else "false"
|
should_log = "true" if v.should_log else "false"
|
||||||
decimation = -1 if v.decimation is None else v.decimation
|
decimation = -1 if v.decimation is None else v.decimation
|
||||||
h += ' { "%s", {"%s", %s, %d, %d}},\n' % \
|
h += ' { "%s", {"%s", %s, %f, %d}},\n' % \
|
||||||
(k, k, should_log, v.frequency, decimation)
|
(k, k, should_log, v.frequency, decimation)
|
||||||
h += "};\n"
|
h += "};\n"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
#define DEFAULT_MODEL "Steam Powered (Default)"
|
#define DEFAULT_MODEL "Firehose (Default)"
|
||||||
|
|||||||
+38
-3
@@ -94,7 +94,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_StorageMissing", {CLEAR_ON_MANAGER_START, JSON}},
|
|
||||||
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
@@ -146,16 +145,28 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
||||||
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
||||||
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
|
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
||||||
|
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
|
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
|
||||||
|
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
|
||||||
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
|
{"LastGPSPositionLLK", {PERSISTENT, STRING}},
|
||||||
|
{"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
||||||
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||||
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
|
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
|
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "100"}},
|
||||||
|
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
|
||||||
|
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
// MADS params
|
// MADS params
|
||||||
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
@@ -167,6 +178,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
||||||
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
|
||||||
|
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
||||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||||
|
|
||||||
@@ -180,7 +192,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
|
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
|
||||||
{"SunnylinkDongleId", {PERSISTENT, STRING}},
|
{"SunnylinkDongleId", {PERSISTENT, STRING}},
|
||||||
{"SunnylinkdPid", {PERSISTENT, INT}},
|
{"SunnylinkdPid", {PERSISTENT, INT}},
|
||||||
{"SunnylinkEnabled", {PERSISTENT, BOOL}},
|
{"SunnylinkEnabled", {PERSISTENT, BOOL, "1"}},
|
||||||
|
|
||||||
// Backup Manager params
|
// Backup Manager params
|
||||||
{"BackupManager_CreateBackup", {PERSISTENT, BOOL}},
|
{"BackupManager_CreateBackup", {PERSISTENT, BOOL}},
|
||||||
@@ -192,10 +204,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
// model panel params
|
// sunnypilot model params
|
||||||
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
||||||
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
||||||
|
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||||
|
|
||||||
// mapd
|
// mapd
|
||||||
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
||||||
@@ -216,4 +230,25 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"OsmStateTitle", {PERSISTENT, STRING}},
|
{"OsmStateTitle", {PERSISTENT, STRING}},
|
||||||
{"OsmWayTest", {PERSISTENT, STRING}},
|
{"OsmWayTest", {PERSISTENT, STRING}},
|
||||||
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
|
{"RoadNameToggle", {PERSISTENT, STRING}},
|
||||||
|
|
||||||
|
// Speed Limit
|
||||||
|
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
|
||||||
|
{"SpeedLimitOffsetType", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
{"SpeedLimitPolicy", {PERSISTENT | BACKUP, INT, "3"}},
|
||||||
|
{"SpeedLimitValueOffset", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
|
||||||
|
// Smart Cruise Control
|
||||||
|
{"MapTargetVelocities", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
|
{"SmartCruiseControlMap", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"SmartCruiseControlVision", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
|
// Torque lateral control custom params
|
||||||
|
{"CustomTorqueParams", {PERSISTENT | BACKUP , BOOL}},
|
||||||
|
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
||||||
|
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
|
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
|
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
|
||||||
|
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const double MS_TO_KPH = 3.6;
|
|||||||
const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE;
|
const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE;
|
||||||
const double METER_TO_MILE = KM_TO_MILE / 1000.0;
|
const double METER_TO_MILE = KM_TO_MILE / 1000.0;
|
||||||
const double METER_TO_FOOT = 3.28084;
|
const double METER_TO_FOOT = 3.28084;
|
||||||
|
const double METER_TO_KM = 1. / 1000.0;
|
||||||
|
|
||||||
#define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1))
|
#define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1))
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ All of these are examples of good PRs:
|
|||||||
### First contribution
|
### First contribution
|
||||||
|
|
||||||
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
|
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
|
||||||
There's lot of bounties that don't require a comma 3/3X or a car.
|
There's lot of bounties that don't require a comma 3X or a car.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Debugging Panda Safety with Replay Drive + LLDB
|
||||||
|
|
||||||
|
## 1. Start the debugger in VS Code
|
||||||
|
|
||||||
|
* Select **Replay drive + Safety LLDB**.
|
||||||
|
* Enter the route or segment when prompted.
|
||||||
|
[<img src="https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604">](https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604)
|
||||||
|
|
||||||
|
## 2. Attach LLDB
|
||||||
|
|
||||||
|
* When prompted, pick the running **`replay_drive` process**.
|
||||||
|
* ⚠️ Attach quickly, or `replay_drive` will start consuming messages.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Add a Python breakpoint at the start of `replay_drive.py` to pause execution and give yourself time to attach LLDB.
|
||||||
|
|
||||||
|
## 3. Set breakpoints in VS Code
|
||||||
|
Breakpoints can be set directly in `modes/xxx.h` (or any C file).
|
||||||
|
No extra LLDB commands are required — just place breakpoints in the editor.
|
||||||
|
|
||||||
|
## 4. Resume execution
|
||||||
|
Once attached, you can step through both Python (on the replay) and C safety code as CAN logs are replayed.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> * Use short routes for quicker iteration.
|
||||||
|
> * Pause `replay_drive` early to avoid wasting log messages.
|
||||||
|
|
||||||
|
## Video
|
||||||
|
|
||||||
|
View a demo of this workflow on the PR that added it: https://github.com/commaai/openpilot/pull/36055#issue-3352911578
|
||||||
+14
-4
@@ -16,7 +16,7 @@ industry standards of safety for Level 2 Driver Assistance Systems. In particula
|
|||||||
ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf)
|
ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf)
|
||||||
released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/))
|
released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/))
|
||||||
on parts of openpilot that are safety relevant. We also perform software-in-the-loop,
|
on parts of openpilot that are safety relevant. We also perform software-in-the-loop,
|
||||||
hardware-in-the-loop and in-vehicle tests before each software release.
|
hardware-in-the-loop, and in-vehicle tests before each software release.
|
||||||
|
|
||||||
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
|
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
|
||||||
ensuring two main safety requirements.
|
ensuring two main safety requirements.
|
||||||
@@ -29,8 +29,18 @@ ensuring two main safety requirements.
|
|||||||
|
|
||||||
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
|
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
|
||||||
|
|
||||||
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
|
|
||||||
not fully meeting the above requirements.
|
|
||||||
|
|
||||||
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
|
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Forks of openpilot
|
||||||
|
|
||||||
|
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
|
||||||
|
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
|
||||||
|
* If your fork modifies any of the code in `opendbc/safety/`:
|
||||||
|
* your fork cannot use the openpilot trademark
|
||||||
|
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
|
||||||
|
|
||||||
|
Failure to comply with these standards will get you and your users banned from comma.ai servers.
|
||||||
|
|
||||||
|
**comma.ai strongly discourages the use of openpilot forks with safety code either missing or not fully meeting the above requirements.**
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# connect to a comma 3/3X
|
# connect to a comma 3X
|
||||||
|
|
||||||
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).
|
A comma 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
|
## Serial Console
|
||||||
|
|
||||||
On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
|
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.
|
Connect the comma 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/scripts/serial.sh` can be used to connect.
|
On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect.
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u
|
|||||||
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
|
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The default port for ADB is 5555 on the comma 3/3X.
|
> The default port for ADB is 5555 on the comma 3X.
|
||||||
|
|
||||||
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).
|
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging.
|
|||||||
Just run `tools/replay/replay --demo`.
|
Just run `tools/replay/replay --demo`.
|
||||||
|
|
||||||
## Replaying CAN data
|
## Replaying CAN data
|
||||||
*Hardware required: jungle and comma 3/3X*
|
*Hardware required: jungle and comma 3X*
|
||||||
|
|
||||||
1. Connect your PC to a jungle.
|
1. Connect your PC to a jungle.
|
||||||
2.
|
2.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
In 30 minutes, we'll get an openpilot development environment set up on your computer and make some changes to openpilot's UI.
|
In 30 minutes, we'll get an openpilot development environment set up 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.
|
And if you have a comma 3X, we'll deploy the change to your device for testing.
|
||||||
|
|
||||||
## 1. Set up your development environment
|
## 1. Set up your development environment
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
|||||||
export VECLIB_MAXIMUM_THREADS=1
|
export VECLIB_MAXIMUM_THREADS=1
|
||||||
|
|
||||||
if [ -z "$AGNOS_VERSION" ]; then
|
if [ -z "$AGNOS_VERSION" ]; then
|
||||||
export AGNOS_VERSION="12.8"
|
export AGNOS_VERSION="13.1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export STAGING_ROOT="/data/safe_staging"
|
export STAGING_ROOT="/data/safe_staging"
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ nav:
|
|||||||
- What is openpilot?: getting-started/what-is-openpilot.md
|
- What is openpilot?: getting-started/what-is-openpilot.md
|
||||||
- How-to:
|
- How-to:
|
||||||
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
||||||
- Connect to a comma 3/3X: how-to/connect-to-comma.md
|
- Connect to a comma 3X: how-to/connect-to-comma.md
|
||||||
# - Make your first pull request: how-to/make-first-pr.md
|
# - Make your first pull request: how-to/make-first-pr.md
|
||||||
#- Replay a drive: how-to/replay-a-drive.md
|
#- Replay a drive: how-to/replay-a-drive.md
|
||||||
- Concepts:
|
- Concepts:
|
||||||
|
|||||||
+1
-1
Submodule opendbc_repo updated: 004fa8df07...29fe003b2f
+1
-1
Submodule panda updated: f10ddc6a89...69ab12ee2a
+8
-3
@@ -36,6 +36,9 @@ dependencies = [
|
|||||||
"pyopenssl < 24.3.0",
|
"pyopenssl < 24.3.0",
|
||||||
"pyaudio",
|
"pyaudio",
|
||||||
|
|
||||||
|
# ubloxd (TODO: just use struct)
|
||||||
|
"kaitaistruct",
|
||||||
|
|
||||||
# panda
|
# panda
|
||||||
"libusb1",
|
"libusb1",
|
||||||
"spidev; platform_system == 'Linux'",
|
"spidev; platform_system == 'Linux'",
|
||||||
@@ -101,8 +104,9 @@ dev = [
|
|||||||
"av",
|
"av",
|
||||||
"azure-identity",
|
"azure-identity",
|
||||||
"azure-storage-blob",
|
"azure-storage-blob",
|
||||||
"dbus-next",
|
"dbus-next", # TODO: remove once we moved everything to jeepney
|
||||||
"dictdiffer",
|
"dictdiffer",
|
||||||
|
"jeepney",
|
||||||
"matplotlib",
|
"matplotlib",
|
||||||
"opencv-python-headless",
|
"opencv-python-headless",
|
||||||
"parameterized >=0.8, <0.9",
|
"parameterized >=0.8, <0.9",
|
||||||
@@ -120,6 +124,7 @@ dev = [
|
|||||||
|
|
||||||
tools = [
|
tools = [
|
||||||
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')",
|
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')",
|
||||||
|
"dearpygui>=2.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
@@ -157,7 +162,6 @@ testpaths = [
|
|||||||
"system/camerad",
|
"system/camerad",
|
||||||
"system/hardware",
|
"system/hardware",
|
||||||
"system/loggerd",
|
"system/loggerd",
|
||||||
"system/proclogd",
|
|
||||||
"system/tests",
|
"system/tests",
|
||||||
"system/ubloxd",
|
"system/ubloxd",
|
||||||
"system/webrtc",
|
"system/webrtc",
|
||||||
@@ -173,7 +177,7 @@ quiet-level = 3
|
|||||||
# if you've got a short variable name that's getting flagged, add it here
|
# 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,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite"
|
ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite"
|
||||||
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
|
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
|
||||||
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*"
|
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*"
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.11"
|
python_version = "3.11"
|
||||||
@@ -247,6 +251,7 @@ exclude = [
|
|||||||
"teleoprtc_repo",
|
"teleoprtc_repo",
|
||||||
"third_party",
|
"third_party",
|
||||||
"*.ipynb",
|
"*.ipynb",
|
||||||
|
"generated",
|
||||||
]
|
]
|
||||||
lint.flake8-implicit-str-concat.allow-multiline = false
|
lint.flake8-implicit-str-concat.allow-multiline = false
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def setup_argument_parser():
|
|||||||
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
|
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
|
||||||
parser.add_argument('--source-branch', type=str, default='master',
|
parser.add_argument('--source-branch', type=str, default='master',
|
||||||
help='Source branch for merging')
|
help='Source branch for merging')
|
||||||
parser.add_argument('--target-branch', type=str, default='master-dev-c3-new-test',
|
parser.add_argument('--target-branch', type=str, default='master-dev-test',
|
||||||
help='Target branch for merging')
|
help='Target branch for merging')
|
||||||
parser.add_argument('--squash-script-path', type=str, required=True,
|
parser.add_argument('--squash-script-path', type=str, required=True,
|
||||||
help='Path to the squash_and_merge.py script')
|
help='Path to the squash_and_merge.py script')
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class Car:
|
|||||||
|
|
||||||
def __init__(self, CI=None, RI=None) -> None:
|
def __init__(self, CI=None, RI=None) -> None:
|
||||||
self.can_sock = messaging.sub_sock('can', timeout=20)
|
self.can_sock = messaging.sub_sock('can', timeout=20)
|
||||||
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'] + ['carControlSP'])
|
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'] + ['carControlSP', 'longitudinalPlanSP'])
|
||||||
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'] + ['carParamsSP', 'carStateSP'])
|
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'] + ['carParamsSP', 'carStateSP'])
|
||||||
|
|
||||||
self.can_rcv_cum_timeout_counter = 0
|
self.can_rcv_cum_timeout_counter = 0
|
||||||
@@ -124,7 +124,7 @@ class Car:
|
|||||||
|
|
||||||
self.CP.alternativeExperience = 0
|
self.CP.alternativeExperience = 0
|
||||||
# mads
|
# mads
|
||||||
set_alternative_experience(self.CP, self.params)
|
set_alternative_experience(self.CP, self.CP_SP, self.params)
|
||||||
set_car_specific_params(self.CP, self.CP_SP, self.params)
|
set_car_specific_params(self.CP, self.CP_SP, self.params)
|
||||||
|
|
||||||
# Dynamic Experimental Control
|
# Dynamic Experimental Control
|
||||||
@@ -179,7 +179,7 @@ class Car:
|
|||||||
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
|
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
|
||||||
|
|
||||||
self.mock_carstate = MockCarState()
|
self.mock_carstate = MockCarState()
|
||||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
|
||||||
|
|
||||||
self.is_metric = self.params.get_bool("IsMetric")
|
self.is_metric = self.params.get_bool("IsMetric")
|
||||||
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
||||||
@@ -216,6 +216,7 @@ class Car:
|
|||||||
if can_rcv_valid and REPLAY:
|
if can_rcv_valid and REPLAY:
|
||||||
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
|
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
|
||||||
|
|
||||||
|
self.v_cruise_helper.update_speed_limit_assist(self.is_metric, self.sm['longitudinalPlanSP'])
|
||||||
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
|
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
|
||||||
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
|
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
|
||||||
# Use CarState w/ buttons from the step selfdrived enables on
|
# Use CarState w/ buttons from the step selfdrived enables on
|
||||||
|
|||||||
+15
-5
@@ -30,8 +30,8 @@ CRUISE_INTERVAL_SIGN = {
|
|||||||
|
|
||||||
|
|
||||||
class VCruiseHelper(VCruiseHelperSP):
|
class VCruiseHelper(VCruiseHelperSP):
|
||||||
def __init__(self, CP):
|
def __init__(self, CP, CP_SP):
|
||||||
VCruiseHelperSP.__init__(self)
|
VCruiseHelperSP.__init__(self, CP, CP_SP)
|
||||||
self.CP = CP
|
self.CP = CP
|
||||||
self.v_cruise_kph = V_CRUISE_UNSET
|
self.v_cruise_kph = V_CRUISE_UNSET
|
||||||
self.v_cruise_cluster_kph = V_CRUISE_UNSET
|
self.v_cruise_cluster_kph = V_CRUISE_UNSET
|
||||||
@@ -46,10 +46,14 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
def update_v_cruise(self, CS, enabled, is_metric):
|
def update_v_cruise(self, CS, enabled, is_metric):
|
||||||
self.v_cruise_kph_last = self.v_cruise_kph
|
self.v_cruise_kph_last = self.v_cruise_kph
|
||||||
|
|
||||||
|
self.get_minimum_set_speed(is_metric)
|
||||||
|
|
||||||
if CS.cruiseState.available:
|
if CS.cruiseState.available:
|
||||||
if not self.CP.pcmCruise:
|
_enabled = self.update_enabled_state(CS, enabled)
|
||||||
|
if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled):
|
||||||
# if stock cruise is completely disabled, then we can use our own set speed logic
|
# if stock cruise is completely disabled, then we can use our own set speed logic
|
||||||
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
|
self._update_v_cruise_non_pcm(CS, _enabled, is_metric)
|
||||||
|
self.update_speed_limit_assist_v_cruise_non_pcm()
|
||||||
self.v_cruise_cluster_kph = self.v_cruise_kph
|
self.v_cruise_cluster_kph = self.v_cruise_kph
|
||||||
self.update_button_timers(CS, enabled)
|
self.update_button_timers(CS, enabled)
|
||||||
else:
|
else:
|
||||||
@@ -101,6 +105,12 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
if not self.button_change_states[button_type]["enabled"]:
|
if not self.button_change_states[button_type]["enabled"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Speed Limit Assist for Non PCM long cars.
|
||||||
|
# True: Disallow set speed changes when user confirmed the target set speed during preActive state
|
||||||
|
# False: Allow set speed changes as SLA is not requesting user confirmation
|
||||||
|
if self.update_speed_limit_assist_pre_active_confirmed(button_type):
|
||||||
|
return
|
||||||
|
|
||||||
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
|
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
|
||||||
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
|
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
|
||||||
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
|
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
|
||||||
@@ -111,7 +121,7 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
||||||
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
|
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
|
||||||
|
|
||||||
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
|
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), self.v_cruise_min, V_CRUISE_MAX)
|
||||||
|
|
||||||
def update_button_timers(self, CS, enabled):
|
def update_button_timers(self, CS, enabled):
|
||||||
# increment timer for buttons still pressed
|
# increment timer for buttons still pressed
|
||||||
|
|||||||
@@ -60,5 +60,8 @@ def convert_carControlSP(struct: capnp.lib.capnp._DynamicStructReader) -> struct
|
|||||||
struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
|
struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
|
||||||
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
|
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
|
||||||
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
|
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
|
||||||
|
struct_dataclass.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(
|
||||||
|
**remove_deprecated(struct_dict.get('intelligentCruiseButtonManagement', {}))
|
||||||
|
)
|
||||||
|
|
||||||
return struct_dataclass
|
return struct_dataclass
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import math
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import Phase, given, settings
|
from hypothesis import Phase, given, settings
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
|
|
||||||
from cereal import car, custom
|
from cereal import car, custom
|
||||||
from opendbc.car import DT_CTRL
|
from opendbc.car import DT_CTRL
|
||||||
from opendbc.car.car_helpers import interfaces
|
|
||||||
from opendbc.car.structs import CarParams
|
from opendbc.car.structs import CarParams
|
||||||
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface_args
|
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface
|
||||||
from opendbc.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
|
|
||||||
from opendbc.car.mock.values import CAR as MOCK
|
from opendbc.car.mock.values import CAR as MOCK
|
||||||
from opendbc.car.values import PLATFORMS
|
from opendbc.car.values import PLATFORMS
|
||||||
from openpilot.selfdrive.car.helpers import convert_carControlSP
|
from openpilot.selfdrive.car.helpers import convert_carControlSP
|
||||||
@@ -21,11 +18,6 @@ from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator
|
|||||||
|
|
||||||
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
|
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
|
||||||
|
|
||||||
ALL_ECUS = {ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}
|
|
||||||
ALL_ECUS |= {ecu for config in FW_QUERY_CONFIGS.values() for ecu in config.extra_ecus}
|
|
||||||
|
|
||||||
ALL_REQUESTS = {tuple(r.request) for config in FW_QUERY_CONFIGS.values() for r in config.requests}
|
|
||||||
|
|
||||||
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60'))
|
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60'))
|
||||||
|
|
||||||
|
|
||||||
@@ -37,43 +29,10 @@ class TestCarInterfaces:
|
|||||||
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
||||||
@given(data=st.data())
|
@given(data=st.data())
|
||||||
def test_car_interfaces(self, car_name, data):
|
def test_car_interfaces(self, car_name, data):
|
||||||
CarInterface = interfaces[car_name]
|
car_interface = get_fuzzy_car_interface(car_name, data.draw)
|
||||||
|
car_params = car_interface.CP.as_reader()
|
||||||
args = get_fuzzy_car_interface_args(data.draw)
|
car_params_sp = car_interface.CP_SP
|
||||||
|
|
||||||
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
|
|
||||||
alpha_long=args['alpha_long'], is_release=False, docs=False)
|
|
||||||
car_params_sp = CarInterface.get_params_sp(car_params, car_name, args['fingerprints'], args['car_fw'],
|
|
||||||
alpha_long=args['alpha_long'], docs=False)
|
|
||||||
car_params = car_params.as_reader()
|
|
||||||
car_interface = CarInterface(car_params, car_params_sp)
|
|
||||||
sunnypilot_interfaces.setup_interfaces(car_interface)
|
sunnypilot_interfaces.setup_interfaces(car_interface)
|
||||||
assert car_params
|
|
||||||
assert car_params_sp
|
|
||||||
assert car_interface
|
|
||||||
|
|
||||||
assert car_params.mass > 1
|
|
||||||
assert car_params.wheelbase > 0
|
|
||||||
# centerToFront is center of gravity to front wheels, assert a reasonable range
|
|
||||||
assert car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7
|
|
||||||
assert car_params.maxLateralAccel > 0
|
|
||||||
|
|
||||||
# Longitudinal sanity checks
|
|
||||||
assert len(car_params.longitudinalTuning.kpV) == len(car_params.longitudinalTuning.kpBP)
|
|
||||||
assert len(car_params.longitudinalTuning.kiV) == len(car_params.longitudinalTuning.kiBP)
|
|
||||||
|
|
||||||
# Lateral sanity checks
|
|
||||||
if car_params.steerControlType != CarParams.SteerControlType.angle:
|
|
||||||
tune = car_params.lateralTuning
|
|
||||||
if tune.which() == 'pid':
|
|
||||||
if car_name != MOCK.MOCK:
|
|
||||||
assert not math.isnan(tune.pid.kf) and tune.pid.kf > 0
|
|
||||||
assert len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)
|
|
||||||
assert len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)
|
|
||||||
|
|
||||||
elif tune.which() == 'torque':
|
|
||||||
assert not math.isnan(tune.torque.kf) and tune.torque.kf > 0
|
|
||||||
assert not math.isnan(tune.torque.friction) and tune.torque.friction > 0
|
|
||||||
|
|
||||||
cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True)
|
cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True)
|
||||||
cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)
|
cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)
|
||||||
@@ -101,7 +60,7 @@ class TestCarInterfaces:
|
|||||||
# Test controller initialization
|
# Test controller initialization
|
||||||
# TODO: wait until card refactor is merged to run controller a few times,
|
# TODO: wait until card refactor is merged to run controller a few times,
|
||||||
# hypothesis also slows down significantly with just one more message draw
|
# hypothesis also slows down significantly with just one more message draw
|
||||||
LongControl(car_params)
|
LongControl(car_params, car_params_sp)
|
||||||
if car_params.steerControlType == CarParams.SteerControlType.angle:
|
if car_params.steerControlType == CarParams.SteerControlType.angle:
|
||||||
LatControlAngle(car_params, car_params_sp, car_interface)
|
LatControlAngle(car_params, car_params_sp, car_interface)
|
||||||
elif car_params.lateralTuning.which() == 'pid':
|
elif car_params.lateralTuning.which() == 'pid':
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import numpy as np
|
|||||||
from parameterized import parameterized_class
|
from parameterized import parameterized_class
|
||||||
from cereal import log
|
from cereal import log
|
||||||
from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
|
from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
|
||||||
from cereal import car
|
from cereal import car, custom
|
||||||
from openpilot.common.constants import CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
||||||
|
|
||||||
@@ -44,12 +44,13 @@ class TestCruiseSpeed:
|
|||||||
assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s'
|
assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s'
|
||||||
|
|
||||||
|
|
||||||
# TODO: test pcmCruise
|
# TODO: test pcmCruise and pcmCruiseSpeed
|
||||||
@parameterized_class(('pcm_cruise',), [(False,)])
|
@parameterized_class(('pcm_cruise', 'pcm_cruise_speed'), [(False, True)])
|
||||||
class TestVCruiseHelper:
|
class TestVCruiseHelper:
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
|
self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
|
||||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
self.CP_SP = custom.CarParamsSP(pcmCruiseSpeed=self.pcm_cruise_speed)
|
||||||
|
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
|
||||||
self.reset_cruise_speed_state()
|
self.reset_cruise_speed_state()
|
||||||
|
|
||||||
def reset_cruise_speed_state(self):
|
def reset_cruise_speed_state(self):
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class Controls(ControlsExt, ModelStateBase):
|
|||||||
self.pose_calibrator = PoseCalibrator()
|
self.pose_calibrator = PoseCalibrator()
|
||||||
self.calibrated_pose: Pose | None = None
|
self.calibrated_pose: Pose | None = None
|
||||||
|
|
||||||
self.LoC = LongControl(self.CP)
|
self.LoC = LongControl(self.CP, self.CP_SP)
|
||||||
self.VM = VehicleModel(self.CP)
|
self.VM = VehicleModel(self.CP)
|
||||||
self.LaC: LatControl
|
self.LaC: LatControl
|
||||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||||
@@ -116,7 +116,8 @@ class Controls(ControlsExt, ModelStateBase):
|
|||||||
|
|
||||||
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||||
(not standstill or self.CP.steerAtStandstill)
|
(not standstill or self.CP.steerAtStandstill)
|
||||||
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
|
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and \
|
||||||
|
(self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
|
||||||
|
|
||||||
actuators = CC.actuators
|
actuators = CC.actuators
|
||||||
actuators.longControlState = self.LoC.long_control_state
|
actuators.longControlState = self.LoC.long_control_state
|
||||||
@@ -168,7 +169,7 @@ class Controls(ControlsExt, ModelStateBase):
|
|||||||
CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist()
|
CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist()
|
||||||
CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist()
|
CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist()
|
||||||
|
|
||||||
CC.cruiseControl.override = CC.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl
|
CC.cruiseControl.override = CC.enabled and not CC.longActive and (self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
|
||||||
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise)
|
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise)
|
||||||
CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop
|
CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from cereal import log
|
from cereal import log, custom
|
||||||
from openpilot.common.constants import CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.common.realtime import DT_MDL
|
from openpilot.common.realtime import DT_MDL
|
||||||
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
|
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
|
||||||
|
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
|
||||||
|
|
||||||
LaneChangeState = log.LaneChangeState
|
LaneChangeState = log.LaneChangeState
|
||||||
LaneChangeDirection = log.LaneChangeDirection
|
LaneChangeDirection = log.LaneChangeDirection
|
||||||
@@ -30,6 +31,12 @@ DESIRES = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TURN_DESIRES = {
|
||||||
|
custom.TurnDirection.none: log.Desire.none,
|
||||||
|
custom.TurnDirection.turnLeft: log.Desire.turnLeft,
|
||||||
|
custom.TurnDirection.turnRight: log.Desire.turnRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DesireHelper:
|
class DesireHelper:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -41,13 +48,25 @@ class DesireHelper:
|
|||||||
self.prev_one_blinker = False
|
self.prev_one_blinker = False
|
||||||
self.desire = log.Desire.none
|
self.desire = log.Desire.none
|
||||||
self.alc = AutoLaneChangeController(self)
|
self.alc = AutoLaneChangeController(self)
|
||||||
|
self.lane_turn_controller = LaneTurnController(self)
|
||||||
|
self.lane_turn_direction = custom.TurnDirection.none
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_lane_change_direction(CS):
|
||||||
|
return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right
|
||||||
|
|
||||||
def update(self, carstate, lateral_active, lane_change_prob):
|
def update(self, carstate, lateral_active, lane_change_prob):
|
||||||
self.alc.update_params()
|
self.alc.update_params()
|
||||||
|
self.lane_turn_controller.update_params()
|
||||||
v_ego = carstate.vEgo
|
v_ego = carstate.vEgo
|
||||||
one_blinker = carstate.leftBlinker != carstate.rightBlinker
|
one_blinker = carstate.leftBlinker != carstate.rightBlinker
|
||||||
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN
|
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN
|
||||||
|
|
||||||
|
# Lane turn controller update
|
||||||
|
self.lane_turn_controller.update_lane_turn(blindspot_left=carstate.leftBlindspot, blindspot_right=carstate.rightBlindspot,
|
||||||
|
left_blinker=carstate.leftBlinker, right_blinker=carstate.rightBlinker, v_ego=v_ego)
|
||||||
|
self.lane_turn_direction = self.lane_turn_controller.get_turn_direction()
|
||||||
|
|
||||||
if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF:
|
if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF:
|
||||||
self.lane_change_state = LaneChangeState.off
|
self.lane_change_state = LaneChangeState.off
|
||||||
self.lane_change_direction = LaneChangeDirection.none
|
self.lane_change_direction = LaneChangeDirection.none
|
||||||
@@ -56,12 +75,13 @@ class DesireHelper:
|
|||||||
if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
|
if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
|
||||||
self.lane_change_state = LaneChangeState.preLaneChange
|
self.lane_change_state = LaneChangeState.preLaneChange
|
||||||
self.lane_change_ll_prob = 1.0
|
self.lane_change_ll_prob = 1.0
|
||||||
|
# Initialize lane change direction to prevent UI alert flicker
|
||||||
|
self.lane_change_direction = self.get_lane_change_direction(carstate)
|
||||||
|
|
||||||
# LaneChangeState.preLaneChange
|
# LaneChangeState.preLaneChange
|
||||||
elif self.lane_change_state == LaneChangeState.preLaneChange:
|
elif self.lane_change_state == LaneChangeState.preLaneChange:
|
||||||
# Set lane change direction
|
# Update lane change direction
|
||||||
self.lane_change_direction = LaneChangeDirection.left if \
|
self.lane_change_direction = self.get_lane_change_direction(carstate)
|
||||||
carstate.leftBlinker else LaneChangeDirection.right
|
|
||||||
|
|
||||||
torque_applied = carstate.steeringPressed and \
|
torque_applied = carstate.steeringPressed and \
|
||||||
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
|
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
|
||||||
@@ -106,7 +126,10 @@ class DesireHelper:
|
|||||||
|
|
||||||
self.prev_one_blinker = one_blinker
|
self.prev_one_blinker = one_blinker
|
||||||
|
|
||||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
if self.lane_turn_direction != custom.TurnDirection.none:
|
||||||
|
self.desire = TURN_DESIRES[self.lane_turn_direction]
|
||||||
|
else:
|
||||||
|
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||||
|
|
||||||
# Send keep pulse once per second during LaneChangeStart.preLaneChange
|
# Send keep pulse once per second during LaneChangeStart.preLaneChange
|
||||||
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
|
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ class LatControlTorque(LatControl):
|
|||||||
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
|
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
|
||||||
|
|
||||||
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
|
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
|
||||||
|
# Override torque params from extension
|
||||||
|
if self.extension.update_override_torque_params(self.torque_params):
|
||||||
|
self.update_limits()
|
||||||
|
|
||||||
pid_log = log.ControlsState.LateralTorqueState.new_message()
|
pid_log = log.ControlsState.LateralTorqueState.new_message()
|
||||||
if not active:
|
if not active:
|
||||||
output_torque = 0.0
|
output_torque = 0.0
|
||||||
@@ -56,10 +60,8 @@ class LatControlTorque(LatControl):
|
|||||||
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
||||||
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
|
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
|
||||||
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
|
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
|
||||||
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
|
|
||||||
|
|
||||||
# desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
|
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
|
||||||
# desired_lateral_jerk = desired_curvature_rate * CS.vEgo ** 2
|
|
||||||
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
|
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
|
||||||
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
||||||
|
|
||||||
@@ -71,6 +73,8 @@ class LatControlTorque(LatControl):
|
|||||||
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
|
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
|
||||||
pid_log.error = float(setpoint - measurement)
|
pid_log.error = float(setpoint - measurement)
|
||||||
ff = gravity_adjusted_lateral_accel
|
ff = gravity_adjusted_lateral_accel
|
||||||
|
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
|
||||||
|
ff -= self.torque_params.latAccelOffset
|
||||||
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
|
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
|
||||||
|
|
||||||
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
|
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
|
|||||||
LongCtrlState = car.CarControl.Actuators.LongControlState
|
LongCtrlState = car.CarControl.Actuators.LongControlState
|
||||||
|
|
||||||
|
|
||||||
def long_control_state_trans(CP, active, long_control_state, v_ego,
|
def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego,
|
||||||
should_stop, brake_pressed, cruise_standstill):
|
should_stop, brake_pressed, cruise_standstill):
|
||||||
|
# Gas Interceptor
|
||||||
|
cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor
|
||||||
|
|
||||||
stopping_condition = should_stop
|
stopping_condition = should_stop
|
||||||
starting_condition = (not should_stop and
|
starting_condition = (not should_stop and
|
||||||
not cruise_standstill and
|
not cruise_standstill and
|
||||||
@@ -45,8 +48,9 @@ def long_control_state_trans(CP, active, long_control_state, v_ego,
|
|||||||
return long_control_state
|
return long_control_state
|
||||||
|
|
||||||
class LongControl:
|
class LongControl:
|
||||||
def __init__(self, CP):
|
def __init__(self, CP, CP_SP):
|
||||||
self.CP = CP
|
self.CP = CP
|
||||||
|
self.CP_SP = CP_SP
|
||||||
self.long_control_state = LongCtrlState.off
|
self.long_control_state = LongCtrlState.off
|
||||||
self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
|
self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
|
||||||
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
|
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
|
||||||
@@ -61,7 +65,7 @@ class LongControl:
|
|||||||
self.pid.neg_limit = accel_limits[0]
|
self.pid.neg_limit = accel_limits[0]
|
||||||
self.pid.pos_limit = accel_limits[1]
|
self.pid.pos_limit = accel_limits[1]
|
||||||
|
|
||||||
self.long_control_state = long_control_state_trans(self.CP, active, self.long_control_state, CS.vEgo,
|
self.long_control_state = long_control_state_trans(self.CP, self.CP_SP, active, self.long_control_state, CS.vEgo,
|
||||||
should_stop, CS.brakePressed,
|
should_stop, CS.brakePressed,
|
||||||
CS.cruiseState.standstill)
|
CS.cruiseState.standstill)
|
||||||
if self.long_control_state == LongCtrlState.off:
|
if self.long_control_state == LongCtrlState.off:
|
||||||
|
|||||||
@@ -146,6 +146,9 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
|
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
|
||||||
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
|
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
|
||||||
|
|
||||||
|
# Get new v_cruise and a_desired from Smart Cruise Control and Speed Limit Assist
|
||||||
|
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
|
||||||
|
|
||||||
if force_slow_decel:
|
if force_slow_decel:
|
||||||
v_cruise = 0.0
|
v_cruise = 0.0
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from cereal import car
|
from cereal import car
|
||||||
|
from openpilot.common.gps import get_gps_location_service
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.realtime import Priority, config_realtime_process
|
from openpilot.common.realtime import Priority, config_realtime_process
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
@@ -16,14 +17,18 @@ def main():
|
|||||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||||
cloudlog.info("plannerd got CarParams: %s", CP.brand)
|
cloudlog.info("plannerd got CarParams: %s", CP.brand)
|
||||||
|
|
||||||
|
gps_location_service = get_gps_location_service(params)
|
||||||
|
|
||||||
ldw = LaneDepartureWarning()
|
ldw = LaneDepartureWarning()
|
||||||
longitudinal_planner = LongitudinalPlanner(CP)
|
longitudinal_planner = LongitudinalPlanner(CP)
|
||||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
||||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
|
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
||||||
poll='modelV2')
|
'liveMapDataSP', 'carStateSP', gps_location_service],
|
||||||
|
poll='carState')
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
sm.update()
|
sm.update()
|
||||||
|
longitudinal_planner.sla.update_car_state(sm['carState'])
|
||||||
if sm.updated['modelV2']:
|
if sm.updated['modelV2']:
|
||||||
longitudinal_planner.update(sm)
|
longitudinal_planner.update(sm)
|
||||||
longitudinal_planner.publish(sm, pm)
|
longitudinal_planner.publish(sm, pm)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from cereal import car
|
from cereal import car, custom
|
||||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
|
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
|
||||||
|
|
||||||
|
|
||||||
@@ -8,49 +8,52 @@ class TestLongControlStateTransition:
|
|||||||
|
|
||||||
def test_stay_stopped(self):
|
def test_stay_stopped(self):
|
||||||
CP = car.CarParams.new_message()
|
CP = car.CarParams.new_message()
|
||||||
|
CP_SP = custom.CarParamsSP.new_message()
|
||||||
active = True
|
active = True
|
||||||
current_state = LongCtrlState.stopping
|
current_state = LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.pid
|
assert next_state == LongCtrlState.pid
|
||||||
active = False
|
active = False
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.off
|
assert next_state == LongCtrlState.off
|
||||||
|
|
||||||
def test_engage():
|
def test_engage():
|
||||||
CP = car.CarParams.new_message()
|
CP = car.CarParams.new_message()
|
||||||
|
CP_SP = custom.CarParamsSP.new_message()
|
||||||
active = True
|
active = True
|
||||||
current_state = LongCtrlState.off
|
current_state = LongCtrlState.off
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.pid
|
assert next_state == LongCtrlState.pid
|
||||||
|
|
||||||
def test_starting():
|
def test_starting():
|
||||||
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
|
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
|
||||||
|
CP_SP = custom.CarParamsSP.new_message()
|
||||||
active = True
|
active = True
|
||||||
current_state = LongCtrlState.starting
|
current_state = LongCtrlState.starting
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.starting
|
assert next_state == LongCtrlState.starting
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.pid
|
assert next_state == LongCtrlState.pid
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import numpy as np
|
||||||
|
from cereal import car, messaging
|
||||||
|
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
|
||||||
|
from opendbc.car import structs
|
||||||
|
from opendbc.car.lateral import get_friction, FRICTION_THRESHOLD
|
||||||
|
from openpilot.common.realtime import DT_MDL
|
||||||
|
from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS
|
||||||
|
|
||||||
|
np.random.seed(0)
|
||||||
|
|
||||||
|
LA_ERR_STD = 1.0
|
||||||
|
INPUT_NOISE_STD = 0.08
|
||||||
|
V_EGO = 30.0
|
||||||
|
|
||||||
|
WARMUP_BUCKET_POINTS = (1.5*MIN_BUCKET_POINTS).astype(int)
|
||||||
|
STRAIGHT_ROAD_LA_BOUNDS = (0.02, 0.03)
|
||||||
|
|
||||||
|
ROLL_BIAS_DEG = 2.0
|
||||||
|
ROLL_COMPENSATION_BIAS = ACCELERATION_DUE_TO_GRAVITY*float(np.sin(np.deg2rad(ROLL_BIAS_DEG)))
|
||||||
|
TORQUE_TUNE = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=0.0, friction=0.2)
|
||||||
|
TORQUE_TUNE_BIASED = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=-ROLL_COMPENSATION_BIAS, friction=0.2)
|
||||||
|
|
||||||
|
def generate_inputs(torque_tune, la_err_std, input_noise_std=None):
|
||||||
|
rng = np.random.default_rng(0)
|
||||||
|
steer_torques = np.concat([rng.uniform(bnd[0], bnd[1], pts) for bnd, pts in zip(STEER_BUCKET_BOUNDS, WARMUP_BUCKET_POINTS, strict=True)])
|
||||||
|
la_errs = rng.normal(scale=la_err_std, size=steer_torques.size)
|
||||||
|
frictions = np.array([get_friction(la_err, 0.0, FRICTION_THRESHOLD, torque_tune) for la_err in la_errs])
|
||||||
|
lat_accels = torque_tune.latAccelFactor*steer_torques + torque_tune.latAccelOffset + frictions
|
||||||
|
if input_noise_std is not None:
|
||||||
|
steer_torques += rng.normal(scale=input_noise_std, size=steer_torques.size)
|
||||||
|
lat_accels += rng.normal(scale=input_noise_std, size=steer_torques.size)
|
||||||
|
return steer_torques, lat_accels
|
||||||
|
|
||||||
|
def get_warmed_up_estimator(steer_torques, lat_accels):
|
||||||
|
est = TorqueEstimator(car.CarParams())
|
||||||
|
for steer_torque, lat_accel in zip(steer_torques, lat_accels, strict=True):
|
||||||
|
est.filtered_points.add_point(steer_torque, lat_accel)
|
||||||
|
return est
|
||||||
|
|
||||||
|
def simulate_straight_road_msgs(est):
|
||||||
|
carControl = messaging.new_message('carControl').carControl
|
||||||
|
carOutput = messaging.new_message('carOutput').carOutput
|
||||||
|
carState = messaging.new_message('carState').carState
|
||||||
|
livePose = messaging.new_message('livePose').livePose
|
||||||
|
carControl.latActive = True
|
||||||
|
carState.vEgo = V_EGO
|
||||||
|
carState.steeringPressed = False
|
||||||
|
ts = DT_MDL*np.arange(2*POINTS_PER_BUCKET)
|
||||||
|
steer_torques = np.concat((np.linspace(-0.03, -0.02, POINTS_PER_BUCKET), np.linspace(0.02, 0.03, POINTS_PER_BUCKET)))
|
||||||
|
lat_accels = TORQUE_TUNE.latAccelFactor * steer_torques
|
||||||
|
for t, steer_torque, lat_accel in zip(ts, steer_torques, lat_accels, strict=True):
|
||||||
|
carOutput.actuatorsOutput.torque = float(-steer_torque)
|
||||||
|
livePose.orientationNED.x = float(np.deg2rad(ROLL_BIAS_DEG))
|
||||||
|
livePose.angularVelocityDevice.z = float(lat_accel / V_EGO)
|
||||||
|
for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)):
|
||||||
|
est.handle_log(t, which, msg)
|
||||||
|
|
||||||
|
def test_estimated_offset():
|
||||||
|
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||||
|
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||||
|
msg = est.get_msg()
|
||||||
|
# TODO add lataccelfactor and friction check when we have more accurate estimates
|
||||||
|
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
|
||||||
|
|
||||||
|
def test_straight_road_roll_bias():
|
||||||
|
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||||
|
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||||
|
simulate_straight_road_msgs(est)
|
||||||
|
msg = est.get_msg()
|
||||||
|
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
|
||||||
@@ -11,6 +11,7 @@ from openpilot.common.filter_simple import FirstOrderFilter
|
|||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
|
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
|
from openpilot.sunnypilot.selfdrive.locationd.torqued_ext import TorqueEstimatorExt
|
||||||
|
|
||||||
HISTORY = 5 # secs
|
HISTORY = 5 # secs
|
||||||
POINTS_PER_BUCKET = 1500
|
POINTS_PER_BUCKET = 1500
|
||||||
@@ -50,9 +51,10 @@ class TorqueBuckets(PointBuckets):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
class TorqueEstimator(ParameterEstimator):
|
class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
|
||||||
def __init__(self, CP, decimated=False, track_all_points=False):
|
def __init__(self, CP, decimated=False, track_all_points=False):
|
||||||
super().__init__()
|
ParameterEstimator.__init__(self)
|
||||||
|
TorqueEstimatorExt.__init__(self, CP)
|
||||||
self.CP = CP
|
self.CP = CP
|
||||||
self.hist_len = int(HISTORY / DT_MDL)
|
self.hist_len = int(HISTORY / DT_MDL)
|
||||||
self.lag = 0.0
|
self.lag = 0.0
|
||||||
@@ -82,6 +84,8 @@ class TorqueEstimator(ParameterEstimator):
|
|||||||
|
|
||||||
self.calibrator = PoseCalibrator()
|
self.calibrator = PoseCalibrator()
|
||||||
|
|
||||||
|
TorqueEstimatorExt.initialize_custom_params(self, decimated)
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
initial_params = {
|
initial_params = {
|
||||||
@@ -260,6 +264,8 @@ def main(demo=False):
|
|||||||
t = sm.logMonoTime[which] * 1e-9
|
t = sm.logMonoTime[which] * 1e-9
|
||||||
estimator.handle_log(t, which, sm[which])
|
estimator.handle_log(t, which, sm[which])
|
||||||
|
|
||||||
|
TorqueEstimatorExt.update_use_params(estimator)
|
||||||
|
|
||||||
# 4Hz driven by livePose
|
# 4Hz driven by livePose
|
||||||
if sm.frame % 5 == 0:
|
if sm.frame % 5 == 0:
|
||||||
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
|
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
|
||||||
|
|||||||
@@ -13,12 +13,9 @@ class ModelConstants:
|
|||||||
META_T_IDXS = [2., 4., 6., 8., 10.]
|
META_T_IDXS = [2., 4., 6., 8., 10.]
|
||||||
|
|
||||||
# model inputs constants
|
# model inputs constants
|
||||||
MODEL_FREQ = 20
|
N_FRAMES = 2
|
||||||
HISTORY_FREQ = 5
|
MODEL_RUN_FREQ = 20
|
||||||
HISTORY_LEN_SECONDS = 5
|
MODEL_CONTEXT_FREQ = 5 # "model_trained_fps"
|
||||||
TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ
|
|
||||||
FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS
|
|
||||||
INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS
|
|
||||||
|
|
||||||
FEATURE_LEN = 512
|
FEATURE_LEN = 512
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import capnp
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from cereal import log
|
from cereal import log
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta
|
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta
|
||||||
|
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
|
||||||
|
|
||||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||||
|
|
||||||
@@ -95,8 +96,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
|||||||
# action
|
# action
|
||||||
modelV2.action = action
|
modelV2.action = action
|
||||||
|
|
||||||
# times at X_IDXS of edges and lines aren't used
|
# times at X_IDXS of edges and lines
|
||||||
LINE_T_IDXS: list[float] = []
|
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
|
||||||
|
|
||||||
# lane lines
|
# lane lines
|
||||||
modelV2.init('laneLines', 4)
|
modelV2.init('laneLines', 4)
|
||||||
@@ -149,7 +150,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
|||||||
meta.hardBrakePredicted = hard_brake_predicted.item()
|
meta.hardBrakePredicted = hard_brake_predicted.item()
|
||||||
|
|
||||||
# confidence
|
# confidence
|
||||||
if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0:
|
if vipc_frame_id % (2*ModelConstants.MODEL_RUN_FREQ) == 0:
|
||||||
# any disengage prob
|
# any disengage prob
|
||||||
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
|
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
|
||||||
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]
|
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]
|
||||||
|
|||||||
+77
-24
@@ -80,6 +80,64 @@ class FrameMeta:
|
|||||||
if vipc is not None:
|
if vipc is not None:
|
||||||
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
||||||
|
|
||||||
|
class InputQueues:
|
||||||
|
def __init__ (self, model_fps, env_fps, n_frames_input):
|
||||||
|
assert env_fps % model_fps == 0
|
||||||
|
assert env_fps >= model_fps
|
||||||
|
self.model_fps = model_fps
|
||||||
|
self.env_fps = env_fps
|
||||||
|
self.n_frames_input = n_frames_input
|
||||||
|
|
||||||
|
self.dtypes = {}
|
||||||
|
self.shapes = {}
|
||||||
|
self.q = {}
|
||||||
|
|
||||||
|
def update_dtypes_and_shapes(self, input_dtypes, input_shapes) -> None:
|
||||||
|
self.dtypes.update(input_dtypes)
|
||||||
|
if self.env_fps == self.model_fps:
|
||||||
|
self.shapes.update(input_shapes)
|
||||||
|
else:
|
||||||
|
for k in input_shapes:
|
||||||
|
shape = list(input_shapes[k])
|
||||||
|
if 'img' in k:
|
||||||
|
n_channels = shape[1] // self.n_frames_input
|
||||||
|
shape[1] = (self.env_fps // self.model_fps + (self.n_frames_input - 1)) * n_channels
|
||||||
|
else:
|
||||||
|
shape[1] = (self.env_fps // self.model_fps) * shape[1]
|
||||||
|
self.shapes[k] = tuple(shape)
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
self.q = {k: np.zeros(self.shapes[k], dtype=self.dtypes[k]) for k in self.dtypes.keys()}
|
||||||
|
|
||||||
|
def enqueue(self, inputs:dict[str, np.ndarray]) -> None:
|
||||||
|
for k in inputs.keys():
|
||||||
|
if inputs[k].dtype != self.dtypes[k]:
|
||||||
|
raise ValueError(f'supplied input <{k}({inputs[k].dtype})> has wrong dtype, expected {self.dtypes[k]}')
|
||||||
|
input_shape = list(self.shapes[k])
|
||||||
|
input_shape[1] = -1
|
||||||
|
single_input = inputs[k].reshape(tuple(input_shape))
|
||||||
|
sz = single_input.shape[1]
|
||||||
|
self.q[k][:,:-sz] = self.q[k][:,sz:]
|
||||||
|
self.q[k][:,-sz:] = single_input
|
||||||
|
|
||||||
|
def get(self, *names) -> dict[str, np.ndarray]:
|
||||||
|
if self.env_fps == self.model_fps:
|
||||||
|
return {k: self.q[k] for k in names}
|
||||||
|
else:
|
||||||
|
out = {}
|
||||||
|
for k in names:
|
||||||
|
shape = self.shapes[k]
|
||||||
|
if 'img' in k:
|
||||||
|
n_channels = shape[1] // (self.env_fps // self.model_fps + (self.n_frames_input - 1))
|
||||||
|
out[k] = np.concatenate([self.q[k][:, s:s+n_channels] for s in np.linspace(0, shape[1] - n_channels, self.n_frames_input, dtype=int)], axis=1)
|
||||||
|
elif 'pulse' in k:
|
||||||
|
# any pulse within interval counts
|
||||||
|
out[k] = self.q[k].reshape((shape[0], shape[1] * self.model_fps // self.env_fps, self.env_fps // self.model_fps, -1)).max(axis=2)
|
||||||
|
else:
|
||||||
|
idxs = np.arange(-1, -shape[1], -self.env_fps // self.model_fps)[::-1]
|
||||||
|
out[k] = self.q[k][:, idxs]
|
||||||
|
return out
|
||||||
|
|
||||||
class ModelState(ModelStateBase):
|
class ModelState(ModelStateBase):
|
||||||
frames: dict[str, DrivingModelFrame]
|
frames: dict[str, DrivingModelFrame]
|
||||||
inputs: dict[str, np.ndarray]
|
inputs: dict[str, np.ndarray]
|
||||||
@@ -102,19 +160,15 @@ class ModelState(ModelStateBase):
|
|||||||
self.policy_output_slices = policy_metadata['output_slices']
|
self.policy_output_slices = policy_metadata['output_slices']
|
||||||
policy_output_size = policy_metadata['output_shapes']['outputs'][1]
|
policy_output_size = policy_metadata['output_shapes']['outputs'][1]
|
||||||
|
|
||||||
self.frames = {name: DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) for name in self.vision_input_names}
|
self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names}
|
||||||
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||||
|
|
||||||
self.full_features_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
|
|
||||||
self.full_desire = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32)
|
|
||||||
self.temporal_idxs = slice(-1-(ModelConstants.TEMPORAL_SKIP*(ModelConstants.INPUT_HISTORY_BUFFER_LEN-1)), None, ModelConstants.TEMPORAL_SKIP)
|
|
||||||
|
|
||||||
# policy inputs
|
# policy inputs
|
||||||
self.numpy_inputs = {
|
self.numpy_inputs = {k: np.zeros(self.policy_input_shapes[k], dtype=np.float32) for k in self.policy_input_shapes}
|
||||||
'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
|
self.full_input_queues = InputQueues(ModelConstants.MODEL_CONTEXT_FREQ, ModelConstants.MODEL_RUN_FREQ, ModelConstants.N_FRAMES)
|
||||||
'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32),
|
for k in ['desire_pulse', 'features_buffer']:
|
||||||
'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
|
self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape})
|
||||||
}
|
self.full_input_queues.reset()
|
||||||
|
|
||||||
# img buffers are managed in openCL transform code
|
# img buffers are managed in openCL transform code
|
||||||
self.vision_inputs: dict[str, Tensor] = {}
|
self.vision_inputs: dict[str, Tensor] = {}
|
||||||
@@ -136,15 +190,10 @@ class ModelState(ModelStateBase):
|
|||||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
||||||
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
||||||
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
||||||
inputs['desire'][0] = 0
|
inputs['desire_pulse'][0] = 0
|
||||||
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
|
new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0)
|
||||||
self.prev_desire[:] = inputs['desire']
|
self.prev_desire[:] = inputs['desire_pulse']
|
||||||
|
|
||||||
self.full_desire[0,:-1] = self.full_desire[0,1:]
|
|
||||||
self.full_desire[0,-1] = new_desire
|
|
||||||
self.numpy_inputs['desire'][:] = self.full_desire.reshape((1,ModelConstants.INPUT_HISTORY_BUFFER_LEN,ModelConstants.TEMPORAL_SKIP,-1)).max(axis=2)
|
|
||||||
|
|
||||||
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
|
|
||||||
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names}
|
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names}
|
||||||
|
|
||||||
if TICI and not USBGPU:
|
if TICI and not USBGPU:
|
||||||
@@ -163,9 +212,10 @@ class ModelState(ModelStateBase):
|
|||||||
self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy()
|
self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy()
|
||||||
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices))
|
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices))
|
||||||
|
|
||||||
self.full_features_buffer[0,:-1] = self.full_features_buffer[0,1:]
|
self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire})
|
||||||
self.full_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :]
|
for k in ['desire_pulse', 'features_buffer']:
|
||||||
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
|
self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k]
|
||||||
|
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
|
||||||
|
|
||||||
self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy()
|
self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy()
|
||||||
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices))
|
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices))
|
||||||
@@ -216,14 +266,14 @@ def main(demo=False):
|
|||||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||||
|
|
||||||
# messaging
|
# messaging
|
||||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||||
|
|
||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
params = Params()
|
params = Params()
|
||||||
|
|
||||||
# setup filter to track dropped frames
|
# setup filter to track dropped frames
|
||||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ)
|
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_RUN_FREQ)
|
||||||
frame_id = 0
|
frame_id = 0
|
||||||
last_vipc_frame_id = 0
|
last_vipc_frame_id = 0
|
||||||
run_count = 0
|
run_count = 0
|
||||||
@@ -320,7 +370,7 @@ def main(demo=False):
|
|||||||
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
|
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
|
||||||
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
|
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
|
||||||
inputs:dict[str, np.ndarray] = {
|
inputs:dict[str, np.ndarray] = {
|
||||||
'desire': vec_desire,
|
'desire_pulse': vec_desire,
|
||||||
'traffic_convention': traffic_convention,
|
'traffic_convention': traffic_convention,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +383,7 @@ def main(demo=False):
|
|||||||
modelv2_send = messaging.new_message('modelV2')
|
modelv2_send = messaging.new_message('modelV2')
|
||||||
drivingdata_send = messaging.new_message('drivingModelData')
|
drivingdata_send = messaging.new_message('drivingModelData')
|
||||||
posenet_send = messaging.new_message('cameraOdometry')
|
posenet_send = messaging.new_message('cameraOdometry')
|
||||||
|
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||||
|
|
||||||
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
||||||
prev_action = action
|
prev_action = action
|
||||||
@@ -347,6 +398,7 @@ def main(demo=False):
|
|||||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||||
|
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
||||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||||
|
|
||||||
@@ -354,6 +406,7 @@ def main(demo=False):
|
|||||||
pm.send('modelV2', modelv2_send)
|
pm.send('modelV2', modelv2_send)
|
||||||
pm.send('drivingModelData', drivingdata_send)
|
pm.send('drivingModelData', drivingdata_send)
|
||||||
pm.send('cameraOdometry', posenet_send)
|
pm.send('cameraOdometry', posenet_send)
|
||||||
|
pm.send('modelDataV2SP', mdv2sp_send)
|
||||||
last_vipc_frame_id = meta_main.frame_id
|
last_vipc_frame_id = meta_main.frame_id
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:04b763fb71efe57a8a4c4168a8043ecd58939015026ded0dc755ded6905ac251
|
oid sha256:ebb38a934d6472c061cc6010f46d9720ca132d631a47e585a893bdd41ade2419
|
||||||
size 12343523
|
size 12343535
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e66bb8d53eced3786ed71a59b55ffc6810944cb217f0518621cc76303260a1ef
|
oid sha256:befac016a247b7ad5dc5b55d339d127774ed7bd2b848f1583f72aa4caee37781
|
||||||
size 46271991
|
size 46271991
|
||||||
|
|||||||
@@ -84,12 +84,10 @@ Panda *connect(std::string serial="", uint32_t index=0) {
|
|||||||
panda->set_can_fd_auto(i, true);
|
panda->set_can_fd_auto(i, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_deprecated_panda = std::find(DEPRECATED_PANDA_TYPES.begin(),
|
bool is_supported_panda = std::find(SUPPORTED_PANDA_TYPES.begin(), SUPPORTED_PANDA_TYPES.end(), panda->hw_type) != SUPPORTED_PANDA_TYPES.end();
|
||||||
DEPRECATED_PANDA_TYPES.end(),
|
|
||||||
panda->hw_type) != DEPRECATED_PANDA_TYPES.end();
|
|
||||||
|
|
||||||
if (is_deprecated_panda) {
|
if (!is_supported_panda) {
|
||||||
LOGW("panda %s is deprecated (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
|
LOGW("panda %s is not supported (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
|
||||||
return panda.release();
|
return panda.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +173,6 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda
|
|||||||
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
|
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
|
||||||
ps.setInterruptLoad(health.interrupt_load_pkt);
|
ps.setInterruptLoad(health.interrupt_load_pkt);
|
||||||
ps.setFanPower(health.fan_power);
|
ps.setFanPower(health.fan_power);
|
||||||
ps.setFanStallCount(health.fan_stall_count);
|
|
||||||
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
|
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
|
||||||
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
||||||
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
||||||
|
|||||||
@@ -9,13 +9,10 @@
|
|||||||
void pandad_main_thread(std::vector<std::string> serials);
|
void pandad_main_thread(std::vector<std::string> serials);
|
||||||
|
|
||||||
// deprecated devices
|
// deprecated devices
|
||||||
static const std::vector<cereal::PandaState::PandaType> DEPRECATED_PANDA_TYPES = {
|
static const std::vector<cereal::PandaState::PandaType> SUPPORTED_PANDA_TYPES = {
|
||||||
cereal::PandaState::PandaType::WHITE_PANDA,
|
cereal::PandaState::PandaType::RED_PANDA,
|
||||||
cereal::PandaState::PandaType::GREY_PANDA,
|
cereal::PandaState::PandaType::TRES,
|
||||||
cereal::PandaState::PandaType::BLACK_PANDA,
|
cereal::PandaState::PandaType::CUATRO,
|
||||||
cereal::PandaState::PandaType::PEDAL,
|
|
||||||
cereal::PandaState::PandaType::UNO,
|
|
||||||
cereal::PandaState::PandaType::RED_PANDA_V2
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
-11
@@ -29,6 +29,12 @@ def flash_panda(panda_serial: str) -> Panda:
|
|||||||
HARDWARE.recover_internal_panda()
|
HARDWARE.recover_internal_panda()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
# skip flashing if the detected panda is not supported
|
||||||
|
supported_panda = check_panda_support(panda)
|
||||||
|
if not supported_panda:
|
||||||
|
cloudlog.warning(f"Panda {panda_serial} is not supported (hw_type: {panda.get_type()}), skipping flash...")
|
||||||
|
return panda
|
||||||
|
|
||||||
fw_signature = get_expected_signature(panda)
|
fw_signature = get_expected_signature(panda)
|
||||||
internal_panda = panda.is_internal()
|
internal_panda = panda.is_internal()
|
||||||
|
|
||||||
@@ -36,12 +42,6 @@ def flash_panda(panda_serial: str) -> Panda:
|
|||||||
panda_signature = b"" if panda.bootstub else panda.get_signature()
|
panda_signature = b"" if panda.bootstub else panda.get_signature()
|
||||||
cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}")
|
cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}")
|
||||||
|
|
||||||
# skip flashing if the detected device is deprecated from upstream
|
|
||||||
hw_type = panda.get_type()
|
|
||||||
if hw_type in Panda.DEPRECATED_DEVICES:
|
|
||||||
cloudlog.warning(f"Panda {panda_serial} is deprecated (hw_type: {hw_type}), skipping flash...")
|
|
||||||
return panda
|
|
||||||
|
|
||||||
if panda.bootstub or panda_signature != fw_signature:
|
if panda.bootstub or panda_signature != fw_signature:
|
||||||
cloudlog.info("Panda firmware out of date, update required")
|
cloudlog.info("Panda firmware out of date, update required")
|
||||||
panda.flash()
|
panda.flash()
|
||||||
@@ -67,6 +67,14 @@ def flash_panda(panda_serial: str) -> Panda:
|
|||||||
return panda
|
return panda
|
||||||
|
|
||||||
|
|
||||||
|
def check_panda_support(panda) -> bool:
|
||||||
|
hw_type = panda.get_type()
|
||||||
|
if hw_type in Panda.SUPPORTED_DEVICES:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
# signal pandad to close the relay and exit
|
# signal pandad to close the relay and exit
|
||||||
def signal_handler(signum, frame):
|
def signal_handler(signum, frame):
|
||||||
@@ -91,11 +99,6 @@ def main() -> None:
|
|||||||
cloudlog.event("pandad.flash_and_connect", count=count)
|
cloudlog.event("pandad.flash_and_connect", count=count)
|
||||||
params.remove("PandaSignatures")
|
params.remove("PandaSignatures")
|
||||||
|
|
||||||
# TODO: remove this in the next AGNOS
|
|
||||||
# wait until USB is up before counting
|
|
||||||
if time.monotonic() < 60.:
|
|
||||||
no_internal_panda_count = 0
|
|
||||||
|
|
||||||
# Handle missing internal panda
|
# Handle missing internal panda
|
||||||
if no_internal_panda_count > 0:
|
if no_internal_panda_count > 0:
|
||||||
if no_internal_panda_count == 3:
|
if no_internal_panda_count == 3:
|
||||||
@@ -145,6 +148,12 @@ def main() -> None:
|
|||||||
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
|
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
|
||||||
|
|
||||||
for panda in pandas:
|
for panda in pandas:
|
||||||
|
# skip health check if the detected panda is not supported
|
||||||
|
supported_panda = check_panda_support(panda)
|
||||||
|
if not supported_panda:
|
||||||
|
cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...")
|
||||||
|
continue
|
||||||
|
|
||||||
# check health for lost heartbeat
|
# check health for lost heartbeat
|
||||||
health = panda.health()
|
health = panda.health()
|
||||||
if health["heartbeat_lost"]:
|
if health["heartbeat_lost"]:
|
||||||
|
|||||||
Binary file not shown.
@@ -22,8 +22,6 @@ class TestPandad:
|
|||||||
if len(Panda.list()) == 0:
|
if len(Panda.list()) == 0:
|
||||||
self._run_test(60)
|
self._run_test(60)
|
||||||
|
|
||||||
self.spi = HARDWARE.get_device_type() != 'tici'
|
|
||||||
|
|
||||||
def teardown_method(self):
|
def teardown_method(self):
|
||||||
managed_processes['pandad'].stop()
|
managed_processes['pandad'].stop()
|
||||||
|
|
||||||
@@ -106,11 +104,9 @@ class TestPandad:
|
|||||||
# - 0.2s pandad -> pandad
|
# - 0.2s pandad -> pandad
|
||||||
# - plus some buffer
|
# - plus some buffer
|
||||||
print("startup times", ts, sum(ts) / len(ts))
|
print("startup times", ts, sum(ts) / len(ts))
|
||||||
assert 0.1 < (sum(ts)/len(ts)) < (0.7 if self.spi else 5.0)
|
assert 0.1 < (sum(ts)/len(ts)) < 0.7
|
||||||
|
|
||||||
def test_protocol_version_check(self):
|
def test_protocol_version_check(self):
|
||||||
if not self.spi:
|
|
||||||
pytest.skip("SPI test")
|
|
||||||
# flash old fw
|
# flash old fw
|
||||||
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
|
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
|
||||||
self._flash_bootstub_and_test(fn, expect_mismatch=True)
|
self._flash_bootstub_and_test(fn, expect_mismatch=True)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import random
|
|||||||
|
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
from cereal.services import SERVICE_LIST
|
from cereal.services import SERVICE_LIST
|
||||||
from openpilot.system.hardware import HARDWARE
|
|
||||||
from openpilot.selfdrive.test.helpers import with_processes
|
from openpilot.selfdrive.test.helpers import with_processes
|
||||||
from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages
|
from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages
|
||||||
|
|
||||||
@@ -16,8 +15,6 @@ JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ
|
|||||||
class TestBoarddSpi:
|
class TestBoarddSpi:
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_class(cls):
|
def setup_class(cls):
|
||||||
if HARDWARE.get_device_type() == 'tici':
|
|
||||||
pytest.skip("only for spi pandas")
|
|
||||||
os.environ['STARTED'] = '1'
|
os.environ['STARTED'] = '1'
|
||||||
os.environ['SPI_ERR_PROB'] = '0.001'
|
os.environ['SPI_ERR_PROB'] = '0.001'
|
||||||
if not JUNGLE_SPAM:
|
if not JUNGLE_SPAM:
|
||||||
|
|||||||
@@ -29,10 +29,6 @@
|
|||||||
"text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.",
|
"text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.",
|
||||||
"severity": 1
|
"severity": 1
|
||||||
},
|
},
|
||||||
"Offroad_StorageMissing": {
|
|
||||||
"text": "NVMe drive not mounted.",
|
|
||||||
"severity": 1
|
|
||||||
},
|
|
||||||
"Offroad_CarUnrecognized": {
|
"Offroad_CarUnrecognized": {
|
||||||
"text": "sunnypilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.",
|
"text": "sunnypilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.",
|
||||||
"severity": 0
|
"severity": 0
|
||||||
@@ -49,5 +45,10 @@
|
|||||||
"text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.",
|
"text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.",
|
||||||
"severity": 1,
|
"severity": 1,
|
||||||
"_comment": "Set extra field to lateral or longitudinal."
|
"_comment": "Set extra field to lateral or longitudinal."
|
||||||
|
},
|
||||||
|
"Offroad_TiciSupport": {
|
||||||
|
"text": "<b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three.",
|
||||||
|
"severity": 1,
|
||||||
|
"_comment": "Set extra field to the current branch name."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S
|
|||||||
f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
|
f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4)
|
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4)
|
||||||
|
|
||||||
|
|
||||||
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ from openpilot.selfdrive.selfdrived.helpers import ExcessiveActuationCheck
|
|||||||
from openpilot.selfdrive.selfdrived.state import StateMachine
|
from openpilot.selfdrive.selfdrived.state import StateMachine
|
||||||
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
|
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
|
||||||
|
|
||||||
from openpilot.system.hardware import HARDWARE
|
|
||||||
from openpilot.system.version import get_build_metadata
|
from openpilot.system.version import get_build_metadata
|
||||||
|
|
||||||
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
||||||
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
||||||
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
|
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
|
||||||
|
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
|
||||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||||
|
|
||||||
REPLAY = "REPLAY" in os.environ
|
REPLAY = "REPLAY" in os.environ
|
||||||
@@ -86,7 +86,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
|
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
|
||||||
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
|
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
|
||||||
|
|
||||||
ignore = self.sensor_packets + self.gps_packets + ['alertDebug']
|
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
|
||||||
if SIMULATION:
|
if SIMULATION:
|
||||||
ignore += ['driverCameraState', 'managerState']
|
ignore += ['driverCameraState', 'managerState']
|
||||||
if REPLAY:
|
if REPLAY:
|
||||||
@@ -95,7 +95,8 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
|
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
|
||||||
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
|
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
|
||||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback'] + \
|
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
|
||||||
|
'modelDataV2SP', 'longitudinalPlanSP'] + \
|
||||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||||
ignore_alive=ignore, ignore_avg_freq=ignore,
|
ignore_alive=ignore, ignore_avg_freq=ignore,
|
||||||
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
||||||
@@ -134,13 +135,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.state_machine = StateMachine()
|
self.state_machine = StateMachine()
|
||||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||||
|
|
||||||
# some comma three with NVMe experience NVMe dropouts mid-drive that
|
self.ignored_processes = {'mapd', }
|
||||||
# cause loggerd to crash on write, so ignore it only on that platform
|
|
||||||
self.ignored_processes = set()
|
|
||||||
nvme_expected = os.path.exists('/dev/nvme0n1') or (not os.path.isfile("/persist/comma/living-in-the-moment"))
|
|
||||||
if HARDWARE.get_device_type() == 'tici' and nvme_expected:
|
|
||||||
self.ignored_processes = {'loggerd', }
|
|
||||||
self.ignored_processes.update({'mapd'})
|
|
||||||
|
|
||||||
# Determine startup event
|
# Determine startup event
|
||||||
is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote
|
is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote
|
||||||
@@ -162,8 +157,9 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.events_sp_prev = []
|
self.events_sp_prev = []
|
||||||
|
|
||||||
self.mads = ModularAssistiveDrivingSystem(self)
|
self.mads = ModularAssistiveDrivingSystem(self)
|
||||||
|
self.icbm = IntelligentCruiseButtonManagement(self.CP, self.CP_SP)
|
||||||
|
|
||||||
self.car_events_sp = CarSpecificEventsSP(self.CP, self.params)
|
self.car_events_sp = CarSpecificEventsSP(self.CP, self.CP_SP)
|
||||||
|
|
||||||
CruiseHelper.__init__(self, self.CP)
|
CruiseHelper.__init__(self, self.CP)
|
||||||
|
|
||||||
@@ -209,6 +205,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
|
|
||||||
if not self.CP.notCar:
|
if not self.CP.notCar:
|
||||||
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
|
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
|
||||||
|
self.events_sp.add_from_msg(self.sm['longitudinalPlanSP'].events)
|
||||||
|
|
||||||
# Add car events, ignore if CAN isn't valid
|
# Add car events, ignore if CAN isn't valid
|
||||||
if CS.canValid:
|
if CS.canValid:
|
||||||
@@ -300,6 +297,13 @@ class SelfdriveD(CruiseHelper):
|
|||||||
LaneChangeState.laneChangeFinishing):
|
LaneChangeState.laneChangeFinishing):
|
||||||
self.events.add(EventName.laneChange)
|
self.events.add(EventName.laneChange)
|
||||||
|
|
||||||
|
# Handle lane turn
|
||||||
|
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
|
||||||
|
if lane_turn_direction == custom.TurnDirection.turnLeft:
|
||||||
|
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
|
||||||
|
elif lane_turn_direction == custom.TurnDirection.turnRight:
|
||||||
|
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
|
||||||
|
|
||||||
for i, pandaState in enumerate(self.sm['pandaStates']):
|
for i, pandaState in enumerate(self.sm['pandaStates']):
|
||||||
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
|
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
|
||||||
if i < len(self.CP.safetyConfigs):
|
if i < len(self.CP.safetyConfigs):
|
||||||
@@ -441,6 +445,8 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.events.add(EventName.personalityChanged)
|
self.events.add(EventName.personalityChanged)
|
||||||
self.experimental_mode_switched = False
|
self.experimental_mode_switched = False
|
||||||
|
|
||||||
|
self.icbm.run(CS, self.sm['carControl'], self.sm['longitudinalPlanSP'], self.is_metric)
|
||||||
|
|
||||||
def data_sample(self):
|
def data_sample(self):
|
||||||
_car_state = messaging.recv_one(self.car_state_sock)
|
_car_state = messaging.recv_one(self.car_state_sock)
|
||||||
CS = _car_state.carState if _car_state else self.CS_prev
|
CS = _car_state.carState if _car_state else self.CS_prev
|
||||||
@@ -545,6 +551,11 @@ class SelfdriveD(CruiseHelper):
|
|||||||
mads.active = self.mads.active
|
mads.active = self.mads.active
|
||||||
mads.available = self.mads.enabled_toggle
|
mads.available = self.mads.enabled_toggle
|
||||||
|
|
||||||
|
icbm = ss_sp.intelligentCruiseButtonManagement
|
||||||
|
icbm.state = self.icbm.state
|
||||||
|
icbm.sendButton = self.icbm.cruise_button
|
||||||
|
icbm.vTarget = self.icbm.v_target
|
||||||
|
|
||||||
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
||||||
|
|
||||||
# onroadEventsSP - logged every second or on change
|
# onroadEventsSP - logged every second or on change
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ class Plant:
|
|||||||
lp = messaging.new_message('liveParameters')
|
lp = messaging.new_message('liveParameters')
|
||||||
car_control = messaging.new_message('carControl')
|
car_control = messaging.new_message('carControl')
|
||||||
model = messaging.new_message('modelV2')
|
model = messaging.new_message('modelV2')
|
||||||
|
car_state_sp = messaging.new_message('carStateSP')
|
||||||
|
live_map_data_sp = messaging.new_message('liveMapDataSP')
|
||||||
|
gps_data = messaging.new_message('gpsLocation')
|
||||||
a_lead = (v_lead - self.v_lead_prev)/self.ts
|
a_lead = (v_lead - self.v_lead_prev)/self.ts
|
||||||
self.v_lead_prev = v_lead
|
self.v_lead_prev = v_lead
|
||||||
|
|
||||||
@@ -133,7 +136,10 @@ class Plant:
|
|||||||
'controlsState': control.controlsState,
|
'controlsState': control.controlsState,
|
||||||
'selfdriveState': ss.selfdriveState,
|
'selfdriveState': ss.selfdriveState,
|
||||||
'liveParameters': lp.liveParameters,
|
'liveParameters': lp.liveParameters,
|
||||||
'modelV2': model.modelV2}
|
'modelV2': model.modelV2,
|
||||||
|
'carStateSP': car_state_sp.carStateSP,
|
||||||
|
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
|
||||||
|
'gpsLocation': gps_data.gpsLocation}
|
||||||
self.planner.update(sm)
|
self.planner.update(sm)
|
||||||
self.acceleration = self.planner.output_a_target
|
self.acceleration = self.planner.output_a_target
|
||||||
self.speed = self.speed + self.acceleration * self.ts
|
self.speed = self.speed + self.acceleration * self.ts
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
|
|||||||
# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
|
# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
|
||||||
# 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
|
# 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
|
||||||
# 5. all migration functions must be independent of each other
|
# 5. all migration functions must be independent of each other
|
||||||
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False):
|
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False,
|
||||||
|
live_location_kalman: bool = True):
|
||||||
migrations = [
|
migrations = [
|
||||||
migrate_sensorEvents,
|
migrate_sensorEvents,
|
||||||
migrate_carParams,
|
migrate_carParams,
|
||||||
@@ -37,7 +38,6 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo
|
|||||||
migrate_carOutput,
|
migrate_carOutput,
|
||||||
migrate_controlsState,
|
migrate_controlsState,
|
||||||
migrate_carState,
|
migrate_carState,
|
||||||
migrate_liveLocationKalman,
|
|
||||||
migrate_liveTracks,
|
migrate_liveTracks,
|
||||||
migrate_driverAssistance,
|
migrate_driverAssistance,
|
||||||
migrate_drivingModelData,
|
migrate_drivingModelData,
|
||||||
@@ -51,6 +51,8 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo
|
|||||||
migrations.extend([migrate_pandaStates, migrate_peripheralState])
|
migrations.extend([migrate_pandaStates, migrate_peripheralState])
|
||||||
if camera_states:
|
if camera_states:
|
||||||
migrations.append(migrate_cameraStates)
|
migrations.append(migrate_cameraStates)
|
||||||
|
if live_location_kalman:
|
||||||
|
migrations.append(migrate_liveLocationKalman)
|
||||||
|
|
||||||
return migrate(lr, migrations)
|
return migrate(lr, migrations)
|
||||||
|
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ CONFIGS = [
|
|||||||
pubs=[
|
pubs=[
|
||||||
"cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState"
|
"cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState"
|
||||||
],
|
],
|
||||||
subs=["livePose"],
|
subs=["liveLocationKalman", "livePose"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
|
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
@@ -595,7 +595,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") ->
|
|||||||
if len(live_calibration) > 0:
|
if len(live_calibration) > 0:
|
||||||
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
|
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
|
||||||
if len(live_parameters) > 0:
|
if len(live_parameters) > 0:
|
||||||
custom_params["LiveParameters"] = live_parameters[msg_index].as_builder().to_bytes()
|
custom_params["LiveParametersV2"] = live_parameters[msg_index].as_builder().to_bytes()
|
||||||
if len(live_torque_parameters) > 0:
|
if len(live_torque_parameters) > 0:
|
||||||
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
|
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
6d3219bca9f66a229b38a5382d301a92b0147edb
|
afcab1abb62b9d5678342956cced4712f44e909e
|
||||||
@@ -18,7 +18,6 @@ from openpilot.common.timeout import Timeout
|
|||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.selfdrive.selfdrived.events import EVENTS, ET
|
from openpilot.selfdrive.selfdrived.events import EVENTS, ET
|
||||||
from openpilot.selfdrive.test.helpers import set_params_enabled, release_only
|
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.hardware.hw import Paths
|
||||||
from openpilot.tools.lib.logreader import LogReader
|
from openpilot.tools.lib.logreader import LogReader
|
||||||
from openpilot.tools.lib.log_time_series import msgs_to_time_series
|
from openpilot.tools.lib.log_time_series import msgs_to_time_series
|
||||||
@@ -33,7 +32,7 @@ CPU usage budget
|
|||||||
TEST_DURATION = 25
|
TEST_DURATION = 25
|
||||||
LOG_OFFSET = 8
|
LOG_OFFSET = 8
|
||||||
|
|
||||||
MAX_TOTAL_CPU = 280. # total for all 8 cores
|
MAX_TOTAL_CPU = 300. # total for all 8 cores
|
||||||
PROCS = {
|
PROCS = {
|
||||||
# Baseline CPU usage by process
|
# Baseline CPU usage by process
|
||||||
"selfdrive.controls.controlsd": 16.0,
|
"selfdrive.controls.controlsd": 16.0,
|
||||||
@@ -57,30 +56,20 @@ PROCS = {
|
|||||||
"selfdrive.ui.soundd": 3.0,
|
"selfdrive.ui.soundd": 3.0,
|
||||||
"selfdrive.ui.feedback.feedbackd": 1.0,
|
"selfdrive.ui.feedback.feedbackd": 1.0,
|
||||||
"selfdrive.monitoring.dmonitoringd": 4.0,
|
"selfdrive.monitoring.dmonitoringd": 4.0,
|
||||||
"./proclogd": 2.0,
|
"system.proclogd": 3.0,
|
||||||
"system.logmessaged": 1.0,
|
"system.logmessaged": 1.0,
|
||||||
"system.tombstoned": 0,
|
"system.tombstoned": 0,
|
||||||
"./logcatd": 1.0,
|
"system.journald": 1.0,
|
||||||
"system.micd": 5.0,
|
"system.micd": 5.0,
|
||||||
"system.timed": 0,
|
"system.timed": 0,
|
||||||
"selfdrive.pandad.pandad": 0,
|
"selfdrive.pandad.pandad": 0,
|
||||||
"system.statsd": 1.0,
|
"system.statsd": 1.0,
|
||||||
"system.loggerd.uploader": 15.0,
|
"system.loggerd.uploader": 15.0,
|
||||||
"system.loggerd.deleter": 1.0,
|
"system.loggerd.deleter": 1.0,
|
||||||
|
"./pandad": 19.0,
|
||||||
|
"system.qcomgpsd.qcomgpsd": 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
PROCS.update({
|
|
||||||
"tici": {
|
|
||||||
"./pandad": 5.0,
|
|
||||||
"./ubloxd": 1.0,
|
|
||||||
"system.ubloxd.pigeond": 6.0,
|
|
||||||
},
|
|
||||||
"tizi": {
|
|
||||||
"./pandad": 19.0,
|
|
||||||
"system.qcomgpsd.qcomgpsd": 1.0,
|
|
||||||
}
|
|
||||||
}.get(HARDWARE.get_device_type(), {}))
|
|
||||||
|
|
||||||
TIMINGS = {
|
TIMINGS = {
|
||||||
# rtols: max/min, rsd
|
# rtols: max/min, rsd
|
||||||
"can": [2.5, 0.35],
|
"can": [2.5, 0.35],
|
||||||
|
|||||||
@@ -63,14 +63,20 @@ class MainLayout(Widget):
|
|||||||
# Don't hide sidebar from interactive timeout
|
# Don't hide sidebar from interactive timeout
|
||||||
if self._current_mode != MainState.ONROAD:
|
if self._current_mode != MainState.ONROAD:
|
||||||
self._sidebar.set_visible(False)
|
self._sidebar.set_visible(False)
|
||||||
self._current_mode = MainState.ONROAD
|
self._set_current_layout(MainState.ONROAD)
|
||||||
else:
|
else:
|
||||||
self._current_mode = MainState.HOME
|
self._set_current_layout(MainState.HOME)
|
||||||
self._sidebar.set_visible(True)
|
self._sidebar.set_visible(True)
|
||||||
|
|
||||||
|
def _set_current_layout(self, layout: MainState):
|
||||||
|
if layout != self._current_mode:
|
||||||
|
self._layouts[self._current_mode].hide_event()
|
||||||
|
self._current_mode = layout
|
||||||
|
self._layouts[self._current_mode].show_event()
|
||||||
|
|
||||||
def open_settings(self, panel_type: PanelType):
|
def open_settings(self, panel_type: PanelType):
|
||||||
self._layouts[MainState.SETTINGS].set_current_panel(panel_type)
|
self._layouts[MainState.SETTINGS].set_current_panel(panel_type)
|
||||||
self._current_mode = MainState.SETTINGS
|
self._set_current_layout(MainState.SETTINGS)
|
||||||
self._sidebar.set_visible(False)
|
self._sidebar.set_visible(False)
|
||||||
|
|
||||||
def _on_settings_clicked(self):
|
def _on_settings_clicked(self):
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import pyray as rl
|
|
||||||
from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper
|
|
||||||
from openpilot.system.ui.widgets import Widget
|
|
||||||
from openpilot.system.ui.widgets.network import WifiManagerUI
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkLayout(Widget):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.wifi_manager = WifiManagerWrapper()
|
|
||||||
self.wifi_ui = WifiManagerUI(self.wifi_manager)
|
|
||||||
|
|
||||||
def _render(self, rect: rl.Rectangle):
|
|
||||||
self.wifi_ui.render(rect)
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
self.wifi_manager.shutdown()
|
|
||||||
@@ -2,7 +2,7 @@ import pyray as rl
|
|||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from openpilot.common.api import Api, api_get
|
from openpilot.common.api import api_get
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
@@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight
|
|||||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
|
from openpilot.selfdrive.ui.lib.api_helpers import get_token
|
||||||
|
|
||||||
TITLE = "Firehose Mode"
|
TITLE = "Firehose Mode"
|
||||||
DESCRIPTION = (
|
DESCRIPTION = (
|
||||||
@@ -163,7 +163,7 @@ class FirehoseLayout(Widget):
|
|||||||
dongle_id = self.params.get("DongleId")
|
dongle_id = self.params.get("DongleId")
|
||||||
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
|
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
|
||||||
return
|
return
|
||||||
identity_token = Api(dongle_id).get_token()
|
identity_token = get_token(dongle_id)
|
||||||
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token)
|
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import pyray as rl
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from openpilot.selfdrive.ui.layouts.network import NetworkLayout
|
|
||||||
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
|
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
|
||||||
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
|
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
|
||||||
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
|
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
|
||||||
@@ -10,7 +9,9 @@ from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
|
|||||||
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
|
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
|
from openpilot.system.ui.lib.wifi_manager import WifiManager
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
|
from openpilot.system.ui.widgets.network import WifiManagerUI
|
||||||
|
|
||||||
# Settings close button
|
# Settings close button
|
||||||
SETTINGS_CLOSE_TEXT = "×"
|
SETTINGS_CLOSE_TEXT = "×"
|
||||||
@@ -43,7 +44,7 @@ class PanelType(IntEnum):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class PanelInfo:
|
class PanelInfo:
|
||||||
name: str
|
name: str
|
||||||
instance: object
|
instance: Widget
|
||||||
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
|
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
@@ -53,9 +54,12 @@ class SettingsLayout(Widget):
|
|||||||
self._current_panel = PanelType.DEVICE
|
self._current_panel = PanelType.DEVICE
|
||||||
|
|
||||||
# Panel configuration
|
# Panel configuration
|
||||||
|
wifi_manager = WifiManager()
|
||||||
|
wifi_manager.set_active(False)
|
||||||
|
|
||||||
self._panels = {
|
self._panels = {
|
||||||
PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
|
PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
|
||||||
PanelType.NETWORK: PanelInfo("Network", NetworkLayout()),
|
PanelType.NETWORK: PanelInfo("Network", WifiManagerUI(wifi_manager)),
|
||||||
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
|
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
|
||||||
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
|
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
|
||||||
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
|
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
|
||||||
@@ -149,8 +153,14 @@ class SettingsLayout(Widget):
|
|||||||
|
|
||||||
def set_current_panel(self, panel_type: PanelType):
|
def set_current_panel(self, panel_type: PanelType):
|
||||||
if panel_type != self._current_panel:
|
if panel_type != self._current_panel:
|
||||||
|
self._panels[self._current_panel].instance.hide_event()
|
||||||
self._current_panel = panel_type
|
self._current_panel = panel_type
|
||||||
|
self._panels[self._current_panel].instance.show_event()
|
||||||
|
|
||||||
def close_settings(self):
|
def show_event(self):
|
||||||
if self._close_callback:
|
super().show_event()
|
||||||
self._close_callback()
|
self._panels[self._current_panel].instance.show_event()
|
||||||
|
|
||||||
|
def hide_event(self):
|
||||||
|
super().hide_event()
|
||||||
|
self._panels[self._current_panel].instance.hide_event()
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import time
|
||||||
|
from functools import lru_cache
|
||||||
|
from openpilot.common.api import Api
|
||||||
|
|
||||||
|
TOKEN_EXPIRY_HOURS = 2
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def _get_token(dongle_id: str, t: int):
|
||||||
|
return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS)
|
||||||
|
|
||||||
|
|
||||||
|
def get_token(dongle_id: str):
|
||||||
|
return _get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60)))
|
||||||
@@ -2,14 +2,12 @@ from enum import IntEnum
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
from openpilot.common.api import Api, api_get
|
from openpilot.common.api import api_get
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
||||||
|
from openpilot.selfdrive.ui.lib.api_helpers import get_token
|
||||||
TOKEN_EXPIRY_HOURS = 2
|
|
||||||
|
|
||||||
|
|
||||||
class PrimeType(IntEnum):
|
class PrimeType(IntEnum):
|
||||||
@@ -23,12 +21,6 @@ class PrimeType(IntEnum):
|
|||||||
PURPLE = 5,
|
PURPLE = 5,
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
|
||||||
def get_token(dongle_id: str, t: int):
|
|
||||||
print('getting token')
|
|
||||||
return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS)
|
|
||||||
|
|
||||||
|
|
||||||
class PrimeState:
|
class PrimeState:
|
||||||
FETCH_INTERVAL = 5.0 # seconds between API calls
|
FETCH_INTERVAL = 5.0 # seconds between API calls
|
||||||
API_TIMEOUT = 10.0 # seconds for API requests
|
API_TIMEOUT = 10.0 # seconds for API requests
|
||||||
@@ -58,15 +50,13 @@ class PrimeState:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
identity_token = get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60)))
|
identity_token = get_token(dongle_id)
|
||||||
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
|
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
is_paired = data.get("is_paired", False)
|
is_paired = data.get("is_paired", False)
|
||||||
prime_type = data.get("prime_type", 0)
|
prime_type = data.get("prime_type", 0)
|
||||||
self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
|
self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
|
||||||
elif response.status_code == 401:
|
|
||||||
get_token.cache_clear()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
cloudlog.error(f"Failed to fetch prime status: {e}")
|
cloudlog.error(f"Failed to fetch prime status: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "selfdrive/ui/qt/util.h"
|
#include "selfdrive/ui/qt/util.h"
|
||||||
|
#ifdef SUNNYPILOT
|
||||||
|
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
void OnroadAlerts::updateState(const UIState &s) {
|
void OnroadAlerts::updateState(const UIState &s) {
|
||||||
Alert a = getAlert(*(s.sm), s.scene.started_frame);
|
Alert a = getAlert(*(s.sm), s.scene.started_frame);
|
||||||
@@ -73,6 +76,12 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
|
|||||||
}
|
}
|
||||||
QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2);
|
QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2);
|
||||||
|
|
||||||
|
#ifdef SUNNYPILOT
|
||||||
|
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
|
||||||
|
const int adjustment = dev_ui_info > 1 && alert.size != cereal::SelfdriveState::AlertSize::FULL ? 30 : 0;
|
||||||
|
r = QRect(0 + margin, height() - h + margin - adjustment, width() - margin*2, h - margin*2);
|
||||||
|
#endif
|
||||||
|
|
||||||
QPainter p(this);
|
QPainter p(this);
|
||||||
|
|
||||||
// draw background + gradient
|
// draw background + gradient
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "selfdrive/ui/sunnypilot/qt/onroad/model.h"
|
#include "selfdrive/ui/sunnypilot/qt/onroad/model.h"
|
||||||
#define ExperimentalButton ExperimentalButtonSP
|
#define ExperimentalButton ExperimentalButtonSP
|
||||||
#define ModelRenderer ModelRendererSP
|
#define ModelRenderer ModelRendererSP
|
||||||
|
#define HudRenderer HudRendererSP
|
||||||
#else
|
#else
|
||||||
#include "selfdrive/ui/qt/onroad/buttons.h"
|
#include "selfdrive/ui/qt/onroad/buttons.h"
|
||||||
#include "selfdrive/ui/qt/onroad/hud.h"
|
#include "selfdrive/ui/qt/onroad/hud.h"
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ void DriverMonitorRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
|||||||
float y = surface_rect.height() - offset;
|
float y = surface_rect.height() - offset;
|
||||||
float opacity = is_active ? 0.65f : 0.2f;
|
float opacity = is_active ? 0.65f : 0.2f;
|
||||||
|
|
||||||
|
#ifdef SUNNYPILOT
|
||||||
|
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
|
||||||
|
y -= dev_ui_info > 1 ? 50 : 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
drawIcon(painter, QPoint(x, y), dm_img, QColor(0, 0, 0, 70), opacity);
|
drawIcon(painter, QPoint(x, y), dm_img, QColor(0, 0, 0, 70), opacity);
|
||||||
|
|
||||||
QPointF keypoints[std::size(DEFAULT_FACE_KPTS_3D)];
|
QPointF keypoints[std::size(DEFAULT_FACE_KPTS_3D)];
|
||||||
|
|||||||
@@ -47,10 +47,11 @@ void HudRenderer::draw(QPainter &p, const QRect &surface_rect) {
|
|||||||
bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0));
|
bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0));
|
||||||
p.fillRect(0, 0, surface_rect.width(), UI_HEADER_HEIGHT, bg);
|
p.fillRect(0, 0, surface_rect.width(), UI_HEADER_HEIGHT, bg);
|
||||||
|
|
||||||
|
#ifndef SUNNYPILOT
|
||||||
if (is_cruise_available) {
|
if (is_cruise_available) {
|
||||||
drawSetSpeed(p, surface_rect);
|
drawSetSpeed(p, surface_rect);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
drawCurrentSpeed(p, surface_rect);
|
drawCurrentSpeed(p, surface_rect);
|
||||||
|
|
||||||
p.restore();
|
p.restore();
|
||||||
|
|||||||
@@ -336,8 +336,8 @@ QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStrin
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items,
|
TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<TreeFolder> &items,
|
||||||
const QString ¤t, QWidget *parent) : DialogBase(parent) {
|
const QString ¤t, const QString &favParam, QWidget *parent) : DialogBase(parent) {
|
||||||
QFrame *container = new QFrame(this);
|
QFrame *container = new QFrame(this);
|
||||||
container->setStyleSheet(R"(
|
container->setStyleSheet(R"(
|
||||||
QFrame { background-color: #1B1B1B; }
|
QFrame { background-color: #1B1B1B; }
|
||||||
@@ -375,6 +375,9 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair
|
|||||||
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
|
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
|
||||||
main_layout->addSpacing(25);
|
main_layout->addSpacing(25);
|
||||||
|
|
||||||
|
iconBlank = QIcon("../../sunnypilot/selfdrive/assets/icons/star-empty.png");
|
||||||
|
iconFilled = QIcon ("../../sunnypilot/selfdrive/assets/icons/star-filled.png");
|
||||||
|
|
||||||
treeWidget = new QTreeWidget(this);
|
treeWidget = new QTreeWidget(this);
|
||||||
treeWidget->setHeaderHidden(true);
|
treeWidget->setHeaderHidden(true);
|
||||||
treeWidget->setIndentation(50);
|
treeWidget->setIndentation(50);
|
||||||
@@ -396,34 +399,49 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair
|
|||||||
|
|
||||||
QScroller::grabGesture(treeWidget->viewport(), QScroller::LeftMouseButtonGesture);
|
QScroller::grabGesture(treeWidget->viewport(), QScroller::LeftMouseButtonGesture);
|
||||||
|
|
||||||
|
// Create initial list of favorites from param
|
||||||
|
const QString favs = QString::fromStdString(params.get(favParam.toStdString()));
|
||||||
|
mapFavs = new QMap<QString, QList<QPushButton*>>();
|
||||||
|
favRefs = new QStringList(favs.split(";"));
|
||||||
|
for (const QString &item : *favRefs)
|
||||||
|
{
|
||||||
|
mapFavs->insert( item, {});
|
||||||
|
}
|
||||||
|
|
||||||
// Populate tree
|
// Populate tree
|
||||||
QListIterator<QPair<QString, QStringList>> iter(items);
|
QListIterator<TreeFolder> iter(items);
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
QPair currItem = iter.next();
|
TreeFolder currItem = iter.next();
|
||||||
if (currItem.first.isEmpty()) {
|
QString prevFolder;
|
||||||
for (const QString &item : currItem.second) {
|
QString currentFolder;
|
||||||
|
if (currItem.folder.isEmpty()) {
|
||||||
|
for (const TreeNode &item : currItem.items) {
|
||||||
QTreeWidgetItem *topLevel = new QTreeWidgetItem();
|
QTreeWidgetItem *topLevel = new QTreeWidgetItem();
|
||||||
topLevel->setText(0, item);
|
topLevel->setText(0, item.displayName);
|
||||||
|
topLevel->setData(0, Qt::UserRole, item.ref);
|
||||||
topLevel->setFlags(topLevel->flags() | Qt::ItemIsSelectable);
|
topLevel->setFlags(topLevel->flags() | Qt::ItemIsSelectable);
|
||||||
treeWidget->addTopLevelItem(topLevel);
|
treeWidget->addTopLevelItem(topLevel);
|
||||||
if (item == current) {
|
if (item.ref == current) {
|
||||||
topLevel->setSelected(true);
|
topLevel->setSelected(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QTreeWidgetItem *folderItem = new QTreeWidgetItem(treeWidget);
|
QList<QTreeWidgetItem*> folders = treeWidget->findItems(currItem.folder, Qt::MatchExactly, 0);
|
||||||
|
QTreeWidgetItem *folderItem = nullptr;
|
||||||
|
if (folders.isEmpty()) {
|
||||||
|
folderItem = new QTreeWidgetItem(treeWidget);
|
||||||
|
} else {
|
||||||
|
folderItem = folders.first();
|
||||||
|
}
|
||||||
folderItem->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
|
folderItem->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
|
||||||
folderItem->setText(0, " " + currItem.first);
|
folderItem->setText(0, " " + currItem.folder);
|
||||||
folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate);
|
folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate);
|
||||||
folderItem->setFlags(folderItem->flags() & ~Qt::ItemIsSelectable);
|
folderItem->setFlags(folderItem->flags() & ~Qt::ItemIsSelectable);
|
||||||
|
|
||||||
for (const QString &item : currItem.second)
|
for (const TreeNode item : currItem.items)
|
||||||
{
|
{
|
||||||
QTreeWidgetItem *childItem = new QTreeWidgetItem(folderItem);
|
QTreeWidgetItem *childItem = addChildItem(item.displayName, item.ref, folderItem);
|
||||||
childItem->setText(0, item);
|
if (item.ref == current) {
|
||||||
childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable);
|
|
||||||
|
|
||||||
if (item == current) {
|
|
||||||
childItem->setSelected(true);
|
childItem->setSelected(true);
|
||||||
folderItem->setExpanded(true);
|
folderItem->setExpanded(true);
|
||||||
}
|
}
|
||||||
@@ -431,6 +449,39 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create favorites folder
|
||||||
|
favorites = new QTreeWidgetItem();
|
||||||
|
favorites->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
|
||||||
|
favorites->setText(0, " " + tr("Favorites"));
|
||||||
|
favorites->setFlags(favorites->flags() | Qt::ItemIsAutoTristate);
|
||||||
|
favorites->setFlags(favorites->flags() & ~Qt::ItemIsSelectable);
|
||||||
|
treeWidget->insertTopLevelItem(1, favorites);
|
||||||
|
|
||||||
|
// Create favorite nodes
|
||||||
|
for (int i = favRefs->size() - 1; i >= 0; --i) {
|
||||||
|
QString item = favRefs->at(i);
|
||||||
|
if (item.isEmpty()) continue;
|
||||||
|
|
||||||
|
QTreeWidgetItemIterator treeIt(treeWidget);
|
||||||
|
QTreeWidgetItem *nodeItem = nullptr;
|
||||||
|
while (*treeIt) {
|
||||||
|
if (item == (*treeIt)->data(0, Qt::UserRole).toString()) {
|
||||||
|
nodeItem = (*treeIt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++treeIt;
|
||||||
|
}
|
||||||
|
if (nodeItem == nullptr) continue;
|
||||||
|
|
||||||
|
QTreeWidgetItem *childItem = addChildItem(nodeItem->text(0),
|
||||||
|
nodeItem->data(0, Qt::UserRole).toString(), favorites);
|
||||||
|
if (item == current) {
|
||||||
|
treeWidget->collapseAll();
|
||||||
|
childItem->setSelected(true);
|
||||||
|
favorites->setExpanded(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
confirm_btn = new QPushButton(tr("Select"));
|
confirm_btn = new QPushButton(tr("Select"));
|
||||||
confirm_btn->setObjectName("confirm_btn");
|
confirm_btn->setObjectName("confirm_btn");
|
||||||
confirm_btn->setEnabled(false);
|
confirm_btn->setEnabled(false);
|
||||||
@@ -438,7 +489,7 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair
|
|||||||
QObject::connect(treeWidget, &QTreeWidget::itemSelectionChanged, [=]() {
|
QObject::connect(treeWidget, &QTreeWidget::itemSelectionChanged, [=]() {
|
||||||
QList<QTreeWidgetItem*> selectedItems = treeWidget->selectedItems();
|
QList<QTreeWidgetItem*> selectedItems = treeWidget->selectedItems();
|
||||||
if (!selectedItems.isEmpty()) {
|
if (!selectedItems.isEmpty()) {
|
||||||
selection = selectedItems.first()->text(0);
|
selection = selectedItems.first()->data(0, Qt::UserRole).toString();
|
||||||
confirm_btn->setEnabled(selection != current);
|
confirm_btn->setEnabled(selection != current);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -465,11 +516,91 @@ TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair
|
|||||||
outer_layout->addWidget(container);
|
outer_layout->addWidget(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TreeOptionDialog::getSelection(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items,
|
QString TreeOptionDialog::getSelection(const QString &prompt_text, const QList<TreeFolder> &items,
|
||||||
const QString ¤t, QWidget *parent) {
|
const QString ¤t, const QString &favParam, QWidget *parent) {
|
||||||
TreeOptionDialog d(prompt_text, items, current, parent);
|
TreeOptionDialog d(prompt_text, items, current, favParam, parent);
|
||||||
if (d.exec()) {
|
if (d.exec()) {
|
||||||
return d.selection;
|
return d.selection;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the addition or removal of items from the "favorites" list based on the provided reference identifier.
|
||||||
|
*
|
||||||
|
* @param displayName The text label associated with the item to be added or removed in the favorites.
|
||||||
|
* @param ref A unique reference key identifying the item.
|
||||||
|
* @param btn A pointer to the QPushButton associated with the item. The button's icon is updated to indicate
|
||||||
|
* whether the item is currently favorited or not.
|
||||||
|
*
|
||||||
|
* If the item is already in the favorites, it is removed from the list, its associated buttons have their
|
||||||
|
* icons reset, and the favorites tree is updated accordingly. If the item is not in the favorites, it is
|
||||||
|
* added to the list, a new associated button is created, and the favorites tree is updated. The current
|
||||||
|
* state of the favorites is stored in the Params object as a semicolon-separated string.
|
||||||
|
*/
|
||||||
|
void TreeOptionDialog::handleFavorites(const QString &displayName, const QString &ref, QPushButton *btn) {
|
||||||
|
if (mapFavs->keys().contains(ref)) { // Remove from favorites
|
||||||
|
for (auto *itemBtn:mapFavs->value(ref))
|
||||||
|
{
|
||||||
|
itemBtn->setIcon(iconBlank);
|
||||||
|
}
|
||||||
|
mapFavs->remove(ref);
|
||||||
|
favRefs->removeAll(ref);
|
||||||
|
for (int i = 0; i < favorites->childCount(); ++i) {
|
||||||
|
QTreeWidgetItem* child = favorites->child(i);
|
||||||
|
if (child && child->data(0, Qt::UserRole).toString() == ref) {
|
||||||
|
favorites->removeChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Add to favorites
|
||||||
|
QPushButton *favBtn = new QPushButton();
|
||||||
|
btn->setIcon(iconFilled);
|
||||||
|
mapFavs->insert(ref, {btn, favBtn});
|
||||||
|
favRefs->append(ref);
|
||||||
|
addChildItem(displayName, ref, favorites, favBtn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString favs =favRefs->join(";");
|
||||||
|
params.put("ModelManager_Favs", favs.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child item to a given folder item within the QTreeWidget.
|
||||||
|
*
|
||||||
|
* @param displayName The text to display for the child item.
|
||||||
|
* @param ref A reference string that uniquely identifies the child item.
|
||||||
|
* @param folderItem The parent folder item to which the child item will be added.
|
||||||
|
* @param btn A pointer to a QPushButton associated with the child item. If nullptr, a new button will be created.
|
||||||
|
* @param addAtTop If true, the child item is added as the first child of the folder item; otherwise, it is appended to the end.
|
||||||
|
* @return A pointer to the created QTreeWidgetItem representing the child item.
|
||||||
|
*/
|
||||||
|
QTreeWidgetItem* TreeOptionDialog::addChildItem(const QString &displayName, const QString &ref, QTreeWidgetItem *folderItem, QPushButton *btn, bool addAtTop) {
|
||||||
|
QTreeWidgetItem *childItem = new QTreeWidgetItem();
|
||||||
|
if (btn == nullptr) {
|
||||||
|
btn = new QPushButton();
|
||||||
|
}
|
||||||
|
if (mapFavs->keys().contains(ref)) {
|
||||||
|
btn->setIcon(iconFilled);
|
||||||
|
(*mapFavs)[ref].append(btn);
|
||||||
|
} else {
|
||||||
|
btn->setIcon(iconBlank);
|
||||||
|
}
|
||||||
|
btn->setIconSize(QSize(100, 100));
|
||||||
|
QWidget *buttonContainer = new QWidget();
|
||||||
|
QHBoxLayout *layout = new QHBoxLayout(buttonContainer);
|
||||||
|
layout->addWidget(btn, 0, Qt::AlignRight);
|
||||||
|
childItem->setText(0, displayName);
|
||||||
|
childItem->setData(0, Qt::UserRole, ref);
|
||||||
|
childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable);
|
||||||
|
if (addAtTop) {
|
||||||
|
folderItem->insertChild(0, childItem);
|
||||||
|
} else {
|
||||||
|
folderItem->addChild(childItem);
|
||||||
|
}
|
||||||
|
treeWidget->setItemWidget(childItem, 0, buttonContainer);
|
||||||
|
|
||||||
|
connect(btn, &QPushButton::clicked, btn, [=]() {
|
||||||
|
handleFavorites(displayName, ref, btn);
|
||||||
|
});
|
||||||
|
return childItem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,9 +8,22 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QTreeWidget>
|
#include <QTreeWidget>
|
||||||
|
|
||||||
|
#include "common/params.h"
|
||||||
#include "selfdrive/ui/qt/widgets/keyboard.h"
|
#include "selfdrive/ui/qt/widgets/keyboard.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct TreeNode {
|
||||||
|
QString folder;
|
||||||
|
QString displayName;
|
||||||
|
QString ref;
|
||||||
|
int index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TreeFolder {
|
||||||
|
QString folder;
|
||||||
|
QList<TreeNode> items;
|
||||||
|
};
|
||||||
|
|
||||||
class DialogBase : public QDialog {
|
class DialogBase : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -75,11 +88,20 @@ class TreeOptionDialog : public DialogBase {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TreeOptionDialog(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items, const QString ¤t, QWidget *parent = nullptr);
|
explicit TreeOptionDialog(const QString &prompt_text, const QList<TreeFolder> &items, const QString ¤t, const QString &favParam, QWidget *parent = nullptr);
|
||||||
static QString getSelection(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items, const QString ¤t, QWidget *parent = nullptr);
|
static QString getSelection(const QString &prompt_text, const QList<TreeFolder> &items, const QString ¤t, const QString &favParam, QWidget *parent = nullptr);
|
||||||
|
void handleFavorites(const QString &displayName, const QString &ref, QPushButton* btn);
|
||||||
|
QTreeWidgetItem* addChildItem(const QString &displayName, const QString &ref, QTreeWidgetItem* folderItem, QPushButton* btn = nullptr, bool addAtTop = false);
|
||||||
QString selection;
|
QString selection;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTreeWidget *treeWidget;
|
QTreeWidget *treeWidget;
|
||||||
QPushButton *confirm_btn;
|
QPushButton *confirm_btn;
|
||||||
|
Params params;
|
||||||
|
QMap<QString, QList<QPushButton*>> *mapFavs;
|
||||||
|
QStringList *favRefs;
|
||||||
|
QTreeWidgetItem *favorites;
|
||||||
|
|
||||||
|
QIcon iconBlank;
|
||||||
|
QIcon iconFilled;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ qt_src = [
|
|||||||
"sunnypilot/qt/offroad/offroad_home.cc",
|
"sunnypilot/qt/offroad/offroad_home.cc",
|
||||||
"sunnypilot/qt/offroad/settings/developer_panel.cc",
|
"sunnypilot/qt/offroad/settings/developer_panel.cc",
|
||||||
"sunnypilot/qt/offroad/settings/device_panel.cc",
|
"sunnypilot/qt/offroad/settings/device_panel.cc",
|
||||||
|
"sunnypilot/qt/offroad/settings/display_panel.cc",
|
||||||
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
|
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
|
||||||
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc",
|
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc",
|
||||||
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
|
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
|
||||||
@@ -39,6 +40,7 @@ qt_src = [
|
|||||||
"sunnypilot/qt/offroad/settings/visuals_panel.cc",
|
"sunnypilot/qt/offroad/settings/visuals_panel.cc",
|
||||||
"sunnypilot/qt/onroad/annotated_camera.cc",
|
"sunnypilot/qt/onroad/annotated_camera.cc",
|
||||||
"sunnypilot/qt/onroad/buttons.cc",
|
"sunnypilot/qt/onroad/buttons.cc",
|
||||||
|
"sunnypilot/qt/onroad/developer_ui/developer_ui.cc",
|
||||||
"sunnypilot/qt/onroad/hud.cc",
|
"sunnypilot/qt/onroad/hud.cc",
|
||||||
"sunnypilot/qt/onroad/model.cc",
|
"sunnypilot/qt/onroad/model.cc",
|
||||||
"sunnypilot/qt/onroad/onroad_home.cc",
|
"sunnypilot/qt/onroad/onroad_home.cc",
|
||||||
@@ -49,10 +51,14 @@ lateral_panel_qt_src = [
|
|||||||
"sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc",
|
"sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc",
|
||||||
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
|
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
|
||||||
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
|
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
|
||||||
|
"sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_custom_params.cc",
|
||||||
|
"sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_settings.cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
longitudinal_panel_qt_src = [
|
longitudinal_panel_qt_src = [
|
||||||
"sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc",
|
"sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc",
|
||||||
|
"sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc",
|
||||||
|
"sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
network_src = [
|
network_src = [
|
||||||
@@ -87,9 +93,13 @@ brand_settings_qt_src = [
|
|||||||
"sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.cc",
|
"sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
display_panel_qt_src = [
|
||||||
|
"sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc",
|
||||||
|
]
|
||||||
|
|
||||||
sp_widgets_src = widgets_src + network_src
|
sp_widgets_src = widgets_src + network_src
|
||||||
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + longitudinal_panel_qt_src + osm_panel_qt_src
|
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + \
|
||||||
|
longitudinal_panel_qt_src + osm_panel_qt_src + display_panel_qt_src
|
||||||
sp_qt_util = qt_util
|
sp_qt_util = qt_util
|
||||||
|
|
||||||
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
|
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare
|
|||||||
enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true);
|
enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true);
|
||||||
addItem(enableGithubRunner);
|
addItem(enableGithubRunner);
|
||||||
|
|
||||||
|
// Copyparty Toggle
|
||||||
|
enableCopyparty = new ParamControlSP("EnableCopyparty", tr("Enable Copyparty service"), tr("Copyparty is a very capable file server, you can use it to download your routes, view your logs and even make some edits on some files from your browser. Requires you to connect to your comma locally via it's IP."), "", this, false);
|
||||||
|
addItem(enableCopyparty);
|
||||||
|
|
||||||
// Quickboot Mode Toggle
|
// Quickboot Mode Toggle
|
||||||
prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true);
|
prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true);
|
||||||
addItem(prebuiltToggle);
|
addItem(prebuiltToggle);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public:
|
|||||||
explicit DeveloperPanelSP(SettingsWindow *parent);
|
explicit DeveloperPanelSP(SettingsWindow *parent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
ParamControlSP *enableCopyparty;
|
||||||
ParamControlSP *enableGithubRunner;
|
ParamControlSP *enableGithubRunner;
|
||||||
ButtonControlSP *errorLogBtn;
|
ButtonControlSP *errorLogBtn;
|
||||||
ParamControlSP *prebuiltToggle;
|
ParamControlSP *prebuiltToggle;
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.h"
|
||||||
|
|
||||||
|
OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString ¶m, const QString &title,
|
||||||
|
const QString &description, const QString &icon,
|
||||||
|
QWidget *parent)
|
||||||
|
: ExpandableToggleRow(param, title, description, icon, parent) {
|
||||||
|
auto *mainFrame = new QFrame(this);
|
||||||
|
auto *mainFrameLayout = new QGridLayout();
|
||||||
|
mainFrame->setLayout(mainFrameLayout);
|
||||||
|
mainFrameLayout->setSpacing(0);
|
||||||
|
|
||||||
|
onroadScreenOffTimer = new OptionControlSP(
|
||||||
|
"OnroadScreenOffTimer",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
{0, 11}, 1, true, &onroadScreenOffTimerOptions);
|
||||||
|
|
||||||
|
onroadScreenBrightness = new OptionControlSP(
|
||||||
|
"OnroadScreenOffBrightness",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
{0, 100}, 10, true, nullptr, false);
|
||||||
|
|
||||||
|
connect(onroadScreenOffTimer, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
|
||||||
|
connect(onroadScreenBrightness, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
|
||||||
|
onroadScreenOffTimer->setFixedWidth(280);
|
||||||
|
onroadScreenBrightness->setFixedWidth(280);
|
||||||
|
mainFrameLayout->addWidget(onroadScreenOffTimer, 0, 0, Qt::AlignLeft);
|
||||||
|
mainFrameLayout->addWidget(onroadScreenBrightness, 0, 1, Qt::AlignRight);
|
||||||
|
|
||||||
|
addItem(mainFrame);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnroadScreenBrightnessControl::refresh() {
|
||||||
|
// Driving Screen Off Timer
|
||||||
|
int valTimer = std::atoi(params.get("OnroadScreenOffTimer").c_str());
|
||||||
|
std::string labelTimer = "<span style='font-size: 45px; font-weight: 450; color: #FFFFFF;'>";
|
||||||
|
labelTimer += "Delay";
|
||||||
|
labelTimer += " <br><span style='font-size: 40px; font-weight: 450; color:rgb(174, 255, 195);'>";
|
||||||
|
labelTimer += (valTimer < 60 ? std::to_string(valTimer) + "s" : std::to_string(valTimer / 60) + "m");
|
||||||
|
labelTimer += "</span></span>";
|
||||||
|
onroadScreenOffTimer->setLabel(QString::fromStdString(labelTimer));
|
||||||
|
|
||||||
|
// Driving Screen Off Brightness
|
||||||
|
std::string valBrightness = params.get("OnroadScreenOffBrightness");
|
||||||
|
std::string labelBrightness = "<span style='font-size: 45px; font-weight: 450; color: #FFFFFF;'>";
|
||||||
|
labelBrightness += "Brightness";
|
||||||
|
labelBrightness += " <br><span style='font-size: 40px; font-weight: 450; color:rgb(174, 255, 195);'>";
|
||||||
|
labelBrightness += (valBrightness == "0" ? " Screen Off" : valBrightness + "%");
|
||||||
|
labelBrightness += "</span></span>";
|
||||||
|
onroadScreenBrightness->setLabel(QString::fromStdString(labelBrightness));
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
|
||||||
|
|
||||||
|
static const QMap<QString, QString> onroadScreenOffTimerOptions = {
|
||||||
|
{"0", "15"},
|
||||||
|
{"1", "30"},
|
||||||
|
{"2", "60"},
|
||||||
|
{"3", "120"},
|
||||||
|
{"4", "180"},
|
||||||
|
{"5", "240"},
|
||||||
|
{"6", "300"},
|
||||||
|
{"7", "360"},
|
||||||
|
{"8", "420"},
|
||||||
|
{"9", "480"},
|
||||||
|
{"10", "540"},
|
||||||
|
{"11", "600"}
|
||||||
|
};
|
||||||
|
|
||||||
|
class OnroadScreenBrightnessControl : public ExpandableToggleRow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
OnroadScreenBrightnessControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
OptionControlSP *onroadScreenOffTimer;
|
||||||
|
OptionControlSP *onroadScreenBrightness;
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_panel.h"
|
||||||
|
|
||||||
|
DisplayPanel::DisplayPanel(QWidget *parent) : QWidget(parent) {
|
||||||
|
main_layout = new QStackedLayout(this);
|
||||||
|
ListWidgetSP *list = new ListWidgetSP(this, false);
|
||||||
|
|
||||||
|
sunnypilotScreen = new QWidget(this);
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen);
|
||||||
|
vlayout->setContentsMargins(50, 20, 50, 20);
|
||||||
|
|
||||||
|
// Onroad Screen Off/Brightness
|
||||||
|
onroadScreenBrightnessControl = new OnroadScreenBrightnessControl(
|
||||||
|
"OnroadScreenOffControl",
|
||||||
|
tr("Driving Screen Off: Non-Critical Events"),
|
||||||
|
tr("Turn off device screen or reduce brightness after driving starts. "
|
||||||
|
"It automatically brightens again when screen is touched or a critical event occurs."),
|
||||||
|
"",
|
||||||
|
this);
|
||||||
|
list->addItem(onroadScreenBrightnessControl);
|
||||||
|
|
||||||
|
sunnypilotScroller = new ScrollViewSP(list, this);
|
||||||
|
vlayout->addWidget(sunnypilotScroller);
|
||||||
|
main_layout->addWidget(sunnypilotScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayPanel::showEvent(QShowEvent *event) {
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayPanel::refresh() {
|
||||||
|
onroadScreenBrightnessControl->refresh();
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||||
|
|
||||||
|
class DisplayPanel : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DisplayPanel(QWidget *parent = nullptr);
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QStackedLayout* main_layout = nullptr;
|
||||||
|
QWidget* sunnypilotScreen = nullptr;
|
||||||
|
ScrollViewSP *sunnypilotScroller = nullptr;
|
||||||
|
Params params;
|
||||||
|
OnroadScreenBrightnessControl *onroadScreenBrightnessControl = nullptr;
|
||||||
|
};
|
||||||
@@ -52,12 +52,16 @@ void MadsSettings::updateToggles(bool _offroad) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
auto cp_bytes = params.get("CarParamsPersistent");
|
auto cp_bytes = params.get("CarParamsPersistent");
|
||||||
if (!cp_bytes.empty()) {
|
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
||||||
|
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
||||||
AlignedBuffer aligned_buf;
|
AlignedBuffer aligned_buf;
|
||||||
|
AlignedBuffer aligned_buf_sp;
|
||||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||||
|
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
|
||||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||||
|
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
|
||||||
|
|
||||||
if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
|
if (madsLimitedSettings(CP, CP_SP)) {
|
||||||
params.remove("MadsMainCruiseAllowed");
|
params.remove("MadsMainCruiseAllowed");
|
||||||
params.putBool("MadsUnifiedEngagementMode", true);
|
params.putBool("MadsUnifiedEngagementMode", true);
|
||||||
params.put("MadsSteeringMode", std::to_string(static_cast<int>(MadsSteeringMode::DISENGAGE)));
|
params.put("MadsSteeringMode", std::to_string(static_cast<int>(MadsSteeringMode::DISENGAGE)));
|
||||||
|
|||||||
@@ -12,7 +12,16 @@
|
|||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||||
|
|
||||||
const std::vector<std::string> mads_limited_settings_brands = {"rivian", "tesla"};
|
inline bool madsLimitedSettings(const cereal::CarParams::Reader &CP, const cereal::CarParamsSP::Reader &CP_SP) {
|
||||||
|
if (CP.getBrand() == "rivian") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (CP.getBrand() == "tesla") {
|
||||||
|
return !(CP_SP.getFlags() & 1); // 1 == TeslaFlagsSP.HAS_VEHICLE_BUS
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
enum class MadsSteeringMode {
|
enum class MadsSteeringMode {
|
||||||
REMAIN_ACTIVE = 0,
|
REMAIN_ACTIVE = 0,
|
||||||
|
|||||||
+11
-3
@@ -10,14 +10,15 @@
|
|||||||
NeuralNetworkLateralControl::NeuralNetworkLateralControl() :
|
NeuralNetworkLateralControl::NeuralNetworkLateralControl() :
|
||||||
ParamControl("NeuralNetworkLateralControl", tr("Neural Network Lateral Control (NNLC)"), "", "") {
|
ParamControl("NeuralNetworkLateralControl", tr("Neural Network Lateral Control (NNLC)"), "", "") {
|
||||||
setConfirmation(true, false);
|
setConfirmation(true, false);
|
||||||
updateToggle();
|
updateToggle(offroad);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeuralNetworkLateralControl::updateToggle() {
|
void NeuralNetworkLateralControl::updateToggle(bool _offroad) {
|
||||||
QString statusInitText = "<font color='yellow'>" + STATUS_CHECK_COMPATIBILITY + "</font>";
|
QString statusInitText = "<font color='yellow'>" + STATUS_CHECK_COMPATIBILITY + "</font>";
|
||||||
QString notLoadedText = "<font color='yellow'>" + STATUS_NOT_LOADED + "</font>";
|
QString notLoadedText = "<font color='yellow'>" + STATUS_NOT_LOADED + "</font>";
|
||||||
QString loadedText = "<font color=#00ff00>" + STATUS_LOADED + "</font>";
|
QString loadedText = "<font color=#00ff00>" + STATUS_LOADED + "</font>";
|
||||||
|
|
||||||
|
bool allowed = true;
|
||||||
auto cp_bytes = params.get("CarParamsPersistent");
|
auto cp_bytes = params.get("CarParamsPersistent");
|
||||||
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
||||||
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
||||||
@@ -31,7 +32,7 @@ void NeuralNetworkLateralControl::updateToggle() {
|
|||||||
if (CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE) {
|
if (CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE) {
|
||||||
params.remove("NeuralNetworkLateralControl");
|
params.remove("NeuralNetworkLateralControl");
|
||||||
setDescription(nnffDescriptionBuilder(STATUS_NOT_AVAILABLE));
|
setDescription(nnffDescriptionBuilder(STATUS_NOT_AVAILABLE));
|
||||||
setEnabled(false);
|
allowed = false;
|
||||||
} else {
|
} else {
|
||||||
QString nn_model_name = QString::fromStdString(CP_SP.getNeuralNetworkLateralControl().getModel().getName());
|
QString nn_model_name = QString::fromStdString(CP_SP.getNeuralNetworkLateralControl().getModel().getName());
|
||||||
QString nn_fuzzy = CP_SP.getNeuralNetworkLateralControl().getFuzzyFingerprint() ?
|
QString nn_fuzzy = CP_SP.getNeuralNetworkLateralControl().getFuzzyFingerprint() ?
|
||||||
@@ -56,4 +57,11 @@ void NeuralNetworkLateralControl::updateToggle() {
|
|||||||
if (getDescription() != getBaseDescription()) {
|
if (getDescription() != getBaseDescription()) {
|
||||||
showDescription();
|
showDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool enforce_torque_toggle = params.getBool("EnforceTorqueControl");
|
||||||
|
setEnabled(_offroad && allowed && !enforce_torque_toggle);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
offroad = _offroad;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -20,10 +20,11 @@ public:
|
|||||||
NeuralNetworkLateralControl();
|
NeuralNetworkLateralControl();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateToggle();
|
void updateToggle(bool _offroad);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Params params;
|
Params params;
|
||||||
|
bool offroad;
|
||||||
|
|
||||||
// Status messages
|
// Status messages
|
||||||
const QString STATUS_NOT_AVAILABLE = tr("NNLC is currently not available on this platform.");
|
const QString STATUS_NOT_AVAILABLE = tr("NNLC is currently not available on this platform.");
|
||||||
|
|||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_custom_params.h"
|
||||||
|
|
||||||
|
TorqueLateralControlCustomParams::TorqueLateralControlCustomParams(const QString ¶m, const QString &title, const QString &description, const QString &icon, QWidget *parent)
|
||||||
|
: ExpandableToggleRow(param, title, description, icon, parent) {
|
||||||
|
|
||||||
|
QFrame *frame = new QFrame(this);
|
||||||
|
QGridLayout *frame_layout = new QGridLayout();
|
||||||
|
frame->setLayout(frame_layout);
|
||||||
|
frame_layout->setSpacing(0);
|
||||||
|
|
||||||
|
torqueLateralControlParamsOverride = new ParamControl(
|
||||||
|
"TorqueParamsOverrideEnabled",
|
||||||
|
tr("Manual Real-Time Tuning"),
|
||||||
|
tr("Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values."),
|
||||||
|
"../assets/offroad/icon_blank.png",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
connect(torqueLateralControlParamsOverride, &ParamControl::toggleFlipped, this, &TorqueLateralControlCustomParams::refresh);
|
||||||
|
|
||||||
|
torqueParamsOverrideLatAccelFactor = new OptionControlSP("TorqueParamsOverrideLatAccelFactor", tr("Lateral Acceleration Factor"), "", "", {1, 500}, 1, false, nullptr, true, false);
|
||||||
|
connect(torqueParamsOverrideLatAccelFactor, &OptionControlSP::updateLabels, this, &TorqueLateralControlCustomParams::refresh);
|
||||||
|
torqueParamsOverrideLatAccelFactor->setFixedWidth(280);
|
||||||
|
|
||||||
|
torqueParamsOverrideFriction = new OptionControlSP("TorqueParamsOverrideFriction", tr("Friction"), "", "", {1, 100}, 1, false, nullptr, true, false);
|
||||||
|
connect(torqueParamsOverrideFriction, &OptionControlSP::updateLabels, this, &TorqueLateralControlCustomParams::refresh);
|
||||||
|
torqueParamsOverrideFriction->setFixedWidth(280);
|
||||||
|
|
||||||
|
frame_layout->addWidget(torqueLateralControlParamsOverride, 0, 0, 1, 2);
|
||||||
|
QSpacerItem *spacer = new QSpacerItem(20, 40);
|
||||||
|
frame_layout->addItem(spacer, 1, 0, 1, 2);
|
||||||
|
frame_layout->addWidget(torqueParamsOverrideLatAccelFactor, 2, 0, Qt::AlignCenter);
|
||||||
|
frame_layout->addWidget(torqueParamsOverrideFriction, 2, 1, Qt::AlignCenter);
|
||||||
|
|
||||||
|
addItem(frame);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TorqueLateralControlCustomParams::refresh() {
|
||||||
|
bool torque_override_param = params.getBool("TorqueParamsOverrideEnabled");
|
||||||
|
float laf_param = QString::fromStdString(params.get("TorqueParamsOverrideLatAccelFactor")).toFloat();
|
||||||
|
const QString laf_unit = "m/s²";
|
||||||
|
|
||||||
|
float friction_param = QString::fromStdString(params.get("TorqueParamsOverrideFriction")).toFloat();
|
||||||
|
|
||||||
|
torqueParamsOverrideLatAccelFactor->setTitle(tr("Lateral Acceleration Factor") + "\n(" + (torque_override_param ? tr("Real-time and Offline") : tr("Offline Only")) + ")");
|
||||||
|
torqueParamsOverrideFriction->setTitle(tr("Friction") + "\n(" + (torque_override_param ? tr("Real-time and Offline") : tr("Offline Only")) + ")");
|
||||||
|
|
||||||
|
torqueParamsOverrideLatAccelFactor->setLabel(QString::number(laf_param, 'f', 2) + " " + laf_unit);
|
||||||
|
torqueParamsOverrideFriction->setLabel(QString::number(friction_param, 'f', 2));
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
|
||||||
|
|
||||||
|
class TorqueLateralControlCustomParams : public ExpandableToggleRow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
TorqueLateralControlCustomParams(const QString ¶m, const QString &title, const QString &description, const QString &icon, QWidget *parent = nullptr);
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
ParamControl *torqueLateralControlParamsOverride;
|
||||||
|
OptionControlSP *torqueParamsOverrideFriction;
|
||||||
|
OptionControlSP *torqueParamsOverrideLatAccelFactor;
|
||||||
|
};
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_settings.h"
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||||
|
|
||||||
|
TorqueLateralControlSettings::TorqueLateralControlSettings(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<std::tuple<QString, QString, QString, QString>> toggle_defs{
|
||||||
|
{
|
||||||
|
"LiveTorqueParamsToggle",
|
||||||
|
tr("Self-Tune"),
|
||||||
|
tr("Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default."),
|
||||||
|
"../assets/offroad/icon_blank.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LiveTorqueParamsRelaxedToggle",
|
||||||
|
tr("Less Restrict Settings for Self-Tune (Beta)"),
|
||||||
|
tr("Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values."),
|
||||||
|
"../assets/offroad/icon_blank.png",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &[param, title, desc, icon] : toggle_defs) {
|
||||||
|
auto toggle = new ParamControlSP(param, title, desc, icon, this);
|
||||||
|
list->addItem(toggle);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
torqueLateralControlCustomParams = new TorqueLateralControlCustomParams(
|
||||||
|
"CustomTorqueParams",
|
||||||
|
tr("Enable Custom Tuning"),
|
||||||
|
tr("Enables custom tuning for Torque lateral control. Modifying Lateral Acceleration Factor and Friction below will override the offline values indicated in the YAML files within \"opendbc/car/torque_data\". "
|
||||||
|
"The values will also be used live when \"Manual Real-Time Tuning\" toggle is enabled."),
|
||||||
|
"../assets/offroad/icon_blank.png",
|
||||||
|
this);
|
||||||
|
list->addItem(torqueLateralControlCustomParams);
|
||||||
|
|
||||||
|
QObject::connect(uiState(), &UIState::offroadTransition, this, &TorqueLateralControlSettings::updateToggles);
|
||||||
|
QObject::connect(toggles["LiveTorqueParamsToggle"], &ParamControlSP::toggleFlipped, [=](bool state) {
|
||||||
|
if (!state) {
|
||||||
|
params.remove("LiveTorqueParamsRelaxedToggle");
|
||||||
|
toggles["LiveTorqueParamsRelaxedToggle"]->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateToggles(offroad);
|
||||||
|
});
|
||||||
|
|
||||||
|
main_layout->addWidget(new ScrollViewSP(list, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TorqueLateralControlSettings::showEvent(QShowEvent *event) {
|
||||||
|
updateToggles(offroad);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TorqueLateralControlSettings::updateToggles(bool _offroad) {
|
||||||
|
bool live_toggle = toggles["LiveTorqueParamsToggle"]->isToggled();
|
||||||
|
|
||||||
|
toggles["LiveTorqueParamsToggle"]->setEnabled(_offroad);
|
||||||
|
toggles["LiveTorqueParamsRelaxedToggle"]->setEnabled(_offroad && live_toggle);
|
||||||
|
|
||||||
|
torqueLateralControlCustomParams->setEnabled(_offroad);
|
||||||
|
torqueLateralControlCustomParams->refresh();
|
||||||
|
|
||||||
|
offroad = _offroad;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/util.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_custom_params.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||||
|
|
||||||
|
class TorqueLateralControlSettings : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TorqueLateralControlSettings(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void backPress();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateToggles(bool _offroad);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
bool offroad;
|
||||||
|
std::map<std::string, ParamControlSP*> toggles;
|
||||||
|
|
||||||
|
TorqueLateralControlCustomParams *torqueLateralControlCustomParams;
|
||||||
|
};
|
||||||
@@ -75,6 +75,36 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
|
|||||||
|
|
||||||
list->addItem(horizontal_line());
|
list->addItem(horizontal_line());
|
||||||
|
|
||||||
|
// Customized Torque Lateral Control
|
||||||
|
torqueLateralControlToggle = new ParamControl(
|
||||||
|
"EnforceTorqueControl",
|
||||||
|
tr("Enforce Torque Lateral Control"),
|
||||||
|
tr("Enable this to enforce sunnypilot to steer with Torque lateral control."),
|
||||||
|
"");
|
||||||
|
list->addItem(torqueLateralControlToggle);
|
||||||
|
|
||||||
|
torqueLateralControlSettingsButton = new PushButtonSP(tr("Customize Params"));
|
||||||
|
torqueLateralControlSettingsButton->setObjectName("torque_btn");
|
||||||
|
connect(torqueLateralControlSettingsButton, &QPushButton::clicked, [=]() {
|
||||||
|
sunnypilotScroller->setLastScrollPosition();
|
||||||
|
main_layout->setCurrentWidget(torqueLateralControlWidget);
|
||||||
|
});
|
||||||
|
QObject::connect(torqueLateralControlToggle, &ToggleControl::toggleFlipped, [=](bool state) {
|
||||||
|
torqueLateralControlSettingsButton->setEnabled(state);
|
||||||
|
nnlcToggle->updateToggle(offroad);
|
||||||
|
updateToggles(offroad);
|
||||||
|
});
|
||||||
|
|
||||||
|
torqueLateralControlWidget = new TorqueLateralControlSettings(this);
|
||||||
|
connect(torqueLateralControlWidget, &TorqueLateralControlSettings::backPress, [=]() {
|
||||||
|
sunnypilotScroller->restoreScrollPosition();
|
||||||
|
main_layout->setCurrentWidget(sunnypilotScreen);
|
||||||
|
});
|
||||||
|
list->addItem(torqueLateralControlSettingsButton);
|
||||||
|
|
||||||
|
list->addItem(vertical_space(0));
|
||||||
|
list->addItem(horizontal_line());
|
||||||
|
|
||||||
// Neural Network Lateral Control
|
// Neural Network Lateral Control
|
||||||
nnlcToggle = new NeuralNetworkLateralControl();
|
nnlcToggle = new NeuralNetworkLateralControl();
|
||||||
list->addItem(nnlcToggle);
|
list->addItem(nnlcToggle);
|
||||||
@@ -86,12 +116,10 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
|
|||||||
nnlcToggle->hideDescription();
|
nnlcToggle->hideDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
nnlcToggle->updateToggle();
|
nnlcToggle->updateToggle(offroad);
|
||||||
|
updateToggles(offroad);
|
||||||
});
|
});
|
||||||
|
|
||||||
toggleOffroadOnly = {
|
|
||||||
madsToggle, nnlcToggle,
|
|
||||||
};
|
|
||||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &LateralPanel::updateToggles);
|
QObject::connect(uiState(), &UIState::offroadTransition, this, &LateralPanel::updateToggles);
|
||||||
|
|
||||||
sunnypilotScroller = new ScrollViewSP(list, this);
|
sunnypilotScroller = new ScrollViewSP(list, this);
|
||||||
@@ -100,6 +128,7 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
|
|||||||
main_layout->addWidget(sunnypilotScreen);
|
main_layout->addWidget(sunnypilotScreen);
|
||||||
main_layout->addWidget(madsWidget);
|
main_layout->addWidget(madsWidget);
|
||||||
main_layout->addWidget(laneChangeWidget);
|
main_layout->addWidget(laneChangeWidget);
|
||||||
|
main_layout->addWidget(torqueLateralControlWidget);
|
||||||
|
|
||||||
setStyleSheet(R"(
|
setStyleSheet(R"(
|
||||||
#back_btn {
|
#back_btn {
|
||||||
@@ -120,7 +149,7 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LateralPanel::showEvent(QShowEvent *event) {
|
void LateralPanel::showEvent(QShowEvent *event) {
|
||||||
nnlcToggle->updateToggle();
|
nnlcToggle->updateToggle(offroad);
|
||||||
updateToggles(offroad);
|
updateToggles(offroad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,27 +158,40 @@ void LateralPanel::hideEvent(QHideEvent *event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LateralPanel::updateToggles(bool _offroad) {
|
void LateralPanel::updateToggles(bool _offroad) {
|
||||||
for (auto *toggle : toggleOffroadOnly) {
|
bool torque_allowed = true;
|
||||||
toggle->setEnabled(_offroad);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cp_bytes = params.get("CarParamsPersistent");
|
auto cp_bytes = params.get("CarParamsPersistent");
|
||||||
if (!cp_bytes.empty()) {
|
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
||||||
|
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
||||||
AlignedBuffer aligned_buf;
|
AlignedBuffer aligned_buf;
|
||||||
|
AlignedBuffer aligned_buf_sp;
|
||||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||||
|
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
|
||||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||||
|
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
|
||||||
|
|
||||||
if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
|
if (madsLimitedSettings(CP, CP_SP)) {
|
||||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY, MADS_BASE_DESC));
|
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY, MADS_BASE_DESC));
|
||||||
} else {
|
} else {
|
||||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_FULL_COMPATIBILITY, MADS_BASE_DESC));
|
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_FULL_COMPATIBILITY, MADS_BASE_DESC));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE) {
|
||||||
|
params.remove("EnforceTorqueControl");
|
||||||
|
torque_allowed = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_CHECK_COMPATIBILITY, MADS_BASE_DESC));
|
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_CHECK_COMPATIBILITY, MADS_BASE_DESC));
|
||||||
|
|
||||||
|
params.remove("EnforceTorqueControl");
|
||||||
|
torque_allowed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
madsToggle->setEnabled(_offroad);
|
||||||
madsSettingsButton->setEnabled(madsToggle->isToggled());
|
madsSettingsButton->setEnabled(madsToggle->isToggled());
|
||||||
|
|
||||||
|
torqueLateralControlToggle->setEnabled(_offroad && torque_allowed && !nnlcToggle->isToggled());
|
||||||
|
torqueLateralControlSettingsButton->setEnabled(torqueLateralControlToggle->isToggled());
|
||||||
|
|
||||||
blinkerPauseLateralSettings->refresh();
|
blinkerPauseLateralSettings->refresh();
|
||||||
|
|
||||||
offroad = _offroad;
|
offroad = _offroad;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h"
|
||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h"
|
||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_settings.h"
|
||||||
#include "selfdrive/ui/qt/util.h"
|
#include "selfdrive/ui/qt/util.h"
|
||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||||
@@ -35,7 +36,6 @@ private:
|
|||||||
QStackedLayout* main_layout = nullptr;
|
QStackedLayout* main_layout = nullptr;
|
||||||
QWidget* sunnypilotScreen = nullptr;
|
QWidget* sunnypilotScreen = nullptr;
|
||||||
ScrollViewSP *sunnypilotScroller = nullptr;
|
ScrollViewSP *sunnypilotScroller = nullptr;
|
||||||
std::vector<ParamControl *> toggleOffroadOnly;
|
|
||||||
bool offroad;
|
bool offroad;
|
||||||
|
|
||||||
ParamControl *madsToggle;
|
ParamControl *madsToggle;
|
||||||
@@ -45,6 +45,9 @@ private:
|
|||||||
LaneChangeSettings *laneChangeWidget = nullptr;
|
LaneChangeSettings *laneChangeWidget = nullptr;
|
||||||
NeuralNetworkLateralControl *nnlcToggle = nullptr;
|
NeuralNetworkLateralControl *nnlcToggle = nullptr;
|
||||||
BlinkerPauseLateralSettings *blinkerPauseLateralSettings = nullptr;
|
BlinkerPauseLateralSettings *blinkerPauseLateralSettings = nullptr;
|
||||||
|
ParamControl *torqueLateralControlToggle;
|
||||||
|
PushButtonSP *torqueLateralControlSettingsButton;
|
||||||
|
TorqueLateralControlSettings *torqueLateralControlWidget = nullptr;
|
||||||
|
|
||||||
const QString MADS_BASE_DESC = tr("Enables independent engagements of Automatic Lane Centering (ALC) and Adaptive Cruise Control (ACC).");
|
const QString MADS_BASE_DESC = tr("Enables independent engagements of Automatic Lane Centering (ALC) and Adaptive Cruise Control (ACC).");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
enum class SpeedLimitOffsetType {
|
||||||
|
NONE,
|
||||||
|
FIXED,
|
||||||
|
PERCENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const QString SpeedLimitOffsetTypeTexts[]{
|
||||||
|
QObject::tr("None"),
|
||||||
|
QObject::tr("Fixed"),
|
||||||
|
QObject::tr("Percent"),
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SpeedLimitSourcePolicy {
|
||||||
|
CAR_ONLY,
|
||||||
|
MAP_ONLY,
|
||||||
|
CAR_FIRST,
|
||||||
|
MAP_FIRST,
|
||||||
|
COMBINED,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const QString SpeedLimitSourcePolicyTexts[]{
|
||||||
|
QObject::tr("Car\nOnly"),
|
||||||
|
QObject::tr("Map\nOnly"),
|
||||||
|
QObject::tr("Car\nFirst"),
|
||||||
|
QObject::tr("Map\nFirst"),
|
||||||
|
QObject::tr("Combined\nData")
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SpeedLimitMode {
|
||||||
|
OFF,
|
||||||
|
INFORMATION,
|
||||||
|
WARNING,
|
||||||
|
ASSIST,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const QString SpeedLimitModeTexts[]{
|
||||||
|
QObject::tr("Off"),
|
||||||
|
QObject::tr("Information"),
|
||||||
|
QObject::tr("Warning"),
|
||||||
|
QObject::tr("Assist"),
|
||||||
|
};
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.h"
|
||||||
|
|
||||||
|
SpeedLimitPolicy::SpeedLimitPolicy(QWidget *parent) : QWidget(parent) {
|
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||||
|
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
main_layout->setSpacing(0);
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
PanelBackButton *back = new PanelBackButton(tr("Back"));
|
||||||
|
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
|
||||||
|
main_layout->addWidget(back, 0, Qt::AlignLeft);
|
||||||
|
|
||||||
|
main_layout->addSpacing(10);
|
||||||
|
|
||||||
|
ListWidgetSP *list = new ListWidgetSP(this);
|
||||||
|
|
||||||
|
std::vector<QString> speed_limit_policy_texts{
|
||||||
|
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)],
|
||||||
|
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)],
|
||||||
|
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)],
|
||||||
|
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)],
|
||||||
|
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)]
|
||||||
|
};
|
||||||
|
speed_limit_policy = new ButtonParamControlSP(
|
||||||
|
"SpeedLimitPolicy",
|
||||||
|
tr("Speed Limit Source"),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
speed_limit_policy_texts,
|
||||||
|
250);
|
||||||
|
list->addItem(speed_limit_policy);
|
||||||
|
connect(speed_limit_policy, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitPolicy::refresh);
|
||||||
|
|
||||||
|
speedLimitPolicyScroller = new ScrollViewSP(list, this);
|
||||||
|
main_layout->addWidget(speedLimitPolicyScroller);
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
void SpeedLimitPolicy::refresh() {
|
||||||
|
SpeedLimitSourcePolicy policy_param = static_cast<SpeedLimitSourcePolicy>(std::atoi(params.get("SpeedLimitPolicy").c_str()));
|
||||||
|
speed_limit_policy->setDescription(sourceDescription(policy_param));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpeedLimitPolicy::showEvent(QShowEvent *event) {
|
||||||
|
speedLimitPolicyScroller->verticalScrollBar()->setValue(0);
|
||||||
|
refresh();
|
||||||
|
speed_limit_policy->showDescription();
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||||
|
|
||||||
|
class SpeedLimitPolicy : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SpeedLimitPolicy(QWidget *parent = nullptr);
|
||||||
|
void refresh();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void backPress();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
ButtonParamControlSP *speed_limit_policy;
|
||||||
|
ScrollViewSP *speedLimitPolicyScroller;
|
||||||
|
|
||||||
|
static QString sourceDescription(SpeedLimitSourcePolicy type = SpeedLimitSourcePolicy::CAR_ONLY) {
|
||||||
|
QString car_only = tr("⦿ Car Only: Use Speed Limit data only from Car");
|
||||||
|
QString map_only = tr("⦿ Map Only: Use Speed Limit data only from OpenStreetMaps");
|
||||||
|
QString car_first = tr("⦿ Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps");
|
||||||
|
QString map_first = tr("⦿ Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car");
|
||||||
|
QString combined = tr("⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps");
|
||||||
|
|
||||||
|
if (type == SpeedLimitSourcePolicy::CAR_ONLY) {
|
||||||
|
car_only = "<font color='white'><b>" + car_only + "</b></font>";
|
||||||
|
} else if (type == SpeedLimitSourcePolicy::MAP_ONLY) {
|
||||||
|
map_only = "<font color='white'><b>" + map_only + "</b></font>";
|
||||||
|
} else if (type == SpeedLimitSourcePolicy::CAR_FIRST) {
|
||||||
|
car_first = "<font color='white'><b>" + car_first + "</b></font>";
|
||||||
|
} else if (type == SpeedLimitSourcePolicy::MAP_FIRST) {
|
||||||
|
map_first = "<font color='white'><b>" + map_first + "</b></font>";
|
||||||
|
} else {
|
||||||
|
combined = "<font color='white'><b>" + combined + "</b></font>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("%1<br>%2<br>%3<br>%4<br>%5")
|
||||||
|
.arg(car_only)
|
||||||
|
.arg(map_only)
|
||||||
|
.arg(car_first)
|
||||||
|
.arg(map_first)
|
||||||
|
.arg(combined);
|
||||||
|
}
|
||||||
|
};
|
||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h"
|
||||||
|
|
||||||
|
SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) {
|
||||||
|
subPanelFrame = new QFrame();
|
||||||
|
QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame);
|
||||||
|
subPanelLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
subPanelLayout->setSpacing(0);
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
PanelBackButton *back = new PanelBackButton(tr("Back"));
|
||||||
|
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
|
||||||
|
subPanelLayout->addWidget(back, 0, Qt::AlignLeft);
|
||||||
|
|
||||||
|
subPanelLayout->addSpacing(20);
|
||||||
|
|
||||||
|
ListWidgetSP *list = new ListWidgetSP(this, false);
|
||||||
|
|
||||||
|
speedLimitPolicyScreen = new SpeedLimitPolicy(this);
|
||||||
|
|
||||||
|
std::vector<QString> speed_limit_mode_texts{
|
||||||
|
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)],
|
||||||
|
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)],
|
||||||
|
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)],
|
||||||
|
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)],
|
||||||
|
};
|
||||||
|
speed_limit_mode_settings = new ButtonParamControlSP(
|
||||||
|
"SpeedLimitMode",
|
||||||
|
tr("Speed Limit"),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
speed_limit_mode_texts,
|
||||||
|
380);
|
||||||
|
list->addItem(speed_limit_mode_settings);
|
||||||
|
|
||||||
|
list->addItem(horizontal_line());
|
||||||
|
list->addItem(vertical_space());
|
||||||
|
|
||||||
|
speedLimitSource = new PushButtonSP(tr("Customize Source"));
|
||||||
|
connect(speedLimitSource, &QPushButton::clicked, [&]() {
|
||||||
|
speedLimitScroller->setLastScrollPosition();
|
||||||
|
setCurrentWidget(speedLimitPolicyScreen);
|
||||||
|
speedLimitPolicyScreen->refresh();
|
||||||
|
});
|
||||||
|
connect(speedLimitPolicyScreen, &SpeedLimitPolicy::backPress, [&]() {
|
||||||
|
speedLimitScroller->restoreScrollPosition();
|
||||||
|
setCurrentWidget(subPanelFrame);
|
||||||
|
showEvent(new QShowEvent());
|
||||||
|
});
|
||||||
|
|
||||||
|
speedLimitSource->setFixedWidth(720);
|
||||||
|
list->addItem(speedLimitSource);
|
||||||
|
|
||||||
|
list->addItem(vertical_space(0));
|
||||||
|
list->addItem(horizontal_line());
|
||||||
|
|
||||||
|
QFrame *offsetFrame = new QFrame(this);
|
||||||
|
QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame);
|
||||||
|
|
||||||
|
std::vector<QString> speed_limit_offset_texts{
|
||||||
|
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)],
|
||||||
|
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)],
|
||||||
|
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)]
|
||||||
|
};
|
||||||
|
speed_limit_offset_settings = new ButtonParamControlSP(
|
||||||
|
"SpeedLimitOffsetType",
|
||||||
|
tr("Speed Limit Offset"),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
speed_limit_offset_texts,
|
||||||
|
500);
|
||||||
|
|
||||||
|
offsetLayout->addWidget(speed_limit_offset_settings);
|
||||||
|
|
||||||
|
speed_limit_offset = new OptionControlSP(
|
||||||
|
"SpeedLimitValueOffset",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
{-30, 30}
|
||||||
|
);
|
||||||
|
offsetLayout->addWidget(speed_limit_offset);
|
||||||
|
|
||||||
|
list->addItem(offsetFrame);
|
||||||
|
|
||||||
|
connect(speed_limit_mode_settings, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitSettings::refresh);
|
||||||
|
connect(speed_limit_offset, &OptionControlSP::updateLabels, this, &SpeedLimitSettings::refresh);
|
||||||
|
connect(speed_limit_offset_settings, &ButtonParamControlSP::showDescriptionEvent, speed_limit_offset, &OptionControlSP::showDescription);
|
||||||
|
connect(speed_limit_offset_settings, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitSettings::refresh);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
speedLimitScroller = new ScrollViewSP(list, this);
|
||||||
|
subPanelLayout->addWidget(speedLimitScroller);
|
||||||
|
addWidget(subPanelFrame);
|
||||||
|
addWidget(speedLimitPolicyScreen);
|
||||||
|
setCurrentWidget(subPanelFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpeedLimitSettings::refresh() {
|
||||||
|
bool is_metric_param = params.getBool("IsMetric");
|
||||||
|
SpeedLimitMode speed_limit_mode_param = static_cast<SpeedLimitMode>(std::atoi(params.get("SpeedLimitMode").c_str()));
|
||||||
|
SpeedLimitOffsetType offset_type_param = static_cast<SpeedLimitOffsetType>(std::atoi(params.get("SpeedLimitOffsetType").c_str()));
|
||||||
|
QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset"));
|
||||||
|
|
||||||
|
bool has_longitudinal_control;
|
||||||
|
bool intelligent_cruise_button_management_available;
|
||||||
|
auto cp_bytes = params.get("CarParamsPersistent");
|
||||||
|
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
||||||
|
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
||||||
|
AlignedBuffer aligned_buf;
|
||||||
|
AlignedBuffer aligned_buf_sp;
|
||||||
|
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||||
|
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
|
||||||
|
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||||
|
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
|
||||||
|
|
||||||
|
has_longitudinal_control = hasLongitudinalControl(CP);
|
||||||
|
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
|
||||||
|
} else {
|
||||||
|
has_longitudinal_control = false;
|
||||||
|
intelligent_cruise_button_management_available = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param));
|
||||||
|
speed_limit_offset->setDescription(offsetDescription(offset_type_param));
|
||||||
|
|
||||||
|
if (offset_type_param == SpeedLimitOffsetType::PERCENT) {
|
||||||
|
offsetLabel += "%";
|
||||||
|
} else if (offset_type_param == SpeedLimitOffsetType::FIXED) {
|
||||||
|
offsetLabel += QString(" %1").arg(is_metric_param ? "km/h" : "mph");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset_type_param == SpeedLimitOffsetType::NONE) {
|
||||||
|
speed_limit_offset->setVisible(false);
|
||||||
|
} else {
|
||||||
|
speed_limit_offset->setVisible(true);
|
||||||
|
speed_limit_offset->setLabel(offsetLabel);
|
||||||
|
speed_limit_offset->showDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
|
||||||
|
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues()));
|
||||||
|
} else {
|
||||||
|
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(
|
||||||
|
{SpeedLimitMode::OFF,SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
|
||||||
|
}
|
||||||
|
|
||||||
|
speed_limit_mode_settings->showDescription();
|
||||||
|
speed_limit_offset->showDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpeedLimitSettings::showEvent(QShowEvent *event) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||||
|
*
|
||||||
|
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
|
* See the LICENSE.md file in the root directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||||
|
|
||||||
|
class SpeedLimitSettings : public QStackedWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
SpeedLimitSettings(QWidget *parent = nullptr);
|
||||||
|
void refresh();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void backPress();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
ScrollViewSP *speedLimitScroller;
|
||||||
|
QFrame *subPanelFrame;
|
||||||
|
ButtonParamControlSP *speed_limit_mode_settings;
|
||||||
|
PushButtonSP *speedLimitSource;
|
||||||
|
SpeedLimitPolicy *speedLimitPolicyScreen;
|
||||||
|
ButtonParamControlSP *speed_limit_offset_settings;
|
||||||
|
OptionControlSP *speed_limit_offset;
|
||||||
|
|
||||||
|
static QString offsetDescription(SpeedLimitOffsetType type = SpeedLimitOffsetType::NONE) {
|
||||||
|
QString none_str = tr("⦿ None: No Offset");
|
||||||
|
QString fixed_str = tr("⦿ Fixed: Adds a fixed offset [Speed Limit + Offset]");
|
||||||
|
QString percent_str = tr("⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]");
|
||||||
|
|
||||||
|
if (type == SpeedLimitOffsetType::FIXED) {
|
||||||
|
fixed_str = "<font color='white'><b>" + fixed_str + "</b></font>";
|
||||||
|
} else if (type == SpeedLimitOffsetType::PERCENT) {
|
||||||
|
percent_str = "<font color='white'><b>" + percent_str + "</b></font>";
|
||||||
|
} else {
|
||||||
|
none_str = "<font color='white'><b>" + none_str + "</b></font>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("%1<br>%2<br>%3")
|
||||||
|
.arg(none_str)
|
||||||
|
.arg(fixed_str)
|
||||||
|
.arg(percent_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString modeDescription(SpeedLimitMode mode = SpeedLimitMode::OFF) {
|
||||||
|
QString off_str = tr("⦿ Off: Disables the Speed Limit functions.");
|
||||||
|
QString info_str = tr("⦿ Information: Displays the current road's speed limit.");
|
||||||
|
QString warning_str = tr("⦿ Warning: Provides a warning when exceeding the current road's speed limit.");
|
||||||
|
QString assist_str = tr("⦿ Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons.");
|
||||||
|
|
||||||
|
if (mode == SpeedLimitMode::ASSIST) {
|
||||||
|
assist_str = "<font color='white'><b>" + assist_str + "</b></font>";
|
||||||
|
} else if (mode == SpeedLimitMode::WARNING) {
|
||||||
|
warning_str = "<font color='white'><b>" + warning_str + "</b></font>";
|
||||||
|
} else if (mode == SpeedLimitMode::INFORMATION) {
|
||||||
|
info_str = "<font color='white'><b>" + info_str + "</b></font>";
|
||||||
|
} else {
|
||||||
|
off_str = "<font color='white'><b>" + off_str + "</b></font>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("%1<br>%2<br>%3<br>%4")
|
||||||
|
.arg(off_str)
|
||||||
|
.arg(info_str)
|
||||||
|
.arg(warning_str)
|
||||||
|
.arg(assist_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<SpeedLimitMode> getSpeedLimitModeValues() {
|
||||||
|
std::vector<SpeedLimitMode> values;
|
||||||
|
for (int i = static_cast<int>(SpeedLimitMode::OFF);
|
||||||
|
i <= static_cast<int>(SpeedLimitMode::ASSIST); ++i) {
|
||||||
|
values.push_back(static_cast<SpeedLimitMode>(i));
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<int> convertSpeedLimitModeValues(const std::vector<SpeedLimitMode> &modes) {
|
||||||
|
std::vector<int> values;
|
||||||
|
for (const auto& mode : modes) {
|
||||||
|
values.push_back(static_cast<int>(mode));
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,6 +8,21 @@
|
|||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
|
||||||
|
|
||||||
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
||||||
|
setStyleSheet(R"(
|
||||||
|
#back_btn {
|
||||||
|
font-size: 50px;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 15px;
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 30px;
|
||||||
|
color: #dddddd;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
#back_btn:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
|
||||||
main_layout = new QStackedLayout(this);
|
main_layout = new QStackedLayout(this);
|
||||||
ListWidget *list = new ListWidget(this, false);
|
ListWidget *list = new ListWidget(this, false);
|
||||||
|
|
||||||
@@ -18,12 +33,50 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
|||||||
cruisePanelScroller = new ScrollViewSP(list, this);
|
cruisePanelScroller = new ScrollViewSP(list, this);
|
||||||
vlayout->addWidget(cruisePanelScroller);
|
vlayout->addWidget(cruisePanelScroller);
|
||||||
|
|
||||||
|
intelligentCruiseButtonManagement = new ParamControlSP(
|
||||||
|
"IntelligentCruiseButtonManagement",
|
||||||
|
tr("Intelligent Cruise Button Management (ICBM) (Alpha)"),
|
||||||
|
tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."),
|
||||||
|
"",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
intelligentCruiseButtonManagement->setConfirmation(true, false);
|
||||||
|
list->addItem(intelligentCruiseButtonManagement);
|
||||||
|
|
||||||
|
SmartCruiseControlVision = new ParamControl(
|
||||||
|
"SmartCruiseControlVision",
|
||||||
|
tr("Smart Cruise Control - Vision"),
|
||||||
|
tr("Use vision path predictions to estimate the appropriate speed to drive through turns ahead."),
|
||||||
|
"");
|
||||||
|
list->addItem(SmartCruiseControlVision);
|
||||||
|
|
||||||
|
SmartCruiseControlMap = new ParamControl(
|
||||||
|
"SmartCruiseControlMap",
|
||||||
|
tr("Smart Cruise Control - Map"),
|
||||||
|
tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
|
||||||
|
"");
|
||||||
|
list->addItem(SmartCruiseControlMap);
|
||||||
|
|
||||||
customAccIncrement = new CustomAccIncrement("CustomAccIncrementsEnabled", tr("Custom ACC Speed Increments"), "", "", this);
|
customAccIncrement = new CustomAccIncrement("CustomAccIncrementsEnabled", tr("Custom ACC Speed Increments"), "", "", this);
|
||||||
list->addItem(customAccIncrement);
|
list->addItem(customAccIncrement);
|
||||||
|
|
||||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
|
QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
|
||||||
|
|
||||||
|
speedLimitSettings = new PushButtonSP(tr("Speed Limit"), 750, this);
|
||||||
|
connect(speedLimitSettings, &QPushButton::clicked, [&]() {
|
||||||
|
cruisePanelScroller->setLastScrollPosition();
|
||||||
|
main_layout->setCurrentWidget(speedLimitScreen);
|
||||||
|
});
|
||||||
|
list->addItem(speedLimitSettings);
|
||||||
|
|
||||||
|
speedLimitScreen = new SpeedLimitSettings(this);
|
||||||
|
connect(speedLimitScreen, &SpeedLimitSettings::backPress, [=]() {
|
||||||
|
cruisePanelScroller->restoreScrollPosition();
|
||||||
|
main_layout->setCurrentWidget(cruisePanelScreen);
|
||||||
|
});
|
||||||
|
|
||||||
main_layout->addWidget(cruisePanelScreen);
|
main_layout->addWidget(cruisePanelScreen);
|
||||||
|
main_layout->addWidget(speedLimitScreen);
|
||||||
main_layout->setCurrentWidget(cruisePanelScreen);
|
main_layout->setCurrentWidget(cruisePanelScreen);
|
||||||
refresh(offroad);
|
refresh(offroad);
|
||||||
}
|
}
|
||||||
@@ -35,16 +88,22 @@ void LongitudinalPanel::showEvent(QShowEvent *event) {
|
|||||||
|
|
||||||
void LongitudinalPanel::refresh(bool _offroad) {
|
void LongitudinalPanel::refresh(bool _offroad) {
|
||||||
auto cp_bytes = params.get("CarParamsPersistent");
|
auto cp_bytes = params.get("CarParamsPersistent");
|
||||||
if (!cp_bytes.empty()) {
|
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
||||||
|
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
||||||
AlignedBuffer aligned_buf;
|
AlignedBuffer aligned_buf;
|
||||||
|
AlignedBuffer aligned_buf_sp;
|
||||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||||
|
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
|
||||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||||
|
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
|
||||||
|
|
||||||
has_longitudinal_control = hasLongitudinalControl(CP);
|
has_longitudinal_control = hasLongitudinalControl(CP);
|
||||||
is_pcm_cruise = CP.getPcmCruise();
|
is_pcm_cruise = CP.getPcmCruise();
|
||||||
|
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
|
||||||
} else {
|
} else {
|
||||||
has_longitudinal_control = false;
|
has_longitudinal_control = false;
|
||||||
is_pcm_cruise = false;
|
is_pcm_cruise = false;
|
||||||
|
intelligent_cruise_button_management_available = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
|
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
|
||||||
@@ -56,7 +115,7 @@ void LongitudinalPanel::refresh(bool _offroad) {
|
|||||||
customAccIncrement->setDescription(onroadOnlyDescription);
|
customAccIncrement->setDescription(onroadOnlyDescription);
|
||||||
customAccIncrement->showDescription();
|
customAccIncrement->showDescription();
|
||||||
} else {
|
} else {
|
||||||
if (has_longitudinal_control) {
|
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
|
||||||
if (is_pcm_cruise) {
|
if (is_pcm_cruise) {
|
||||||
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
|
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
|
||||||
customAccIncrement->showDescription();
|
customAccIncrement->showDescription();
|
||||||
@@ -68,12 +127,21 @@ void LongitudinalPanel::refresh(bool _offroad) {
|
|||||||
customAccIncrement->toggleFlipped(false);
|
customAccIncrement->toggleFlipped(false);
|
||||||
customAccIncrement->setDescription(accNoLongDescription);
|
customAccIncrement->setDescription(accNoLongDescription);
|
||||||
customAccIncrement->showDescription();
|
customAccIncrement->showDescription();
|
||||||
|
params.remove("IntelligentCruiseButtonManagement");
|
||||||
|
intelligentCruiseButtonManagement->toggleFlipped(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool icbm_allowed = intelligent_cruise_button_management_available && !has_longitudinal_control;
|
||||||
|
intelligentCruiseButtonManagement->setEnabled(icbm_allowed && offroad);
|
||||||
|
|
||||||
// enable toggle when long is available and is not PCM cruise
|
// enable toggle when long is available and is not PCM cruise
|
||||||
customAccIncrement->setEnabled(has_longitudinal_control && !is_pcm_cruise && !offroad);
|
bool cai_allowed = (has_longitudinal_control && !is_pcm_cruise) || icbm_allowed;
|
||||||
|
customAccIncrement->setEnabled(cai_allowed && !offroad);
|
||||||
customAccIncrement->refresh();
|
customAccIncrement->refresh();
|
||||||
|
|
||||||
|
SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed);
|
||||||
|
SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed);
|
||||||
|
|
||||||
offroad = _offroad;
|
offroad = _offroad;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
|
||||||
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h"
|
||||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||||
|
|
||||||
@@ -23,10 +24,16 @@ private:
|
|||||||
Params params;
|
Params params;
|
||||||
bool has_longitudinal_control = false;
|
bool has_longitudinal_control = false;
|
||||||
bool is_pcm_cruise = false;
|
bool is_pcm_cruise = false;
|
||||||
|
bool intelligent_cruise_button_management_available = false;;
|
||||||
bool offroad = false;
|
bool offroad = false;
|
||||||
|
|
||||||
QStackedLayout *main_layout = nullptr;
|
QStackedLayout *main_layout = nullptr;
|
||||||
ScrollViewSP *cruisePanelScroller = nullptr;
|
ScrollViewSP *cruisePanelScroller = nullptr;
|
||||||
QWidget *cruisePanelScreen = nullptr;
|
QWidget *cruisePanelScreen = nullptr;
|
||||||
CustomAccIncrement *customAccIncrement = nullptr;
|
CustomAccIncrement *customAccIncrement = nullptr;
|
||||||
|
ParamControl *SmartCruiseControlVision;
|
||||||
|
ParamControl *SmartCruiseControlMap;
|
||||||
|
ParamControl *intelligentCruiseButtonManagement = nullptr;
|
||||||
|
SpeedLimitSettings *speedLimitScreen;
|
||||||
|
PushButtonSP *speedLimitSettings;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,32 +101,42 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
|
|||||||
QString policyType = tr("Policy Model");
|
QString policyType = tr("Policy Model");
|
||||||
policyFrame = createModelDetailFrame(this, policyType, policyProgressBar);
|
policyFrame = createModelDetailFrame(this, policyType, policyProgressBar);
|
||||||
list->addItem(policyFrame);
|
list->addItem(policyFrame);
|
||||||
|
|
||||||
list->addItem(horizontal_line());
|
list->addItem(horizontal_line());
|
||||||
|
|
||||||
|
// Lane Turn Desire toggle
|
||||||
|
lane_turn_desire_toggle = new ParamControlSP("LaneTurnDesire", tr("Use Lane Turn Desires"),
|
||||||
|
"If you’re driving at 20 mph (32 km/h) or below and have your blinker on, "
|
||||||
|
"the car will plan a turn in that direction at the nearest drivable path. "
|
||||||
|
"This prevents situations (like at red lights) where the car might plan the wrong turn direction.",
|
||||||
|
"../assets/offroad/icon_shell.png");
|
||||||
|
list->addItem(lane_turn_desire_toggle);
|
||||||
|
|
||||||
|
// Lane Turn Value control
|
||||||
|
int max_value_mph = 20;
|
||||||
|
bool is_metric_initial = params.getBool("IsMetric");
|
||||||
|
const float K = 1.609344f;
|
||||||
|
int per_value_change_scaled = is_metric_initial ? static_cast<int>(std::round((1.0f / K) * 100.0f)) : 100; // 100 -> 1 mph
|
||||||
|
lane_turn_value_control = new OptionControlSP("LaneTurnValue", tr("Adjust Lane Turn Speed"),
|
||||||
|
tr("Set the maximum speed for lane turn desires. Default is 19 %1.").arg(is_metric_initial ? "km/h" : "mph"),
|
||||||
|
"", {5 * 100, max_value_mph * 100}, per_value_change_scaled, false, nullptr, true, true);
|
||||||
|
lane_turn_value_control->showDescription();
|
||||||
|
list->addItem(lane_turn_value_control);
|
||||||
|
|
||||||
|
// Show based on toggle
|
||||||
|
refreshLaneTurnValueControl();
|
||||||
|
connect(lane_turn_desire_toggle, &ParamControlSP::toggleFlipped, this, &ModelsPanel::refreshLaneTurnValueControl);
|
||||||
|
connect(lane_turn_value_control, &OptionControlSP::updateLabels, this, &ModelsPanel::refreshLaneTurnValueControl);
|
||||||
|
|
||||||
// LiveDelay toggle
|
// LiveDelay toggle
|
||||||
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
|
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
|
||||||
lagd_toggle_control->showDescription();
|
lagd_toggle_control->showDescription();
|
||||||
list->addItem(lagd_toggle_control);
|
list->addItem(lagd_toggle_control);
|
||||||
|
|
||||||
// Software delay control
|
// Software delay control
|
||||||
int liveDelayMaxInt = 30;
|
|
||||||
std::string liveDelayBytes = params.get("LiveDelay");
|
|
||||||
if (!liveDelayBytes.empty()) {
|
|
||||||
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
|
|
||||||
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
|
|
||||||
liveDelayBytes.size() / sizeof(capnp::word)));
|
|
||||||
auto event = msg.getRoot<cereal::Event>();
|
|
||||||
if (event.hasLiveDelay()) {
|
|
||||||
auto liveDelay = event.getLiveDelay();
|
|
||||||
float lateralDelay = liveDelay.getLateralDelay();
|
|
||||||
liveDelayMaxInt = static_cast<int>(lateralDelay * 100.0f) + 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay_control = new OptionControlSP("LagdToggleDelay", tr("Adjust Software Delay"),
|
delay_control = new OptionControlSP("LagdToggleDelay", tr("Adjust Software Delay"),
|
||||||
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
|
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
|
||||||
"\nThe default software delay value is 0.2"),
|
"\nThe default software delay value is 0.2"),
|
||||||
"", {5, liveDelayMaxInt}, 1, false, nullptr, true, true);
|
"", {5, 50}, 1, false, nullptr, true, true);
|
||||||
|
|
||||||
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
|
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
|
||||||
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
||||||
@@ -159,6 +169,19 @@ QFrame* ModelsPanel::createModelDetailFrame(QWidget *parent, QString &typeName,
|
|||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModelsPanel::refreshLaneTurnValueControl() {
|
||||||
|
if (!lane_turn_value_control) return;
|
||||||
|
float stored_mph = QString::fromStdString(params.get("LaneTurnValue")).toFloat();
|
||||||
|
bool is_metric = params.getBool("IsMetric");
|
||||||
|
QString unit = is_metric ? "km/h" : "mph";
|
||||||
|
float display_value = stored_mph;
|
||||||
|
if (is_metric) {
|
||||||
|
display_value = stored_mph * 1.609344f;
|
||||||
|
}
|
||||||
|
lane_turn_value_control->setLabel(QString::number(static_cast<int>(std::round(display_value))) + " " + unit);
|
||||||
|
lane_turn_value_control->setVisible(params.getBool("LaneTurnDesire"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates the UI with bundle download progress information
|
* @brief Updates the UI with bundle download progress information
|
||||||
* Reads status from modelManagerSP cereal message and displays status for all models
|
* Reads status from modelManagerSP cereal message and displays status for all models
|
||||||
@@ -259,6 +282,18 @@ QString ModelsPanel::GetActiveModelInternalName() {
|
|||||||
return DEFAULT_MODEL;
|
return DEFAULT_MODEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the ref of the currently selected model bundle
|
||||||
|
* @return ref of the selected bundle or default model name
|
||||||
|
*/
|
||||||
|
QString ModelsPanel::GetActiveModelRef() {
|
||||||
|
if (model_manager.hasActiveBundle()) {
|
||||||
|
return QString::fromStdString(model_manager.getActiveBundle().getRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
void ModelsPanel::updateModelManagerState() {
|
void ModelsPanel::updateModelManagerState() {
|
||||||
const SubMaster &sm = *(uiStateSP()->sm);
|
const SubMaster &sm = *(uiStateSP()->sm);
|
||||||
model_manager = sm["modelManagerSP"].getModelManagerSP();
|
model_manager = sm["modelManagerSP"].getModelManagerSP();
|
||||||
@@ -272,34 +307,31 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
|
|||||||
currentModelLblBtn->setEnabled(false);
|
currentModelLblBtn->setEnabled(false);
|
||||||
currentModelLblBtn->setValue(tr("Fetching models..."));
|
currentModelLblBtn->setValue(tr("Fetching models..."));
|
||||||
|
|
||||||
struct ModelEntry {
|
QList<TreeNode> sortedModels;
|
||||||
QString folder;
|
|
||||||
QString displayName;
|
|
||||||
int index;
|
|
||||||
};
|
|
||||||
QList<ModelEntry> sortedModels;
|
|
||||||
QSet<QString> modelFolders;
|
QSet<QString> modelFolders;
|
||||||
|
QRegularExpression re("\\(([^)]*)\\)[^(]*$");
|
||||||
const auto bundles = model_manager.getAvailableBundles();
|
const auto bundles = model_manager.getAvailableBundles();
|
||||||
|
|
||||||
for (const auto &bundle : bundles) {
|
for (const auto &bundle : bundles) {
|
||||||
auto overrides = bundle.getOverrides();
|
auto overrides = bundle.getOverrides();
|
||||||
QString gen;
|
QString folder;
|
||||||
for (const auto &override : overrides) {
|
for (const auto &override : overrides) {
|
||||||
if (override.getKey() == "folder") {
|
if (override.getKey() == "folder") {
|
||||||
gen = QString::fromStdString(override.getValue().cStr());
|
folder = QString::fromStdString(override.getValue().cStr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modelFolders.insert(gen);
|
modelFolders.insert(folder);
|
||||||
sortedModels.append(ModelEntry{
|
sortedModels.append(TreeNode{
|
||||||
gen,
|
folder,
|
||||||
QString::fromStdString(bundle.getDisplayName()),
|
QString::fromStdString(bundle.getDisplayName()),
|
||||||
|
QString::fromStdString(bundle.getRef()),
|
||||||
static_cast<int>(bundle.getIndex())
|
static_cast<int>(bundle.getIndex())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(sortedModels.begin(), sortedModels.end(),
|
std::sort(sortedModels.begin(), sortedModels.end(),
|
||||||
[](const ModelEntry &a, const ModelEntry &b) {
|
[](const TreeNode &a, const TreeNode &b) {
|
||||||
return a.index > b.index;
|
return a.index > b.index;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -322,37 +354,46 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create the final items list using sorted folders
|
// Create the final items list using sorted folders
|
||||||
QList<QPair<QString, QStringList>> items;
|
QList<TreeFolder> items;
|
||||||
for (const auto &folderPair : folderMaxIndices) {
|
for (const auto &folderPair : folderMaxIndices) {
|
||||||
QStringList folderModels;
|
QList<TreeNode> folderModels;
|
||||||
|
QString folder = folderPair.first;
|
||||||
for (const auto &model : sortedModels) {
|
for (const auto &model : sortedModels) {
|
||||||
if (model.folder == folderPair.first) {
|
if (model.folder == folderPair.first) {
|
||||||
folderModels.append(model.displayName);
|
if (model.index == folderPair.second) {
|
||||||
|
QRegularExpressionMatch match = re.match(model.displayName);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
folder.append(" - (Updated: ").append(match.captured(1)).append(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
folderModels.append(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.append(qMakePair(folderPair.first, folderModels));
|
items.append(TreeFolder{folder, folderModels});
|
||||||
}
|
}
|
||||||
|
|
||||||
items.insert(0, qMakePair(QString(""), QStringList{DEFAULT_MODEL}));
|
items.insert(0, TreeFolder{"", {
|
||||||
|
TreeNode{"", DEFAULT_MODEL, DEFAULT_MODEL, -1}
|
||||||
|
}});
|
||||||
|
|
||||||
currentModelLblBtn->setValue(GetActiveModelInternalName());
|
currentModelLblBtn->setValue(GetActiveModelInternalName());
|
||||||
|
|
||||||
const QString selectedBundleName = TreeOptionDialog::getSelection(
|
const QString selectedBundleRef = TreeOptionDialog::getSelection(
|
||||||
tr("Select a Model"), items, GetActiveModelName(), this);
|
tr("Select a Model"), items, GetActiveModelRef(), QString("ModelManager_Favs"), this);
|
||||||
|
|
||||||
if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) {
|
if (selectedBundleRef.isEmpty() || !canContinueOnMeteredDialog()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle "Stock" selection differently
|
// Handle "Stock" selection differently
|
||||||
if (selectedBundleName == DEFAULT_MODEL) {
|
if (selectedBundleRef == DEFAULT_MODEL) {
|
||||||
params.remove("ModelManager_ActiveBundle");
|
params.remove("ModelManager_ActiveBundle");
|
||||||
currentModelLblBtn->setValue(tr("Default"));
|
currentModelLblBtn->setValue(tr("Default"));
|
||||||
showResetParamsDialog();
|
showResetParamsDialog();
|
||||||
} else {
|
} else {
|
||||||
// Find selected bundle and initiate download
|
// Find selected bundle and initiate download
|
||||||
for (const auto &bundle: bundles) {
|
for (const auto &bundle: bundles) {
|
||||||
if (QString::fromStdString(bundle.getDisplayName()) == selectedBundleName) {
|
if (QString::fromStdString(bundle.getRef()) == selectedBundleRef) {
|
||||||
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
|
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
|
||||||
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {
|
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {
|
||||||
showResetParamsDialog();
|
showResetParamsDialog();
|
||||||
@@ -383,34 +424,28 @@ void ModelsPanel::updateLabels() {
|
|||||||
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
|
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
|
||||||
bool lagdEnabled = params.getBool("LagdToggle");
|
bool lagdEnabled = params.getBool("LagdToggle");
|
||||||
if (lagdEnabled) {
|
if (lagdEnabled) {
|
||||||
std::string liveDelayBytes = params.get("LiveDelay");
|
auto liveDelayBytes = params.get("LiveDelay");
|
||||||
if (!liveDelayBytes.empty()) {
|
if (!liveDelayBytes.empty()) {
|
||||||
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
|
auto LD = loadCerealEvent(params, "LiveDelay");
|
||||||
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
|
float lateralDelay = LD->getLiveDelay().getLateralDelay();
|
||||||
liveDelayBytes.size() / sizeof(capnp::word)));
|
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
|
||||||
auto event = msg.getRoot<cereal::Event>();
|
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
|
||||||
if (event.hasLiveDelay()) {
|
|
||||||
auto liveDelay = event.getLiveDelay();
|
|
||||||
float lateralDelay = liveDelay.getLateralDelay();
|
|
||||||
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
|
|
||||||
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::string carParamsBytes = params.get("CarParamsPersistent");
|
auto carParamsBytes = params.get("CarParamsPersistent");
|
||||||
if (!carParamsBytes.empty()) {
|
if (!carParamsBytes.empty()) {
|
||||||
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
|
AlignedBuffer aligned_buf_cp;
|
||||||
reinterpret_cast<const capnp::word*>(carParamsBytes.data()),
|
capnp::FlatArrayMessageReader cmsg(aligned_buf_cp.align(carParamsBytes.data(), carParamsBytes.size()));
|
||||||
carParamsBytes.size() / sizeof(capnp::word)));
|
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||||
auto carParams = msg.getRoot<cereal::CarParams>();
|
|
||||||
float steerDelay = carParams.getSteerActuatorDelay();
|
float steerDelay = CP.getSteerActuatorDelay();
|
||||||
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
||||||
float totalLag = steerDelay + softwareDelay;
|
float totalLag = steerDelay + softwareDelay;
|
||||||
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
|
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
|
||||||
"<b>%1</b> %2 s + <b>%3</b> %4 s = <b>%5</b> %6 s</span>")
|
"<b>%1</b> %2 s + <b>%3</b> %4 s = <b>%5</b> %6 s</span>")
|
||||||
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
|
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
|
||||||
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
|
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
|
||||||
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
|
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lagd_toggle_control->setDescription(desc);
|
lagd_toggle_control->setDescription(desc);
|
||||||
@@ -421,6 +456,9 @@ void ModelsPanel::updateLabels() {
|
|||||||
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
|
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update lane turn desire label and visibility
|
||||||
|
refreshLaneTurnValueControl();
|
||||||
|
|
||||||
clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
|
clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user