mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-03 10:49:25 +08:00
Merge remote-tracking branch 'sunnypilot/sunnypilot/master-new' into feature/slc
# Conflicts: # cereal/custom.capnp # sunnypilot/selfdrive/selfdrived/events.py
This commit is contained in:
@@ -16,7 +16,7 @@ simulation:
|
||||
|
||||
ui:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'selfdrive/ui/**'
|
||||
- any-glob-to-all-files: '{selfdrive/ui/**,system/ui/**}'
|
||||
|
||||
tools:
|
||||
- changed-files:
|
||||
|
||||
@@ -12,7 +12,7 @@ inputs:
|
||||
required: true
|
||||
save:
|
||||
description: 'whether to save the cache'
|
||||
default: 'false'
|
||||
default: 'true'
|
||||
required: false
|
||||
outputs:
|
||||
cache-hit:
|
||||
|
||||
@@ -53,10 +53,6 @@ jobs:
|
||||
timeout-minutes: 1
|
||||
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Check submodules
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
timeout-minutes: 3
|
||||
run: release/check-submodules.sh
|
||||
- name: Build openpilot and run checks
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||
run: |
|
||||
@@ -67,6 +63,10 @@ jobs:
|
||||
run: |
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "release/check-dirty.sh"
|
||||
- name: Check submodules
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
timeout-minutes: 3
|
||||
run: release/check-submodules.sh
|
||||
|
||||
build:
|
||||
runs-on:
|
||||
|
||||
@@ -8,14 +8,16 @@ env:
|
||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||
|
||||
# Branch configurations
|
||||
MASTER_BRANCH: "master"
|
||||
MASTER_NEW_BRANCH: "master-new"
|
||||
DEV_C3_SOURCE_BRANCH: "master-dev-c3-new"
|
||||
STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master-new' }} # vars are set on repo settings.
|
||||
DEV_C3_SOURCE_BRANCH: ${{ vars.DEV_C3_SOURCE_BRANCH || 'master-dev-c3-new' }} # vars are set on repo settings.
|
||||
|
||||
# Target branch configurations
|
||||
STAGING_TARGET_BRANCH: "staging-c3-new"
|
||||
DEV_TARGET_BRANCH: "dev-c3-new"
|
||||
RELEASE_TARGET_BRANCH: "release-c3-new"
|
||||
STAGING_TARGET_BRANCH: ${{ vars.STAGING_TARGET_BRANCH || 'staging-c3-new' }} # vars are set on repo settings.
|
||||
DEV_TARGET_BRANCH: ${{ vars.DEV_TARGET_BRANCH || 'dev-c3-new' }} # vars are set on repo settings.
|
||||
RELEASE_TARGET_BRANCH: ${{ vars.RELEASE_TARGET_BRANCH || 'release-c3-new' }} # vars are set on repo settings.
|
||||
|
||||
# Runtime configuration
|
||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -59,7 +61,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{ github.head_ref || github.ref_name }}
|
||||
ref: ${{ env.SOURCE_BRANCH }}
|
||||
repository: ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
- run: git lfs pull
|
||||
|
||||
@@ -67,53 +69,62 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{env.SCONS_CACHE_DIR}}
|
||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
|
||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
|
||||
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
||||
restore-keys: |
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_NEW_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_C3_SOURCE_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Set Configuration
|
||||
if: (!github.event.pull_request.head.repo.fork)
|
||||
- name: Set Feature Branch Prebuilt Configuration
|
||||
id: set_feature_configuration
|
||||
if: (
|
||||
!(env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH) &&
|
||||
!(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH) &&
|
||||
!(startsWith(github.ref, 'refs/tags/'))
|
||||
)
|
||||
run: |
|
||||
if [[ "${{ github.head_ref || github.ref_name }}" == "${{ env.DEV_C3_SOURCE_BRANCH }}" ]]; then
|
||||
# Dev configuration
|
||||
echo "BRANCH_TYPE=dev" >> $GITHUB_ENV
|
||||
echo "NEW_BRANCH=${{ env.DEV_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=${{ github.run_number }}" >> $GITHUB_ENV
|
||||
|
||||
elif [[ "${{ github.head_ref || github.ref_name }}" == "${{ env.MASTER_BRANCH }}" || "${{ github.ref_name }}" == "${{ env.MASTER_NEW_BRANCH }}" ]]; then
|
||||
# Master configuration
|
||||
echo "BRANCH_TYPE=master" >> $GITHUB_ENV
|
||||
echo "NEW_BRANCH=${{ env.STAGING_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=staging" >> $GITHUB_ENV
|
||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-staging" >> $GITHUB_ENV
|
||||
|
||||
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
# Tag configuration
|
||||
echo "BRANCH_TYPE=tag" >> $GITHUB_ENV
|
||||
echo "NEW_BRANCH=${{ env.RELEASE_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=release" >> $GITHUB_ENV
|
||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-release" >> $GITHUB_ENV
|
||||
|
||||
else
|
||||
# Feature branch configuration
|
||||
echo "BRANCH_TYPE=dispatch" >> $GITHUB_ENV
|
||||
echo "NEW_BRANCH=${{ github.head_ref || github.ref_name }}-prebuilt" >> $GITHUB_ENV
|
||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Set Configuration (only forks)
|
||||
if: (github.event.pull_request.head.repo.fork)
|
||||
run: |
|
||||
echo "BRANCH_TYPE=dispatch" >> $GITHUB_ENV
|
||||
echo "NEW_BRANCH=${{ github.head_ref || github.ref_name }}-fork-prebuilt" >> $GITHUB_ENV
|
||||
echo "NEW_BRANCH=${{ env.SOURCE_BRANCH }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-prebuilt" >> $GITHUB_ENV
|
||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set dev-c3-new prebuilt Configuration
|
||||
id: set_dev_configuration
|
||||
if: (
|
||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
||||
env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH
|
||||
)
|
||||
run: |
|
||||
echo "NEW_BRANCH=${{ env.DEV_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=${{ github.run_number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set staging-c3-new prebuilt Configuration
|
||||
id: set_staging_configuration
|
||||
if: (
|
||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
||||
!contains(github.event_name, 'pull_request') &&
|
||||
steps.set_dev_configuration.outcome == 'skipped' &&
|
||||
(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH)
|
||||
)
|
||||
run: |
|
||||
echo "NEW_BRANCH=${{ env.STAGING_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=staging" >> $GITHUB_ENV
|
||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-staging" >> $GITHUB_ENV
|
||||
|
||||
- name: Set release-c3-new prebuilt Configuration
|
||||
id: set_tag_configuration
|
||||
if: (
|
||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
||||
!contains(github.event_name, 'pull_request') &&
|
||||
steps.set_staging_configuration.outcome == 'skipped' &&
|
||||
startsWith(github.ref, 'refs/tags/')
|
||||
)
|
||||
run: |
|
||||
echo "NEW_BRANCH=${{ env.RELEASE_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=release" >> $GITHUB_ENV
|
||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-release" >> $GITHUB_ENV
|
||||
|
||||
- name: Set environment variables
|
||||
id: set-env
|
||||
run: |
|
||||
@@ -278,12 +289,12 @@ jobs:
|
||||
|
||||
- name: Send Discord Notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), github.head_ref || github.ref_name) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }}
|
||||
DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), env.SOURCE_BRANCH) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }}
|
||||
run: |
|
||||
TEMPLATE='${{ vars.DISCORD_GENERAL_UPDATE_NOTICE }}'
|
||||
export EXTRA_VERSION_IDENTIFIER="${{ needs.build.outputs.extra_version_identifier }}"
|
||||
export VERSION="${{ needs.build.outputs.version }}"
|
||||
export branch_name=${{ github.head_ref || github.ref_name }}
|
||||
export branch_name=${{ env.SOURCE_BRANCH }}
|
||||
export new_branch=${{ needs.build.outputs.new_branch }}
|
||||
export extra_version_identifier=${{ needs.build.outputs.extra_version_identifier || github.run_number}}
|
||||
echo ${TEMPLATE} | envsubst | jq -c '.' | tee payload.json
|
||||
|
||||
@@ -40,5 +40,13 @@ runs:
|
||||
RUN_ID=$(gh run list --workflow=${{ inputs.workflow }} --branch="$BRANCH" --limit=1 --json databaseId --jq '.[0].databaseId')
|
||||
echo "Watching run ID: $RUN_ID"
|
||||
gh run watch "$RUN_ID"
|
||||
|
||||
CONCLUSION=$(gh run view "$RUN_ID" --json conclusion --jq '.conclusion')
|
||||
echo "Run concluded with: $CONCLUSION"
|
||||
|
||||
if [[ "$CONCLUSION" != "success" ]]; then
|
||||
echo "❌ Workflow run failed with conclusion: $CONCLUSION"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
|
||||
@@ -47,10 +47,8 @@ selfdrive/pandad/pandad
|
||||
cereal/services.h
|
||||
cereal/gen
|
||||
cereal/messaging/bridge
|
||||
selfdrive/logcatd/logcatd
|
||||
selfdrive/mapd/default_speeds_by_region.json
|
||||
system/proclogd/proclogd
|
||||
selfdrive/ui/translations/alerts_generated.h
|
||||
selfdrive/ui/translations/tmp
|
||||
selfdrive/test/longitudinal_maneuvers/out
|
||||
selfdrive/car/tests/cars_dump
|
||||
|
||||
@@ -4,6 +4,7 @@ Version 0.9.9 (2025-05-15)
|
||||
* New training architecture supervised by MLSIM
|
||||
* Steering actuator delay is now learned online
|
||||
* Tesla Model 3 and Y support thanks to lukasloetkolben!
|
||||
* Lexus RC 2023 support thanks to nelsonjchen!
|
||||
* Coming soon
|
||||
* New Honda models
|
||||
* Bigger vision model
|
||||
|
||||
+30
-22
@@ -39,20 +39,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
sha256 @1 :Text;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
drive @0;
|
||||
navigation @1;
|
||||
metadata @2;
|
||||
}
|
||||
|
||||
struct Model {
|
||||
fullName @0 :Text;
|
||||
fileName @1 :Text;
|
||||
downloadUri @2 :DownloadUri;
|
||||
downloadProgress @3 :DownloadProgress;
|
||||
type @4 :Type;
|
||||
}
|
||||
|
||||
enum DownloadStatus {
|
||||
notDownloading @0;
|
||||
downloading @1;
|
||||
@@ -67,6 +53,25 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
eta @2 :UInt32;
|
||||
}
|
||||
|
||||
struct Artifact {
|
||||
fileName @0 :Text;
|
||||
downloadUri @1 :DownloadUri;
|
||||
downloadProgress @2 :DownloadProgress;
|
||||
}
|
||||
|
||||
struct Model {
|
||||
type @0 :Type;
|
||||
artifact @1 :Artifact; # Main artifact
|
||||
metadata @2 :Artifact; # Metadata artifact
|
||||
|
||||
enum Type {
|
||||
supercombo @0;
|
||||
navigation @1;
|
||||
vision @2;
|
||||
policy @3;
|
||||
}
|
||||
}
|
||||
|
||||
enum Runner {
|
||||
snpe @0;
|
||||
tinygrad @1;
|
||||
@@ -83,6 +88,8 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
environment @6 :Text;
|
||||
runner @7 :Runner;
|
||||
is20hz @8 :Bool;
|
||||
ref @9 :Text; # New field
|
||||
minimumSelectorVersion @10 :UInt32;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,10 +152,11 @@ struct OnroadEventSP @0xda96579883444c35 {
|
||||
hyundaiRadarTracksConfirmed @13;
|
||||
experimentalModeSwitched @14;
|
||||
wrongCarModeAlertOnly @15;
|
||||
speedLimitPreActive @16;
|
||||
speedLimitActive @17;
|
||||
speedLimitConfirmed @18;
|
||||
speedLimitValueChange @19;
|
||||
pedalPressedAlertOnly @16;
|
||||
speedLimitPreActive @17;
|
||||
speedLimitActive @18;
|
||||
speedLimitConfirmed @19;
|
||||
speedLimitValueChange @20;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,14 +189,14 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||
lastError @4 :Text;
|
||||
currentBackup @5 :BackupInfo;
|
||||
backupHistory @6 :List(BackupInfo);
|
||||
|
||||
|
||||
enum Status {
|
||||
idle @0;
|
||||
inProgress @1;
|
||||
completed @2;
|
||||
failed @3;
|
||||
}
|
||||
|
||||
|
||||
struct Version {
|
||||
major @0 :UInt16;
|
||||
minor @1 :UInt16;
|
||||
@@ -196,13 +204,13 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||
build @3 :UInt16;
|
||||
branch @4 :Text;
|
||||
}
|
||||
|
||||
|
||||
struct MetadataEntry {
|
||||
key @0 :Text;
|
||||
value @1 :Text;
|
||||
tags @2 :List(Text);
|
||||
}
|
||||
|
||||
|
||||
struct BackupInfo {
|
||||
deviceId @0 :Text;
|
||||
version @1 :UInt32;
|
||||
|
||||
@@ -48,6 +48,7 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
||||
preEnableStandstill @12; # added during pre-enable state with brake
|
||||
gasPressedOverride @13; # added when user is pressing gas with no disengage on gas
|
||||
steerOverride @14;
|
||||
steerDisengage @94; # exits active state
|
||||
cruiseDisabled @15;
|
||||
speedTooLow @16;
|
||||
outOfSpace @17;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define DEFAULT_MODEL "Tomb_Raider_6 (Default)"
|
||||
#define DEFAULT_MODEL "Tomb Raider 7 (Default)"
|
||||
|
||||
@@ -138,7 +138,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
|
||||
// MADS params
|
||||
{"Mads", PERSISTENT | BACKUP},
|
||||
{"MadsMainCruiseAllowed", PERSISTENT | BACKUP},
|
||||
{"MadsPauseLateralOnBrake", PERSISTENT | BACKUP},
|
||||
{"MadsSteeringMode", PERSISTENT | BACKUP},
|
||||
{"MadsUnifiedEngagementMode", PERSISTENT | BACKUP},
|
||||
|
||||
// Model Manager params
|
||||
@@ -164,6 +164,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"BackupManager_RestoreVersion", PERSISTENT},
|
||||
|
||||
// sunnypilot car specific params
|
||||
{"HyundaiLongitudinalTuning", PERSISTENT},
|
||||
{"HyundaiRadarTracks", PERSISTENT},
|
||||
{"HyundaiRadarTracksConfirmed", PERSISTENT},
|
||||
{"HyundaiRadarTracksPersistent", PERSISTENT},
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
"""Utilities for reading real time clocks and keeping soft real time constraints."""
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from setproctitle import getproctitle
|
||||
@@ -28,13 +29,13 @@ class Priority:
|
||||
|
||||
|
||||
def set_core_affinity(cores: list[int]) -> None:
|
||||
if not PC:
|
||||
if sys.platform == 'linux' and not PC:
|
||||
os.sched_setaffinity(0, cores)
|
||||
|
||||
|
||||
def config_realtime_process(cores: int | list[int], priority: int) -> None:
|
||||
gc.disable()
|
||||
if not PC:
|
||||
if sys.platform == 'linux' and not PC:
|
||||
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(priority))
|
||||
c = cores if isinstance(cores, list) else [cores, ]
|
||||
set_core_affinity(c)
|
||||
|
||||
+13
-6
@@ -4,12 +4,13 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||
|
||||
# 304 Supported Cars
|
||||
# 311 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Acura&model=ILX 2016-19">Buy Here</a></sub></details>||
|
||||
|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Acura&model=RDX 2016-18">Buy Here</a></sub></details>||
|
||||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Acura&model=ILX 2016-18">Buy Here</a></sub></details>||
|
||||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Acura&model=ILX 2019">Buy Here</a></sub></details>||
|
||||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Acura&model=RDX 2016-18">Buy Here</a></sub></details>||
|
||||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Acura&model=RDX 2019-21">Buy Here</a></sub></details>||
|
||||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=A3 2014-19">Buy Here</a></sub></details>||
|
||||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>||
|
||||
@@ -32,17 +33,22 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Dodge&model=Durango 2020-21">Buy Here</a></sub></details>||
|
||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Bronco Sport 2021-24">Buy Here</a></sub></details>||
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Hybrid 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer 2020-24">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer Hybrid 2020-24">Buy Here</a></sub></details>||
|
||||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=F-150 2021-23">Buy Here</a></sub></details>||
|
||||
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=F-150 Hybrid 2021-23">Buy Here</a></sub></details>||
|
||||
|Ford|Focus 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Focus Hybrid 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus Hybrid 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Hybrid 2024">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2022">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2022">Buy Here</a></sub></details>||
|
||||
@@ -186,6 +192,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Lexus|NX Hybrid 2018-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=NX Hybrid 2018-19">Buy Here</a></sub></details>||
|
||||
|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=NX Hybrid 2020-21">Buy Here</a></sub></details>||
|
||||
|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=RC 2018-20">Buy Here</a></sub></details>||
|
||||
|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=RC 2023">Buy Here</a></sub></details>||
|
||||
|Lexus|RX 2016|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=RX 2016">Buy Here</a></sub></details>||
|
||||
|Lexus|RX 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=RX 2017-19">Buy Here</a></sub></details>||
|
||||
|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=RX 2020-22">Buy Here</a></sub></details>||
|
||||
|
||||
+2
-2
@@ -25,9 +25,9 @@ ensuring two main safety requirements.
|
||||
by stepping on the brake pedal or by pressing the cancel button.
|
||||
2. The vehicle must not alter its trajectory too quickly for the driver to safely
|
||||
react. This means that while the system is engaged, the actuators are constrained
|
||||
to operate within reasonable limits[^1].
|
||||
to operate within reasonable limits[^1].
|
||||
|
||||
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 [panda/board/safety/](https://github.com/commaai/panda/tree/master/board/safety).
|
||||
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
|
||||
|
||||
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
|
||||
not fully meeting the above requirements.
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="11.13"
|
||||
export AGNOS_VERSION="12.1"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 63fbea047b...5db6b93bc5
@@ -47,6 +47,7 @@ dependencies = [
|
||||
# logging
|
||||
"pyzmq",
|
||||
"sentry-sdk",
|
||||
"xattr", # used in place of 'os.getxattr' for macos compatibility
|
||||
|
||||
# athena
|
||||
"PyJWT",
|
||||
|
||||
@@ -191,6 +191,8 @@ class CarSpecificEvents:
|
||||
events.add(EventName.accFaulted)
|
||||
if CS.steeringPressed:
|
||||
events.add(EventName.steerOverride)
|
||||
if CS.steeringDisengage and not CS_prev.steeringDisengage:
|
||||
events.add(EventName.steerDisengage)
|
||||
if CS.brakePressed and CS.standstill:
|
||||
events.add(EventName.preEnableStandstill)
|
||||
if CS.gasPressed:
|
||||
|
||||
@@ -108,7 +108,7 @@ class Car:
|
||||
fixed_fingerprint = json.loads(self.params.get("CarPlatformBundle", encoding='utf-8') or "{}").get("platform", None)
|
||||
|
||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, num_pandas, cached_params, fixed_fingerprint)
|
||||
sunnypilot_interfaces.setup_interfaces(self.CI.CP, self.CI.CP_SP, self.params)
|
||||
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
|
||||
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
|
||||
self.CP = self.CI.CP
|
||||
self.CP_SP = self.CI.CP_SP
|
||||
|
||||
@@ -45,9 +45,9 @@ class TestCarInterfaces:
|
||||
alpha_long=args['alpha_long'], 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)
|
||||
sunnypilot_interfaces.setup_interfaces(car_params, car_params_sp)
|
||||
car_params = car_params.as_reader()
|
||||
car_interface = CarInterface(car_params, car_params_sp)
|
||||
sunnypilot_interfaces.setup_interfaces(car_interface)
|
||||
assert car_params
|
||||
assert car_params_sp
|
||||
assert car_interface
|
||||
|
||||
@@ -4,7 +4,7 @@ from openpilot.common.basedir import BASEDIR
|
||||
from opendbc.car.docs import generate_cars_md, get_all_car_docs
|
||||
from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs
|
||||
from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff
|
||||
from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE
|
||||
from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE
|
||||
|
||||
|
||||
class TestCarDocs:
|
||||
@@ -13,11 +13,7 @@ class TestCarDocs:
|
||||
cls.all_cars = get_all_car_docs()
|
||||
|
||||
def test_generator(self):
|
||||
generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
|
||||
with open(CARS_MD_OUT) as f:
|
||||
current_cars_md = f.read()
|
||||
|
||||
assert generated_cars_md == current_cars_md, "Run selfdrive/car/docs.py to update the compatibility documentation"
|
||||
generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
|
||||
|
||||
def test_docs_diff(self):
|
||||
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
|
||||
|
||||
@@ -338,6 +338,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
prev_panda_gas = self.safety.get_gas_pressed_prev()
|
||||
prev_panda_brake = self.safety.get_brake_pressed_prev()
|
||||
prev_panda_regen_braking = self.safety.get_regen_braking_prev()
|
||||
prev_panda_steering_disengage = self.safety.get_steering_disengage_prev()
|
||||
prev_panda_vehicle_moving = self.safety.get_vehicle_moving()
|
||||
prev_panda_vehicle_speed_min = self.safety.get_vehicle_speed_min()
|
||||
prev_panda_vehicle_speed_max = self.safety.get_vehicle_speed_max()
|
||||
@@ -365,6 +366,9 @@ class TestCarModelBase(unittest.TestCase):
|
||||
if self.safety.get_regen_braking_prev() != prev_panda_regen_braking:
|
||||
self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev())
|
||||
|
||||
if self.safety.get_steering_disengage_prev() != prev_panda_steering_disengage:
|
||||
self.assertEqual(CS.steeringDisengage, self.safety.get_steering_disengage_prev())
|
||||
|
||||
if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving:
|
||||
self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving())
|
||||
|
||||
@@ -440,6 +444,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
brake_pressed = False
|
||||
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
|
||||
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
|
||||
checks['steeringDisengage'] += CS.steeringDisengage != self.safety.get_steering_disengage_prev()
|
||||
|
||||
if self.CP.pcmCruise:
|
||||
# On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import math
|
||||
from typing import SupportsFloat
|
||||
|
||||
from cereal import car, log, custom
|
||||
from cereal import car, log
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.common.params import Params
|
||||
@@ -19,6 +19,8 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
|
||||
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
|
||||
|
||||
from openpilot.sunnypilot.selfdrive.controls.controlsd_ext import ControlsExt
|
||||
|
||||
State = log.SelfdriveState.OpenpilotState
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
@@ -26,24 +28,23 @@ LaneChangeDirection = log.LaneChangeDirection
|
||||
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
|
||||
|
||||
|
||||
class Controls:
|
||||
class Controls(ControlsExt):
|
||||
def __init__(self) -> None:
|
||||
self.params = Params()
|
||||
cloudlog.info("controlsd is waiting for CarParams")
|
||||
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("controlsd got CarParams")
|
||||
|
||||
cloudlog.info("controlsd is waiting for CarParamsSP")
|
||||
self.CP_SP = messaging.log_from_bytes(self.params.get("CarParamsSP", block=True), custom.CarParamsSP)
|
||||
cloudlog.info("controlsd got CarParamsSP")
|
||||
# Initialize sunnypilot controlsd extension
|
||||
ControlsExt.__init__(self, self.params)
|
||||
|
||||
self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP)
|
||||
|
||||
self.sm = messaging.SubMaster(['liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState',
|
||||
'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput',
|
||||
'driverMonitoringState', 'onroadEvents', 'driverAssistance'] + ['selfdriveStateSP'],
|
||||
'driverMonitoringState', 'onroadEvents', 'driverAssistance'] + self.sm_services_ext,
|
||||
poll='selfdriveState')
|
||||
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + ['carControlSP'])
|
||||
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + self.pm_services_ext)
|
||||
|
||||
self.steer_limited_by_controls = False
|
||||
self.curvature = 0.0
|
||||
@@ -100,11 +101,8 @@ class Controls:
|
||||
# Check which actuators can be enabled
|
||||
standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, 0.3) or CS.standstill
|
||||
|
||||
ss_sp = self.sm['selfdriveStateSP']
|
||||
if ss_sp.mads.available:
|
||||
_lat_active = ss_sp.mads.active
|
||||
else:
|
||||
_lat_active = self.sm['selfdriveState'].active
|
||||
# Get which state to use for active lateral control
|
||||
_lat_active = self.get_lat_active(self.sm)
|
||||
|
||||
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||
(not standstill or self.CP.steerAtStandstill)
|
||||
@@ -148,12 +146,9 @@ class Controls:
|
||||
cloudlog.error(f"actuators.{p} not finite {actuators.to_dict()}")
|
||||
setattr(actuators, p, 0.0)
|
||||
|
||||
CC_SP = custom.CarControlSP.new_message()
|
||||
CC_SP.mads = ss_sp.mads
|
||||
return CC, lac_log
|
||||
|
||||
return CC, CC_SP, lac_log
|
||||
|
||||
def publish(self, CC, CC_SP, lac_log):
|
||||
def publish(self, CC, lac_log):
|
||||
CS = self.sm['carState']
|
||||
|
||||
# Orientation and angle rates can be useful for carcontroller
|
||||
@@ -227,18 +222,13 @@ class Controls:
|
||||
cc_send.carControl = CC
|
||||
self.pm.send('carControl', cc_send)
|
||||
|
||||
# carControlSP
|
||||
cc_sp_send = messaging.new_message('carControlSP')
|
||||
cc_sp_send.valid = CS.canValid
|
||||
cc_sp_send.carControlSP = CC_SP
|
||||
self.pm.send('carControlSP', cc_sp_send)
|
||||
|
||||
def run(self):
|
||||
rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
while True:
|
||||
self.update()
|
||||
CC, CC_SP, lac_log = self.state_control()
|
||||
self.publish(CC, CC_SP, lac_log)
|
||||
CC, lac_log = self.state_control()
|
||||
self.publish(CC, lac_log)
|
||||
self.run_ext(self.sm, self.pm)
|
||||
rk.monitor_time()
|
||||
|
||||
|
||||
|
||||
@@ -93,8 +93,8 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
return x, v, a, j, throttle_prob
|
||||
|
||||
def update(self, sm):
|
||||
LongitudinalPlannerSP.update(self, sm)
|
||||
self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
|
||||
LongitudinalPlannerSP.update(self, sm)
|
||||
if dec_mpc_mode := self.get_mpc_mode():
|
||||
self.mode = dec_mpc_mode
|
||||
|
||||
@@ -178,6 +178,10 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
output_a_target = min(output_a_target_mpc, output_a_target_e2e)
|
||||
self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc
|
||||
|
||||
if not self.is_stock:
|
||||
# To support non Tomb Raider models
|
||||
output_a_target, self.output_should_stop = output_a_target_mpc, output_should_stop_mpc
|
||||
|
||||
for idx in range(2):
|
||||
accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05)
|
||||
self.output_a_target = np.clip(output_a_target, accel_clip[0], accel_clip[1])
|
||||
|
||||
@@ -23,7 +23,7 @@ class TestLatControl:
|
||||
CP = CarInterface.get_non_essential_params(car_name)
|
||||
CP_SP = CarInterface.get_non_essential_params_sp(CP, car_name)
|
||||
CI = CarInterface(CP, CP_SP)
|
||||
sunnypilot_interfaces.setup_interfaces(CP, CP_SP)
|
||||
sunnypilot_interfaces.setup_interfaces(CI)
|
||||
CP_SP = convert_to_capnp(CP_SP)
|
||||
VM = VehicleModel(CP)
|
||||
|
||||
|
||||
@@ -71,6 +71,13 @@ SUPPORTED_FW_VERSIONS = {
|
||||
b"DLhe SCC FHCUP 1.00 1.02 99110-L7000 \x01 \x102 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2022 Niro EV
|
||||
b"DEev SCC F-CUP 1.00 1.00 99110-Q4600\x01\x42 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
b"DEev SCC F-CUP 1.00 1.00 99110-Q4600 \x07\x03\t% ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -52,7 +52,7 @@ class TorqueBuckets(PointBuckets):
|
||||
class TorqueEstimator(ParameterEstimator):
|
||||
def __init__(self, CP, decimated=False, track_all_points=False):
|
||||
self.hist_len = int(HISTORY / DT_MDL)
|
||||
self.lag = CP.steerActuatorDelay + .2 # from controlsd
|
||||
self.lag = 0.0
|
||||
self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters
|
||||
if decimated:
|
||||
self.min_bucket_points = MIN_BUCKET_POINTS / 10
|
||||
@@ -175,7 +175,8 @@ class TorqueEstimator(ParameterEstimator):
|
||||
self.raw_points["steer_override"].append(msg.steeringPressed)
|
||||
elif which == "liveCalibration":
|
||||
self.calibrator.feed_live_calib(msg)
|
||||
|
||||
elif which == "liveDelay":
|
||||
self.lag = msg.lateralDelay
|
||||
# calculate lateral accel from past steering torque
|
||||
elif which == "livePose":
|
||||
if len(self.raw_points['steer_torque']) == self.hist_len:
|
||||
@@ -241,7 +242,7 @@ def main(demo=False):
|
||||
config_realtime_process([0, 1, 2, 3], 5)
|
||||
|
||||
pm = messaging.PubMaster(['liveTorqueParameters'])
|
||||
sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose'], poll='livePose')
|
||||
sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose', 'liveDelay'], poll='livePose')
|
||||
|
||||
params = Params()
|
||||
estimator = TorqueEstimator(messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams))
|
||||
|
||||
@@ -47,8 +47,10 @@ elif arch == 'Darwin':
|
||||
else:
|
||||
device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0'
|
||||
|
||||
for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
|
||||
fn = File(f"models/{model_name}").abspath
|
||||
cmd = f'{pythonpath_string} {device_string} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl'
|
||||
lenv.Command(fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, cmd)
|
||||
# TODO-SP: after 15.4 it's not possible to compile models locally on mac https://discord.com/channels/469524606043160576/1362735424644055230
|
||||
if arch != 'Darwin':
|
||||
for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
|
||||
fn = File(f"models/{model_name}").abspath
|
||||
cmd = f'{pythonpath_string} {device_string} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl'
|
||||
lenv.Command(fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, cmd)
|
||||
|
||||
|
||||
@@ -90,18 +90,11 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
|
||||
|
||||
# temporal pose
|
||||
temporal_pose = modelV2.temporalPose
|
||||
if 'sim_pose' in net_output_data:
|
||||
temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
else:
|
||||
temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist()
|
||||
temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist()
|
||||
temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist()
|
||||
temporal_pose.rotStd = net_output_data['plan_stds'][0,0,Plan.ORIENTATION_RATE].tolist()
|
||||
|
||||
#temporal_pose = modelV2.temporalPose
|
||||
#temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
#temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
#temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
#temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
|
||||
# poly path
|
||||
fill_xyz_poly(driving_model_data.path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T)
|
||||
|
||||
@@ -41,7 +41,7 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl'
|
||||
VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl'
|
||||
POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl'
|
||||
|
||||
LAT_SMOOTH_SECONDS = 0.3
|
||||
LAT_SMOOTH_SECONDS = 0.1
|
||||
LONG_SMOOTH_SECONDS = 0.3
|
||||
MIN_LAT_CONTROL_SPEED = 0.3
|
||||
|
||||
@@ -54,9 +54,12 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
|
||||
ModelConstants.T_IDXS,
|
||||
action_t=long_action_t)
|
||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
||||
desired_curvature = get_curvature_from_plan(plan[:, Plan.T_FROM_CURRENT_EULER][:, 2],
|
||||
plan[:, Plan.ORIENTATION_RATE][:, 2],
|
||||
ModelConstants.T_IDXS, v_ego, lat_action_t)
|
||||
|
||||
desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2],
|
||||
plan[:,Plan.ORIENTATION_RATE][:,2],
|
||||
ModelConstants.T_IDXS,
|
||||
v_ego,
|
||||
lat_action_t)
|
||||
if v_ego > MIN_LAT_CONTROL_SPEED:
|
||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
||||
else:
|
||||
@@ -221,7 +224,7 @@ def main(demo=False):
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
params = Params()
|
||||
@@ -248,7 +251,6 @@ def main(demo=False):
|
||||
|
||||
# TODO this needs more thought, use .2s extra for now to estimate other delays
|
||||
# TODO Move smooth seconds to action function
|
||||
lat_delay = CP.steerActuatorDelay + .2 + LAT_SMOOTH_SECONDS
|
||||
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
|
||||
prev_action = log.ModelDataV2.Action()
|
||||
|
||||
@@ -292,6 +294,7 @@ def main(demo=False):
|
||||
is_rhd = sm["driverMonitoringState"].isRHD
|
||||
frame_id = sm["roadCameraState"].frameId
|
||||
v_ego = max(sm["carState"].vEgo, 0.)
|
||||
lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
||||
lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
|
||||
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
|
||||
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8a478376723ba79e2393ca95e2d3e497571ba6fed113e5f13a36f0e4b4d4a7c5
|
||||
oid sha256:19e30484236efff72d519938c3e26461dbeb89c11d81fa7ecbff8e0263333c18
|
||||
size 15588463
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
#define CUTOFF_IL 400
|
||||
#define SATURATE_IL 1000
|
||||
|
||||
#define ALT_EXP_DISENGAGE_LATERAL_ON_BRAKE 2048
|
||||
#define ALT_EXP_MADS_DISENGAGE_LATERAL_ON_BRAKE 2048
|
||||
|
||||
ExitHandler do_exit;
|
||||
|
||||
@@ -57,7 +57,7 @@ bool check_all_connected(const std::vector<Panda *> &pandas) {
|
||||
|
||||
bool process_mads_heartbeat(SubMaster *sm) {
|
||||
const int &alt_exp = (*sm)["carParams"].getCarParams().getAlternativeExperience();
|
||||
const bool disengage_lateral_on_brake = (alt_exp & ALT_EXP_DISENGAGE_LATERAL_ON_BRAKE) != 0;
|
||||
const bool disengage_lateral_on_brake = (alt_exp & ALT_EXP_MADS_DISENGAGE_LATERAL_ON_BRAKE) != 0;
|
||||
|
||||
const auto &mads = (*sm)["selfdriveStateSP"].getSelfdriveStateSP().getMads();
|
||||
const bool heartbeat_type = disengage_lateral_on_brake ? mads.getActive() : mads.getEnabled();
|
||||
@@ -474,7 +474,12 @@ void pandad_run(std::vector<Panda *> &pandas) {
|
||||
for (auto *panda : pandas) {
|
||||
std::string log = panda->serial_read();
|
||||
if (!log.empty()) {
|
||||
LOGD("%s", log.c_str());
|
||||
if (log.find("Register 0x") != std::string::npos) {
|
||||
// Log register divergent faults as errors
|
||||
LOGE("%s", log.c_str());
|
||||
} else {
|
||||
LOGD("%s", log.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -503,6 +503,11 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
visual_alert=VisualAlert.brakePressed),
|
||||
},
|
||||
|
||||
EventName.steerDisengage: {
|
||||
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
||||
ET.NO_ENTRY: NoEntryAlert("Steering Pressed"),
|
||||
},
|
||||
|
||||
EventName.preEnableStandstill: {
|
||||
ET.PRE_ENABLE: Alert(
|
||||
"Release Brake to Engage",
|
||||
|
||||
@@ -198,7 +198,7 @@ class SelfdriveD(CruiseHelper):
|
||||
car_events = self.car_events.update(CS, self.CS_prev, self.sm['carControl']).to_msg()
|
||||
self.events.add_from_msg(car_events)
|
||||
|
||||
car_events_sp = self.car_events_sp.update().to_msg()
|
||||
car_events_sp = self.car_events_sp.update(CS, self.events).to_msg()
|
||||
self.events_sp.add_from_msg(car_events_sp)
|
||||
|
||||
if self.CP.notCar:
|
||||
|
||||
@@ -571,7 +571,7 @@ CONFIGS = [
|
||||
),
|
||||
ProcessConfig(
|
||||
proc_name="torqued",
|
||||
pubs=["livePose", "liveCalibration", "carState", "carControl", "carOutput"],
|
||||
pubs=["livePose", "liveCalibration", "liveDelay", "carState", "carControl", "carOutput"],
|
||||
subs=["liveTorqueParameters"],
|
||||
ignore=["logMonoTime"],
|
||||
init_callback=get_car_params_callback,
|
||||
@@ -580,7 +580,7 @@ CONFIGS = [
|
||||
),
|
||||
ProcessConfig(
|
||||
proc_name="modeld",
|
||||
pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState", "carState", "carControl"],
|
||||
pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "liveDelay", "driverMonitoringState", "carState", "carControl"],
|
||||
subs=["modelV2", "drivingModelData", "cameraOdometry"],
|
||||
ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"],
|
||||
should_recv_callback=ModeldCameraSyncRcvCallback(),
|
||||
|
||||
@@ -1 +1 @@
|
||||
4c7ac1a362933af9d97697979162087c1ab2584b
|
||||
7bf4ae5b92a3ad1f073f675e24e28babad0f2aa0
|
||||
@@ -21,6 +21,7 @@ qt_src = [
|
||||
"sunnypilot/qt/offroad/offroad_home.cc",
|
||||
"sunnypilot/qt/offroad/settings/device_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
|
||||
"sunnypilot/qt/offroad/settings/settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/software_panel.cc",
|
||||
@@ -49,11 +50,28 @@ network_src = [
|
||||
]
|
||||
|
||||
vehicle_panel_qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/vehicle/brand_settings_factory.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/platform_selector.cc",
|
||||
]
|
||||
|
||||
brand_settings_qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/vehicle/chrysler_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/ford_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/gm_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/honda_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/mazda_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/nissan_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/rivian_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/toyota_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.cc",
|
||||
]
|
||||
|
||||
sp_widgets_src = widgets_src + network_src
|
||||
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src
|
||||
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src
|
||||
sp_qt_util = qt_util
|
||||
|
||||
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
|
||||
|
||||
@@ -112,9 +112,18 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
|
||||
|
||||
addItem(power_group_layout);
|
||||
|
||||
std::vector always_enabled_btns = {
|
||||
rebootBtn,
|
||||
poweroffBtn,
|
||||
offroadBtn,
|
||||
buttons["quietModeBtn"],
|
||||
};
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
|
||||
for (auto btn : findChildren<PushButtonSP*>()) {
|
||||
if (btn != rebootBtn && btn != poweroffBtn && btn != offroadBtn) {
|
||||
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
|
||||
|
||||
if (!always_enabled) {
|
||||
btn->setEnabled(offroad);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,35 +21,19 @@ MadsSettings::MadsSettings(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
ListWidget *list = new ListWidget(this, false);
|
||||
// Main cruise
|
||||
madsMainCruiseToggle = new ParamControl(
|
||||
"MadsMainCruiseAllowed",
|
||||
tr("Toggle with Main Cruise"),
|
||||
tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement."),
|
||||
"");
|
||||
madsMainCruiseToggle = new ParamControl("MadsMainCruiseAllowed", tr("Toggle with Main Cruise"), "", "");
|
||||
list->addItem(madsMainCruiseToggle);
|
||||
|
||||
// Unified Engagement Mode
|
||||
madsUnifiedEngagementModeToggle = new ParamControl(
|
||||
"MadsUnifiedEngagementMode",
|
||||
tr("Unified Engagement Mode (UEM)"),
|
||||
QString("%1<br>"
|
||||
"<h4>%2</h4>")
|
||||
.arg(tr("Engage lateral and longitudinal control with cruise control engagement."))
|
||||
.arg(tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off.")),
|
||||
"");
|
||||
madsUnifiedEngagementModeToggle = new ParamControl("MadsUnifiedEngagementMode", tr("Unified Engagement Mode (UEM)"), "", "");
|
||||
list->addItem(madsUnifiedEngagementModeToggle);
|
||||
|
||||
// Pause Lateral On Brake
|
||||
std::vector<QString> lateral_on_brake_texts{tr("Remain Active"), tr("Pause Steering")};
|
||||
madsPauseLateralOnBrake = new ButtonParamControl(
|
||||
"MadsPauseLateralOnBrake",
|
||||
tr("Steering Mode After Braking"),
|
||||
tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\n"
|
||||
"Remain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."),
|
||||
"",
|
||||
lateral_on_brake_texts,
|
||||
500);
|
||||
list->addItem(madsPauseLateralOnBrake);
|
||||
// Steering Mode On Brake
|
||||
madsSteeringMode = new ButtonParamControl("MadsSteeringMode", tr("Steering Mode on Brake Pedal"), "", "", madsSteeringModeTexts(), 500);
|
||||
QObject::connect(madsSteeringMode, &ButtonParamControl::buttonToggled, [=] {
|
||||
updateToggles(offroad);
|
||||
});
|
||||
list->addItem(madsSteeringMode);
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &MadsSettings::updateToggles);
|
||||
|
||||
@@ -61,7 +45,58 @@ void MadsSettings::showEvent(QShowEvent *event) {
|
||||
}
|
||||
|
||||
void MadsSettings::updateToggles(bool _offroad) {
|
||||
madsPauseLateralOnBrake->setEnabled(_offroad);
|
||||
auto mads_steering_mode_param = std::atoi(params.get("MadsSteeringMode").c_str());
|
||||
|
||||
auto steering_mode = static_cast<MadsSteeringMode>(
|
||||
std::clamp(mads_steering_mode_param, static_cast<int>(MadsSteeringMode::REMAIN_ACTIVE), static_cast<int>(MadsSteeringMode::DISENGAGE))
|
||||
);
|
||||
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
|
||||
params.remove("MadsMainCruiseAllowed");
|
||||
params.putBool("MadsUnifiedEngagementMode", true);
|
||||
params.put("MadsSteeringMode", std::to_string(static_cast<int>(MadsSteeringMode::DISENGAGE)));
|
||||
|
||||
madsMainCruiseToggle->setEnabled(false);
|
||||
madsMainCruiseToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_OFF, MADS_MAIN_CRUISE_BASE_DESC));
|
||||
madsMainCruiseToggle->showDescription();
|
||||
|
||||
madsUnifiedEngagementModeToggle->setEnabled(false);
|
||||
madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_ON, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
|
||||
madsUnifiedEngagementModeToggle->showDescription();
|
||||
|
||||
madsSteeringModeValues = convertMadsSteeringModeValues({MadsSteeringMode::DISENGAGE});
|
||||
madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_DISENGAGE_ONLY, madsSteeringModeDescription(steering_mode)));
|
||||
} else {
|
||||
madsMainCruiseToggle->setEnabled(true);
|
||||
madsMainCruiseToggle->setDescription(MADS_MAIN_CRUISE_BASE_DESC);
|
||||
|
||||
madsUnifiedEngagementModeToggle->setEnabled(true);
|
||||
madsUnifiedEngagementModeToggle->setDescription(MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC);
|
||||
|
||||
madsSteeringModeValues = convertMadsSteeringModeValues(getMadsSteeringModeValues());
|
||||
madsSteeringMode->setDescription(madsSteeringModeDescription(steering_mode));
|
||||
}
|
||||
} else {
|
||||
madsMainCruiseToggle->setEnabled(false);
|
||||
madsMainCruiseToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_MAIN_CRUISE_BASE_DESC));
|
||||
madsMainCruiseToggle->showDescription();
|
||||
|
||||
madsUnifiedEngagementModeToggle->setEnabled(false);
|
||||
madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
|
||||
madsUnifiedEngagementModeToggle->showDescription();
|
||||
|
||||
madsSteeringModeValues = {};
|
||||
madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, madsSteeringModeDescription(steering_mode)));
|
||||
}
|
||||
|
||||
madsSteeringMode->setEnableSelectedButtons(_offroad, madsSteeringModeValues);
|
||||
madsSteeringMode->showDescription();
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,20 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
const std::vector<std::string> mads_limited_settings_brands = {"rivian", "tesla"};
|
||||
|
||||
enum class MadsSteeringMode {
|
||||
REMAIN_ACTIVE = 0,
|
||||
PAUSE = 1,
|
||||
DISENGAGE = 2,
|
||||
};
|
||||
|
||||
struct MadsSteeringModeOption {
|
||||
MadsSteeringMode mode;
|
||||
QString display_text;
|
||||
QString description;
|
||||
};
|
||||
|
||||
class MadsSettings : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -32,5 +46,70 @@ private:
|
||||
|
||||
ParamControl *madsMainCruiseToggle;
|
||||
ParamControl *madsUnifiedEngagementModeToggle;
|
||||
ButtonParamControl *madsPauseLateralOnBrake;
|
||||
ButtonParamControl *madsSteeringMode;
|
||||
|
||||
std::vector<int> madsSteeringModeValues = {};
|
||||
|
||||
const QString MADS_MAIN_CRUISE_BASE_DESC = tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement.");
|
||||
const QString MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC = QString("%1<br>"
|
||||
"<h4>%2</h4>")
|
||||
.arg(tr("Engage lateral and longitudinal control with cruise control engagement."))
|
||||
.arg(tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off."));
|
||||
|
||||
const QString STATUS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
|
||||
const QString DEFAULT_TO_OFF = tr("This feature defaults to OFF, and does not allow selection due to vehicle limitations.");
|
||||
const QString DEFAULT_TO_ON = tr("This feature defaults to ON, and does not allow selection due to vehicle limitations.");
|
||||
const QString STATUS_DISENGAGE_ONLY = tr("This platform only supports Disengage mode due to vehicle limitations.");
|
||||
|
||||
static const std::vector<MadsSteeringModeOption> &madsSteeringModeOptions() {
|
||||
static const std::vector<MadsSteeringModeOption> options = {
|
||||
{MadsSteeringMode::REMAIN_ACTIVE, tr("Remain Active"), tr("Remain Active: ALC will remain active when the brake pedal is pressed.")},
|
||||
{MadsSteeringMode::PAUSE, tr("Pause"), tr("Pause: ALC will pause when the brake pedal is pressed.")},
|
||||
{MadsSteeringMode::DISENGAGE, tr("Disengage"), tr("Disengage: ALC will disengage when the brake pedal is pressed.")},
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
static std::vector<MadsSteeringMode> getMadsSteeringModeValues() {
|
||||
std::vector<MadsSteeringMode> values;
|
||||
for (const auto& option : madsSteeringModeOptions()) {
|
||||
values.push_back(option.mode);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static std::vector<int> convertMadsSteeringModeValues(const std::vector<MadsSteeringMode> &modes) {
|
||||
std::vector<int> values;
|
||||
for (const auto& mode : modes) {
|
||||
values.push_back(static_cast<int>(mode));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static std::vector<QString> madsSteeringModeTexts() {
|
||||
std::vector<QString> texts;
|
||||
for (const auto& option : madsSteeringModeOptions()) {
|
||||
texts.push_back(option.display_text);
|
||||
}
|
||||
return texts;
|
||||
}
|
||||
|
||||
static QString madsSteeringModeDescription(const MadsSteeringMode mode = MadsSteeringMode::REMAIN_ACTIVE) {
|
||||
QString base_desc = tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.");
|
||||
QString result = base_desc + "<br><br>";
|
||||
|
||||
for (const auto& option : madsSteeringModeOptions()) {
|
||||
QString desc = option.description;
|
||||
if (option.mode == mode) {
|
||||
desc = "<font color='white'><b>" + desc + "</b></font>";
|
||||
}
|
||||
result += desc + "<br>";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString madsDescriptionBuilder(const QString &custom_description, const QString &base_description) {
|
||||
return "<font color='white'><b>" + custom_description + "</b></font><br><br>" + base_description;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,6 +122,21 @@ void LateralPanel::updateToggles(bool _offroad) {
|
||||
toggle->setEnabled(_offroad);
|
||||
}
|
||||
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
|
||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY, MADS_BASE_DESC));
|
||||
} else {
|
||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_FULL_COMPATIBILITY, MADS_BASE_DESC));
|
||||
}
|
||||
} else {
|
||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_CHECK_COMPATIBILITY, MADS_BASE_DESC));
|
||||
}
|
||||
|
||||
madsSettingsButton->setEnabled(madsToggle->isToggled());
|
||||
|
||||
offroad = _offroad;
|
||||
|
||||
@@ -30,6 +30,7 @@ public slots:
|
||||
void updateToggles(bool _offroad);
|
||||
|
||||
private:
|
||||
Params params;
|
||||
QStackedLayout* main_layout = nullptr;
|
||||
QWidget* sunnypilotScreen = nullptr;
|
||||
ScrollViewSP *sunnypilotScroller = nullptr;
|
||||
@@ -42,4 +43,14 @@ private:
|
||||
PushButtonSP *laneChangeSettingsButton;
|
||||
LaneChangeSettings *laneChangeWidget = nullptr;
|
||||
NeuralNetworkLateralControl *nnlcToggle = nullptr;
|
||||
|
||||
const QString MADS_BASE_DESC = tr("Enables independent engagements of Automatic Lane Centering (ALC) and Adaptive Cruise Control (ACC).");
|
||||
|
||||
const QString STATUS_MADS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
|
||||
const QString STATUS_MADS_SETTINGS_FULL_COMPATIBILITY = tr("This platform supports all MADS settings.");
|
||||
const QString STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY = tr("This platform supports limited MADS settings.");
|
||||
|
||||
static QString descriptionBuilder(const QString &custom_description, const QString &base_description) {
|
||||
return "<font color='white'><b>" + custom_description + "</b></font><br><br>" + base_description;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
|
||||
|
||||
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
|
||||
class LongitudinalPanel : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LongitudinalPanel(QWidget *parent = nullptr);
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/trips_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_panel.h"
|
||||
|
||||
@@ -79,6 +80,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
|
||||
PanelInfo(" " + tr("Toggles"), toggles, "../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
|
||||
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
|
||||
PanelInfo(" " + tr("Steering"), new LateralPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"),
|
||||
PanelInfo(" " + tr("Cruise"), new LongitudinalPanel(this), "../assets/offroad/icon_speed_limit.png"),
|
||||
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
|
||||
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
|
||||
PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"),
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
|
||||
inline bool isBrandInList(const std::string &brand, const std::vector<std::string> &list) {
|
||||
return std::find(list.begin(), list.end(), brand) != list.end();
|
||||
}
|
||||
|
||||
class SettingsWindowSP : public SettingsWindow {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -47,24 +47,24 @@ void SoftwarePanelSP::handleBundleDownloadProgress() {
|
||||
// Get status for each model type in order
|
||||
for (const auto &model: models) {
|
||||
QString typeName;
|
||||
QString modelName;
|
||||
QString modelName = QString::fromStdString(bundle.getDisplayName());
|
||||
|
||||
switch (model.getType()) {
|
||||
case cereal::ModelManagerSP::Type::DRIVE:
|
||||
case cereal::ModelManagerSP::Model::Type::SUPERCOMBO:
|
||||
typeName = tr("Driving");
|
||||
modelName = QString::fromStdString(bundle.getDisplayName());
|
||||
break;
|
||||
case cereal::ModelManagerSP::Type::NAVIGATION:
|
||||
case cereal::ModelManagerSP::Model::Type::NAVIGATION:
|
||||
typeName = tr("Navigation");
|
||||
modelName = QString::fromStdString(model.getFullName());
|
||||
break;
|
||||
case cereal::ModelManagerSP::Type::METADATA:
|
||||
typeName = tr("Metadata");
|
||||
modelName = QString::fromStdString(model.getFullName());
|
||||
case cereal::ModelManagerSP::Model::Type::VISION:
|
||||
typeName = tr("Vision");
|
||||
break;
|
||||
case cereal::ModelManagerSP::Model::Type::POLICY:
|
||||
typeName = tr("Policy");
|
||||
break;
|
||||
}
|
||||
|
||||
const auto &progress = model.getDownloadProgress();
|
||||
const auto &progress = model.getArtifact().getDownloadProgress();
|
||||
QString line;
|
||||
|
||||
if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING) {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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/vehicle/brand_settings_factory.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brands.h"
|
||||
|
||||
static const QStringList supportedBrands = {
|
||||
"chrysler",
|
||||
"ford",
|
||||
"gm",
|
||||
"honda",
|
||||
"hyundai",
|
||||
"mazda",
|
||||
"nissan",
|
||||
"rivian",
|
||||
"subaru",
|
||||
"tesla",
|
||||
"toyota",
|
||||
"volkswagen",
|
||||
};
|
||||
|
||||
BrandSettingsInterface* BrandSettingsFactory::createBrandSettings(const QString& brand, QWidget* parent) {
|
||||
if (brand == "chrysler")
|
||||
return new ChryslerSettings(parent);
|
||||
if (brand == "ford")
|
||||
return new FordSettings(parent);
|
||||
if (brand == "gm")
|
||||
return new GMSettings(parent);
|
||||
if (brand == "honda")
|
||||
return new HondaSettings(parent);
|
||||
if (brand == "hyundai")
|
||||
return new HyundaiSettings(parent);
|
||||
if (brand == "mazda")
|
||||
return new MazdaSettings(parent);
|
||||
if (brand == "nissan")
|
||||
return new NissanSettings(parent);
|
||||
if (brand == "rivian")
|
||||
return new RivianSettings(parent);
|
||||
if (brand == "subaru")
|
||||
return new SubaruSettings(parent);
|
||||
if (brand == "tesla")
|
||||
return new TeslaSettings(parent);
|
||||
if (brand == "toyota")
|
||||
return new ToyotaSettings(parent);
|
||||
if (brand == "volkswagen")
|
||||
return new VolkswagenSettings(parent);
|
||||
|
||||
// Default empty settings if brand not supported
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool BrandSettingsFactory::isBrandSupported(const QString& brand) {
|
||||
return supportedBrands.contains(brand);
|
||||
}
|
||||
|
||||
QStringList BrandSettingsFactory::getSupportedBrands() {
|
||||
return supportedBrands;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
class BrandSettingsFactory {
|
||||
|
||||
public:
|
||||
static BrandSettingsInterface* createBrandSettings(const QString &brand, QWidget *parent = nullptr);
|
||||
static bool isBrandSupported(const QString& brand);
|
||||
static QStringList getSupportedBrands();
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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/vehicle/brand_settings_interface.h"
|
||||
|
||||
BrandSettingsInterface::BrandSettingsInterface(QWidget *parent) : QWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
list = new ListWidget(this, false);
|
||||
main_layout->addWidget(list);
|
||||
}
|
||||
|
||||
void BrandSettingsInterface::updatePanel(bool _offroad) {
|
||||
offroad = _offroad;
|
||||
updateSettings();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class BrandSettingsInterface : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrandSettingsInterface(QWidget *parent = nullptr);
|
||||
virtual ~BrandSettingsInterface() = default;
|
||||
|
||||
void updatePanel(bool _offroad);
|
||||
virtual void updateSettings() = 0;
|
||||
|
||||
protected:
|
||||
ListWidget *list = nullptr;
|
||||
Params params;
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/chrysler_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/ford_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/gm_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/honda_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/mazda_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/nissan_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/rivian_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/toyota_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.h"
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/chrysler_settings.h"
|
||||
|
||||
ChryslerSettings::ChryslerSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void ChryslerSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class ChryslerSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChryslerSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/ford_settings.h"
|
||||
|
||||
FordSettings::FordSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void FordSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class FordSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FordSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/gm_settings.h"
|
||||
|
||||
GMSettings::GMSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void GMSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class GMSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GMSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/honda_settings.h"
|
||||
|
||||
HondaSettings::HondaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void HondaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class HondaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HondaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h"
|
||||
|
||||
HyundaiSettings::HyundaiSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
std::vector<QString> tuning_texts{ tr("Off"), tr("Dynamic"), tr("Predictive") };
|
||||
longitudinalTuningToggle = new ButtonParamControl(
|
||||
"HyundaiLongitudinalTuning",
|
||||
tr("Custom Longitudinal Tuning"),
|
||||
"",
|
||||
"",
|
||||
tuning_texts,
|
||||
500
|
||||
);
|
||||
QObject::connect(longitudinalTuningToggle, &ButtonParamControlSP::buttonToggled, this, &HyundaiSettings::updateSettings);
|
||||
list->addItem(longitudinalTuningToggle);
|
||||
longitudinalTuningToggle->showDescription();
|
||||
}
|
||||
|
||||
void HyundaiSettings::updateSettings() {
|
||||
auto longitudinal_tuning_param = std::atoi(params.get("HyundaiLongitudinalTuning").c_str());
|
||||
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
has_longitudinal_control = hasLongitudinalControl(CP);
|
||||
} else {
|
||||
has_longitudinal_control = false;
|
||||
}
|
||||
|
||||
LongitudinalTuningOption longitudinal_tuning_option;
|
||||
if (longitudinal_tuning_param == int(LongitudinalTuningOption::PREDICTIVE)) {
|
||||
longitudinal_tuning_option = LongitudinalTuningOption::PREDICTIVE;
|
||||
} else if (longitudinal_tuning_param == int(LongitudinalTuningOption::DYNAMIC)) {
|
||||
longitudinal_tuning_option = LongitudinalTuningOption::DYNAMIC;
|
||||
} else {
|
||||
longitudinal_tuning_option = LongitudinalTuningOption::OFF;
|
||||
}
|
||||
|
||||
bool longitudinal_tuning_disabled = !offroad || !has_longitudinal_control;
|
||||
QString longitudinal_tuning_description = longitudinalTuningDescription(longitudinal_tuning_option);
|
||||
if (longitudinal_tuning_disabled) {
|
||||
longitudinal_tuning_description = toggleDisableMsg(offroad, has_longitudinal_control);
|
||||
}
|
||||
|
||||
longitudinalTuningToggle->setEnabled(!longitudinal_tuning_disabled);
|
||||
longitudinalTuningToggle->setDescription(longitudinal_tuning_description);
|
||||
longitudinalTuningToggle->showDescription();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
enum class LongitudinalTuningOption {
|
||||
OFF,
|
||||
DYNAMIC,
|
||||
PREDICTIVE,
|
||||
};
|
||||
|
||||
class HyundaiSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HyundaiSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool has_longitudinal_control = false;
|
||||
ButtonParamControl *longitudinalTuningToggle = nullptr;
|
||||
|
||||
static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) {
|
||||
if (!_has_longitudinal_control) {
|
||||
return tr("This feature can only be used with openpilot longitudinal control enabled.");
|
||||
}
|
||||
|
||||
if (!_offroad) {
|
||||
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to select an option.");
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
static QString longitudinalTuningDescription(LongitudinalTuningOption option = LongitudinalTuningOption::OFF) {
|
||||
QString off_str = tr("Off: Uses default tuning");
|
||||
QString dynamic_str = tr("Dynamic: Adjusts acceleration limits based on current speed");
|
||||
QString predictive_str = tr("Predictive: Uses future trajectory data to anticipate needed adjustments");
|
||||
|
||||
if (option == LongitudinalTuningOption::PREDICTIVE) {
|
||||
predictive_str = "<font color='white'><b>" + predictive_str + "</b></font>";
|
||||
} else if (option == LongitudinalTuningOption::DYNAMIC) {
|
||||
dynamic_str = "<font color='white'><b>" + dynamic_str + "</b></font>";
|
||||
} else {
|
||||
off_str = "<font color='white'><b>" + off_str + "</b></font>";
|
||||
}
|
||||
|
||||
return QString("%1<br><br>%2<br>%3<br>%4<br>")
|
||||
.arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with openpilot longitudinal control."))
|
||||
.arg(off_str)
|
||||
.arg(dynamic_str)
|
||||
.arg(predictive_str);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/mazda_settings.h"
|
||||
|
||||
MazdaSettings::MazdaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void MazdaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class MazdaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MazdaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/nissan_settings.h"
|
||||
|
||||
NissanSettings::NissanSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void NissanSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class NissanSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NissanSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -43,36 +43,73 @@ PlatformSelector::PlatformSelector() : ButtonControl(tr("Vehicle"), "", "") {
|
||||
}
|
||||
});
|
||||
|
||||
main_layout->addStretch(0);
|
||||
refresh(offroad);
|
||||
}
|
||||
|
||||
void PlatformSelector::refresh(bool _offroad) {
|
||||
QString name = getPlatformBundle("name").toString();
|
||||
platform = unrecognized_str;
|
||||
QString platform_color = YELLOW_PLATFORM;
|
||||
|
||||
if (!name.isEmpty()) {
|
||||
setValue(name);
|
||||
platform = name;
|
||||
platform_color = BLUE_PLATFORM;
|
||||
brand = getPlatformBundle("brand").toString();
|
||||
setText(tr("REMOVE"));
|
||||
} else {
|
||||
setText(tr("SEARCH"));
|
||||
|
||||
platform = unrecognized_str;
|
||||
brand = "";
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
setValue(QString::fromStdString(CP.getCarFingerprint().cStr()));
|
||||
|
||||
platform = QString::fromStdString(CP.getCarFingerprint().cStr());
|
||||
|
||||
for (auto it = platforms.constBegin(); it != platforms.constEnd(); ++it) {
|
||||
if (it.value()["platform"].toString() == platform) {
|
||||
brand = it.value()["brand"].toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform == "MOCK") {
|
||||
platform = unrecognized_str;
|
||||
} else {
|
||||
platform_color = GREEN_PLATFORM;
|
||||
}
|
||||
}
|
||||
}
|
||||
setValue(platform, platform_color);
|
||||
setEnabled(true);
|
||||
emit refreshPanel();
|
||||
|
||||
offroad = _offroad;
|
||||
|
||||
FingerprintStatus cur_status;
|
||||
if (platform_color == GREEN_PLATFORM) {
|
||||
cur_status = FingerprintStatus::AUTO_FINGERPRINT;
|
||||
} else if (platform_color == BLUE_PLATFORM) {
|
||||
cur_status = FingerprintStatus::MANUAL_FINGERPRINT;
|
||||
} else {
|
||||
cur_status = FingerprintStatus::UNRECOGNIZED;
|
||||
}
|
||||
|
||||
setDescription(platformDescription(cur_status));
|
||||
showDescription();
|
||||
}
|
||||
|
||||
void PlatformSelector::setPlatform(const QString &platform) {
|
||||
QVariantMap platform_data = platforms[platform];
|
||||
void PlatformSelector::setPlatform(const QString &_platform) {
|
||||
QVariantMap platform_data = platforms[_platform];
|
||||
|
||||
const QString offroad_msg = offroad ? tr("This setting will take effect immediately.") :
|
||||
tr("This setting will take effect once the device enters offroad state.");
|
||||
const QString msg = QString("<b>%1</b><br><br>%2")
|
||||
.arg(platform, offroad_msg);
|
||||
.arg(_platform, offroad_msg);
|
||||
|
||||
QString content("<body><h2 style=\"text-align: center;\">" + tr("Vehicle Selector") + "</h2><br>"
|
||||
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + msg + "</p></body>");
|
||||
@@ -80,7 +117,7 @@ void PlatformSelector::setPlatform(const QString &platform) {
|
||||
if (ConfirmationDialog(content, tr("Confirm"), tr("Cancel"), true, this).exec()) {
|
||||
QJsonObject json_bundle;
|
||||
json_bundle["platform"] = platform_data["platform"].toString();
|
||||
json_bundle["name"] = platform;
|
||||
json_bundle["name"] = _platform;
|
||||
json_bundle["make"] = platform_data["make"].toString();
|
||||
json_bundle["brand"] = platform_data["brand"].toString();
|
||||
json_bundle["model"] = platform_data["model"].toString();
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
|
||||
static const QString GREEN_PLATFORM = "#00F100";
|
||||
static const QString BLUE_PLATFORM = "#0086E9";
|
||||
static const QString YELLOW_PLATFORM = "#FFD500";
|
||||
|
||||
enum class FingerprintStatus {
|
||||
AUTO_FINGERPRINT,
|
||||
MANUAL_FINGERPRINT,
|
||||
UNRECOGNIZED,
|
||||
};
|
||||
|
||||
class PlatformSelector : public ButtonControl {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -16,9 +26,15 @@ public:
|
||||
PlatformSelector();
|
||||
QVariant getPlatformBundle(const QString &key);
|
||||
|
||||
QString platform;
|
||||
QString brand;
|
||||
|
||||
public slots:
|
||||
void refresh(bool _offroad);
|
||||
|
||||
signals:
|
||||
void refreshPanel();
|
||||
|
||||
private:
|
||||
void searchPlatforms(const QString &query);
|
||||
void setPlatform(const QString &platform = "");
|
||||
@@ -26,4 +42,27 @@ private:
|
||||
|
||||
Params params;
|
||||
bool offroad;
|
||||
|
||||
QString unrecognized_str = tr("Unrecognized Vehicle");
|
||||
|
||||
static QString platformDescription(FingerprintStatus status = FingerprintStatus::UNRECOGNIZED) {
|
||||
QString auto_str = "🟢 - " + tr("Fingerprinted automatically");
|
||||
QString manual_str = "🔵 - " + tr("Manually selected");
|
||||
QString unrecognized_str = "🟡 - " + tr("Not fingerprinted or manually selected");
|
||||
|
||||
if (status == FingerprintStatus::AUTO_FINGERPRINT) {
|
||||
auto_str = "<font color='white'><b>" + auto_str + "</b></font>";
|
||||
} else if (status == FingerprintStatus::MANUAL_FINGERPRINT) {
|
||||
manual_str = "<font color='white'><b>" + manual_str + "</b></font>";
|
||||
} else {
|
||||
unrecognized_str = "<font color='white'><b>" + unrecognized_str + "</b></font>";
|
||||
}
|
||||
|
||||
return QString("%1<br>%2<br><br>%3<br>%4<br>%5")
|
||||
.arg(tr("Select vehicle to force fingerprint manually."))
|
||||
.arg(tr("Colors represent fingerprint status:"))
|
||||
.arg(auto_str)
|
||||
.arg(manual_str)
|
||||
.arg(unrecognized_str);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/rivian_settings.h"
|
||||
|
||||
RivianSettings::RivianSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void RivianSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class RivianSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RivianSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/subaru_settings.h"
|
||||
|
||||
SubaruSettings::SubaruSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void SubaruSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class SubaruSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SubaruSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/tesla_settings.h"
|
||||
|
||||
TeslaSettings::TeslaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void TeslaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class TeslaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TeslaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/toyota_settings.h"
|
||||
|
||||
ToyotaSettings::ToyotaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void ToyotaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class ToyotaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ToyotaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/volkswagen_settings.h"
|
||||
|
||||
VolkswagenSettings::VolkswagenSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void VolkswagenSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class VolkswagenSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VolkswagenSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -7,26 +7,32 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_panel.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_factory.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brands.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||
|
||||
VehiclePanel::VehiclePanel(QWidget *parent) : QFrame(parent) {
|
||||
main_layout = new QStackedLayout(this);
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(50, 20, 50, 20);
|
||||
|
||||
ListWidget *list = new ListWidget(this);
|
||||
|
||||
vehicleScreen = new QWidget(this);
|
||||
QVBoxLayout *vlayout = new QVBoxLayout(vehicleScreen);
|
||||
vlayout->setContentsMargins(50, 20, 50, 20);
|
||||
|
||||
platformSelector = new PlatformSelector();
|
||||
QObject::connect(platformSelector, &PlatformSelector::refreshPanel, this, &VehiclePanel::updateBrandSettings);
|
||||
list->addItem(platformSelector);
|
||||
|
||||
brandSettingsContainer = new QWidget(this);
|
||||
brandSettingsContainerLayout = new QVBoxLayout(brandSettingsContainer);
|
||||
brandSettingsContainerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
brandSettingsContainerLayout->setSpacing(0);
|
||||
list->addItem(brandSettingsContainer);
|
||||
|
||||
ScrollViewSP *scroller = new ScrollViewSP(list, this);
|
||||
vlayout->addWidget(scroller);
|
||||
main_layout->addWidget(scroller);
|
||||
|
||||
currentBrandSettings = nullptr;
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &VehiclePanel::updatePanel);
|
||||
|
||||
main_layout->addWidget(vehicleScreen);
|
||||
main_layout->setCurrentWidget(vehicleScreen);
|
||||
}
|
||||
|
||||
void VehiclePanel::showEvent(QShowEvent *event) {
|
||||
@@ -34,7 +40,28 @@ void VehiclePanel::showEvent(QShowEvent *event) {
|
||||
}
|
||||
|
||||
void VehiclePanel::updatePanel(bool _offroad) {
|
||||
platformSelector->refresh(_offroad);
|
||||
|
||||
offroad = _offroad;
|
||||
platformSelector->refresh(_offroad);
|
||||
updateBrandSettings();
|
||||
}
|
||||
|
||||
void VehiclePanel::updateBrandSettings() {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentBrandSettings) {
|
||||
brandSettingsContainerLayout->removeWidget(currentBrandSettings);
|
||||
delete currentBrandSettings;
|
||||
currentBrandSettings = nullptr;
|
||||
}
|
||||
|
||||
if (BrandSettingsFactory::isBrandSupported(platformSelector->brand)) {
|
||||
currentBrandSettings = BrandSettingsFactory::createBrandSettings(platformSelector->brand, this);
|
||||
if (currentBrandSettings) {
|
||||
currentBrandSettings->setContentsMargins(0, 0, 0, 0);
|
||||
brandSettingsContainerLayout->addWidget(currentBrandSettings);
|
||||
currentBrandSettings->updatePanel(offroad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/platform_selector.h"
|
||||
|
||||
class VehiclePanel : public QFrame {
|
||||
@@ -22,8 +23,12 @@ public slots:
|
||||
void updatePanel(bool _offroad);
|
||||
|
||||
private:
|
||||
QStackedLayout* main_layout = nullptr;
|
||||
QWidget* vehicleScreen = nullptr;
|
||||
PlatformSelector *platformSelector = nullptr;
|
||||
bool offroad;
|
||||
PlatformSelector* platformSelector = nullptr;
|
||||
BrandSettingsInterface* currentBrandSettings = nullptr;
|
||||
QWidget* brandSettingsContainer = nullptr;
|
||||
QVBoxLayout* brandSettingsContainerLayout = nullptr;
|
||||
bool offroad = false;
|
||||
|
||||
private slots:
|
||||
void updateBrandSettings();
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, con
|
||||
if (!title.isEmpty()) {
|
||||
title_label = new QPushButton(title);
|
||||
title_label->setFixedHeight(120);
|
||||
title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none; padding: 20 0 0 0");
|
||||
title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none; padding: 0 0 0 0");
|
||||
main_layout->addWidget(title_label, 1);
|
||||
|
||||
connect(title_label, &QPushButton::clicked, [=]() {
|
||||
@@ -148,7 +148,7 @@ AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, con
|
||||
|
||||
// description
|
||||
description = new QLabel(desc);
|
||||
description->setContentsMargins(0, 20, 40, 20);
|
||||
description->setContentsMargins(40, 20, 40, 20);
|
||||
description->setStyleSheet("font-size: 40px; color: grey");
|
||||
description->setWordWrap(true);
|
||||
description->setVisible(false);
|
||||
|
||||
@@ -219,7 +219,7 @@ class ButtonParamControlSP : public AbstractControlSP_SELECTOR {
|
||||
|
||||
public:
|
||||
ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||
const std::vector<QString> &button_texts, const int minimum_button_width = 300) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts) {
|
||||
const std::vector<QString> &button_texts, const int minimum_button_width = 300, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) {
|
||||
const QString style = R"(
|
||||
QPushButton {
|
||||
border-radius: 20px;
|
||||
@@ -246,6 +246,19 @@ public:
|
||||
key = param.toStdString();
|
||||
int value = atoi(params.get(key).c_str());
|
||||
|
||||
if (inline_layout) {
|
||||
button_param_layout->setMargin(0);
|
||||
button_param_layout->setSpacing(0);
|
||||
if (!title.isEmpty()) {
|
||||
main_layout->removeWidget(title_label);
|
||||
hlayout->addWidget(title_label, 1);
|
||||
}
|
||||
if (spacingItem != nullptr && main_layout->indexOf(spacingItem) != -1) {
|
||||
main_layout->removeItem(spacingItem);
|
||||
spacingItem = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
button_group = new QButtonGroup(this);
|
||||
button_group->setExclusive(true);
|
||||
for (int i = 0; i < button_texts.size(); i++) {
|
||||
@@ -254,12 +267,18 @@ public:
|
||||
button->setChecked(i == value);
|
||||
button->setStyleSheet(style);
|
||||
button->setMinimumWidth(minimum_button_width);
|
||||
if (i == 0) hlayout->addSpacing(2);
|
||||
hlayout->addWidget(button);
|
||||
if (i == 0) button_param_layout->addSpacing(2);
|
||||
button_param_layout->addWidget(button);
|
||||
button_group->addButton(button, i);
|
||||
}
|
||||
|
||||
hlayout->setAlignment(Qt::AlignLeft);
|
||||
button_param_layout->setAlignment(Qt::AlignLeft);
|
||||
if (is_inline_layout) {
|
||||
QFrame *container = new QFrame;
|
||||
container->setLayout(button_param_layout);
|
||||
container->setStyleSheet("background-color: #393939; border-radius: 20px;");
|
||||
hlayout->addWidget(container);
|
||||
}
|
||||
|
||||
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
|
||||
params.put(key, std::to_string(id));
|
||||
@@ -305,15 +324,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void setDisabledSelectedButton(std::string val) {
|
||||
int value = atoi(val.c_str());
|
||||
void setEnableSelectedButtons(bool enable, const std::vector<int>& enabled_btns = {}) const {
|
||||
for (int i = 0; i < button_group->buttons().size(); i++) {
|
||||
button_group->buttons()[i]->setEnabled(i != value);
|
||||
// Enable the button if its index is in the enabled list
|
||||
bool should_enable = std::find(enabled_btns.begin(), enabled_btns.end(), i) != enabled_btns.end();
|
||||
button_group->buttons()[i]->setEnabled(enable && should_enable);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override {
|
||||
if (is_inline_layout) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
@@ -348,6 +372,8 @@ private:
|
||||
std::vector<QString> button_texts;
|
||||
|
||||
bool button_group_enabled = true;
|
||||
bool is_inline_layout;
|
||||
QHBoxLayout *button_param_layout = is_inline_layout ? new QHBoxLayout() : hlayout;
|
||||
};
|
||||
|
||||
class ListWidgetSP : public QWidget {
|
||||
@@ -360,7 +386,7 @@ public:
|
||||
outer_layout.addLayout(&inner_layout);
|
||||
inner_layout.setMargin(0);
|
||||
inner_layout.setSpacing(25); // default spacing is 25
|
||||
outer_layout.addStretch();
|
||||
outer_layout.addStretch(1);
|
||||
}
|
||||
inline void addItem(QWidget *w) { inner_layout.addWidget(w); }
|
||||
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
|
||||
@@ -416,8 +442,8 @@ class OptionControlSP : public AbstractControlSP_SELECTOR {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
bool isInlineLayout;
|
||||
QHBoxLayout *optionSelectorLayout = isInlineLayout ? new QHBoxLayout() : hlayout;
|
||||
bool is_inline_layout;
|
||||
QHBoxLayout *optionSelectorLayout = is_inline_layout ? new QHBoxLayout() : hlayout;
|
||||
|
||||
struct MinMaxValue {
|
||||
int min_value;
|
||||
@@ -438,7 +464,7 @@ private:
|
||||
|
||||
public:
|
||||
OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||
const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const QMap<QString, QString> *valMap = nullptr) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), isInlineLayout(inline_layout) {
|
||||
const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const QMap<QString, QString> *valMap = nullptr) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout) {
|
||||
const QString style = R"(
|
||||
QPushButton {
|
||||
border-radius: 20px;
|
||||
@@ -513,7 +539,7 @@ public:
|
||||
}
|
||||
|
||||
optionSelectorLayout->setAlignment(Qt::AlignLeft);
|
||||
if (isInlineLayout) {
|
||||
if (is_inline_layout) {
|
||||
QFrame *container = new QFrame;
|
||||
container->setLayout(optionSelectorLayout);
|
||||
container->setStyleSheet("background-color: #393939; border-radius: 20px;");
|
||||
@@ -540,7 +566,7 @@ public:
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override {
|
||||
if (isInlineLayout) {
|
||||
if (is_inline_layout) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -633,16 +659,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// Override mouse release event to handle style updates smoothly
|
||||
void mouseReleaseEvent(QMouseEvent *event) override {
|
||||
if (!key.empty()) {
|
||||
bool next_state = !params.getBool(key);
|
||||
updateStyle(next_state);
|
||||
}
|
||||
QPushButton::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string key = "";
|
||||
Params params;
|
||||
@@ -651,13 +667,14 @@ private:
|
||||
|
||||
QString btn_enabled_off_style = "QPushButton:enabled { background-color: #393939; }";
|
||||
QString btn_enabled_on_style = "QPushButton:enabled { background-color: #1e79e8; }";
|
||||
QString btn_pressed_style = "QPushButton:pressed { background-color: #4A4A4A; }";
|
||||
QString btn_disabled_stype = "QPushButton:disabled { background-color: #121212; color: #5C5C5C; }";
|
||||
QString btn_off_pressed_style = "QPushButton:pressed { background-color: #4A4A4A; }";
|
||||
QString btn_on_pressed_style = "QPushButton:pressed { background-color: #1E8FFF; }";
|
||||
QString btn_disabled_style = "QPushButton:disabled { background-color: #121212; color: #5C5C5C; }";
|
||||
|
||||
void updateStyle(bool enabled) {
|
||||
QString enabled_style = enabled ? btn_enabled_on_style : btn_enabled_off_style;
|
||||
|
||||
setStyleSheet(buttonStyle + enabled_style + btn_pressed_style + btn_disabled_stype);
|
||||
QString pressed_style = enabled ? btn_on_pressed_style : btn_off_pressed_style;
|
||||
setStyleSheet(buttonStyle + enabled_style + pressed_style + btn_disabled_style);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ DATA: dict[str, capnp.lib.capnp._DynamicStructBuilder] = dict.fromkeys(
|
||||
"driverStateV2", "roadCameraState", "wideRoadCameraState", "driverCameraState"], None)
|
||||
|
||||
def setup_homescreen(click, pm: PubMaster, scroll=None):
|
||||
pass
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_device(click, pm: PubMaster, scroll=None):
|
||||
click(100, 100)
|
||||
@@ -61,6 +61,7 @@ def setup_settings_software(click, pm: PubMaster, scroll=None):
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_firehose(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
scroll(-400, 278, 962)
|
||||
click(278, 862)
|
||||
|
||||
@@ -151,7 +152,7 @@ def setup_keyboard_uppercase(click, pm: PubMaster, scroll=None):
|
||||
|
||||
def setup_driver_camera(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
click(1720, 620)
|
||||
click(1720, 825)
|
||||
DATA['deviceState'].deviceState.started = False
|
||||
setup_onroad(click, pm)
|
||||
DATA['deviceState'].deviceState.started = True
|
||||
@@ -239,11 +240,17 @@ def setup_settings_steering_alc(click, pm: PubMaster, scroll=None):
|
||||
click(970, 534)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_trips(click, pm: PubMaster, scroll=None):
|
||||
def setup_settings_driving(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
click(278, 962)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_trips(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
scroll(-400, 278, 962)
|
||||
click(278, 646)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_vehicle(click, pm: PubMaster, scroll=None):
|
||||
Params().put("CarPlatformBundle", json.dumps(
|
||||
{
|
||||
@@ -291,6 +298,7 @@ CASES.update({
|
||||
"settings_steering": setup_settings_steering,
|
||||
"settings_steering_mads": setup_settings_steering_mads,
|
||||
"settings_steering_alc": setup_settings_steering_alc,
|
||||
"settings_driving": setup_settings_driving,
|
||||
"settings_trips": setup_settings_trips,
|
||||
"settings_vehicle": setup_settings_vehicle,
|
||||
})
|
||||
|
||||
+34
-12
@@ -11,15 +11,37 @@ from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP, HyundaiSafetyFlagsSP
|
||||
|
||||
|
||||
class MadsSteeringModeOnBrake:
|
||||
REMAIN_ACTIVE = 0
|
||||
PAUSE = 1
|
||||
DISENGAGE = 2
|
||||
|
||||
|
||||
def get_mads_limited_brands(CP: structs.CarParams) -> bool:
|
||||
return CP.brand in ("rivian", "tesla")
|
||||
|
||||
|
||||
def read_steering_mode_param(CP: structs.CarParams, params: Params):
|
||||
if get_mads_limited_brands(CP):
|
||||
return MadsSteeringModeOnBrake.DISENGAGE
|
||||
|
||||
try:
|
||||
return int(params.get("MadsSteeringMode"))
|
||||
except (ValueError, TypeError):
|
||||
return MadsSteeringModeOnBrake.REMAIN_ACTIVE
|
||||
|
||||
|
||||
def set_alternative_experience(CP: structs.CarParams, params: Params):
|
||||
enabled = params.get_bool("Mads")
|
||||
pause_lateral_on_brake = params.get_bool("MadsPauseLateralOnBrake")
|
||||
steering_mode = read_steering_mode_param(CP, params)
|
||||
|
||||
if enabled:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ENABLE_MADS
|
||||
|
||||
if pause_lateral_on_brake:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISENGAGE_LATERAL_ON_BRAKE
|
||||
if steering_mode == MadsSteeringModeOnBrake.DISENGAGE:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.MADS_DISENGAGE_LATERAL_ON_BRAKE
|
||||
elif steering_mode == MadsSteeringModeOnBrake.PAUSE:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.MADS_PAUSE_LATERAL_ON_BRAKE
|
||||
|
||||
|
||||
def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params):
|
||||
@@ -31,12 +53,12 @@ def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, p
|
||||
CP_SP.flags |= HyundaiFlagsSP.LONGITUDINAL_MAIN_CRUISE_TOGGLEABLE.value
|
||||
CP_SP.safetyParam |= HyundaiSafetyFlagsSP.LONG_MAIN_CRUISE_TOGGLEABLE
|
||||
|
||||
# MADS is currently not supported in Tesla due to lack of consistent states to engage controls
|
||||
# TODO-SP: To enable MADS for Tesla, identify consistent signals for MADS toggling
|
||||
if CP.brand == "tesla":
|
||||
params.remove("Mads")
|
||||
|
||||
# MADS is currently not supported in Rivian due to lack of consistent states to engage controls
|
||||
# TODO-SP: To enable MADS for Rivian, identify consistent signals for MADS toggling
|
||||
if CP.brand == "rivian":
|
||||
params.remove("Mads")
|
||||
# MADS Partial Support
|
||||
# MADS is currently partially supported for these platforms due to lack of consistent states to engage controls
|
||||
# Only MadsSteeringModeOnBrake.DISENGAGE is supported for these platforms
|
||||
# TODO-SP: To enable MADS full support for Rivian/Tesla, identify consistent signals for MADS toggling
|
||||
mads_partial_support = get_mads_limited_brands(CP)
|
||||
if mads_partial_support:
|
||||
params.put("MadsSteeringMode", "2")
|
||||
params.put_bool("MadsUnifiedEngagementMode", True)
|
||||
params.remove("MadsMainCruiseAllowed")
|
||||
|
||||
+99
-47
@@ -9,6 +9,8 @@ from cereal import log, custom
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.values import HyundaiFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param, get_mads_limited_brands
|
||||
from openpilot.sunnypilot.mads.state import StateMachine, GEARS_ALLOW_PAUSED_SILENT
|
||||
|
||||
State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
|
||||
@@ -24,75 +26,105 @@ IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
||||
|
||||
class ModularAssistiveDrivingSystem:
|
||||
def __init__(self, selfdrive):
|
||||
self.CP = selfdrive.CP
|
||||
self.params = selfdrive.params
|
||||
|
||||
self.enabled = False
|
||||
self.active = False
|
||||
self.available = False
|
||||
self.allow_always = False
|
||||
self.no_main_cruise = False
|
||||
self.selfdrive = selfdrive
|
||||
self.selfdrive.enabled_prev = False
|
||||
self.state_machine = StateMachine(self)
|
||||
self.events = self.selfdrive.events
|
||||
self.events_sp = self.selfdrive.events_sp
|
||||
self.disengage_on_accelerator = not self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
if self.selfdrive.CP.brand == "hyundai":
|
||||
if self.selfdrive.CP.flags & (HyundaiFlags.HAS_LDA_BUTTON | HyundaiFlags.CANFD):
|
||||
if self.CP.brand == "hyundai":
|
||||
if self.CP.flags & (HyundaiFlags.HAS_LDA_BUTTON | HyundaiFlags.CANFD):
|
||||
self.allow_always = True
|
||||
|
||||
if get_mads_limited_brands(self.CP):
|
||||
self.no_main_cruise = True
|
||||
|
||||
# read params on init
|
||||
self.enabled_toggle = self.params.get_bool("Mads")
|
||||
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
|
||||
self.pause_lateral_on_brake_toggle = self.params.get_bool("MadsPauseLateralOnBrake")
|
||||
self.steering_mode_on_brake = read_steering_mode_param(self.CP, self.params)
|
||||
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
|
||||
|
||||
def read_params(self):
|
||||
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
|
||||
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
|
||||
|
||||
def pedal_pressed_non_gas_pressed(self, CS: structs.CarState) -> bool:
|
||||
if self.events.has(EventName.pedalPressed) and not (CS.gasPressed and not self.selfdrive.CS_prev.gasPressed and self.disengage_on_accelerator):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def should_silent_lkas_enable(self, CS: structs.CarState) -> bool:
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.PAUSE and self.pedal_pressed_non_gas_pressed(CS):
|
||||
return False
|
||||
|
||||
if self.events_sp.contains_in_list(GEARS_ALLOW_PAUSED_SILENT):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def block_unified_engagement_mode(self) -> bool:
|
||||
# UEM disabled
|
||||
if not self.unified_engagement_mode:
|
||||
return True
|
||||
|
||||
if self.enabled:
|
||||
return True
|
||||
|
||||
if self.selfdrive.enabled and self.selfdrive.enabled_prev:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_wrong_car_mode(self, alert_only: bool) -> None:
|
||||
if alert_only:
|
||||
if self.events.has(EventName.wrongCarMode):
|
||||
self.replace_event(EventName.wrongCarMode, EventNameSP.wrongCarModeAlertOnly)
|
||||
else:
|
||||
self.events.remove(EventName.wrongCarMode)
|
||||
|
||||
def transition_paused_state(self):
|
||||
if self.state_machine.state != State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasDisable)
|
||||
|
||||
def replace_event(self, old_event: int, new_event: int):
|
||||
self.events.remove(old_event)
|
||||
self.events_sp.add(new_event)
|
||||
|
||||
def update_events(self, CS: structs.CarState):
|
||||
def update_unified_engagement_mode():
|
||||
uem_blocked = self.enabled or (self.selfdrive.enabled and self.selfdrive.enabled_prev)
|
||||
if (self.unified_engagement_mode and uem_blocked) or not self.unified_engagement_mode:
|
||||
self.events.remove(EventName.pcmEnable)
|
||||
self.events.remove(EventName.buttonEnable)
|
||||
|
||||
def transition_paused_state():
|
||||
if self.state_machine.state != State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasDisable)
|
||||
|
||||
def replace_event(old_event: int, new_event: int):
|
||||
self.events.remove(old_event)
|
||||
self.events_sp.add(new_event)
|
||||
|
||||
if not self.selfdrive.enabled and self.enabled:
|
||||
if self.events.has(EventName.doorOpen):
|
||||
replace_event(EventName.doorOpen, EventNameSP.silentDoorOpen)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.doorOpen, EventNameSP.silentDoorOpen)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.seatbeltNotLatched):
|
||||
replace_event(EventName.seatbeltNotLatched, EventNameSP.silentSeatbeltNotLatched)
|
||||
transition_paused_state()
|
||||
if self.events.has(EventName.wrongGear) and (CS.standstill or CS.gearShifter == GearShifter.reverse):
|
||||
replace_event(EventName.wrongGear, EventNameSP.silentWrongGear)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.seatbeltNotLatched, EventNameSP.silentSeatbeltNotLatched)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.wrongGear) and (CS.vEgo < 2.5 or CS.gearShifter == GearShifter.reverse):
|
||||
self.replace_event(EventName.wrongGear, EventNameSP.silentWrongGear)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.reverseGear):
|
||||
replace_event(EventName.reverseGear, EventNameSP.silentReverseGear)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.reverseGear, EventNameSP.silentReverseGear)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.brakeHold):
|
||||
replace_event(EventName.brakeHold, EventNameSP.silentBrakeHold)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.brakeHold, EventNameSP.silentBrakeHold)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.parkBrake):
|
||||
replace_event(EventName.parkBrake, EventNameSP.silentParkBrake)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.parkBrake, EventNameSP.silentParkBrake)
|
||||
self.transition_paused_state()
|
||||
|
||||
if self.pause_lateral_on_brake_toggle:
|
||||
if CS.brakePressed:
|
||||
transition_paused_state()
|
||||
|
||||
if not (self.pause_lateral_on_brake_toggle and CS.brakePressed) and \
|
||||
not self.events_sp.contains_in_list(GEARS_ALLOW_PAUSED_SILENT):
|
||||
if self.state_machine.state == State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasEnable)
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.PAUSE:
|
||||
if self.pedal_pressed_non_gas_pressed(CS):
|
||||
self.transition_paused_state()
|
||||
|
||||
self.events.remove(EventName.preEnableStandstill)
|
||||
self.events.remove(EventName.belowEngageSpeed)
|
||||
@@ -100,8 +132,19 @@ class ModularAssistiveDrivingSystem:
|
||||
self.events.remove(EventName.cruiseDisabled)
|
||||
self.events.remove(EventName.manualRestart)
|
||||
|
||||
if self.events.has(EventName.pcmEnable) or self.events.has(EventName.buttonEnable):
|
||||
update_unified_engagement_mode()
|
||||
selfdrive_enable_events = self.events.has(EventName.pcmEnable) or self.events.has(EventName.buttonEnable)
|
||||
set_speed_btns_enable = any(be.type in SET_SPEED_BUTTONS for be in CS.buttonEvents)
|
||||
|
||||
# wrongCarMode alert only or actively block control
|
||||
self.get_wrong_car_mode(selfdrive_enable_events or set_speed_btns_enable)
|
||||
|
||||
if selfdrive_enable_events:
|
||||
if self.pedal_pressed_non_gas_pressed(CS):
|
||||
self.events_sp.add(EventNameSP.pedalPressedAlertOnly)
|
||||
|
||||
if self.block_unified_engagement_mode():
|
||||
self.events.remove(EventName.pcmEnable)
|
||||
self.events.remove(EventName.buttonEnable)
|
||||
else:
|
||||
if self.main_enabled_toggle:
|
||||
if CS.cruiseState.available and not self.selfdrive.CS_prev.cruiseState.available:
|
||||
@@ -120,20 +163,29 @@ class ModularAssistiveDrivingSystem:
|
||||
else:
|
||||
self.events_sp.add(EventNameSP.lkasEnable)
|
||||
|
||||
if not CS.cruiseState.available:
|
||||
if not CS.cruiseState.available and not self.no_main_cruise:
|
||||
self.events.remove(EventName.buttonEnable)
|
||||
if self.selfdrive.CS_prev.cruiseState.available:
|
||||
self.events_sp.add(EventNameSP.lkasDisable)
|
||||
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.DISENGAGE:
|
||||
if self.pedal_pressed_non_gas_pressed(CS):
|
||||
if self.enabled:
|
||||
self.events_sp.add(EventNameSP.lkasDisable)
|
||||
else:
|
||||
# block lkasEnable if being sent, then send pedalPressedAlertOnly event
|
||||
if self.events_sp.contains(EventNameSP.lkasEnable):
|
||||
self.events_sp.remove(EventNameSP.lkasEnable)
|
||||
self.events_sp.add(EventNameSP.pedalPressedAlertOnly)
|
||||
|
||||
if self.should_silent_lkas_enable(CS):
|
||||
if self.state_machine.state == State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasEnable)
|
||||
|
||||
self.events.remove(EventName.pcmDisable)
|
||||
self.events.remove(EventName.buttonCancel)
|
||||
self.events.remove(EventName.pedalPressed)
|
||||
self.events.remove(EventName.wrongCruiseMode)
|
||||
if any(be.type in SET_SPEED_BUTTONS for be in CS.buttonEvents):
|
||||
if self.events.has(EventName.wrongCarMode):
|
||||
replace_event(EventName.wrongCarMode, EventNameSP.wrongCarModeAlertOnly)
|
||||
else:
|
||||
self.events.remove(EventName.wrongCarMode)
|
||||
|
||||
def update(self, CS: structs.CarState):
|
||||
if not self.enabled_toggle:
|
||||
@@ -141,7 +193,7 @@ class ModularAssistiveDrivingSystem:
|
||||
|
||||
self.update_events(CS)
|
||||
|
||||
if not self.selfdrive.CP.passive and self.selfdrive.initialized:
|
||||
if not self.CP.passive and self.selfdrive.initialized:
|
||||
self.enabled, self.active = self.state_machine.update()
|
||||
|
||||
# Copy of previous SelfdriveD states for MADS events handling
|
||||
|
||||
@@ -51,7 +51,7 @@ class StateMachine:
|
||||
if self.state != State.disabled:
|
||||
# user and immediate disable always have priority in a non-disabled state
|
||||
if self.check_contains(ET.USER_DISABLE):
|
||||
if self._events_sp.has(EventNameSP.silentLkasDisable) or self._events_sp.has(EventNameSP.silentBrakeHold):
|
||||
if self._events_sp.has(EventNameSP.silentLkasDisable):
|
||||
self.state = State.paused
|
||||
else:
|
||||
self.state = State.disabled
|
||||
|
||||
@@ -73,7 +73,7 @@ class TestMADSStateMachine:
|
||||
self.clear_events()
|
||||
|
||||
def test_user_disable_to_paused(self):
|
||||
paused_events = (EventNameSP.silentLkasDisable, EventNameSP.silentBrakeHold)
|
||||
paused_events = (EventNameSP.silentLkasDisable, )
|
||||
for state in ALL_STATES:
|
||||
for et in MAINTAIN_STATES[state]:
|
||||
self.events_sp.add(make_event([et, ET.USER_DISABLE]))
|
||||
|
||||
@@ -53,14 +53,7 @@ lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=c
|
||||
lenvCython.Program('runners/snpemodel_pyx.so', 'runners/snpemodel_pyx.pyx', LIBS=[snpemodel_lib, snpe_lib, *cython_libs], FRAMEWORKS=frameworks, RPATH=snpe_rpath)
|
||||
lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks)
|
||||
|
||||
tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath)]
|
||||
|
||||
# Get model metadata
|
||||
fn = File("models/supercombo").abspath
|
||||
cmd = f'python3 {Dir("#sunnypilot/modeld").abspath}/get_model_metadata.py {fn}.onnx'
|
||||
lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files, cmd)
|
||||
|
||||
if arch == "larch64":
|
||||
thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[gpucommon, common, 'OpenCL', 'dl'])
|
||||
thneedmodel_lib = env.Library('thneedmodel', ['runners/thneedmodel.cc'])
|
||||
lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, gpucommon, common, 'dl', 'OpenCL'])
|
||||
lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, gpucommon, common, 'dl', 'OpenCL'])
|
||||
@@ -23,7 +23,7 @@ from openpilot.sunnypilot.modeld.parse_model_outputs import Parser
|
||||
from openpilot.sunnypilot.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
|
||||
from openpilot.sunnypilot.modeld.constants import ModelConstants
|
||||
from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext
|
||||
from openpilot.sunnypilot.modeld.runners.run_helpers import get_model_path, load_metadata, prepare_inputs, load_meta_constants
|
||||
from openpilot.sunnypilot.models.helpers import get_model_path, load_metadata, prepare_inputs, load_meta_constants
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld_snpe"
|
||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fb2018c74cdd9e5cb070ec7bed7f8581fabd55e39057d0a03aaffd2e42408154
|
||||
size 62486347
|
||||
@@ -1,92 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import numpy as np
|
||||
|
||||
from cereal import custom
|
||||
from openpilot.sunnypilot.modeld.constants import Meta, MetaTombRaider, MetaSimPose
|
||||
from openpilot.sunnypilot.modeld.runners import ModelRunner
|
||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from pathlib import Path
|
||||
|
||||
USE_ONNX = os.getenv('USE_ONNX', PC)
|
||||
|
||||
CUSTOM_MODEL_PATH = Paths.model_root()
|
||||
METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl'
|
||||
|
||||
ModelManager = custom.ModelManagerSP
|
||||
|
||||
|
||||
def get_model_path():
|
||||
if USE_ONNX:
|
||||
return {ModelRunner.ONNX: Path(__file__).parent / '../models/supercombo.onnx'}
|
||||
|
||||
if bundle := get_active_bundle():
|
||||
drive_model = next(model for model in bundle.models if model.type == ModelManager.Type.drive)
|
||||
return {ModelRunner.THNEED: f"{CUSTOM_MODEL_PATH}/{drive_model.fileName}"}
|
||||
|
||||
return {ModelRunner.THNEED: Path(__file__).parent / '../models/supercombo.thneed'}
|
||||
|
||||
|
||||
def load_metadata():
|
||||
metadata_path = METADATA_PATH
|
||||
|
||||
if bundle := get_active_bundle():
|
||||
metadata_model = next(model for model in bundle.models if model.type == ModelManager.Type.metadata)
|
||||
metadata_path = f"{CUSTOM_MODEL_PATH}/{metadata_model.fileName}"
|
||||
|
||||
with open(metadata_path, 'rb') as f:
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
def prepare_inputs(model_metadata) -> dict[str, np.ndarray]:
|
||||
# img buffers are managed in openCL transform code so we don't pass them as inputs
|
||||
inputs = {
|
||||
k: np.zeros(v, dtype=np.float32).flatten()
|
||||
for k, v in model_metadata['input_shapes'].items()
|
||||
if 'img' not in k
|
||||
}
|
||||
|
||||
return inputs
|
||||
|
||||
|
||||
def load_meta_constants(model_metadata):
|
||||
"""
|
||||
Determines and loads the appropriate meta model class based on the metadata provided. The function checks
|
||||
specific keys and conditions within the provided metadata dictionary to identify the corresponding meta
|
||||
model class to return.
|
||||
|
||||
:param model_metadata: Dictionary containing metadata about the model. It includes
|
||||
details such as input shapes, output slices, and other configurations for identifying
|
||||
metadata-dependent meta model classes.
|
||||
:type model_metadata: dict
|
||||
:return: The appropriate meta model class (Meta, MetaSimPose, or MetaTombRaider)
|
||||
based on the conditions and metadata provided.
|
||||
:rtype: type
|
||||
"""
|
||||
meta = Meta # Default Meta
|
||||
|
||||
if 'sim_pose' in model_metadata['input_shapes'].keys():
|
||||
# Meta for models with sim_pose input
|
||||
meta = MetaSimPose
|
||||
else:
|
||||
# Meta for Tomb Raider, it does not include sim_pose input but has the same meta slice as previous models
|
||||
meta_slice = model_metadata['output_slices']['meta']
|
||||
meta_tf_slice = slice(5868, 5921, None)
|
||||
|
||||
if (
|
||||
meta_slice.start == meta_tf_slice.start and
|
||||
meta_slice.stop == meta_tf_slice.stop and
|
||||
meta_slice.step == meta_tf_slice.step
|
||||
):
|
||||
meta = MetaTombRaider
|
||||
|
||||
return meta
|
||||
@@ -29,22 +29,4 @@ for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transfor
|
||||
cython_libs = envCython["LIBS"] + libs
|
||||
commonmodel_lib = lenv.Library('commonmodel', common_src)
|
||||
lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks)
|
||||
tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]
|
||||
|
||||
# Get model metadata
|
||||
fn = File("models/supercombo").abspath
|
||||
cmd = f'python3 {Dir("#sunnypilot/modeld_v2").abspath}/get_model_metadata.py {fn}.onnx'
|
||||
lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files, cmd)
|
||||
|
||||
# Compile tinygrad model
|
||||
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
|
||||
if arch == 'larch64':
|
||||
device_string = 'QCOM=1'
|
||||
else:
|
||||
device_string = 'CLANG=1 IMAGE=0'
|
||||
|
||||
for model_name in ['supercombo', 'dmonitoring_model']:
|
||||
fn = File(f"models/{model_name}").abspath
|
||||
cmd = f'{pythonpath_string} {device_string} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl'
|
||||
lenv.Command(fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, cmd)
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
exec "$DIR/dmonitoringmodeld.py" "$@"
|
||||
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from openpilot.system.hardware import TICI
|
||||
if TICI:
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.dtype import dtypes
|
||||
from openpilot.sunnypilot.modeld_v2.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
|
||||
os.environ['QCOM'] = '1'
|
||||
else:
|
||||
from openpilot.sunnypilot.modeld_v2.runners.ort_helpers import make_onnx_cpu_runner
|
||||
import math
|
||||
import time
|
||||
import pickle
|
||||
import ctypes
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from setproctitle import setproctitle
|
||||
|
||||
from cereal import messaging
|
||||
from cereal.messaging import PubMaster, SubMaster
|
||||
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.realtime import config_realtime_process
|
||||
from openpilot.common.transformations.model import dmonitoringmodel_intrinsics, DM_INPUT_SIZE
|
||||
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
|
||||
from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import CLContext, MonitoringModelFrame
|
||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import sigmoid
|
||||
from openpilot.system import sentry
|
||||
|
||||
MODEL_WIDTH, MODEL_HEIGHT = DM_INPUT_SIZE
|
||||
CALIB_LEN = 3
|
||||
FEATURE_LEN = 512
|
||||
OUTPUT_SIZE = 84 + FEATURE_LEN
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld"
|
||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||
MODEL_PATH = Path(__file__).parent / 'models/dmonitoring_model.onnx'
|
||||
MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl'
|
||||
|
||||
|
||||
class DriverStateResult(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("face_orientation", ctypes.c_float*3),
|
||||
("face_position", ctypes.c_float*3),
|
||||
("face_orientation_std", ctypes.c_float*3),
|
||||
("face_position_std", ctypes.c_float*3),
|
||||
("face_prob", ctypes.c_float),
|
||||
("_unused_a", ctypes.c_float*8),
|
||||
("left_eye_prob", ctypes.c_float),
|
||||
("_unused_b", ctypes.c_float*8),
|
||||
("right_eye_prob", ctypes.c_float),
|
||||
("left_blink_prob", ctypes.c_float),
|
||||
("right_blink_prob", ctypes.c_float),
|
||||
("sunglasses_prob", ctypes.c_float),
|
||||
("occluded_prob", ctypes.c_float),
|
||||
("ready_prob", ctypes.c_float*4),
|
||||
("not_ready_prob", ctypes.c_float*2)]
|
||||
|
||||
|
||||
class DMonitoringModelResult(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("driver_state_lhd", DriverStateResult),
|
||||
("driver_state_rhd", DriverStateResult),
|
||||
("poor_vision_prob", ctypes.c_float),
|
||||
("wheel_on_right_prob", ctypes.c_float),
|
||||
("features", ctypes.c_float*FEATURE_LEN)]
|
||||
|
||||
|
||||
class ModelState:
|
||||
inputs: dict[str, np.ndarray]
|
||||
output: np.ndarray
|
||||
|
||||
def __init__(self, cl_ctx):
|
||||
assert ctypes.sizeof(DMonitoringModelResult) == OUTPUT_SIZE * ctypes.sizeof(ctypes.c_float)
|
||||
|
||||
self.frame = MonitoringModelFrame(cl_ctx)
|
||||
self.numpy_inputs = {
|
||||
'calib': np.zeros((1, CALIB_LEN), dtype=np.float32),
|
||||
}
|
||||
|
||||
if TICI:
|
||||
self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()}
|
||||
with open(MODEL_PKL_PATH, "rb") as f:
|
||||
self.model_run = pickle.load(f)
|
||||
else:
|
||||
self.onnx_cpu_runner = make_onnx_cpu_runner(MODEL_PATH)
|
||||
|
||||
def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]:
|
||||
self.numpy_inputs['calib'][0,:] = calib
|
||||
|
||||
t1 = time.perf_counter()
|
||||
|
||||
input_img_cl = self.frame.prepare(buf, transform.flatten())
|
||||
if TICI:
|
||||
# The imgs tensors are backed by opencl memory, only need init once
|
||||
if 'input_img' not in self.tensor_inputs:
|
||||
self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, (1, MODEL_WIDTH*MODEL_HEIGHT), dtype=dtypes.uint8)
|
||||
else:
|
||||
self.numpy_inputs['input_img'] = self.frame.buffer_from_cl(input_img_cl).reshape((1, MODEL_WIDTH*MODEL_HEIGHT))
|
||||
|
||||
if TICI:
|
||||
output = self.model_run(**self.tensor_inputs).numpy().flatten()
|
||||
else:
|
||||
output = self.onnx_cpu_runner.run(None, self.numpy_inputs)[0].flatten()
|
||||
|
||||
t2 = time.perf_counter()
|
||||
return output, t2 - t1
|
||||
|
||||
|
||||
def fill_driver_state(msg, ds_result: DriverStateResult):
|
||||
msg.faceOrientation = list(ds_result.face_orientation)
|
||||
msg.faceOrientationStd = [math.exp(x) for x in ds_result.face_orientation_std]
|
||||
msg.facePosition = list(ds_result.face_position[:2])
|
||||
msg.facePositionStd = [math.exp(x) for x in ds_result.face_position_std[:2]]
|
||||
msg.faceProb = float(sigmoid(ds_result.face_prob))
|
||||
msg.leftEyeProb = float(sigmoid(ds_result.left_eye_prob))
|
||||
msg.rightEyeProb = float(sigmoid(ds_result.right_eye_prob))
|
||||
msg.leftBlinkProb = float(sigmoid(ds_result.left_blink_prob))
|
||||
msg.rightBlinkProb = float(sigmoid(ds_result.right_blink_prob))
|
||||
msg.sunglassesProb = float(sigmoid(ds_result.sunglasses_prob))
|
||||
msg.occludedProb = float(sigmoid(ds_result.occluded_prob))
|
||||
msg.readyProb = [float(sigmoid(x)) for x in ds_result.ready_prob]
|
||||
msg.notReadyProb = [float(sigmoid(x)) for x in ds_result.not_ready_prob]
|
||||
|
||||
|
||||
def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, gpu_execution_time: float):
|
||||
model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents
|
||||
msg = messaging.new_message('driverStateV2', valid=True)
|
||||
ds = msg.driverStateV2
|
||||
ds.frameId = frame_id
|
||||
ds.modelExecutionTime = execution_time
|
||||
ds.gpuExecutionTime = gpu_execution_time
|
||||
ds.poorVisionProb = float(sigmoid(model_result.poor_vision_prob))
|
||||
ds.wheelOnRightProb = float(sigmoid(model_result.wheel_on_right_prob))
|
||||
ds.rawPredictions = model_output.tobytes() if SEND_RAW_PRED else b''
|
||||
fill_driver_state(ds.leftDriverData, model_result.driver_state_lhd)
|
||||
fill_driver_state(ds.rightDriverData, model_result.driver_state_rhd)
|
||||
return msg
|
||||
|
||||
|
||||
def main():
|
||||
setproctitle(PROCESS_NAME)
|
||||
config_realtime_process([0, 1, 2, 3], 5)
|
||||
|
||||
sentry.set_tag("daemon", PROCESS_NAME)
|
||||
cloudlog.bind(daemon=PROCESS_NAME)
|
||||
|
||||
cl_context = CLContext()
|
||||
model = ModelState(cl_context)
|
||||
cloudlog.warning("models loaded, dmonitoringmodeld starting")
|
||||
|
||||
cloudlog.warning("connecting to driver stream")
|
||||
vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context)
|
||||
while not vipc_client.connect(False):
|
||||
time.sleep(0.1)
|
||||
assert vipc_client.is_connected()
|
||||
cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}")
|
||||
|
||||
sm = SubMaster(["liveCalibration"])
|
||||
pm = PubMaster(["driverStateV2"])
|
||||
|
||||
calib = np.zeros(CALIB_LEN, dtype=np.float32)
|
||||
model_transform = None
|
||||
|
||||
while True:
|
||||
buf = vipc_client.recv()
|
||||
if buf is None:
|
||||
continue
|
||||
|
||||
if model_transform is None:
|
||||
cam = _os_fisheye if buf.width == _os_fisheye.width else _ar_ox_fisheye
|
||||
model_transform = np.linalg.inv(np.dot(dmonitoringmodel_intrinsics, np.linalg.inv(cam.intrinsics))).astype(np.float32)
|
||||
|
||||
sm.update(0)
|
||||
if sm.updated["liveCalibration"]:
|
||||
calib[:] = np.array(sm["liveCalibration"].rpyCalib)
|
||||
|
||||
t1 = time.perf_counter()
|
||||
model_output, gpu_execution_time = model.run(buf, calib, model_transform)
|
||||
t2 = time.perf_counter()
|
||||
|
||||
pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
cloudlog.warning(f"child {PROCESS_NAME} got SIGINT")
|
||||
except Exception:
|
||||
sentry.capture_exception()
|
||||
raise
|
||||
@@ -8,6 +8,7 @@ from openpilot.sunnypilot.modeld_v2 import MODEL_PATH, MODEL_PKL_PATH, METADATA_
|
||||
from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLMem
|
||||
from openpilot.sunnypilot.modeld_v2.runners.ort_helpers import make_onnx_cpu_runner, ORT_TYPES_TO_NP_TYPES
|
||||
from openpilot.sunnypilot.modeld_v2.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
|
||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
|
||||
from openpilot.system.hardware import TICI
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
@@ -34,8 +35,8 @@ class ModelRunner(ABC):
|
||||
|
||||
if bundle := get_active_bundle():
|
||||
bundle_models = {model.type.raw: model for model in bundle.models}
|
||||
self._drive_model = bundle_models.get(ModelManager.Type.drive)
|
||||
self._metadata_model = bundle_models.get(ModelManager.Type.metadata)
|
||||
self._drive_model = bundle_models.get(ModelManager.Model.Type.supercombo)
|
||||
self._metadata_model = self._drive_model.metadata
|
||||
self.is_20hz = bundle.is20hz
|
||||
|
||||
# Override the metadata path if a metadata model is found in the active bundle
|
||||
@@ -48,6 +49,7 @@ class ModelRunner(ABC):
|
||||
self.input_shapes = self.model_metadata['input_shapes']
|
||||
self.output_slices = self.model_metadata['output_slices']
|
||||
self.inputs: dict = {}
|
||||
self.parser = Parser()
|
||||
|
||||
@abstractmethod
|
||||
def prepare_inputs(self, imgs_cl: dict[str, CLMem], numpy_inputs: dict[str, np.ndarray], frames: dict[str, DrivingModelFrame]) -> dict:
|
||||
@@ -55,16 +57,22 @@ class ModelRunner(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def run_model(self):
|
||||
def _run_model(self):
|
||||
"""Run model inference with prepared inputs."""
|
||||
raise NotImplementedError("This method should be implemented in subclasses.")
|
||||
|
||||
def slice_outputs(self, model_outputs: np.ndarray) -> dict:
|
||||
def _slice_outputs(self, model_outputs: np.ndarray) -> dict:
|
||||
"""Slice model outputs according to metadata configuration."""
|
||||
parsed_outputs = {k: model_outputs[np.newaxis, v] for k, v in self.output_slices.items()}
|
||||
if SEND_RAW_PRED:
|
||||
parsed_outputs['raw_pred'] = model_outputs.copy()
|
||||
return parsed_outputs
|
||||
|
||||
def run_model(self) -> dict[str, np.ndarray]:
|
||||
"""Run model inference with prepared inputs and parse outputs."""
|
||||
result: dict[str, np.ndarray] = self.parser.parse_outputs(self._slice_outputs(self._run_model()))
|
||||
return result
|
||||
|
||||
|
||||
class TinygradRunner(ModelRunner):
|
||||
"""Tinygrad implementation of model runner for TICI hardware."""
|
||||
@@ -74,7 +82,7 @@ class TinygradRunner(ModelRunner):
|
||||
|
||||
model_pkl_path = MODEL_PKL_PATH
|
||||
if self._drive_model:
|
||||
model_pkl_path = f"{CUSTOM_MODEL_PATH}/{self._drive_model.fileName}"
|
||||
model_pkl_path = f"{CUSTOM_MODEL_PATH}/{self._drive_model.artifact.fileName}"
|
||||
assert model_pkl_path.endswith('_tinygrad.pkl'), f"Invalid model file: {model_pkl_path} for TinygradRunner"
|
||||
|
||||
# Load Tinygrad model
|
||||
@@ -108,7 +116,7 @@ class TinygradRunner(ModelRunner):
|
||||
|
||||
return self.inputs
|
||||
|
||||
def run_model(self):
|
||||
def _run_model(self):
|
||||
return self.model_run(**self.inputs).numpy().flatten()
|
||||
|
||||
|
||||
@@ -130,5 +138,5 @@ class ONNXRunner(ModelRunner):
|
||||
self.inputs[key] = frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.input_shapes[key]).astype(dtype=self.input_to_nptype[key])
|
||||
return self.inputs
|
||||
|
||||
def run_model(self):
|
||||
def _run_model(self):
|
||||
return self.runner.run(None, self.inputs)[0].flatten()
|
||||
|
||||
@@ -18,7 +18,6 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
from openpilot.common.transformations.model import get_warp_matrix
|
||||
from openpilot.system import sentry
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
|
||||
from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
|
||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
||||
from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLContext
|
||||
@@ -27,6 +26,7 @@ from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
|
||||
from openpilot.sunnypilot.modeld_v2.model_runner import ONNXRunner, TinygradRunner
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld"
|
||||
LAT_SMOOTH_SECONDS = 0.0
|
||||
|
||||
|
||||
class FrameMeta:
|
||||
@@ -41,7 +41,6 @@ class FrameMeta:
|
||||
class ModelState:
|
||||
frames: dict[str, DrivingModelFrame]
|
||||
inputs: dict[str, np.ndarray]
|
||||
output: np.ndarray
|
||||
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
||||
|
||||
def __init__(self, context: CLContext):
|
||||
@@ -54,8 +53,8 @@ class ModelState:
|
||||
self.frames = {'input_imgs': DrivingModelFrame(context, buffer_length), 'big_input_imgs': DrivingModelFrame(context, buffer_length)}
|
||||
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||
if self.model_runner.is_20hz:
|
||||
self.full_features_20Hz = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
|
||||
self.desire_20Hz = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN + 1, ModelConstants.DESIRE_LEN), dtype=np.float32)
|
||||
self.full_features_buffer = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
|
||||
self.full_desire = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN + 1, ModelConstants.DESIRE_LEN), dtype=np.float32)
|
||||
|
||||
# img buffers are managed in openCL transform code
|
||||
self.numpy_inputs = {}
|
||||
@@ -64,15 +63,10 @@ class ModelState:
|
||||
if key not in self.frames: # Managed by opencl
|
||||
self.numpy_inputs[key] = np.zeros(shape, dtype=np.float32)
|
||||
|
||||
self.parser = Parser()
|
||||
|
||||
if self.model_runner.is_20hz:
|
||||
net_output_size = self.model_runner.model_metadata['output_shapes']['outputs'][1]
|
||||
self.output = np.zeros(net_output_size, dtype=np.float32)
|
||||
|
||||
num_elements = self.numpy_inputs['features_buffer'].shape[1]
|
||||
step_size = int(-100 / num_elements)
|
||||
self.full_features_20Hz_idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
|
||||
self.full_features_buffer_idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
|
||||
self.desire_reshape_dims = (self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], -1, self.numpy_inputs['desire'].shape[2])
|
||||
|
||||
def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray,
|
||||
@@ -83,9 +77,9 @@ class ModelState:
|
||||
self.prev_desire[:] = inputs['desire']
|
||||
|
||||
if self.model_runner.is_20hz:
|
||||
self.desire_20Hz[:-1] = self.desire_20Hz[1:]
|
||||
self.desire_20Hz[-1] = new_desire
|
||||
self.numpy_inputs['desire'][:] = self.desire_20Hz.reshape(self.desire_reshape_dims).max(axis=2)
|
||||
self.full_desire[:-1] = self.full_desire[1:]
|
||||
self.full_desire[-1] = new_desire
|
||||
self.numpy_inputs['desire'][:] = self.full_desire.reshape(self.desire_reshape_dims).max(axis=2)
|
||||
else:
|
||||
length = inputs['desire'].shape[0]
|
||||
self.numpy_inputs['desire'][0, :-1] = self.numpy_inputs['desire'][0, 1:]
|
||||
@@ -105,13 +99,12 @@ class ModelState:
|
||||
return None
|
||||
|
||||
# Run model inference
|
||||
self.output = self.model_runner.run_model()
|
||||
outputs = self.parser.parse_outputs(self.model_runner.slice_outputs(self.output))
|
||||
outputs = self.model_runner.run_model()
|
||||
|
||||
if self.model_runner.is_20hz:
|
||||
self.full_features_20Hz[:-1] = self.full_features_20Hz[1:]
|
||||
self.full_features_20Hz[-1] = outputs['hidden_state'][0, :]
|
||||
self.numpy_inputs['features_buffer'][:] = self.full_features_20Hz[self.full_features_20Hz_idxs]
|
||||
self.full_features_buffer[:-1] = self.full_features_buffer[1:]
|
||||
self.full_features_buffer[-1] = outputs['hidden_state'][0, :]
|
||||
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[self.full_features_buffer_idxs]
|
||||
else:
|
||||
feature_len = outputs['hidden_state'].shape[1]
|
||||
self.numpy_inputs['features_buffer'][0, :-1] = self.numpy_inputs['features_buffer'][0, 1:]
|
||||
@@ -171,7 +164,7 @@ def main(demo=False):
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
params = Params()
|
||||
@@ -196,8 +189,8 @@ def main(demo=False):
|
||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("modeld got CarParams: %s", CP.brand)
|
||||
|
||||
# TODO this needs more thought, use .2s extra for now to estimate other delays
|
||||
steer_delay = CP.steerActuatorDelay + .2
|
||||
# Enable lagd support for modeld_v2
|
||||
steer_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
||||
|
||||
DH = DesireHelper()
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
fa69be01-b430-4504-9d72-7dcb058eb6dd
|
||||
d9fb22d1c4fa3ca3d201dbc8edf1d0f0918e53e6
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:50efe6451a3fb3fa04b6bb0e846544533329bd46ecefe9e657e91214dee2aaeb
|
||||
size 7196502
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d21daa542227ecc5972da45df4e26f018ba113c0461f270e367d57e3ad89221a
|
||||
size 51461700
|
||||
@@ -11,6 +11,7 @@ import time
|
||||
import requests
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from sunnypilot.models.helpers import is_bundle_version_compatible
|
||||
|
||||
from cereal import custom
|
||||
|
||||
@@ -19,68 +20,49 @@ class ModelParser:
|
||||
"""Handles parsing of model data into cereal objects"""
|
||||
|
||||
@staticmethod
|
||||
def _parse_model(full_name: str, file_name: str, uri_data: dict,
|
||||
model_type: custom.ModelManagerSP.Type) -> custom.ModelManagerSP.Model:
|
||||
model = custom.ModelManagerSP.Model()
|
||||
def _parse_download_uri(download_uri_data) -> custom.ModelManagerSP.DownloadUri:
|
||||
download_uri = custom.ModelManagerSP.DownloadUri()
|
||||
download_uri.uri = download_uri_data.get("url")
|
||||
download_uri.sha256 = download_uri_data.get("sha256")
|
||||
return download_uri
|
||||
|
||||
download_uri.uri = uri_data["url"]
|
||||
download_uri.sha256 = uri_data["sha256"]
|
||||
@staticmethod
|
||||
def _parse_artifact(artifact_data) -> custom.ModelManagerSP.Artifact:
|
||||
artifact = custom.ModelManagerSP.Artifact()
|
||||
artifact.fileName = artifact_data.get("file_name")
|
||||
artifact.downloadUri = ModelParser._parse_download_uri(artifact_data.get("download_uri", {}))
|
||||
return artifact
|
||||
|
||||
model.fullName = full_name
|
||||
model.fileName = file_name
|
||||
model.downloadUri = download_uri
|
||||
model.type = model_type
|
||||
@staticmethod
|
||||
def _parse_model(model_data) -> custom.ModelManagerSP.Model:
|
||||
model = custom.ModelManagerSP.Model()
|
||||
|
||||
model.type = model_data.get("type")
|
||||
model.artifact = ModelParser._parse_artifact(model_data.get("artifact", {}))
|
||||
if metadata := model_data.get("metadata"):
|
||||
model.metadata = ModelParser._parse_artifact(metadata)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def _parse_bundle(key: str, value: dict) -> custom.ModelManagerSP.ModelBundle:
|
||||
def _parse_bundle(bundle) -> custom.ModelManagerSP.ModelBundle:
|
||||
model_bundle = custom.ModelManagerSP.ModelBundle()
|
||||
|
||||
# Parse main driving model
|
||||
models = [
|
||||
ModelParser._parse_model(
|
||||
value["full_name"],
|
||||
value["file_name"],
|
||||
value["download_uri"],
|
||||
custom.ModelManagerSP.Type.drive
|
||||
)
|
||||
]
|
||||
|
||||
# Parse navigation model if exists
|
||||
if value.get("download_uri_nav"):
|
||||
models.append(ModelParser._parse_model(
|
||||
value["full_name_nav"],
|
||||
value["file_name_nav"],
|
||||
value["download_uri_nav"],
|
||||
custom.ModelManagerSP.Type.navigation
|
||||
))
|
||||
|
||||
# Parse metadata model if exists
|
||||
if value.get("download_uri_metadata"):
|
||||
models.append(ModelParser._parse_model(
|
||||
value["full_name_metadata"],
|
||||
value["file_name_metadata"],
|
||||
value["download_uri_metadata"],
|
||||
custom.ModelManagerSP.Type.metadata
|
||||
))
|
||||
|
||||
model_bundle.index = int(value["index"])
|
||||
model_bundle.internalName = key
|
||||
model_bundle.displayName = value["display_name"]
|
||||
model_bundle.models = models
|
||||
model_bundle.index = int(bundle["index"])
|
||||
model_bundle.internalName = bundle["short_name"]
|
||||
model_bundle.displayName = bundle["display_name"]
|
||||
model_bundle.models = [ModelParser._parse_model(model) for model in bundle.get("models",[])]
|
||||
model_bundle.status = 0
|
||||
model_bundle.generation = int(value["generation"])
|
||||
model_bundle.environment = value["environment"]
|
||||
model_bundle.runner = value.get("runner", custom.ModelManagerSP.Runner.snpe)
|
||||
model_bundle.is20hz = value.get("is_20hz", False)
|
||||
model_bundle.generation = int(bundle["generation"])
|
||||
model_bundle.environment = bundle["environment"]
|
||||
model_bundle.runner = bundle.get("runner", custom.ModelManagerSP.Runner.snpe)
|
||||
model_bundle.is20hz = bundle.get("is_20hz", False)
|
||||
model_bundle.minimumSelectorVersion = int(bundle["minimum_selector_version"])
|
||||
|
||||
return model_bundle
|
||||
|
||||
@staticmethod
|
||||
def parse_models(json_data: dict) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
return [ModelParser._parse_bundle(key, value) for key, value in json_data.items()]
|
||||
found_bundles = [ModelParser._parse_bundle(bundle) for bundle in json_data.get("bundles", [])]
|
||||
return [bundle for bundle in found_bundles if is_bundle_version_compatible(bundle.to_dict())]
|
||||
|
||||
|
||||
class ModelCache:
|
||||
@@ -122,7 +104,7 @@ class ModelCache:
|
||||
|
||||
class ModelFetcher:
|
||||
"""Handles fetching and caching of model data from remote source"""
|
||||
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v2.json"
|
||||
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v3.json"
|
||||
|
||||
def __init__(self, params: Params):
|
||||
self.params = params
|
||||
@@ -143,7 +125,7 @@ class ModelFetcher:
|
||||
cloudlog.exception("Error fetching models")
|
||||
raise
|
||||
|
||||
def get_available_models(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
def get_available_bundles(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
"""Gets the list of available models, with smart cache handling"""
|
||||
cached_data, is_expired = self.model_cache.get()
|
||||
|
||||
@@ -160,3 +142,16 @@ class ModelFetcher:
|
||||
|
||||
cloudlog.warning("Failed to fetch fresh data. Using expired cache as fallback")
|
||||
return self.model_parser.parse_models(cached_data)
|
||||
|
||||
if __name__ == "__main__":
|
||||
params = Params()
|
||||
model_fetcher = ModelFetcher(params)
|
||||
bundles = model_fetcher.get_available_bundles()
|
||||
for bundle in bundles:
|
||||
for model in bundle.models:
|
||||
# Print model details
|
||||
print(f"Bundle: {bundle.internalName}, Type: {model.type}, Status: {bundle.status}")
|
||||
# Print artifact details
|
||||
print(f"Artifact: {model.artifact.fileName}, Download URI: {model.artifact.downloadUri.uri}")
|
||||
# Print metadata details
|
||||
print(f"Metadata: {model.metadata.fileName}, Download URI: {model.metadata.downloadUri.uri}")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user