mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-11 07:04:40 +08:00
Compare commits
119 Commits
mapd-sp
...
optional-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8cf99c2a | ||
|
|
1f8941367d | ||
|
|
784e1d6658 | ||
|
|
cb94d3b055 | ||
|
|
94f93a9f26 | ||
|
|
7aa9d43187 | ||
|
|
747460363f | ||
|
|
b32c6dafee | ||
|
|
bffb2fb6fa | ||
|
|
688b694266 | ||
|
|
ec8f036850 | ||
|
|
4f44d6e643 | ||
|
|
920fd823ca | ||
|
|
1be13fdc55 | ||
|
|
810a2d9448 | ||
|
|
c9dbf97649 | ||
|
|
1bb4ca2547 | ||
|
|
2c04a27a2a | ||
|
|
b7f8dd11a5 | ||
|
|
70c0592e84 | ||
|
|
572c03dbac | ||
|
|
fa498221da | ||
|
|
67238d5045 | ||
|
|
994170ddb5 | ||
|
|
3c28188d7a | ||
|
|
4ccd17903b | ||
|
|
0e1b573f89 | ||
|
|
10580aca92 | ||
|
|
6b13175338 | ||
|
|
d0171084b5 | ||
|
|
2bfdd0d61d | ||
|
|
ea53111afc | ||
|
|
0739d4ac2d | ||
|
|
d450a4479f | ||
|
|
70b4de2c6b | ||
|
|
7611857719 | ||
|
|
82467e9436 | ||
|
|
bff22dd699 | ||
|
|
71e1149ee3 | ||
|
|
698e0ca00f | ||
|
|
b63d0a1d18 | ||
|
|
f6b30ef364 | ||
|
|
c85acd8d7f | ||
|
|
8dca43881a | ||
|
|
a885111c0c | ||
|
|
fd3df55a0c | ||
|
|
566210d678 | ||
|
|
bd73664f4c | ||
|
|
608c16007e | ||
|
|
d5e3410c65 | ||
|
|
7028301532 | ||
|
|
275abc1eb5 | ||
|
|
ff34b8af76 | ||
|
|
15a3d5fa7e | ||
|
|
255ca767cd | ||
|
|
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 | ||
|
|
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 |
8
.github/workflows/auto_pr_review.yaml
vendored
8
.github/workflows/auto_pr_review.yaml
vendored
@@ -40,10 +40,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
|
||||
env:
|
||||
PR_LABEL: 'dev-c3'
|
||||
PR_LABEL: 'dev'
|
||||
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
|
||||
steps:
|
||||
- name: Check if PR has dev-c3 label
|
||||
- name: Check if PR has dev label
|
||||
id: check-labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
@@ -62,11 +62,11 @@ jobs:
|
||||
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
||||
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');
|
||||
|
||||
- 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
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
1
.github/workflows/repo-maintenance.yaml
vendored
1
.github/workflows/repo-maintenance.yaml
vendored
@@ -43,6 +43,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: uv lock
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
run: |
|
||||
python3 -m ensurepip --upgrade
|
||||
pip3 install uv
|
||||
|
||||
5
.github/workflows/selfdrive_tests.yaml
vendored
5
.github/workflows/selfdrive_tests.yaml
vendored
@@ -107,7 +107,6 @@ jobs:
|
||||
|
||||
build_mac:
|
||||
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' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -125,8 +124,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: ./tools/mac_setup.sh
|
||||
env:
|
||||
# package install has DeprecationWarnings
|
||||
PYTHONWARNINGS: default
|
||||
PYTHONWARNINGS: default # package install has DeprecationWarnings
|
||||
HOMEBREW_DISPLAY_INSTALL_TIMES: 1
|
||||
- name: Save Homebrew cache
|
||||
uses: actions/cache/save@v4
|
||||
if: github.ref == 'refs/heads/master'
|
||||
|
||||
@@ -8,14 +8,14 @@ env:
|
||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||
|
||||
# Branch configurations
|
||||
STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master' }} # vars are set on repo settings.
|
||||
STAGING_SOURCE_BRANCH: 'master'
|
||||
|
||||
# Runtime configuration
|
||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, master-dev-c3-new ]
|
||||
branches: [ master, master-dev ]
|
||||
tags: [ 'release/*' ]
|
||||
pull_request_target:
|
||||
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.
|
||||
restore-keys: |
|
||||
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 }}
|
||||
|
||||
- name: Set environment variables
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: Build dev-c3-new
|
||||
name: Build dev
|
||||
|
||||
env:
|
||||
DEFAULT_SOURCE_BRANCH: "master"
|
||||
DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
|
||||
PR_LABEL: "dev-c3"
|
||||
DEFAULT_TARGET_BRANCH: "master-dev"
|
||||
PR_LABEL: "dev"
|
||||
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'
|
||||
|
||||
@@ -25,7 +25,7 @@ on:
|
||||
target_branch:
|
||||
description: 'Target branch to reset and squash into'
|
||||
required: true
|
||||
default: 'master-dev-c3-new'
|
||||
default: 'master-dev'
|
||||
type: string
|
||||
cancel_in_progress:
|
||||
description: 'Cancel any in-progress runs of this workflow'
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
if: (
|
||||
(github.event_name == 'workflow_dispatch')
|
||||
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|
||||
|| (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:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||
if: (
|
||||
(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:
|
||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -50,7 +50,6 @@ cereal/services.h
|
||||
cereal/gen
|
||||
cereal/messaging/bridge
|
||||
selfdrive/mapd/default_speeds_by_region.json
|
||||
system/proclogd/proclogd
|
||||
selfdrive/ui/translations/tmp
|
||||
selfdrive/test/longitudinal_maneuvers/out
|
||||
selfdrive/car/tests/cars_dump
|
||||
|
||||
41
.vscode/launch.json
vendored
41
.vscode/launch.json
vendored
@@ -23,6 +23,11 @@
|
||||
"id": "args",
|
||||
"description": "Arguments to pass to the process",
|
||||
"type": "promptString"
|
||||
},
|
||||
{
|
||||
"id": "replayArg",
|
||||
"type": "promptString",
|
||||
"description": "Enter route or segment to replay."
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
@@ -40,7 +45,41 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
32
Jenkinsfile
vendored
32
Jenkinsfile
vendored
@@ -178,7 +178,7 @@ node {
|
||||
|
||||
try {
|
||||
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"),
|
||||
])
|
||||
}
|
||||
@@ -186,12 +186,12 @@ node {
|
||||
if (env.BRANCH_NAME == '__nightly') {
|
||||
parallel (
|
||||
'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"),
|
||||
])
|
||||
},
|
||||
'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"),
|
||||
])
|
||||
},
|
||||
@@ -200,39 +200,30 @@ node {
|
||||
|
||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||
parallel (
|
||||
// tici 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("check dirty", "release/check-dirty.sh"),
|
||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||
])
|
||||
},
|
||||
'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("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 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"),
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.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': {
|
||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
||||
deviceStage("OX03C10", "tizi-ox03c10", ["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"),
|
||||
@@ -246,17 +237,13 @@ node {
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tici-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"], [
|
||||
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||
])
|
||||
},
|
||||
'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("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("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", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||
// TODO: enable once new AGNOS is available
|
||||
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
||||
|
||||
10
RELEASES.md
10
RELEASES.md
@@ -1,7 +1,13 @@
|
||||
Version 0.10.1 (2025-09-08)
|
||||
========================
|
||||
* Record driving feedback using LKAS button
|
||||
* Honda City 2023 support thanks to drFritz!
|
||||
* New driving model
|
||||
* 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)
|
||||
========================
|
||||
|
||||
@@ -359,11 +359,6 @@ SConscript([
|
||||
'system/ubloxd/SConscript',
|
||||
'system/loggerd/SConscript',
|
||||
])
|
||||
if arch != "Darwin":
|
||||
SConscript([
|
||||
'system/logcatd/SConscript',
|
||||
'system/proclogd/SConscript',
|
||||
])
|
||||
|
||||
if arch == "larch64":
|
||||
SConscript(['system/camerad/SConscript'])
|
||||
|
||||
@@ -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
|
||||
struct LeadData {
|
||||
dRel @0 :Float32;
|
||||
@@ -48,6 +68,7 @@ struct LeadData {
|
||||
|
||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||
mads @0 :ModularAssistiveDrivingSystem;
|
||||
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||
}
|
||||
|
||||
struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
@@ -122,6 +143,8 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
|
||||
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
dec @0 :DynamicExperimentalControl;
|
||||
longitudinalPlanSource @1 :LongitudinalPlanSource;
|
||||
smartCruiseControl @2 :SmartCruiseControl;
|
||||
|
||||
struct DynamicExperimentalControl {
|
||||
state @0 :DynamicExperimentalControlState;
|
||||
@@ -133,6 +156,34 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
blended @1;
|
||||
}
|
||||
}
|
||||
|
||||
struct SmartCruiseControl {
|
||||
vision @0 :Vision;
|
||||
|
||||
struct Vision {
|
||||
state @0 :VisionState;
|
||||
vTarget @1 :Float32;
|
||||
aTarget @2 :Float32;
|
||||
currentLateralAccel @3 :Float32;
|
||||
maxPredictedLateralAccel @4 :Float32;
|
||||
enabled @5 :Bool;
|
||||
active @6 :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 LongitudinalPlanSource {
|
||||
cruise @0;
|
||||
sccVision @1;
|
||||
}
|
||||
}
|
||||
|
||||
struct OnroadEventSP @0xda96579883444c35 {
|
||||
@@ -172,12 +223,16 @@ struct OnroadEventSP @0xda96579883444c35 {
|
||||
experimentalModeSwitched @14;
|
||||
wrongCarModeAlertOnly @15;
|
||||
pedalPressedAlertOnly @16;
|
||||
laneTurnLeft @17;
|
||||
laneTurnRight @18;
|
||||
}
|
||||
}
|
||||
|
||||
struct CarParamsSP @0x80ae746ee2596b11 {
|
||||
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
||||
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
||||
pcmCruiseSpeed @3 :Bool = true;
|
||||
intelligentCruiseButtonManagementAvailable @4 :Bool;
|
||||
|
||||
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
||||
|
||||
@@ -197,10 +252,24 @@ struct CarControlSP @0xa5cd762cd951a455 {
|
||||
params @1 :List(Param);
|
||||
leadOne @2 :LeadData;
|
||||
leadTwo @3 :LeadData;
|
||||
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
|
||||
|
||||
struct Param {
|
||||
key @0 :Text;
|
||||
value @1 :Text;
|
||||
type @2 :ParamType;
|
||||
value @3 :Data;
|
||||
|
||||
valueDEPRECATED @1 :Text; # The data type change may cause issues with backwards compatibility.
|
||||
}
|
||||
|
||||
enum ParamType {
|
||||
string @0;
|
||||
bool @1;
|
||||
int @2;
|
||||
float @3;
|
||||
time @4;
|
||||
json @5;
|
||||
bytes @6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,9 +327,16 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||
roadName @5 :Text;
|
||||
}
|
||||
|
||||
struct CustomReserved9 @0xa1680744031fdb2d {
|
||||
struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||
laneTurnDirection @0 :TurnDirection;
|
||||
}
|
||||
|
||||
enum TurnDirection {
|
||||
none @0;
|
||||
turnLeft @1;
|
||||
turnRight @2;
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
}
|
||||
|
||||
|
||||
@@ -585,7 +585,6 @@ struct PandaState @0xa7649e2575e4591e {
|
||||
heartbeatLost @22 :Bool;
|
||||
interruptLoad @25 :Float32;
|
||||
fanPower @28 :UInt8;
|
||||
fanStallCount @34 :UInt8;
|
||||
|
||||
spiErrorCount @33 :UInt16;
|
||||
|
||||
@@ -714,6 +713,7 @@ struct PandaState @0xa7649e2575e4591e {
|
||||
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
||||
safetyParamDEPRECATED @20 :Int16;
|
||||
safetyParam2DEPRECATED @26 :UInt32;
|
||||
fanStallCountDEPRECATED @34 :UInt8;
|
||||
}
|
||||
|
||||
struct PeripheralState {
|
||||
@@ -2631,7 +2631,7 @@ struct Event {
|
||||
backupManagerSP @113 :Custom.BackupManagerSP;
|
||||
carStateSP @114 :Custom.CarStateSP;
|
||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||
customReserved9 @116 :Custom.CustomReserved9;
|
||||
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||
customReserved10 @136 :Custom.CustomReserved10;
|
||||
customReserved11 @137 :Custom.CustomReserved11;
|
||||
customReserved12 @138 :Custom.CustomReserved12;
|
||||
|
||||
@@ -33,7 +33,7 @@ MessageContext message_context;
|
||||
struct SubMaster::SubMessage {
|
||||
std::string name;
|
||||
SubSocket *socket = nullptr;
|
||||
int freq = 0;
|
||||
float freq = 0.0f;
|
||||
bool updated = false, alive = false, valid = false, ignore_alive;
|
||||
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||
void *allocated_msg_reader = nullptr;
|
||||
|
||||
@@ -88,6 +88,7 @@ _services: dict[str, tuple] = {
|
||||
"carControlSP": (True, 100., 10),
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
"modelDataV2SP": (True, 20.),
|
||||
|
||||
# debug
|
||||
"uiDebug": (True, 0., 1),
|
||||
@@ -120,12 +121,12 @@ def build_header():
|
||||
h += "#include <map>\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"
|
||||
for k, v in SERVICE_LIST.items():
|
||||
should_log = "true" if v.should_log else "false"
|
||||
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)
|
||||
h += "};\n"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define DEFAULT_MODEL "Steam Powered (Default)"
|
||||
#define DEFAULT_MODEL "Firehose (Default)"
|
||||
|
||||
@@ -94,7 +94,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"Offroad_NoFirmware", {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_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
@@ -146,18 +145,23 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
||||
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
||||
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
|
||||
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
|
||||
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
||||
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"SmartCruiseControlVision", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// MADS params
|
||||
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
@@ -195,10 +199,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// model panel params
|
||||
// sunnypilot model params
|
||||
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
||||
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
||||
{"LaneTurnDesire", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||
|
||||
// mapd
|
||||
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
||||
|
||||
@@ -39,7 +39,7 @@ All of these are examples of good PRs:
|
||||
### 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.
|
||||
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
|
||||
|
||||
|
||||
30
docs/DEBUGGING_SAFETY.md
Normal file
30
docs/DEBUGGING_SAFETY.md
Normal file
@@ -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
|
||||
@@ -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)
|
||||
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,
|
||||
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
|
||||
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).
|
||||
|
||||
**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
|
||||
|
||||
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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
> [!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).
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging.
|
||||
Just run `tools/replay/replay --demo`.
|
||||
|
||||
## Replaying CAN data
|
||||
*Hardware required: jungle and comma 3/3X*
|
||||
*Hardware required: jungle and comma 3X*
|
||||
|
||||
1. Connect your PC to a jungle.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="12.8"
|
||||
export AGNOS_VERSION="13.1"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
@@ -21,7 +21,7 @@ nav:
|
||||
- What is openpilot?: getting-started/what-is-openpilot.md
|
||||
- How-to:
|
||||
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
||||
- Connect to a comma 3/3X: how-to/connect-to-comma.md
|
||||
- Connect to a comma 3X: how-to/connect-to-comma.md
|
||||
# - Make your first pull request: how-to/make-first-pr.md
|
||||
#- Replay a drive: how-to/replay-a-drive.md
|
||||
- Concepts:
|
||||
|
||||
Submodule opendbc_repo updated: 004fa8df07...f54d85c7d9
2
panda
2
panda
Submodule panda updated: f10ddc6a89...69ab12ee2a
@@ -36,6 +36,9 @@ dependencies = [
|
||||
"pyopenssl < 24.3.0",
|
||||
"pyaudio",
|
||||
|
||||
# ubloxd (TODO: just use struct)
|
||||
"kaitaistruct",
|
||||
|
||||
# panda
|
||||
"libusb1",
|
||||
"spidev; platform_system == 'Linux'",
|
||||
@@ -101,8 +104,9 @@ dev = [
|
||||
"av",
|
||||
"azure-identity",
|
||||
"azure-storage-blob",
|
||||
"dbus-next",
|
||||
"dbus-next", # TODO: remove once we moved everything to jeepney
|
||||
"dictdiffer",
|
||||
"jeepney",
|
||||
"matplotlib",
|
||||
"opencv-python-headless",
|
||||
"parameterized >=0.8, <0.9",
|
||||
@@ -120,6 +124,7 @@ dev = [
|
||||
|
||||
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')",
|
||||
"dearpygui>=2.1.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -157,7 +162,6 @@ testpaths = [
|
||||
"system/camerad",
|
||||
"system/hardware",
|
||||
"system/loggerd",
|
||||
"system/proclogd",
|
||||
"system/tests",
|
||||
"system/ubloxd",
|
||||
"system/webrtc",
|
||||
@@ -173,7 +177,7 @@ quiet-level = 3
|
||||
# if you've got a short variable name that's getting flagged, add it here
|
||||
ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,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"
|
||||
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]
|
||||
python_version = "3.11"
|
||||
@@ -247,6 +251,7 @@ exclude = [
|
||||
"teleoprtc_repo",
|
||||
"third_party",
|
||||
"*.ipynb",
|
||||
"generated",
|
||||
]
|
||||
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('--source-branch', type=str, default='master',
|
||||
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')
|
||||
parser.add_argument('--squash-script-path', type=str, required=True,
|
||||
help='Path to the squash_and_merge.py script')
|
||||
|
||||
@@ -179,7 +179,7 @@ class Car:
|
||||
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
|
||||
|
||||
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.experimental_mode = self.params.get_bool("ExperimentalMode")
|
||||
|
||||
@@ -30,8 +30,8 @@ CRUISE_INTERVAL_SIGN = {
|
||||
|
||||
|
||||
class VCruiseHelper(VCruiseHelperSP):
|
||||
def __init__(self, CP):
|
||||
VCruiseHelperSP.__init__(self)
|
||||
def __init__(self, CP, CP_SP):
|
||||
VCruiseHelperSP.__init__(self, CP, CP_SP)
|
||||
self.CP = CP
|
||||
self.v_cruise_kph = V_CRUISE_UNSET
|
||||
self.v_cruise_cluster_kph = V_CRUISE_UNSET
|
||||
@@ -46,10 +46,13 @@ class VCruiseHelper(VCruiseHelperSP):
|
||||
def update_v_cruise(self, CS, enabled, is_metric):
|
||||
self.v_cruise_kph_last = self.v_cruise_kph
|
||||
|
||||
self.get_minimum_set_speed(is_metric)
|
||||
|
||||
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
|
||||
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
|
||||
self._update_v_cruise_non_pcm(CS, _enabled, is_metric)
|
||||
self.v_cruise_cluster_kph = self.v_cruise_kph
|
||||
self.update_button_timers(CS, enabled)
|
||||
else:
|
||||
@@ -111,7 +114,7 @@ class VCruiseHelper(VCruiseHelperSP):
|
||||
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 = 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):
|
||||
# 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.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
|
||||
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
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import os
|
||||
import math
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import Phase, given, settings
|
||||
from parameterized import parameterized
|
||||
|
||||
from cereal import car, custom
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface_args
|
||||
from opendbc.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
|
||||
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from opendbc.car.values import PLATFORMS
|
||||
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
|
||||
|
||||
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'))
|
||||
|
||||
|
||||
@@ -37,43 +29,10 @@ class TestCarInterfaces:
|
||||
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
||||
@given(data=st.data())
|
||||
def test_car_interfaces(self, car_name, data):
|
||||
CarInterface = interfaces[car_name]
|
||||
|
||||
args = get_fuzzy_car_interface_args(data.draw)
|
||||
|
||||
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)
|
||||
car_interface = get_fuzzy_car_interface(car_name, data.draw)
|
||||
car_params = car_interface.CP.as_reader()
|
||||
car_params_sp = car_interface.CP_SP
|
||||
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_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)
|
||||
|
||||
@@ -5,7 +5,7 @@ import numpy as np
|
||||
from parameterized import parameterized_class
|
||||
from cereal import log
|
||||
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.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
||||
|
||||
@@ -49,7 +49,8 @@ class TestCruiseSpeed:
|
||||
class TestVCruiseHelper:
|
||||
def setup_method(self):
|
||||
self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
|
||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
||||
self.CP_SP = custom.CarParamsSP()
|
||||
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
|
||||
self.reset_cruise_speed_state()
|
||||
|
||||
def reset_cruise_speed_state(self):
|
||||
|
||||
@@ -116,7 +116,8 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
|
||||
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||
(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.longControlState = self.LoC.long_control_state
|
||||
@@ -168,7 +169,7 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
CC.orientationNED = self.calibrated_pose.orientation.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.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from cereal import log
|
||||
from openpilot.common.constants import CV
|
||||
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.lane_turn_desire import LaneTurnController
|
||||
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
@@ -41,6 +42,11 @@ class DesireHelper:
|
||||
self.prev_one_blinker = False
|
||||
self.desire = log.Desire.none
|
||||
self.alc = AutoLaneChangeController(self)
|
||||
self.lane_turn_controller = LaneTurnController()
|
||||
|
||||
@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):
|
||||
self.alc.update_params()
|
||||
@@ -56,12 +62,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:
|
||||
self.lane_change_state = LaneChangeState.preLaneChange
|
||||
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
|
||||
elif self.lane_change_state == LaneChangeState.preLaneChange:
|
||||
# Set lane change direction
|
||||
self.lane_change_direction = LaneChangeDirection.left if \
|
||||
carstate.leftBlinker else LaneChangeDirection.right
|
||||
# Update lane change direction
|
||||
self.lane_change_direction = self.get_lane_change_direction(carstate)
|
||||
|
||||
torque_applied = carstate.steeringPressed and \
|
||||
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
|
||||
@@ -108,6 +115,12 @@ class DesireHelper:
|
||||
|
||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||
|
||||
# Lane turn controller update
|
||||
turn_desire = self.lane_turn_controller.update(carstate.leftBlindspot, carstate.rightBlindspot, carstate.leftBlinker, carstate.rightBlinker,
|
||||
carstate.vEgo, carstate.steeringPressed, carstate.steeringTorque)
|
||||
if turn_desire != log.Desire.none:
|
||||
self.desire = turn_desire
|
||||
|
||||
# Send keep pulse once per second during LaneChangeStart.preLaneChange
|
||||
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
|
||||
self.keep_pulse_timer = 0.0
|
||||
|
||||
@@ -56,10 +56,8 @@ class LatControlTorque(LatControl):
|
||||
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
||||
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))
|
||||
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_jerk = desired_curvature_rate * CS.vEgo ** 2
|
||||
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
|
||||
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
|
||||
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
||||
|
||||
@@ -71,6 +69,8 @@ class LatControlTorque(LatControl):
|
||||
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
|
||||
pid_log.error = float(setpoint - measurement)
|
||||
ff = gravity_adjusted_lateral_accel
|
||||
# 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)
|
||||
|
||||
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
|
||||
|
||||
@@ -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])
|
||||
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
|
||||
|
||||
# Get new v_cruise and a_desired from Smart Cruise Control
|
||||
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
|
||||
|
||||
if force_slow_decel:
|
||||
v_cruise = 0.0
|
||||
|
||||
|
||||
70
selfdrive/controls/tests/test_torqued_lat_accel_offset.py
Normal file
70
selfdrive/controls/tests/test_torqued_lat_accel_offset.py
Normal file
@@ -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)
|
||||
@@ -13,12 +13,9 @@ class ModelConstants:
|
||||
META_T_IDXS = [2., 4., 6., 8., 10.]
|
||||
|
||||
# model inputs constants
|
||||
MODEL_FREQ = 20
|
||||
HISTORY_FREQ = 5
|
||||
HISTORY_LEN_SECONDS = 5
|
||||
TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ
|
||||
FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS
|
||||
INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS
|
||||
N_FRAMES = 2
|
||||
MODEL_RUN_FREQ = 20
|
||||
MODEL_CONTEXT_FREQ = 5 # "model_trained_fps"
|
||||
|
||||
FEATURE_LEN = 512
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import capnp
|
||||
import numpy as np
|
||||
from cereal import log
|
||||
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')
|
||||
|
||||
@@ -95,8 +96,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
# action
|
||||
modelV2.action = action
|
||||
|
||||
# times at X_IDXS of edges and lines aren't used
|
||||
LINE_T_IDXS: list[float] = []
|
||||
# times at X_IDXS of edges and lines
|
||||
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
|
||||
|
||||
# lane lines
|
||||
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()
|
||||
|
||||
# confidence
|
||||
if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0:
|
||||
if vipc_frame_id % (2*ModelConstants.MODEL_RUN_FREQ) == 0:
|
||||
# any disengage prob
|
||||
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
|
||||
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]
|
||||
|
||||
@@ -80,6 +80,64 @@ class FrameMeta:
|
||||
if vipc is not None:
|
||||
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):
|
||||
frames: dict[str, DrivingModelFrame]
|
||||
inputs: dict[str, np.ndarray]
|
||||
@@ -102,19 +160,15 @@ class ModelState(ModelStateBase):
|
||||
self.policy_output_slices = policy_metadata['output_slices']
|
||||
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.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
|
||||
self.numpy_inputs = {
|
||||
'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
|
||||
'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32),
|
||||
'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
|
||||
}
|
||||
self.numpy_inputs = {k: np.zeros(self.policy_input_shapes[k], dtype=np.float32) for k in self.policy_input_shapes}
|
||||
self.full_input_queues = InputQueues(ModelConstants.MODEL_CONTEXT_FREQ, ModelConstants.MODEL_RUN_FREQ, ModelConstants.N_FRAMES)
|
||||
for k in ['desire_pulse', 'features_buffer']:
|
||||
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
|
||||
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],
|
||||
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
|
||||
inputs['desire'][0] = 0
|
||||
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
|
||||
self.prev_desire[:] = inputs['desire']
|
||||
inputs['desire_pulse'][0] = 0
|
||||
new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0)
|
||||
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}
|
||||
|
||||
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()
|
||||
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_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :]
|
||||
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
|
||||
self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire})
|
||||
for k in ['desire_pulse', 'features_buffer']:
|
||||
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()
|
||||
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})")
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
params = Params()
|
||||
|
||||
# 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
|
||||
last_vipc_frame_id = 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}
|
||||
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] = {
|
||||
'desire': vec_desire,
|
||||
'desire_pulse': vec_desire,
|
||||
'traffic_convention': traffic_convention,
|
||||
}
|
||||
|
||||
@@ -333,6 +383,7 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
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)
|
||||
prev_action = action
|
||||
@@ -347,6 +398,7 @@ def main(demo=False):
|
||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_controller.turn_direction
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
@@ -354,6 +406,7 @@ def main(demo=False):
|
||||
pm.send('modelV2', modelv2_send)
|
||||
pm.send('drivingModelData', drivingdata_send)
|
||||
pm.send('cameraOdometry', posenet_send)
|
||||
pm.send('modelDataV2SP', mdv2sp_send)
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -84,12 +84,10 @@ Panda *connect(std::string serial="", uint32_t index=0) {
|
||||
panda->set_can_fd_auto(i, true);
|
||||
}
|
||||
|
||||
bool is_deprecated_panda = std::find(DEPRECATED_PANDA_TYPES.begin(),
|
||||
DEPRECATED_PANDA_TYPES.end(),
|
||||
panda->hw_type) != DEPRECATED_PANDA_TYPES.end();
|
||||
bool is_supported_panda = std::find(SUPPORTED_PANDA_TYPES.begin(), SUPPORTED_PANDA_TYPES.end(), panda->hw_type) != SUPPORTED_PANDA_TYPES.end();
|
||||
|
||||
if (is_deprecated_panda) {
|
||||
LOGW("panda %s is deprecated (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
|
||||
if (!is_supported_panda) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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.setInterruptLoad(health.interrupt_load_pkt);
|
||||
ps.setFanPower(health.fan_power);
|
||||
ps.setFanStallCount(health.fan_stall_count);
|
||||
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
|
||||
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
||||
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
||||
|
||||
@@ -9,13 +9,10 @@
|
||||
void pandad_main_thread(std::vector<std::string> serials);
|
||||
|
||||
// deprecated devices
|
||||
static const std::vector<cereal::PandaState::PandaType> DEPRECATED_PANDA_TYPES = {
|
||||
cereal::PandaState::PandaType::WHITE_PANDA,
|
||||
cereal::PandaState::PandaType::GREY_PANDA,
|
||||
cereal::PandaState::PandaType::BLACK_PANDA,
|
||||
cereal::PandaState::PandaType::PEDAL,
|
||||
cereal::PandaState::PandaType::UNO,
|
||||
cereal::PandaState::PandaType::RED_PANDA_V2
|
||||
static const std::vector<cereal::PandaState::PandaType> SUPPORTED_PANDA_TYPES = {
|
||||
cereal::PandaState::PandaType::RED_PANDA,
|
||||
cereal::PandaState::PandaType::TRES,
|
||||
cereal::PandaState::PandaType::CUATRO,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ def flash_panda(panda_serial: str) -> Panda:
|
||||
HARDWARE.recover_internal_panda()
|
||||
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)
|
||||
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()
|
||||
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:
|
||||
cloudlog.info("Panda firmware out of date, update required")
|
||||
panda.flash()
|
||||
@@ -67,6 +67,14 @@ def flash_panda(panda_serial: str) -> 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:
|
||||
# signal pandad to close the relay and exit
|
||||
def signal_handler(signum, frame):
|
||||
@@ -91,11 +99,6 @@ def main() -> None:
|
||||
cloudlog.event("pandad.flash_and_connect", count=count)
|
||||
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
|
||||
if no_internal_panda_count > 0:
|
||||
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))
|
||||
|
||||
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
|
||||
health = panda.health()
|
||||
if health["heartbeat_lost"]:
|
||||
|
||||
Binary file not shown.
@@ -22,8 +22,6 @@ class TestPandad:
|
||||
if len(Panda.list()) == 0:
|
||||
self._run_test(60)
|
||||
|
||||
self.spi = HARDWARE.get_device_type() != 'tici'
|
||||
|
||||
def teardown_method(self):
|
||||
managed_processes['pandad'].stop()
|
||||
|
||||
@@ -106,11 +104,9 @@ class TestPandad:
|
||||
# - 0.2s pandad -> pandad
|
||||
# - plus some buffer
|
||||
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):
|
||||
if not self.spi:
|
||||
pytest.skip("SPI test")
|
||||
# flash old fw
|
||||
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
|
||||
self._flash_bootstub_and_test(fn, expect_mismatch=True)
|
||||
|
||||
@@ -6,7 +6,6 @@ import random
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
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:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
if HARDWARE.get_device_type() == 'tici':
|
||||
pytest.skip("only for spi pandas")
|
||||
os.environ['STARTED'] = '1'
|
||||
os.environ['SPI_ERR_PROB'] = '0.001'
|
||||
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.",
|
||||
"severity": 1
|
||||
},
|
||||
"Offroad_StorageMissing": {
|
||||
"text": "NVMe drive not mounted.",
|
||||
"severity": 1
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"severity": 1,
|
||||
"_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)}",
|
||||
"",
|
||||
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:
|
||||
|
||||
@@ -21,12 +21,12 @@ from openpilot.selfdrive.selfdrived.helpers import ExcessiveActuationCheck
|
||||
from openpilot.selfdrive.selfdrived.state import StateMachine
|
||||
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.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
||||
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
||||
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
|
||||
|
||||
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
|
||||
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:
|
||||
ignore += ['driverCameraState', 'managerState']
|
||||
if REPLAY:
|
||||
@@ -95,7 +95,8 @@ class SelfdriveD(CruiseHelper):
|
||||
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
|
||||
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
|
||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback'] + \
|
||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
|
||||
'modelDataV2SP'] + \
|
||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||
ignore_alive=ignore, ignore_avg_freq=ignore,
|
||||
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
||||
@@ -134,13 +135,7 @@ class SelfdriveD(CruiseHelper):
|
||||
self.state_machine = StateMachine()
|
||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
|
||||
# some comma three with NVMe experience NVMe dropouts mid-drive that
|
||||
# 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'})
|
||||
self.ignored_processes = {'mapd', }
|
||||
|
||||
# Determine startup event
|
||||
is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote
|
||||
@@ -162,6 +157,7 @@ class SelfdriveD(CruiseHelper):
|
||||
self.events_sp_prev = []
|
||||
|
||||
self.mads = ModularAssistiveDrivingSystem(self)
|
||||
self.icbm = IntelligentCruiseButtonManagement(self.CP, self.CP_SP)
|
||||
|
||||
self.car_events_sp = CarSpecificEventsSP(self.CP, self.params)
|
||||
|
||||
@@ -300,6 +296,13 @@ class SelfdriveD(CruiseHelper):
|
||||
LaneChangeState.laneChangeFinishing):
|
||||
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']):
|
||||
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
|
||||
if i < len(self.CP.safetyConfigs):
|
||||
@@ -441,6 +444,8 @@ class SelfdriveD(CruiseHelper):
|
||||
self.events.add(EventName.personalityChanged)
|
||||
self.experimental_mode_switched = False
|
||||
|
||||
self.icbm.run(CS, self.sm['carControl'], self.is_metric)
|
||||
|
||||
def data_sample(self):
|
||||
_car_state = messaging.recv_one(self.car_state_sock)
|
||||
CS = _car_state.carState if _car_state else self.CS_prev
|
||||
@@ -545,6 +550,11 @@ class SelfdriveD(CruiseHelper):
|
||||
mads.active = self.mads.active
|
||||
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)
|
||||
|
||||
# onroadEventsSP - logged every second or on change
|
||||
|
||||
@@ -595,7 +595,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") ->
|
||||
if len(live_calibration) > 0:
|
||||
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
|
||||
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:
|
||||
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.selfdrive.selfdrived.events import EVENTS, ET
|
||||
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.tools.lib.logreader import LogReader
|
||||
from openpilot.tools.lib.log_time_series import msgs_to_time_series
|
||||
@@ -33,7 +32,7 @@ CPU usage budget
|
||||
TEST_DURATION = 25
|
||||
LOG_OFFSET = 8
|
||||
|
||||
MAX_TOTAL_CPU = 280. # total for all 8 cores
|
||||
MAX_TOTAL_CPU = 300. # total for all 8 cores
|
||||
PROCS = {
|
||||
# Baseline CPU usage by process
|
||||
"selfdrive.controls.controlsd": 16.0,
|
||||
@@ -57,30 +56,20 @@ PROCS = {
|
||||
"selfdrive.ui.soundd": 3.0,
|
||||
"selfdrive.ui.feedback.feedbackd": 1.0,
|
||||
"selfdrive.monitoring.dmonitoringd": 4.0,
|
||||
"./proclogd": 2.0,
|
||||
"system.proclogd": 3.0,
|
||||
"system.logmessaged": 1.0,
|
||||
"system.tombstoned": 0,
|
||||
"./logcatd": 1.0,
|
||||
"system.journald": 1.0,
|
||||
"system.micd": 5.0,
|
||||
"system.timed": 0,
|
||||
"selfdrive.pandad.pandad": 0,
|
||||
"system.statsd": 1.0,
|
||||
"system.loggerd.uploader": 15.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 = {
|
||||
# rtols: max/min, rsd
|
||||
"can": [2.5, 0.35],
|
||||
|
||||
@@ -63,14 +63,20 @@ class MainLayout(Widget):
|
||||
# Don't hide sidebar from interactive timeout
|
||||
if self._current_mode != MainState.ONROAD:
|
||||
self._sidebar.set_visible(False)
|
||||
self._current_mode = MainState.ONROAD
|
||||
self._set_current_layout(MainState.ONROAD)
|
||||
else:
|
||||
self._current_mode = MainState.HOME
|
||||
self._set_current_layout(MainState.HOME)
|
||||
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):
|
||||
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)
|
||||
|
||||
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 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.swaglog import cloudlog
|
||||
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.wrap_text import wrap_text
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
from openpilot.selfdrive.ui.lib.api_helpers import get_token
|
||||
|
||||
TITLE = "Firehose Mode"
|
||||
DESCRIPTION = (
|
||||
@@ -163,7 +163,7 @@ class FirehoseLayout(Widget):
|
||||
dongle_id = self.params.get("DongleId")
|
||||
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
|
||||
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)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
|
||||
@@ -2,7 +2,6 @@ import pyray as rl
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
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.device import DeviceLayout
|
||||
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.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.wifi_manager import WifiManager
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.network import WifiManagerUI
|
||||
|
||||
# Settings close button
|
||||
SETTINGS_CLOSE_TEXT = "×"
|
||||
@@ -43,7 +44,7 @@ class PanelType(IntEnum):
|
||||
@dataclass
|
||||
class PanelInfo:
|
||||
name: str
|
||||
instance: object
|
||||
instance: Widget
|
||||
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
|
||||
@@ -53,9 +54,12 @@ class SettingsLayout(Widget):
|
||||
self._current_panel = PanelType.DEVICE
|
||||
|
||||
# Panel configuration
|
||||
wifi_manager = WifiManager()
|
||||
wifi_manager.set_active(False)
|
||||
|
||||
self._panels = {
|
||||
PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
|
||||
PanelType.NETWORK: PanelInfo("Network", NetworkLayout()),
|
||||
PanelType.NETWORK: PanelInfo("Network", WifiManagerUI(wifi_manager)),
|
||||
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
|
||||
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
|
||||
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
|
||||
@@ -149,8 +153,14 @@ class SettingsLayout(Widget):
|
||||
|
||||
def set_current_panel(self, panel_type: PanelType):
|
||||
if panel_type != self._current_panel:
|
||||
self._panels[self._current_panel].instance.hide_event()
|
||||
self._current_panel = panel_type
|
||||
self._panels[self._current_panel].instance.show_event()
|
||||
|
||||
def close_settings(self):
|
||||
if self._close_callback:
|
||||
self._close_callback()
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._panels[self._current_panel].instance.show_event()
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
self._panels[self._current_panel].instance.hide_event()
|
||||
|
||||
14
selfdrive/ui/lib/api_helpers.py
Normal file
14
selfdrive/ui/lib/api_helpers.py
Normal file
@@ -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 threading
|
||||
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.swaglog import cloudlog
|
||||
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
||||
|
||||
TOKEN_EXPIRY_HOURS = 2
|
||||
from openpilot.selfdrive.ui.lib.api_helpers import get_token
|
||||
|
||||
|
||||
class PrimeType(IntEnum):
|
||||
@@ -23,12 +21,6 @@ class PrimeType(IntEnum):
|
||||
PURPLE = 5,
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_token(dongle_id: str, t: int):
|
||||
print('getting token')
|
||||
return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS)
|
||||
|
||||
|
||||
class PrimeState:
|
||||
FETCH_INTERVAL = 5.0 # seconds between API calls
|
||||
API_TIMEOUT = 10.0 # seconds for API requests
|
||||
@@ -58,15 +50,13 @@ class PrimeState:
|
||||
return
|
||||
|
||||
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)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
is_paired = data.get("is_paired", False)
|
||||
prime_type = data.get("prime_type", 0)
|
||||
self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
|
||||
elif response.status_code == 401:
|
||||
get_token.cache_clear()
|
||||
except Exception as e:
|
||||
cloudlog.error(f"Failed to fetch prime status: {e}")
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include <map>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#ifdef SUNNYPILOT
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#endif
|
||||
|
||||
void OnroadAlerts::updateState(const UIState &s) {
|
||||
Alert a = getAlert(*(s.sm), s.scene.started_frame);
|
||||
@@ -73,6 +76,12 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
|
||||
}
|
||||
QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2);
|
||||
|
||||
#ifdef SUNNYPILOT
|
||||
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
|
||||
const int adjustment = dev_ui_info > 1 && alert.size != cereal::SelfdriveState::AlertSize::FULL ? 30 : 0;
|
||||
r = QRect(0 + margin, height() - h + margin - adjustment, width() - margin*2, h - margin*2);
|
||||
#endif
|
||||
|
||||
QPainter p(this);
|
||||
|
||||
// draw background + gradient
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/onroad/model.h"
|
||||
#define ExperimentalButton ExperimentalButtonSP
|
||||
#define ModelRenderer ModelRendererSP
|
||||
#define HudRenderer HudRendererSP
|
||||
#else
|
||||
#include "selfdrive/ui/qt/onroad/buttons.h"
|
||||
#include "selfdrive/ui/qt/onroad/hud.h"
|
||||
|
||||
@@ -73,6 +73,11 @@ void DriverMonitorRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
||||
float y = surface_rect.height() - offset;
|
||||
float opacity = is_active ? 0.65f : 0.2f;
|
||||
|
||||
#ifdef SUNNYPILOT
|
||||
const int dev_ui_info = uiStateSP()->scene.dev_ui_info;
|
||||
y -= dev_ui_info > 1 ? 50 : 0;
|
||||
#endif
|
||||
|
||||
drawIcon(painter, QPoint(x, y), dm_img, QColor(0, 0, 0, 70), opacity);
|
||||
|
||||
QPointF keypoints[std::size(DEFAULT_FACE_KPTS_3D)];
|
||||
|
||||
@@ -39,6 +39,7 @@ qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/visuals_panel.cc",
|
||||
"sunnypilot/qt/onroad/annotated_camera.cc",
|
||||
"sunnypilot/qt/onroad/buttons.cc",
|
||||
"sunnypilot/qt/onroad/developer_ui/developer_ui.cc",
|
||||
"sunnypilot/qt/onroad/hud.cc",
|
||||
"sunnypilot/qt/onroad/model.cc",
|
||||
"sunnypilot/qt/onroad/onroad_home.cc",
|
||||
@@ -47,6 +48,7 @@ qt_src = [
|
||||
lateral_panel_qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/lateral/lane_turn_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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/lane_turn_settings.h"
|
||||
|
||||
|
||||
LaneTurnSettings::LaneTurnSettings(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(tr("Back"));
|
||||
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
|
||||
main_layout->addWidget(back, 0, Qt::AlignLeft);
|
||||
|
||||
ListWidgetSP *list = new ListWidgetSP(this, false);
|
||||
// Lane Turn Desire Control
|
||||
laneTurnDesireControl = new LaneTurnDesireControl();
|
||||
laneTurnDesireControl->setUpdateOtherToggles(true);
|
||||
laneTurnDesireControl->showDescription();
|
||||
connect(laneTurnDesireControl, &OptionControlSP::updateLabels, laneTurnDesireControl, &LaneTurnDesireControl::refresh);
|
||||
list->addItem(laneTurnDesireControl);
|
||||
|
||||
// Lane Turn Value control
|
||||
bool is_metric = params.getBool("IsMetric");
|
||||
int per_value_change = is_metric ? 62 : 100; // ~1 km/h or 1 mph
|
||||
laneTurnValueControl = new OptionControlSP("LaneTurnValue", tr("Adjust Lane Turn Speed"),
|
||||
tr("Set the maximum speed for lane turn desires. Default is 19 %1.").arg(is_metric ? "km/h" : "mph"),
|
||||
"", {500, 2000}, per_value_change, false, nullptr, true, true);
|
||||
laneTurnValueControl->showDescription();
|
||||
list->addItem(laneTurnValueControl);
|
||||
|
||||
// show/hide value control based on desire
|
||||
connect(laneTurnDesireControl, &OptionControlSP::updateLabels, this, &LaneTurnSettings::updateValueControlVisibility);
|
||||
connect(laneTurnValueControl, &OptionControlSP::updateLabels, this, &LaneTurnSettings::refreshLaneTurnValueControl);
|
||||
main_layout->addWidget(new ScrollViewSP(list, this));
|
||||
}
|
||||
|
||||
void LaneTurnSettings::showEvent(QShowEvent *event) {
|
||||
updateValueControlVisibility();
|
||||
}
|
||||
|
||||
void LaneTurnSettings::updateValueControlVisibility() {
|
||||
QString option = QString::fromStdString(params.get("LaneTurnDesire"));
|
||||
bool visible = (option != "0");
|
||||
laneTurnValueControl->setVisible(visible);
|
||||
if (visible) {
|
||||
refreshLaneTurnValueControl();
|
||||
}
|
||||
}
|
||||
|
||||
void LaneTurnSettings::refreshLaneTurnValueControl() {
|
||||
if (!laneTurnValueControl) 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 = is_metric ? stored_mph * 1.609344f : stored_mph;
|
||||
laneTurnValueControl->setLabel(QString::number(qRound(display_value)) + " " + unit);
|
||||
}
|
||||
|
||||
// Lane Turn Desire Control
|
||||
LaneTurnDesireControl::LaneTurnDesireControl() : OptionControlSP(
|
||||
"LaneTurnDesire",
|
||||
tr("Lane Turn Desires"),
|
||||
tr("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",
|
||||
{0, 2}) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
void LaneTurnDesireControl::refresh() {
|
||||
QString option = QString::fromStdString(params.get("LaneTurnDesire"));
|
||||
static const QMap<QString, QString> options = {
|
||||
{"0", tr("Off")},
|
||||
{"1", tr("Nudge")},
|
||||
{"2", tr("Nudgeless")},
|
||||
};
|
||||
setLabel(options.value(option, tr("Off")));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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/widgets/controls.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||
|
||||
class LaneTurnDesireControl : public OptionControlSP {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LaneTurnDesireControl();
|
||||
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
Params params;
|
||||
};
|
||||
|
||||
class LaneTurnSettings : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LaneTurnSettings(QWidget* parent = nullptr);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
signals:
|
||||
void backPress();
|
||||
|
||||
private:
|
||||
void refreshLaneTurnValueControl();
|
||||
void updateValueControlVisibility();
|
||||
|
||||
private:
|
||||
Params params;
|
||||
std::map<std::string, ParamControlSP*> toggles;
|
||||
|
||||
LaneTurnDesireControl *laneTurnDesireControl;
|
||||
OptionControlSP *laneTurnValueControl;
|
||||
};
|
||||
@@ -59,7 +59,27 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
|
||||
sunnypilotScroller->restoreScrollPosition();
|
||||
main_layout->setCurrentWidget(sunnypilotScreen);
|
||||
});
|
||||
list->addItem(laneChangeSettingsButton);
|
||||
|
||||
// Lane Turn Settings
|
||||
laneTurnSettingsButton = new PushButtonSP(tr("Customize Lane Turn"));
|
||||
laneTurnSettingsButton->setObjectName("lane_turn_btn");
|
||||
connect(laneTurnSettingsButton, &QPushButton::clicked, [=]() {
|
||||
sunnypilotScroller->setLastScrollPosition();
|
||||
main_layout->setCurrentWidget(laneTurnWidget);
|
||||
});
|
||||
|
||||
laneTurnWidget = new LaneTurnSettings(this);
|
||||
connect(laneTurnWidget, &LaneTurnSettings::backPress, [=]() {
|
||||
sunnypilotScroller->restoreScrollPosition();
|
||||
main_layout->setCurrentWidget(sunnypilotScreen);
|
||||
});
|
||||
|
||||
QWidget *laneButtonsWidget = new QWidget();
|
||||
QHBoxLayout *laneButtonsLayout = new QHBoxLayout(laneButtonsWidget);
|
||||
laneButtonsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
laneChangeSettingsButton->setFixedWidth(750); laneTurnSettingsButton->setFixedWidth(750);
|
||||
laneButtonsLayout->addWidget(laneChangeSettingsButton); laneButtonsLayout->addWidget(laneTurnSettingsButton);
|
||||
list->addItem(laneButtonsWidget);
|
||||
|
||||
list->addItem(vertical_space(0));
|
||||
list->addItem(horizontal_line());
|
||||
@@ -100,6 +120,7 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
|
||||
main_layout->addWidget(sunnypilotScreen);
|
||||
main_layout->addWidget(madsWidget);
|
||||
main_layout->addWidget(laneChangeWidget);
|
||||
main_layout->addWidget(laneTurnWidget);
|
||||
|
||||
setStyleSheet(R"(
|
||||
#back_btn {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#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/lane_change_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_turn_settings.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||
@@ -43,6 +44,8 @@ private:
|
||||
MadsSettings *madsWidget = nullptr;
|
||||
PushButtonSP *laneChangeSettingsButton;
|
||||
LaneChangeSettings *laneChangeWidget = nullptr;
|
||||
PushButtonSP *laneTurnSettingsButton;
|
||||
LaneTurnSettings *laneTurnWidget = nullptr;
|
||||
NeuralNetworkLateralControl *nnlcToggle = nullptr;
|
||||
BlinkerPauseLateralSettings *blinkerPauseLateralSettings = nullptr;
|
||||
|
||||
|
||||
@@ -18,6 +18,23 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
||||
cruisePanelScroller = new ScrollViewSP(list, this);
|
||||
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);
|
||||
|
||||
customAccIncrement = new CustomAccIncrement("CustomAccIncrementsEnabled", tr("Custom ACC Speed Increments"), "", "", this);
|
||||
list->addItem(customAccIncrement);
|
||||
|
||||
@@ -35,16 +52,22 @@ void LongitudinalPanel::showEvent(QShowEvent *event) {
|
||||
|
||||
void LongitudinalPanel::refresh(bool _offroad) {
|
||||
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_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);
|
||||
is_pcm_cruise = CP.getPcmCruise();
|
||||
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
|
||||
} else {
|
||||
has_longitudinal_control = 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.");
|
||||
@@ -56,7 +79,7 @@ void LongitudinalPanel::refresh(bool _offroad) {
|
||||
customAccIncrement->setDescription(onroadOnlyDescription);
|
||||
customAccIncrement->showDescription();
|
||||
} else {
|
||||
if (has_longitudinal_control) {
|
||||
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
|
||||
if (is_pcm_cruise) {
|
||||
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
|
||||
customAccIncrement->showDescription();
|
||||
@@ -68,12 +91,20 @@ void LongitudinalPanel::refresh(bool _offroad) {
|
||||
customAccIncrement->toggleFlipped(false);
|
||||
customAccIncrement->setDescription(accNoLongDescription);
|
||||
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
|
||||
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();
|
||||
|
||||
SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed);
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
@@ -23,10 +23,13 @@ private:
|
||||
Params params;
|
||||
bool has_longitudinal_control = false;
|
||||
bool is_pcm_cruise = false;
|
||||
bool intelligent_cruise_button_management_available = false;;
|
||||
bool offroad = false;
|
||||
|
||||
QStackedLayout *main_layout = nullptr;
|
||||
ScrollViewSP *cruisePanelScroller = nullptr;
|
||||
QWidget *cruisePanelScreen = nullptr;
|
||||
CustomAccIncrement *customAccIncrement = nullptr;
|
||||
ParamControl *SmartCruiseControlVision;
|
||||
ParamControl *intelligentCruiseButtonManagement = nullptr;
|
||||
};
|
||||
|
||||
@@ -101,7 +101,6 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
|
||||
QString policyType = tr("Policy Model");
|
||||
policyFrame = createModelDetailFrame(this, policyType, policyProgressBar);
|
||||
list->addItem(policyFrame);
|
||||
|
||||
list->addItem(horizontal_line());
|
||||
|
||||
// LiveDelay toggle
|
||||
@@ -110,23 +109,10 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
|
||||
list->addItem(lagd_toggle_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"),
|
||||
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
|
||||
"\nThe default software delay value is 0.2"),
|
||||
"", {5, liveDelayMaxInt}, 1, false, nullptr, true, true);
|
||||
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
|
||||
"\nThe default software delay value is 0.2"),
|
||||
"", {5, 50}, 1, false, nullptr, true, true);
|
||||
|
||||
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
|
||||
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
||||
@@ -159,10 +145,6 @@ QFrame* ModelsPanel::createModelDetailFrame(QWidget *parent, QString &typeName,
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the UI with bundle download progress information
|
||||
* Reads status from modelManagerSP cereal message and displays status for all models
|
||||
*/
|
||||
void ModelsPanel::handleBundleDownloadProgress() {
|
||||
supercomboFrame->setVisible(false);
|
||||
visionFrame->setVisible(false);
|
||||
@@ -401,34 +383,28 @@ void ModelsPanel::updateLabels() {
|
||||
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
|
||||
bool lagdEnabled = params.getBool("LagdToggle");
|
||||
if (lagdEnabled) {
|
||||
std::string liveDelayBytes = params.get("LiveDelay");
|
||||
auto 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();
|
||||
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));
|
||||
}
|
||||
auto LD = loadCerealEvent(params, "LiveDelay");
|
||||
float lateralDelay = LD->getLiveDelay().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 {
|
||||
std::string carParamsBytes = params.get("CarParamsPersistent");
|
||||
auto carParamsBytes = params.get("CarParamsPersistent");
|
||||
if (!carParamsBytes.empty()) {
|
||||
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
|
||||
reinterpret_cast<const capnp::word*>(carParamsBytes.data()),
|
||||
carParamsBytes.size() / sizeof(capnp::word)));
|
||||
auto carParams = msg.getRoot<cereal::CarParams>();
|
||||
float steerDelay = carParams.getSteerActuatorDelay();
|
||||
AlignedBuffer aligned_buf_cp;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf_cp.align(carParamsBytes.data(), carParamsBytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
float steerDelay = CP.getSteerActuatorDelay();
|
||||
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
||||
float totalLag = steerDelay + softwareDelay;
|
||||
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
|
||||
"<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),
|
||||
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
|
||||
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
|
||||
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
|
||||
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
|
||||
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
|
||||
}
|
||||
}
|
||||
lagd_toggle_control->setDescription(desc);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <QProgressBar>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
|
||||
class ModelsPanel : public QWidget {
|
||||
@@ -81,5 +82,4 @@ private:
|
||||
Params params;
|
||||
ButtonControlSP *clearModelCacheBtn;
|
||||
ButtonControlSP *refreshAvailableModelsBtn;
|
||||
|
||||
};
|
||||
|
||||
@@ -11,12 +11,39 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
|
||||
// branch selector
|
||||
QObject::disconnect(targetBranchBtn, nullptr, nullptr, nullptr);
|
||||
connect(targetBranchBtn, &ButtonControlSP::clicked, [=]() {
|
||||
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
|
||||
if (Hardware::get_device_type() == cereal::InitData::DeviceType::TICI) {
|
||||
auto current = params.get("GitBranch");
|
||||
QStringList allBranches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
|
||||
QStringList branches;
|
||||
for (const QString &b : allBranches) {
|
||||
if (b.endsWith("-tici")) {
|
||||
branches.append(b);
|
||||
}
|
||||
}
|
||||
|
||||
for (QString b : {current.c_str(), "master-tici", "staging-tici", "release-tici"}) {
|
||||
auto i = branches.indexOf(b);
|
||||
if (i >= 0) {
|
||||
branches.removeAt(i);
|
||||
branches.insert(0, b);
|
||||
}
|
||||
}
|
||||
|
||||
QString cur = QString::fromStdString(params.get("UpdaterTargetBranch"));
|
||||
QString selection = MultiOptionDialog::getSelection(tr("Select a branch"), branches, cur, this);
|
||||
if (!selection.isEmpty()) {
|
||||
params.put("UpdaterTargetBranch", selection.toStdString());
|
||||
targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch")));
|
||||
checkForUpdates();
|
||||
}
|
||||
} else {
|
||||
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
|
||||
d.setMinLength(0);
|
||||
const int ret = d.exec();
|
||||
if (ret) {
|
||||
searchBranches(d.text());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Disable Updates toggle
|
||||
|
||||
@@ -35,6 +35,13 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
|
||||
"../assets/offroad/icon_monitoring.png",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"StandstillTimer",
|
||||
tr("Enable Standstill Timer"),
|
||||
tr("Show a timer on the HUD when the car is at a standstill."),
|
||||
"../assets/offroad/icon_monitoring.png",
|
||||
false,
|
||||
},
|
||||
};
|
||||
|
||||
// Add regular toggles first
|
||||
@@ -72,6 +79,15 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
|
||||
list->addItem(chevron_info_settings);
|
||||
param_watcher->addParam("ChevronInfo");
|
||||
|
||||
// Visuals: Developer UI Info (Dev UI)
|
||||
std::vector<QString> dev_ui_settings_texts{tr("Off"), tr("Right"), tr("Right &&\nBottom")};
|
||||
dev_ui_settings = new ButtonParamControlSP(
|
||||
"DevUIInfo", tr("Developer UI"), tr("Display real-time parameters and metrics from various sources."),
|
||||
"",
|
||||
dev_ui_settings_texts,
|
||||
380);
|
||||
list->addItem(dev_ui_settings);
|
||||
|
||||
sunnypilotScroller = new ScrollViewSP(list, this);
|
||||
vlayout->addWidget(sunnypilotScroller);
|
||||
|
||||
@@ -90,4 +106,7 @@ void VisualsPanel::paramsRefresh() {
|
||||
if (chevron_info_settings) {
|
||||
chevron_info_settings->refresh();
|
||||
}
|
||||
if (dev_ui_settings) {
|
||||
dev_ui_settings->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,5 @@ protected:
|
||||
std::map<std::string, ParamControlSP*> toggles;
|
||||
ParamWatcher * param_watcher;
|
||||
ButtonParamControlSP *chevron_info_settings;
|
||||
ButtonParamControlSP *dev_ui_settings;
|
||||
};
|
||||
|
||||
@@ -14,3 +14,8 @@ AnnotatedCameraWidgetSP::AnnotatedCameraWidgetSP(VisionStreamType type, QWidget
|
||||
void AnnotatedCameraWidgetSP::updateState(const UIState &s) {
|
||||
AnnotatedCameraWidget::updateState(s);
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) {
|
||||
AnnotatedCameraWidget::showEvent(event);
|
||||
ui_update_params_sp(uiState());
|
||||
}
|
||||
|
||||
@@ -15,4 +15,7 @@ class AnnotatedCameraWidgetSP : public AnnotatedCameraWidget {
|
||||
public:
|
||||
explicit AnnotatedCameraWidgetSP(VisionStreamType type, QWidget *parent = nullptr);
|
||||
void updateState(const UIState &s) override;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
};
|
||||
|
||||
227
selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc
Normal file
227
selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
#include <cmath>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h"
|
||||
|
||||
|
||||
// Add Relative Distance to Primary Lead Car
|
||||
// Unit: Meters
|
||||
UiElement DeveloperUi::getDRel(bool lead_status, float lead_d_rel) {
|
||||
QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-";
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
|
||||
if (lead_status) {
|
||||
// Orange if close, Red if very close
|
||||
if (lead_d_rel < 5) {
|
||||
color = QColor(255, 0, 0, 255);
|
||||
} else if (lead_d_rel < 15) {
|
||||
color = QColor(255, 188, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
return UiElement(value, "REL DIST", "m", color);
|
||||
}
|
||||
|
||||
// Add Relative Velocity vs Primary Lead Car
|
||||
// Unit: kph if metric, else mph
|
||||
UiElement DeveloperUi::getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit) {
|
||||
QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-";
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
|
||||
if (lead_status) {
|
||||
// Red if approaching faster than 10mph
|
||||
// Orange if approaching (negative)
|
||||
if (lead_v_rel < -4.4704) {
|
||||
color = QColor(255, 0, 0, 255);
|
||||
} else if (lead_v_rel < 0) {
|
||||
color = QColor(255, 188, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
return UiElement(value, "REL SPEED", speed_unit, color);
|
||||
}
|
||||
|
||||
// Add Real Steering Angle
|
||||
// Unit: Degrees
|
||||
UiElement DeveloperUi::getSteeringAngleDeg(float angle_steers, bool lat_active, bool steer_override) {
|
||||
QString value = QString("%1%2%3").arg(QString::number(angle_steers, 'f', 1)).arg("°").arg("");
|
||||
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
|
||||
|
||||
// Red if large steering angle
|
||||
// Orange if moderate steering angle
|
||||
if (std::fabs(angle_steers) > 180) {
|
||||
color = QColor(255, 0, 0, 255);
|
||||
} else if (std::fabs(angle_steers) > 90) {
|
||||
color = QColor(255, 188, 0, 255);
|
||||
}
|
||||
|
||||
return UiElement(value, "REAL STEER", "", color);
|
||||
}
|
||||
|
||||
// Add Actual Lateral Acceleration (roll compensated) when using Torque
|
||||
// Unit: m/s²
|
||||
UiElement DeveloperUi::getActualLateralAccel(float curvature, float v_ego, float roll, bool lat_active, bool steer_override) {
|
||||
double actualLateralAccel = (curvature * pow(v_ego, 2)) - (roll * 9.81);
|
||||
|
||||
QString value = QString::number(actualLateralAccel, 'f', 2);
|
||||
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, "ACTUAL L.A.", "m/s²", color);
|
||||
}
|
||||
|
||||
// Add Desired Steering Angle when using PID
|
||||
// Unit: Degrees
|
||||
UiElement DeveloperUi::getSteeringAngleDesiredDeg(bool lat_active, float steer_angle_desired, float angle_steers) {
|
||||
QString value = lat_active ? QString("%1%2%3").arg(QString::number(steer_angle_desired, 'f', 1)).arg("°").arg("") : "-";
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
|
||||
if (lat_active) {
|
||||
// Red if large steering angle
|
||||
// Orange if moderate steering angle
|
||||
if (std::fabs(angle_steers) > 180) {
|
||||
color = QColor(255, 0, 0, 255);
|
||||
} else if (std::fabs(angle_steers) > 90) {
|
||||
color = QColor(255, 188, 0, 255);
|
||||
} else {
|
||||
color = QColor(0, 255, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
return UiElement(value, "DESIRED STEER", "", color);
|
||||
}
|
||||
|
||||
// Add Device Memory (RAM) Usage
|
||||
// Unit: Percent
|
||||
UiElement DeveloperUi::getMemoryUsagePercent(int memory_usage_percent) {
|
||||
QString value = QString("%1%2").arg(QString::number(memory_usage_percent, 'd', 0)).arg("%");
|
||||
QColor color = (memory_usage_percent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, "RAM", "", color);
|
||||
}
|
||||
|
||||
// Add Vehicle Current Acceleration
|
||||
// Unit: m/s²
|
||||
UiElement DeveloperUi::getAEgo(float a_ego) {
|
||||
QString value = QString::number(a_ego, 'f', 1);
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, "ACC.", "m/s²", color);
|
||||
}
|
||||
|
||||
// Add Relative Velocity to Primary Lead Car
|
||||
// Unit: kph if metric, else mph
|
||||
UiElement DeveloperUi::getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit) {
|
||||
QString value = lead_status ? QString::number((lead_v_rel + v_ego) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-";
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
|
||||
if (lead_status) {
|
||||
// Red if approaching faster than 10mph
|
||||
// Orange if approaching (negative)
|
||||
if (lead_v_rel < -4.4704) {
|
||||
color = QColor(255, 0, 0, 255);
|
||||
} else if (lead_v_rel < 0) {
|
||||
color = QColor(255, 188, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
return UiElement(value, "L.S.", speed_unit, color);
|
||||
}
|
||||
|
||||
// Add Friction Coefficient Raw from torqued
|
||||
// Unit: None
|
||||
UiElement DeveloperUi::getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid) {
|
||||
QString value = QString::number(friction_coefficient_filtered, 'f', 3);
|
||||
QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, "FRIC.", "", color);
|
||||
}
|
||||
|
||||
// Add Lateral Acceleration Factor Raw from torqued
|
||||
// Unit: m/s²
|
||||
UiElement DeveloperUi::getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid) {
|
||||
QString value = QString::number(lat_accel_factor_filtered, 'f', 3);
|
||||
QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, "L.A.", "m/s²", color);
|
||||
}
|
||||
|
||||
// Add Steering Torque from Car EPS
|
||||
// Unit: Newton Meters
|
||||
UiElement DeveloperUi::getSteeringTorqueEps(float steering_torque_eps) {
|
||||
QString value = QString::number(std::fabs(steering_torque_eps), 'f', 1);
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, "E.T.", "N·dm", color);
|
||||
}
|
||||
|
||||
// Add Bearing Degree and Direction from Car (Compass)
|
||||
// Unit: Meters
|
||||
UiElement DeveloperUi::getBearingDeg(float bearing_accuracy_deg, float bearing_deg) {
|
||||
QString value = (bearing_accuracy_deg != 180.00) ? QString("%1%2%3").arg(QString::number(bearing_deg, 'd', 0)).arg("°").arg("") : "-";
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
QString dir_value;
|
||||
|
||||
if (bearing_accuracy_deg != 180.00) {
|
||||
if (((bearing_deg >= 337.5) && (bearing_deg <= 360)) || ((bearing_deg >= 0) && (bearing_deg <= 22.5))) {
|
||||
dir_value = "N";
|
||||
} else if ((bearing_deg > 22.5) && (bearing_deg < 67.5)) {
|
||||
dir_value = "NE";
|
||||
} else if ((bearing_deg >= 67.5) && (bearing_deg <= 112.5)) {
|
||||
dir_value = "E";
|
||||
} else if ((bearing_deg > 112.5) && (bearing_deg < 157.5)) {
|
||||
dir_value = "SE";
|
||||
} else if ((bearing_deg >= 157.5) && (bearing_deg <= 202.5)) {
|
||||
dir_value = "S";
|
||||
} else if ((bearing_deg > 202.5) && (bearing_deg < 247.5)) {
|
||||
dir_value = "SW";
|
||||
} else if ((bearing_deg >= 247.5) && (bearing_deg <= 292.5)) {
|
||||
dir_value = "W";
|
||||
} else if ((bearing_deg > 292.5) && (bearing_deg < 337.5)) {
|
||||
dir_value = "NW";
|
||||
}
|
||||
} else {
|
||||
dir_value = "OFF";
|
||||
}
|
||||
|
||||
return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color);
|
||||
}
|
||||
|
||||
// Add Altitude of Current Location
|
||||
// Unit: Meters
|
||||
UiElement DeveloperUi::getAltitude(float gps_accuracy, float altitude) {
|
||||
QString value = (gps_accuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-";
|
||||
QColor color = QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, "ALT.", "m", color);
|
||||
}
|
||||
|
||||
// Add Actuators Output
|
||||
// Unit: Degree (angle) or m/s² (torque)
|
||||
UiElement DeveloperUi::getActuatorsOutputLateral(cereal::CarParams::SteerControlType steerControlType,
|
||||
cereal::CarControl::Actuators::Reader &actuators,
|
||||
float desiredCurvature, float v_ego, float roll, bool lat_active, bool steer_override) {
|
||||
QString label;
|
||||
QString value;
|
||||
QString unit;
|
||||
|
||||
if (steerControlType == cereal::CarParams::SteerControlType::ANGLE) {
|
||||
label = "DESIRED STEER";
|
||||
value = QString("%1%2%3").arg(QString::number(actuators.getSteeringAngleDeg(), 'f', 1)).arg("°").arg("");
|
||||
} else {
|
||||
label = "DESIRED L.A.";
|
||||
double desiredLateralAccel = (desiredCurvature * pow(v_ego, 2)) - (roll * 9.81);
|
||||
value = QString::number(desiredLateralAccel, 'f', 2);
|
||||
unit = "m/s²";
|
||||
}
|
||||
|
||||
value = lat_active ? value : "-";
|
||||
QColor color = lat_active ? (steer_override ? QColor(0x91, 0x9b, 0x95, 0xff) : QColor(0, 255, 0, 255)) : QColor(255, 255, 255, 255);
|
||||
|
||||
return UiElement(value, label, unit, color);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h"
|
||||
|
||||
class DeveloperUi {
|
||||
|
||||
public:
|
||||
static UiElement getDRel(bool lead_status, float lead_d_rel);
|
||||
static UiElement getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit);
|
||||
static UiElement getSteeringAngleDeg(float angle_steers, bool lat_active, bool steer_override);
|
||||
static UiElement getActualLateralAccel(float curvature, float v_ego, float roll, bool lat_active, bool steer_override);
|
||||
static UiElement getSteeringAngleDesiredDeg(bool lat_active, float steer_angle_desired, float angle_steers);
|
||||
static UiElement getMemoryUsagePercent(int memory_usage_percent);
|
||||
static UiElement getAEgo(float a_ego);
|
||||
static UiElement getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit);
|
||||
static UiElement getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid);
|
||||
static UiElement getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid);
|
||||
static UiElement getSteeringTorqueEps(float steering_torque_eps);
|
||||
static UiElement getBearingDeg(float bearing_accuracy_deg, float bearing_deg);
|
||||
static UiElement getAltitude(float gps_accuracy, float altitude);
|
||||
static UiElement getActuatorsOutputLateral(cereal::CarParams::SteerControlType steerControlType,
|
||||
cereal::CarControl::Actuators::Reader &actuators,
|
||||
float desiredCurvature, float v_ego, float roll, bool lat_active, bool steer_override);
|
||||
};
|
||||
19
selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h
Normal file
19
selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
|
||||
struct UiElement {
|
||||
QString value{};
|
||||
QString label{};
|
||||
QString units{};
|
||||
QColor color{};
|
||||
|
||||
explicit UiElement(const QString &value = "", const QString &label = "", const QString &units = "", const QColor &color = QColor(255, 255, 255, 255))
|
||||
: value(value), label(label), units(units), color(color) {}
|
||||
};
|
||||
@@ -4,15 +4,310 @@
|
||||
* 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 <QPainterPath>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/onroad/hud.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
|
||||
HudRendererSP::HudRendererSP() {}
|
||||
|
||||
void HudRendererSP::updateState(const UIState &s) {
|
||||
HudRenderer::updateState(s);
|
||||
|
||||
const SubMaster &sm = *(s.sm);
|
||||
const bool cs_alive = sm.alive("controlsState");
|
||||
const auto cs = sm["controlsState"].getControlsState();
|
||||
const auto car_state = sm["carState"].getCarState();
|
||||
const auto car_control = sm["carControl"].getCarControl();
|
||||
const auto radar_state = sm["radarState"].getRadarState();
|
||||
const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1;
|
||||
const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation();
|
||||
const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters();
|
||||
const auto car_params = sm["carParams"].getCarParams();
|
||||
const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
|
||||
|
||||
static int reverse_delay = 0;
|
||||
bool reverse_allowed = false;
|
||||
if (int(car_state.getGearShifter()) != 4) {
|
||||
reverse_delay = 0;
|
||||
reverse_allowed = false;
|
||||
} else {
|
||||
reverse_delay += 50;
|
||||
if (reverse_delay >= 1000) {
|
||||
reverse_allowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
reversing = reverse_allowed;
|
||||
is_metric = s.scene.is_metric;
|
||||
|
||||
// Handle older routes where vEgoCluster is not set
|
||||
v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0;
|
||||
float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo();
|
||||
speed = cs_alive ? std::max<float>(0.0, v_ego) : 0.0;
|
||||
speed *= is_metric ? MS_TO_KPH : MS_TO_MPH;
|
||||
|
||||
latActive = car_control.getLatActive();
|
||||
steerOverride = car_state.getSteeringPressed();
|
||||
|
||||
devUiInfo = s.scene.dev_ui_info;
|
||||
|
||||
speedUnit = is_metric ? tr("km/h") : tr("mph");
|
||||
lead_d_rel = radar_state.getLeadOne().getDRel();
|
||||
lead_v_rel = radar_state.getLeadOne().getVRel();
|
||||
lead_status = radar_state.getLeadOne().getStatus();
|
||||
steerControlType = car_params.getSteerControlType();
|
||||
actuators = car_control.getActuators();
|
||||
torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE;
|
||||
angleSteers = car_state.getSteeringAngleDeg();
|
||||
desiredCurvature = cs.getDesiredCurvature();
|
||||
curvature = cs.getCurvature();
|
||||
roll = sm["liveParameters"].getLiveParameters().getRoll();
|
||||
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
|
||||
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
|
||||
altitude = gpsLocation.getAltitude();
|
||||
vEgo = car_state.getVEgo();
|
||||
aEgo = car_state.getAEgo();
|
||||
steeringTorqueEps = car_state.getSteeringTorqueEps();
|
||||
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
|
||||
bearingDeg = gpsLocation.getBearingDeg();
|
||||
torquedUseParams = ltp.getUseParams();
|
||||
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
|
||||
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
|
||||
liveValid = ltp.getLiveValid();
|
||||
|
||||
standstillTimer = s.scene.standstill_timer;
|
||||
isStandstill = car_state.getStandstill();
|
||||
longOverride = car_control.getCruiseControl().getOverride();
|
||||
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
|
||||
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
|
||||
}
|
||||
|
||||
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
|
||||
HudRenderer::draw(p, surface_rect);
|
||||
if (!reversing) {
|
||||
// Smart Cruise Control
|
||||
int x_offset = -260;
|
||||
int y1_offset = -80;
|
||||
// int y2_offset = -140; // reserved for 2 icons
|
||||
|
||||
bool scc_vision_active_pulse = pulseElement(smartCruiseControlVisionFrame);
|
||||
if ((smartCruiseControlVisionEnabled && !smartCruiseControlVisionActive) || (smartCruiseControlVisionActive && scc_vision_active_pulse)) {
|
||||
drawSmartCruiseControlOnroadIcon(p, surface_rect, x_offset, y1_offset, "SCC-V");
|
||||
}
|
||||
|
||||
if (smartCruiseControlVisionActive) {
|
||||
smartCruiseControlVisionFrame++;
|
||||
} else {
|
||||
smartCruiseControlVisionFrame = 0;
|
||||
}
|
||||
|
||||
// Bottom Dev UI
|
||||
if (devUiInfo == 2) {
|
||||
QRect rect_bottom(surface_rect.left(), surface_rect.bottom() - 60, surface_rect.width(), 61);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(QColor(0, 0, 0, 100));
|
||||
p.drawRect(rect_bottom);
|
||||
drawBottomDevUI(p, rect_bottom.left(), rect_bottom.center().y());
|
||||
}
|
||||
|
||||
// Right Dev UI
|
||||
if (devUiInfo != 0) {
|
||||
QRect rect_right(surface_rect.right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 170);
|
||||
drawRightDevUI(p, surface_rect.right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rect_right.height());
|
||||
}
|
||||
|
||||
// Standstill Timer
|
||||
if (standstillTimer) {
|
||||
drawStandstillTimer(p, surface_rect.right() / 12 * 10, surface_rect.bottom() / 12 * 1.53);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HudRendererSP::drawText(QPainter &p, int x, int y, const QString &text, QColor color) {
|
||||
QRect real_rect = p.fontMetrics().boundingRect(text);
|
||||
real_rect.moveCenter({x, y - real_rect.height() / 2});
|
||||
p.setPen(color);
|
||||
p.drawText(real_rect.x(), real_rect.bottom(), text);
|
||||
}
|
||||
|
||||
bool HudRendererSP::pulseElement(int frame) {
|
||||
if (frame % UI_FREQ < (UI_FREQ / 2.5)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &surface_rect, int x_offset, int y_offset, std::string name) {
|
||||
int x = surface_rect.center().x();
|
||||
int y = surface_rect.height() / 4;
|
||||
|
||||
QString text = QString::fromStdString(name);
|
||||
QFont font = InterFont(36, QFont::Bold);
|
||||
p.setFont(font);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
|
||||
int padding_v = 5;
|
||||
int box_width = 160;
|
||||
int box_height = fm.height() + padding_v * 2;
|
||||
|
||||
QRectF bg_rect(x - (box_width / 2) + x_offset,
|
||||
y - (box_height / 2) + y_offset,
|
||||
box_width, box_height);
|
||||
|
||||
QPainterPath boxPath;
|
||||
boxPath.addRoundedRect(bg_rect, 10, 10);
|
||||
|
||||
int text_w = fm.horizontalAdvance(text);
|
||||
qreal baseline_y = bg_rect.top() + padding_v + fm.ascent();
|
||||
qreal text_x = bg_rect.center().x() - (text_w / 2.0);
|
||||
|
||||
QPainterPath textPath;
|
||||
textPath.addText(QPointF(text_x, baseline_y), font, text);
|
||||
boxPath = boxPath.subtracted(textPath);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(longOverride ? QColor(0x91, 0x9b, 0x95, 0xf1) : QColor(0, 0xff, 0, 0xff));
|
||||
p.drawPath(boxPath);
|
||||
}
|
||||
|
||||
int HudRendererSP::drawRightDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
|
||||
|
||||
p.setFont(InterFont(28, QFont::Bold));
|
||||
x += 92;
|
||||
y += 80;
|
||||
drawText(p, x, y, label);
|
||||
|
||||
p.setFont(InterFont(30 * 2, QFont::Bold));
|
||||
y += 65;
|
||||
drawText(p, x, y, value, color);
|
||||
|
||||
p.setFont(InterFont(28, QFont::Bold));
|
||||
|
||||
if (units.length() > 0) {
|
||||
p.save();
|
||||
x += 120;
|
||||
y -= 25;
|
||||
p.translate(x, y);
|
||||
p.rotate(-90);
|
||||
drawText(p, 0, 0, units);
|
||||
p.restore();
|
||||
}
|
||||
|
||||
return 130;
|
||||
}
|
||||
|
||||
void HudRendererSP::drawRightDevUI(QPainter &p, int x, int y) {
|
||||
int rh = 5;
|
||||
int ry = y;
|
||||
|
||||
UiElement dRelElement = DeveloperUi::getDRel(lead_status, lead_d_rel);
|
||||
rh += drawRightDevUIElement(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color);
|
||||
ry = y + rh;
|
||||
|
||||
UiElement vRelElement = DeveloperUi::getVRel(lead_status, lead_v_rel, is_metric, speedUnit);
|
||||
rh += drawRightDevUIElement(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color);
|
||||
ry = y + rh;
|
||||
|
||||
UiElement steeringAngleDegElement = DeveloperUi::getSteeringAngleDeg(angleSteers, latActive, steerOverride);
|
||||
rh += drawRightDevUIElement(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color);
|
||||
ry = y + rh;
|
||||
|
||||
UiElement actuatorsOutputLateralElement = DeveloperUi::getActuatorsOutputLateral(steerControlType, actuators, desiredCurvature, vEgo, roll, latActive, steerOverride);
|
||||
rh += drawRightDevUIElement(p, x, ry, actuatorsOutputLateralElement.value, actuatorsOutputLateralElement.label, actuatorsOutputLateralElement.units, actuatorsOutputLateralElement.color);
|
||||
ry = y + rh;
|
||||
|
||||
UiElement actualLateralAccelElement = DeveloperUi::getActualLateralAccel(curvature, vEgo, roll, latActive, steerOverride);
|
||||
rh += drawRightDevUIElement(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color);
|
||||
}
|
||||
|
||||
int HudRendererSP::drawBottomDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) {
|
||||
p.setFont(InterFont(38, QFont::Bold));
|
||||
QFontMetrics fm(p.font());
|
||||
QRect init_rect = fm.boundingRect(label + " ");
|
||||
QRect real_rect = fm.boundingRect(init_rect, 0, label + " ");
|
||||
real_rect.moveCenter({x, y});
|
||||
|
||||
QRect init_rect2 = fm.boundingRect(value);
|
||||
QRect real_rect2 = fm.boundingRect(init_rect2, 0, value);
|
||||
real_rect2.moveTop(real_rect.top());
|
||||
real_rect2.moveLeft(real_rect.right() + 10);
|
||||
|
||||
QRect init_rect3 = fm.boundingRect(units);
|
||||
QRect real_rect3 = fm.boundingRect(init_rect3, 0, units);
|
||||
real_rect3.moveTop(real_rect.top());
|
||||
real_rect3.moveLeft(real_rect2.right() + 10);
|
||||
|
||||
p.setPen(Qt::white);
|
||||
p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, label);
|
||||
|
||||
p.setPen(color);
|
||||
p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, value);
|
||||
p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, units);
|
||||
return 430;
|
||||
}
|
||||
|
||||
void HudRendererSP::drawBottomDevUI(QPainter &p, int x, int y) {
|
||||
int rw = 90;
|
||||
|
||||
UiElement aEgoElement = DeveloperUi::getAEgo(aEgo);
|
||||
rw += drawBottomDevUIElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color);
|
||||
|
||||
UiElement vEgoLeadElement = DeveloperUi::getVEgoLead(lead_status, lead_v_rel, vEgo, is_metric, speedUnit);
|
||||
rw += drawBottomDevUIElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color);
|
||||
|
||||
if (torqueLateral && torquedUseParams) {
|
||||
UiElement frictionCoefficientFilteredElement = DeveloperUi::getFrictionCoefficientFiltered(frictionCoefficientFiltered, liveValid);
|
||||
rw += drawBottomDevUIElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color);
|
||||
|
||||
UiElement latAccelFactorFilteredElement = DeveloperUi::getLatAccelFactorFiltered(latAccelFactorFiltered, liveValid);
|
||||
rw += drawBottomDevUIElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color);
|
||||
} else {
|
||||
UiElement steeringTorqueEpsElement = DeveloperUi::getSteeringTorqueEps(steeringTorqueEps);
|
||||
rw += drawBottomDevUIElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color);
|
||||
|
||||
UiElement bearingDegElement = DeveloperUi::getBearingDeg(bearingAccuracyDeg, bearingDeg);
|
||||
rw += drawBottomDevUIElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color);
|
||||
}
|
||||
|
||||
UiElement altitudeElement = DeveloperUi::getAltitude(gpsAccuracy, altitude);
|
||||
rw += drawBottomDevUIElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color);
|
||||
}
|
||||
|
||||
void HudRendererSP::drawStandstillTimer(QPainter &p, int x, int y) {
|
||||
if (isStandstill) {
|
||||
standstillElapsedTime += 1.0 / UI_FREQ;
|
||||
|
||||
int minute = static_cast<int>(standstillElapsedTime / 60);
|
||||
int second = static_cast<int>(standstillElapsedTime - (minute * 60));
|
||||
|
||||
// stop sign for standstill timer
|
||||
const int size = 190; // size
|
||||
const float angle = M_PI / 8.0;
|
||||
|
||||
QPolygon octagon;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
float curr_angle = angle + i * M_PI / 4.0;
|
||||
int point_x = x + size / 2 * cos(curr_angle);
|
||||
int point_y = y + size / 2 * sin(curr_angle);
|
||||
octagon << QPoint(point_x, point_y);
|
||||
}
|
||||
|
||||
p.setPen(QPen(Qt::white, 6));
|
||||
p.setBrush(QColor(255, 90, 81, 200)); // red pastel
|
||||
p.drawPolygon(octagon);
|
||||
|
||||
QString time_str = QString("%1:%2").arg(minute, 1, 10, QChar('0')).arg(second, 2, 10, QChar('0'));
|
||||
p.setFont(InterFont(55, QFont::Bold));
|
||||
p.setPen(Qt::white);
|
||||
QRect timerTextRect = p.fontMetrics().boundingRect(QString(time_str));
|
||||
timerTextRect.moveCenter({x, y});
|
||||
p.drawText(timerTextRect, Qt::AlignCenter, QString(time_str));
|
||||
} else {
|
||||
standstillElapsedTime = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "selfdrive/ui/qt/onroad/hud.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h"
|
||||
|
||||
class HudRendererSP : public HudRenderer {
|
||||
Q_OBJECT
|
||||
@@ -18,4 +17,50 @@ public:
|
||||
HudRendererSP();
|
||||
void updateState(const UIState &s) override;
|
||||
void draw(QPainter &p, const QRect &surface_rect) override;
|
||||
|
||||
private:
|
||||
Params params;
|
||||
void drawText(QPainter &p, int x, int y, const QString &text, QColor color = Qt::white);
|
||||
void drawRightDevUI(QPainter &p, int x, int y);
|
||||
int drawRightDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
|
||||
int drawBottomDevUIElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
|
||||
void drawBottomDevUI(QPainter &p, int x, int y);
|
||||
void drawStandstillTimer(QPainter &p, int x, int y);
|
||||
bool pulseElement(int frame);
|
||||
void drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &surface_rect, int x_offset, int y_offset, std::string name);
|
||||
|
||||
bool lead_status;
|
||||
float lead_d_rel;
|
||||
float lead_v_rel;
|
||||
bool torqueLateral;
|
||||
float angleSteers;
|
||||
float desiredCurvature;
|
||||
float curvature;
|
||||
float roll;
|
||||
int memoryUsagePercent;
|
||||
int devUiInfo;
|
||||
float gpsAccuracy;
|
||||
float altitude;
|
||||
float vEgo;
|
||||
float aEgo;
|
||||
float steeringTorqueEps;
|
||||
float bearingAccuracyDeg;
|
||||
float bearingDeg;
|
||||
bool torquedUseParams;
|
||||
float latAccelFactorFiltered;
|
||||
float frictionCoefficientFiltered;
|
||||
bool liveValid;
|
||||
QString speedUnit;
|
||||
bool latActive;
|
||||
bool steerOverride;
|
||||
bool reversing;
|
||||
cereal::CarParams::SteerControlType steerControlType;
|
||||
cereal::CarControl::Actuators::Reader actuators;
|
||||
bool standstillTimer;
|
||||
bool isStandstill;
|
||||
float standstillElapsedTime;
|
||||
bool longOverride;
|
||||
bool smartCruiseControlVisionEnabled;
|
||||
bool smartCruiseControlVisionActive;
|
||||
int smartCruiseControlVisionFrame;
|
||||
};
|
||||
|
||||
@@ -110,3 +110,16 @@ QStringList searchFromList(const QString &query, const QStringList &list) {
|
||||
}
|
||||
return search_results;
|
||||
}
|
||||
|
||||
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param) {
|
||||
std::string bytes = params.get(_param);
|
||||
|
||||
try {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(bytes.data(), bytes.size()));
|
||||
return cmsg.getRoot<cereal::Event>();
|
||||
} catch (kj::Exception& e) {
|
||||
qInfo() << "invalid " << QString::fromStdString(_param) << ":" << e.getDescription().cStr();
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
|
||||
QString getUserAgent(bool sunnylink = false);
|
||||
std::optional<QString> getSunnylinkDongleId();
|
||||
std::optional<QString> getParamIgnoringDefault(const std::string ¶m_name, const std::string &default_value);
|
||||
QMap<QString, QVariantMap> loadPlatformList();
|
||||
QStringList searchFromList(const QString &query, const QStringList &list);
|
||||
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param);
|
||||
|
||||
@@ -18,13 +18,23 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) {
|
||||
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState",
|
||||
"pandaStates", "carParams", "driverMonitoringState", "carState", "driverStateV2",
|
||||
"wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan",
|
||||
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP"
|
||||
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
|
||||
"carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters",
|
||||
"carStateSP", "liveParameters"
|
||||
});
|
||||
|
||||
// update timer
|
||||
timer = new QTimer(this);
|
||||
QObject::connect(timer, &QTimer::timeout, this, &UIStateSP::update);
|
||||
timer->start(1000 / UI_FREQ);
|
||||
|
||||
// Param watcher for UIScene param updates
|
||||
param_watcher = new ParamWatcher(this);
|
||||
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
|
||||
ui_update_params_sp(this);
|
||||
});
|
||||
param_watcher->addParam("DevUIInfo");
|
||||
param_watcher->addParam("StandstillTimer");
|
||||
}
|
||||
|
||||
// This method overrides completely the update method from the parent class intentionally.
|
||||
@@ -39,6 +49,12 @@ void UIStateSP::update() {
|
||||
emit uiUpdate(*this);
|
||||
}
|
||||
|
||||
void ui_update_params_sp(UIStateSP *s) {
|
||||
auto params = Params();
|
||||
s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str());
|
||||
s->scene.standstill_timer = params.getBool("StandstillTimer");
|
||||
}
|
||||
|
||||
DeviceSP::DeviceSP(QObject *parent) : Device(parent) {
|
||||
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &DeviceSP::update);
|
||||
QObject::connect(this, &Device::displayPowerChanged, this, &DeviceSP::handleDisplayPowerChanged);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
class UIStateSP : public UIState {
|
||||
Q_OBJECT
|
||||
@@ -73,6 +74,7 @@ private slots:
|
||||
private:
|
||||
std::vector<RoleModel> sunnylinkRoles = {};
|
||||
std::vector<UserModel> sunnylinkUsers = {};
|
||||
ParamWatcher *param_watcher;
|
||||
};
|
||||
|
||||
UIStateSP *uiStateSP();
|
||||
@@ -92,3 +94,5 @@ private:
|
||||
|
||||
DeviceSP *deviceSP();
|
||||
inline DeviceSP *device() { return deviceSP(); }
|
||||
|
||||
void ui_update_params_sp(UIStateSP *s);
|
||||
|
||||
13
selfdrive/ui/sunnypilot/ui_scene.h
Normal file
13
selfdrive/ui/sunnypilot/ui_scene.h
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct UISceneSP : UIScene {
|
||||
int dev_ui_info = 0;
|
||||
bool standstill_timer = false;
|
||||
} UISceneSP;
|
||||
@@ -29,7 +29,7 @@ UI_DELAY = 0.5 # may be slower on CI?
|
||||
TEST_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19"
|
||||
|
||||
STREAMS: list[tuple[VisionStreamType, CameraConfig, bytes]] = []
|
||||
OFFROAD_ALERTS = ['Offroad_StorageMissing', 'Offroad_IsTakingSnapshot']
|
||||
OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot', ]
|
||||
DATA: dict[str, capnp.lib.capnp._DynamicStructBuilder] = dict.fromkeys(
|
||||
["carParams", "deviceState", "pandaStates", "controlsState", "selfdriveState",
|
||||
"liveCalibration", "modelV2", "radarState", "driverMonitoringState", "carState",
|
||||
|
||||
@@ -55,8 +55,7 @@ void update_state(UIState *s) {
|
||||
}
|
||||
if (sm.updated("wideRoadCameraState")) {
|
||||
auto cam_state = sm["wideRoadCameraState"].getWideRoadCameraState();
|
||||
float scale = (cam_state.getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f;
|
||||
scene.light_sensor = std::max(100.0f - scale * cam_state.getExposureValPercent(), 0.0f);
|
||||
scene.light_sensor = std::max(100.0f - cam_state.getExposureValPercent(), 0.0f);
|
||||
} else if (!sm.allAliveAndValid({"wideRoadCameraState"})) {
|
||||
scene.light_sensor = -1;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,11 @@ typedef struct UIScene {
|
||||
uint64_t started_frame;
|
||||
} UIScene;
|
||||
|
||||
#ifdef SUNNYPILOT
|
||||
#include "sunnypilot/ui_scene.h"
|
||||
#define UIScene UISceneSP
|
||||
#endif
|
||||
|
||||
class UIState : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -105,10 +105,7 @@ class UIState:
|
||||
# Handle wide road camera state updates
|
||||
if self.sm.updated["wideRoadCameraState"]:
|
||||
cam_state = self.sm["wideRoadCameraState"]
|
||||
|
||||
# Scale factor based on sensor type
|
||||
scale = 6.0 if cam_state.sensor == 'ar0231' else 1.0
|
||||
self.light_sensor = max(100.0 - scale * cam_state.exposureValPercent, 0.0)
|
||||
self.light_sensor = max(100.0 - cam_state.exposureValPercent, 0.0)
|
||||
elif not self.sm.alive["wideRoadCameraState"] or not self.sm.valid["wideRoadCameraState"]:
|
||||
self.light_sensor = -1
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
PARAMS_UPDATE_PERIOD = 3 # seconds
|
||||
|
||||
@@ -3,6 +3,7 @@ import capnp
|
||||
import numpy as np
|
||||
from cereal import log
|
||||
from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan
|
||||
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_lag_adjusted_curvature, MIN_SPEED
|
||||
|
||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||
@@ -120,23 +121,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
modelV2_action.desiredCurvature = desired_curvature
|
||||
|
||||
# times at X_IDXS according to model plan
|
||||
PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N
|
||||
PLAN_T_IDXS[0] = 0.0
|
||||
plan_x = net_output_data['plan'][0,:,Plan.POSITION][:,0].tolist()
|
||||
for xidx in range(1, ModelConstants.IDX_N):
|
||||
tidx = 0
|
||||
# increment tidx until we find an element that's further away than the current xidx
|
||||
while tidx < ModelConstants.IDX_N - 1 and plan_x[tidx+1] < ModelConstants.X_IDXS[xidx]:
|
||||
tidx += 1
|
||||
if tidx == ModelConstants.IDX_N - 1:
|
||||
# if the Plan doesn't extend far enough, set plan_t to the max value (10s), then break
|
||||
PLAN_T_IDXS[xidx] = ModelConstants.T_IDXS[ModelConstants.IDX_N - 1]
|
||||
break
|
||||
# interpolate to find `t` for the current xidx
|
||||
current_x_val = plan_x[tidx]
|
||||
next_x_val = plan_x[tidx+1]
|
||||
p = (ModelConstants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) if abs(next_x_val - current_x_val) > 1e-9 else float('nan')
|
||||
PLAN_T_IDXS[xidx] = p * ModelConstants.T_IDXS[tidx+1] + (1 - p) * ModelConstants.T_IDXS[tidx]
|
||||
PLAN_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
|
||||
|
||||
# lane lines
|
||||
modelV2.init('laneLines', 4)
|
||||
|
||||
@@ -177,7 +177,7 @@ 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})")
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
@@ -304,6 +304,7 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
posenet_send = messaging.new_message('cameraOdometry')
|
||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||
action = model.get_action_from_model(model_output, prev_action, long_delay + DT_MDL)
|
||||
fill_model_msg(drivingdata_send, modelv2_send, model_output, action, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
||||
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen,
|
||||
@@ -316,6 +317,7 @@ def main(demo=False):
|
||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_controller.turn_direction
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
@@ -323,6 +325,7 @@ def main(demo=False):
|
||||
pm.send('modelV2', modelv2_send)
|
||||
pm.send('drivingModelData', drivingdata_send)
|
||||
pm.send('cameraOdometry', posenet_send)
|
||||
pm.send('modelDataV2SP', mdv2sp_send)
|
||||
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import capnp
|
||||
import numpy as np
|
||||
from cereal import log
|
||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants, Plan
|
||||
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import get_curvature_from_plan
|
||||
|
||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||
@@ -118,8 +119,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
# action (includes lateral planning now)
|
||||
modelV2.action = action
|
||||
|
||||
# times at X_IDXS of edges and lines aren't used
|
||||
LINE_T_IDXS: list[float] = []
|
||||
# times at X_IDXS of edges and lines
|
||||
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
|
||||
|
||||
# lane lines
|
||||
modelV2.init('laneLines', 4)
|
||||
|
||||
@@ -27,7 +27,7 @@ from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
|
||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||
from openpilot.sunnypilot.models.runners.helpers import get_model_runner
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld"
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad"
|
||||
|
||||
|
||||
class FrameMeta:
|
||||
@@ -77,42 +77,47 @@ class ModelState(ModelStateBase):
|
||||
self.numpy_inputs[key] = np.zeros(shape, dtype=np.float32)
|
||||
# Temporal input: shape is [batch, history, features]
|
||||
if len(shape) == 3 and shape[1] > 1:
|
||||
buffer_history_len = max(100, (shape[1] * 4 if shape[1] < 100 else shape[1])) # Allow for higher history buffers in the future
|
||||
buffer_history_len = shape[1] * 4 if shape[1] < 99 else shape[1] # Allow for higher history buffers in the future
|
||||
feature_len = shape[2]
|
||||
self.temporal_buffers[key] = np.zeros((1, buffer_history_len, feature_len), dtype=np.float32)
|
||||
features_buffer_shape = self.model_runner.input_shapes.get('features_buffer')
|
||||
if shape[1] in (24, 25) and features_buffer_shape is not None and features_buffer_shape[1] == 24: # 20Hz
|
||||
buffer_history_len = (features_buffer_shape[1] + 1) * 4
|
||||
step = int(-buffer_history_len / shape[1])
|
||||
self.temporal_idxs_map[key] = np.arange(step, step * (shape[1] + 1), step)[::-1]
|
||||
elif shape[1] == 25: # Split
|
||||
skip = buffer_history_len // shape[1]
|
||||
self.temporal_idxs_map[key] = np.arange(buffer_history_len)[-1 - (skip * (shape[1] - 1))::skip]
|
||||
elif shape[1] == buffer_history_len: # non20hz
|
||||
self.temporal_idxs_map[key] = np.arange(buffer_history_len)
|
||||
elif shape[1] >= 99: # non20hz
|
||||
self.temporal_idxs_map[key] = np.arange(shape[1])
|
||||
self.temporal_buffers[key] = np.zeros((1, buffer_history_len, feature_len), dtype=np.float32)
|
||||
|
||||
@property
|
||||
def mlsim(self) -> bool:
|
||||
return bool(self.generation is not None and self.generation >= 11)
|
||||
|
||||
@property
|
||||
def desire_key(self) -> str:
|
||||
return next(key for key in self.numpy_inputs if key.startswith('desire'))
|
||||
|
||||
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:
|
||||
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
||||
inputs['desire'][0] = 0
|
||||
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
|
||||
self.prev_desire[:] = inputs['desire']
|
||||
self.temporal_buffers['desire'][0,:-1] = self.temporal_buffers['desire'][0,1:]
|
||||
self.temporal_buffers['desire'][0,-1] = new_desire
|
||||
inputs[self.desire_key][0] = 0
|
||||
new_desire = np.where(inputs[self.desire_key] - self.prev_desire > .99, inputs[self.desire_key], 0)
|
||||
self.prev_desire[:] = inputs[self.desire_key]
|
||||
self.temporal_buffers[self.desire_key][0,:-1] = self.temporal_buffers[self.desire_key][0,1:]
|
||||
self.temporal_buffers[self.desire_key][0,-1] = new_desire
|
||||
|
||||
# Roll buffer and assign based on desire.shape[1] value
|
||||
if self.temporal_buffers['desire'].shape[1] > self.numpy_inputs['desire'].shape[1]:
|
||||
skip = self.temporal_buffers['desire'].shape[1] // self.numpy_inputs['desire'].shape[1]
|
||||
self.numpy_inputs['desire'][:] = (
|
||||
self.temporal_buffers['desire'][0].reshape(self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], skip, -1).max(axis=2))
|
||||
if self.temporal_buffers[self.desire_key].shape[1] > self.numpy_inputs[self.desire_key].shape[1]:
|
||||
skip = self.temporal_buffers[self.desire_key].shape[1] // self.numpy_inputs[self.desire_key].shape[1]
|
||||
self.numpy_inputs[self.desire_key][:] = (self.temporal_buffers[self.desire_key][0].reshape(
|
||||
self.numpy_inputs[self.desire_key].shape[0], self.numpy_inputs[self.desire_key].shape[1], skip, -1).max(axis=2))
|
||||
else:
|
||||
self.numpy_inputs['desire'][:] = self.temporal_buffers['desire'][0, self.temporal_idxs_map['desire']]
|
||||
self.numpy_inputs[self.desire_key][:] = self.temporal_buffers[self.desire_key][0, self.temporal_idxs_map[self.desire_key]]
|
||||
|
||||
for key in self.numpy_inputs:
|
||||
if key in inputs and key not in ['desire']:
|
||||
if key in inputs and key not in [self.desire_key]:
|
||||
self.numpy_inputs[key][:] = inputs[key]
|
||||
|
||||
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.model_runner.vision_input_names}
|
||||
@@ -156,10 +161,11 @@ class ModelState(ModelStateBase):
|
||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
|
||||
|
||||
desired_curvature = get_curvature_from_output(model_output, v_ego, lat_action_t, self.mlsim)
|
||||
if v_ego > self.MIN_LAT_CONTROL_SPEED:
|
||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
|
||||
else:
|
||||
desired_curvature = prev_action.desiredCurvature
|
||||
if self.generation is not None and self.generation >= 10: # smooth curvature for post FOF models
|
||||
if v_ego > self.MIN_LAT_CONTROL_SPEED:
|
||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
|
||||
else:
|
||||
desired_curvature = prev_action.desiredCurvature
|
||||
|
||||
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),desiredAcceleration=float(desired_accel), shouldStop=bool(should_stop))
|
||||
|
||||
@@ -202,7 +208,7 @@ 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})")
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
@@ -306,7 +312,7 @@ def main(demo=False):
|
||||
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.model_runner.vision_input_names}
|
||||
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.model_runner.vision_input_names}
|
||||
inputs:dict[str, np.ndarray] = {
|
||||
'desire': vec_desire,
|
||||
model.desire_key: vec_desire,
|
||||
'traffic_convention': traffic_convention,
|
||||
}
|
||||
|
||||
@@ -322,6 +328,7 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
posenet_send = messaging.new_message('cameraOdometry')
|
||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||
|
||||
action = model.get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
||||
prev_action = action
|
||||
@@ -336,6 +343,7 @@ def main(demo=False):
|
||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_controller.turn_direction
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
@@ -343,6 +351,7 @@ def main(demo=False):
|
||||
pm.send('modelV2', modelv2_send)
|
||||
pm.send('drivingModelData', drivingdata_send)
|
||||
pm.send('cameraOdometry', posenet_send)
|
||||
pm.send('modelDataV2SP', mdv2sp_send)
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,16 @@ import openpilot.sunnypilot.modeld_v2.modeld as modeld_module
|
||||
|
||||
ModelState = modeld_module.ModelState
|
||||
|
||||
|
||||
# These are the shapes extracted/loaded from the model onnx
|
||||
SHAPE_MODE_PARAMS = [
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 25, 512), 'prev_desired_curv': (1, 25, 1)}, 'split'),
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 24, 512), 'prev_desired_curv': (1, 25, 1)}, '20hz'),
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1)}, 'non20hz'),
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "nav_features": (1, 256), "nav_instructions": (1, 150)}, 'non20hz'), # Optimus Prime
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "lat_planner_state": (1, 4),}, 'non20hz'), # farmville
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "lateral_control_params": (1, 2), "prev_desired_curv": (1, 100, 1)}, 'non20hz'), # wd40
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1), "lateral_control_params": (1, 2),}, 'non20hz'), # NTS
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 24, 512)}, '20hz'), # NPR
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1), "lateral_control_params": (1, 2),}, 'non20hz'), # NTS
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 25, 512)}, 'split'), # Steam Powered v2
|
||||
({'desire_pulse': (1, 25, 8), 'features_buffer': (1, 25, 512)}, 'split'), # desire rename
|
||||
]
|
||||
|
||||
|
||||
@@ -95,9 +99,7 @@ def get_expected_indices(shape, constants, mode, key=None):
|
||||
idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
|
||||
return idxs
|
||||
elif mode == 'non20hz':
|
||||
if key and shape[1] == constants.FULL_HISTORY_BUFFER_LEN:
|
||||
return np.arange(constants.FULL_HISTORY_BUFFER_LEN)
|
||||
return None
|
||||
return np.arange(shape[1])
|
||||
return None
|
||||
|
||||
|
||||
@@ -108,6 +110,8 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
|
||||
for key in shapes:
|
||||
buf = state.temporal_buffers.get(key, None)
|
||||
idxs = state.temporal_idxs_map.get(key, None)
|
||||
if buf is None:
|
||||
continue # not all shapes are 3D, and the non-3D ones are not buffered
|
||||
# Buffer shape logic
|
||||
if mode == 'split':
|
||||
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
|
||||
@@ -116,10 +120,7 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
|
||||
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
|
||||
expected_idxs = get_expected_indices(shapes[key], constants, '20hz', key)
|
||||
elif mode == 'non20hz':
|
||||
if key == 'features_buffer':
|
||||
expected_shape = (1, shapes[key][1]*4, shapes[key][2])
|
||||
else:
|
||||
expected_shape = (1, shapes[key][1], shapes[key][2])
|
||||
expected_shape = (1, shapes[key][1], shapes[key][2])
|
||||
expected_idxs = get_expected_indices(shapes[key], constants, 'non20hz', key)
|
||||
|
||||
assert buf is not None, f"{key}: buffer not found"
|
||||
@@ -130,10 +131,10 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
|
||||
assert idxs is None or idxs.size == 0, f"{key}: buffer idxs should be None or empty"
|
||||
|
||||
|
||||
def legacy_buffer_update(buf, new_val, mode, key, constants, idxs):
|
||||
def legacy_buffer_update(buf, new_val, mode, key, constants, idxs, input_shape, prev_desire=None):
|
||||
# This is what we compare the new dynamic logic to, to ensure it does the same thing
|
||||
if mode == 'split':
|
||||
if key == 'desire':
|
||||
if key == 'desire' or key.startswith('desire'):
|
||||
buf[0,:-1] = buf[0,1:]
|
||||
buf[0,-1] = new_val
|
||||
return buf.reshape((1, constants.INPUT_HISTORY_BUFFER_LEN, constants.TEMPORAL_SKIP, -1)).max(axis=2)
|
||||
@@ -173,15 +174,22 @@ def legacy_buffer_update(buf, new_val, mode, key, constants, idxs):
|
||||
return legacy_buf[idxs]
|
||||
elif mode == 'non20hz':
|
||||
if key == 'desire':
|
||||
length = new_val.shape[0]
|
||||
buf[0,:-1,:length] = buf[0,1:,:length]
|
||||
buf[0,-1,:length] = new_val[:length]
|
||||
desire_len = constants.DESIRE_LEN
|
||||
if prev_desire is None:
|
||||
prev_desire = np.zeros(desire_len, dtype=np.float32)
|
||||
# Set first element to zero
|
||||
new_val = new_val.copy()
|
||||
new_val[0] = 0
|
||||
# Shift buffer by desire len
|
||||
buf[0][:-desire_len] = buf[0][desire_len:]
|
||||
# Only insert new desire if rising edge
|
||||
buf[0][-desire_len:] = np.where(new_val - prev_desire > 0.99, new_val, 0)
|
||||
prev_desire[:] = new_val
|
||||
return buf[0]
|
||||
elif key == 'features_buffer':
|
||||
feature_len = new_val.shape[0]
|
||||
buf[0,:-1,:feature_len] = buf[0,1:,:feature_len]
|
||||
buf[0,-1,:feature_len] = new_val[:feature_len]
|
||||
return buf[0]
|
||||
buf[0, :-1] = buf[0, 1:]
|
||||
buf[0, -1] = new_val
|
||||
return buf[0, -input_shape[1]:] # (99, 512)
|
||||
elif key == 'prev_desired_curv':
|
||||
length = new_val.shape[0]
|
||||
buf[0,:-length,0] = buf[0,length:,0]
|
||||
@@ -191,32 +199,18 @@ def legacy_buffer_update(buf, new_val, mode, key, constants, idxs):
|
||||
|
||||
|
||||
def dynamic_buffer_update(state, key, new_val, mode):
|
||||
if key == 'desire':
|
||||
state.temporal_buffers['desire'][0,:-1] = state.temporal_buffers['desire'][0,1:]
|
||||
state.temporal_buffers['desire'][0,-1] = new_val
|
||||
if state.temporal_buffers['desire'].shape[1] > state.numpy_inputs['desire'].shape[1]:
|
||||
skip = state.temporal_buffers['desire'].shape[1] // state.numpy_inputs['desire'].shape[1]
|
||||
return state.temporal_buffers['desire'][0].reshape(
|
||||
state.numpy_inputs['desire'].shape[0], state.numpy_inputs['desire'].shape[1], skip, -1
|
||||
).max(axis=2)
|
||||
else:
|
||||
return state.temporal_buffers['desire'][0, state.temporal_idxs_map['desire']]
|
||||
|
||||
inputs = {'desire': np.zeros((1, state.constants.DESIRE_LEN), dtype=np.float32)}
|
||||
for k, tb in state.temporal_buffers.items():
|
||||
if k in state.temporal_idxs_map:
|
||||
continue
|
||||
buf_len = tb.shape[1]
|
||||
if k in state.numpy_inputs:
|
||||
out_len = state.numpy_inputs[k].shape[1]
|
||||
if out_len <= buf_len:
|
||||
state.temporal_idxs_map[k] = np.arange(buf_len)[-out_len:]
|
||||
else:
|
||||
state.temporal_idxs_map[k] = np.arange(buf_len)
|
||||
else:
|
||||
state.temporal_idxs_map[k] = np.arange(buf_len)
|
||||
if key == 'desire' or key.startswith('desire'):
|
||||
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
|
||||
for k, v in state.model_runner.input_shapes.items() if k != key}
|
||||
inputs[key] = new_val.copy()
|
||||
# ModelState.run expects desire as a pulse, so we zero the first element.
|
||||
inputs[key][0] = 0
|
||||
state.run({}, {}, inputs, prepare_only=False)
|
||||
return state.numpy_inputs[key]
|
||||
|
||||
if key == 'features_buffer':
|
||||
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
|
||||
for k, v in state.model_runner.input_shapes.items() if k != 'features_buffer'}
|
||||
def run_model_stub():
|
||||
return {
|
||||
'hidden_state': np.asarray(new_val, dtype=np.float32).reshape(1, -1),
|
||||
@@ -226,6 +220,8 @@ def dynamic_buffer_update(state, key, new_val, mode):
|
||||
return state.numpy_inputs['features_buffer'][0]
|
||||
|
||||
if key == 'prev_desired_curv':
|
||||
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
|
||||
for k, v in state.model_runner.input_shapes.items() if k != 'prev_desired_curv'}
|
||||
def run_model_stub():
|
||||
return {
|
||||
'hidden_state': np.zeros((1, state.constants.FEATURE_LEN), dtype=np.float32),
|
||||
@@ -241,16 +237,27 @@ def dynamic_buffer_update(state, key, new_val, mode):
|
||||
@pytest.mark.parametrize("key", ["desire", "features_buffer", "prev_desired_curv"])
|
||||
def test_buffer_update_equivalence(shapes, mode, key, apply_patches):
|
||||
state = ModelState(None)
|
||||
if key == "desire":
|
||||
desire_keys = [k for k in shapes.keys() if k.startswith('desire')]
|
||||
if desire_keys:
|
||||
actual_key = desire_keys[0] # Use the first (and likely only) desire key
|
||||
else:
|
||||
actual_key = key
|
||||
|
||||
if actual_key not in state.numpy_inputs:
|
||||
pytest.skip()
|
||||
|
||||
constants = DummyModelRunner(shapes).constants
|
||||
buf = state.temporal_buffers.get(key, None)
|
||||
idxs = state.temporal_idxs_map.get(key, None)
|
||||
input_shape = shapes[key]
|
||||
buf = state.temporal_buffers.get(actual_key, None)
|
||||
idxs = state.temporal_idxs_map.get(actual_key, None)
|
||||
input_shape = shapes[actual_key]
|
||||
prev_desire = np.zeros(constants.DESIRE_LEN, dtype=np.float32) if key == 'desire' else None
|
||||
|
||||
for step in range(20): # multiple steps to ensure history is built up
|
||||
new_val = np.full((input_shape[2],), step, dtype=np.float32)
|
||||
expected = legacy_buffer_update(buf, new_val, mode, key, constants, idxs)
|
||||
actual = dynamic_buffer_update(state, key, new_val, mode)
|
||||
# Model returns the reduced numpy_inputs history, compare the last n entries so the test is checking the same slices.
|
||||
expected = legacy_buffer_update(buf, new_val, mode, actual_key, constants, idxs, input_shape, prev_desire)
|
||||
actual = dynamic_buffer_update(state, actual_key, new_val, mode)
|
||||
if expected is not None and actual is not None and expected.shape != actual.shape:
|
||||
if expected.ndim == 2 and actual.ndim == 2 and expected.shape[1] == actual.shape[1]:
|
||||
expected = expected[-actual.shape[0]:]
|
||||
assert np.allclose(actual, expected), f"{mode} {key}: dynamic buffer update does not match legacy logic"
|
||||
assert np.allclose(actual, expected), f"{mode} {actual_key}: dynamic buffer update does not match legacy logic"
|
||||
|
||||
@@ -8,6 +8,7 @@ See the LICENSE.md file in the root directory for more details.
|
||||
import time
|
||||
|
||||
import requests
|
||||
from requests.exceptions import (SSLError, RequestException, HTTPError)
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from sunnypilot.models.helpers import is_bundle_version_compatible
|
||||
@@ -122,19 +123,36 @@ class ModelFetcher:
|
||||
self.model_cache = ModelCache(params)
|
||||
self.model_parser = ModelParser()
|
||||
|
||||
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
"""Fetches fresh model data from remote and updates cache"""
|
||||
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
||||
"""Fetches fresh model data from remote and updates cache.
|
||||
Returns None on transport errors. Raises on 404 and other fatal HTTP errors.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(self.MODEL_URL, timeout=10)
|
||||
response.raise_for_status()
|
||||
json_data = response.json()
|
||||
|
||||
# Explicitly handle 404 differently
|
||||
if response.status_code == 404:
|
||||
cloudlog.error(f"Models URL returned 404 Not Found: {self.MODEL_URL}")
|
||||
raise HTTPError(f"404 Not Found: {self.MODEL_URL}", response=response)
|
||||
|
||||
# Raise for any other 4xx/5xx
|
||||
response.raise_for_status()
|
||||
|
||||
json_data = response.json()
|
||||
self.model_cache.set(json_data)
|
||||
cloudlog.debug("Successfully updated models cache")
|
||||
return self.model_parser.parse_models(json_data)
|
||||
except Exception:
|
||||
cloudlog.exception("Error fetching models")
|
||||
raise
|
||||
|
||||
except ConnectionError as e:
|
||||
cloudlog.warning(f"DNS/connection error while fetching models: {e}")
|
||||
except SSLError as e:
|
||||
cloudlog.warning(f"SSL error while fetching models: {e}")
|
||||
except RequestException as e:
|
||||
cloudlog.warning(f"Request transport error while fetching models: {e}")
|
||||
except Exception as e:
|
||||
cloudlog.exception(f"Unexpected error fetching models: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def get_available_bundles(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
"""Gets the list of available models, with smart cache handling"""
|
||||
@@ -144,12 +162,12 @@ class ModelFetcher:
|
||||
cloudlog.debug("Using valid cached models data")
|
||||
return self.model_parser.parse_models(cached_data)
|
||||
|
||||
try:
|
||||
return self._fetch_and_cache_models()
|
||||
except Exception:
|
||||
if not cached_data:
|
||||
cloudlog.exception("Failed to fetch fresh data and no cache available")
|
||||
raise
|
||||
fetched_bundles = self._fetch_and_cache_models()
|
||||
if fetched_bundles is not None:
|
||||
return fetched_bundles
|
||||
|
||||
if not cached_data:
|
||||
cloudlog.warning("Failed to fetch fresh data and no cache available")
|
||||
|
||||
cloudlog.warning("Failed to fetch fresh data. Using expired cache as fallback")
|
||||
return self.model_parser.parse_models(cached_data)
|
||||
|
||||
@@ -19,7 +19,7 @@ from openpilot.system.hardware.hw import Paths
|
||||
from pathlib import Path
|
||||
|
||||
# see the README.md for more details on the model selector versioning
|
||||
CURRENT_SELECTOR_VERSION = 9
|
||||
CURRENT_SELECTOR_VERSION = 10
|
||||
REQUIRED_MIN_SELECTOR_VERSION = 9
|
||||
|
||||
USE_ONNX = os.getenv('USE_ONNX', PC)
|
||||
@@ -185,3 +185,27 @@ def load_meta_constants(model_metadata):
|
||||
meta = MetaTombRaider
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
# The following method(s) are modeld helper methods
|
||||
def plan_x_idxs_helper(constants, plan, model_output) -> list[float]:
|
||||
# times at X_IDXS according to plan.
|
||||
LINE_T_IDXS = [np.nan] * constants.IDX_N
|
||||
LINE_T_IDXS[0] = 0.0
|
||||
plan_x = model_output['plan'][0, :, plan.POSITION][:, 0].tolist()
|
||||
for xidx in range(1, constants.IDX_N):
|
||||
tidx = 0
|
||||
# increment tidx until we find an element that's further away than the current xidx
|
||||
while tidx < constants.IDX_N - 1 and plan_x[tidx + 1] < constants.X_IDXS[xidx]:
|
||||
tidx += 1
|
||||
if tidx == constants.IDX_N - 1:
|
||||
# if the plan doesn't extend far enough, set plan_t to the max value (10s), then break
|
||||
LINE_T_IDXS[xidx] = constants.T_IDXS[constants.IDX_N - 1]
|
||||
break
|
||||
# interpolate to find `t` for the current xidx
|
||||
current_x_val = plan_x[tidx]
|
||||
next_x_val = plan_x[tidx + 1]
|
||||
p = (constants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) if abs(
|
||||
next_x_val - current_x_val) > 1e-9 else float('nan')
|
||||
LINE_T_IDXS[xidx] = p * constants.T_IDXS[tidx + 1] + (1 - p) * constants.T_IDXS[tidx]
|
||||
return LINE_T_IDXS
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user