Compare commits

...

50 Commits

Author SHA1 Message Date
discountchubbs
456e7e2e15 try this one 2026-04-26 19:33:20 -07:00
discountchubbs
d38d68251b tg 2026-04-26 19:23:01 -07:00
discountchubbs
b92588de41 tg 2026-04-26 19:20:29 -07:00
discountchubbs
0ce37844b9 Merge branch 'tinygrad-sync-4/25' into sync-20260426 2026-04-26 19:20:05 -07:00
discountchubbs
db216f5c8b Revert "use catch2 dependency package (#37910)"
This reverts commit d1e069210f.
2026-04-26 19:19:46 -07:00
Jason Wen
fa85153fad Merge branch 'upstream/master' into sync-20260426
# Conflicts:
#	opendbc_repo
#	panda
#	selfdrive/monitoring/helpers.py
#	selfdrive/monitoring/test_monitoring.py
#	selfdrive/selfdrived/selfdrived.py
#	selfdrive/ui/ui_state.py
2026-04-26 03:37:06 -04:00
Jason Wen
8745cd0f38 Revert "modeld: single jit (#37758)"
This reverts commit d81d66193f.
2026-04-26 02:24:19 -04:00
Jason Wen
642421bc9b Revert "modeld: group npy -> qcom copies to avoid graph breaks (#37866)"
This reverts commit 859bd215bf.
2026-04-26 02:23:25 -04:00
Jason Wen
ddddf6231b Revert "modeld: standalone compile script (#37851)"
This reverts commit 551e2f77bf.
2026-04-26 02:22:58 -04:00
Jason Wen
12529e8d18 Revert "dmonitoringmodeld: get frame size from vipc (#37897)"
This reverts commit c3b0f0d11a.
2026-04-26 02:22:55 -04:00
Adeeb Shihadeh
d1e069210f use catch2 dependency package (#37910) 2026-04-25 13:56:25 -07:00
Shane Smiskol
ee54e82090 bump opendbc (#37907) 2026-04-24 17:56:31 -07:00
Adeeb Shihadeh
79cd8420eb jp: 2x faster parsing (#37904)
* jp: 2x faster parsing

* rm dynamic path

* cleanup

* lil more

* livin in the future

* clean that up

* one more
2026-04-24 15:02:37 -07:00
Daniel Koepping
8c533b14c0 AGNOS 18.1 (#37895)
* test agnos18.1 in staging

* loggerd: link va/va-drm/drm on larch64

* agnos 18.1 production
2026-04-24 13:24:29 -07:00
Daniel Koepping
494eba5961 Raise mici thermal limits (#37891)
* adjust thermal bands

* raise OFFROAD_DANGER_TEMP

* rename thermal bands

* rm warm
2026-04-24 13:22:43 -07:00
ZwX1616
ad875632ac camerad: switch on-sensor binning to BPS downscaling (#37876)
* update blob

* fixed

* didnt catch this

* add back

* needs BLC built in

* for real

* attempt2

* clean up override

* this should keep ox as was

* disable for OX

* update descs

---------

Co-authored-by: Comma Device <device@comma.ai>
2026-04-23 16:55:37 -07:00
Daniel Koepping
63068481d7 fix build docs CI (#37899)
docs: fix build
2026-04-23 16:19:30 -07:00
Daniel Koepping
275206c14d increase MAX_ROLL threshold for lateral_maneuvers (#37898)
increase MAX_ROLL for starting lateral maneuvers due to device mounting variance
2026-04-23 15:44:09 -07:00
Armand du Parc Locmaria
c3b0f0d11a dmonitoringmodeld: get frame size from vipc (#37897) 2026-04-23 15:23:31 -07:00
Adeeb Shihadeh
1c69770c53 tools/setup: support all common Linux distros (#37765)
* shorter ubuntu

* tools/setup: support all common Linux distros
2026-04-23 13:15:08 -07:00
Armand du Parc Locmaria
551e2f77bf modeld: standalone compile script (#37851)
* modeld: standalone compile script

* cleanup

* frame skip

* rm last op import

* dm warp

* no graph break

* +x compile_dm_warp.py

* don't import tg before setting device

* compile_modeld exports metadata

* update help

* namedtuple

* lint

* Revert "compile_modeld exports metadata"

This reverts commit 93c3c223567b4d4a074c9071d7f734c56f5aedcc.

* import
2026-04-23 11:55:07 -07:00
Andi Radulescu
ad04c6a038 cruise: fix test_cruise_speed assertion (#37802) 2026-04-23 11:52:12 -07:00
Adeeb Shihadeh
bb4b96e05d qcomgpsd: rm XTRA assistance (#37893)
* qcomgpsd: rm XTRA assistance

* lil more

* lil more
2026-04-23 10:20:09 -07:00
Adeeb Shihadeh
7d71354fd0 ui: remove firehose count (#37886) 2026-04-22 19:57:59 -07:00
Daniel Koepping
49685fc2bc ui: fix long maneuver toggle (#37622) 2026-04-22 17:59:34 -07:00
Adeeb Shihadeh
0eacf34e15 sensord: add note about shared IRQ 2026-04-22 16:49:29 -07:00
Adeeb Shihadeh
0be0d7fa94 add that back, it's used in a test 2026-04-22 16:40:42 -07:00
Adeeb Shihadeh
736cf6d9df clean up deprecated services (#37885)
* clean up deprecated services

* lil more
2026-04-22 16:01:35 -07:00
Adeeb Shihadeh
df6d34e52b remove enhancement issue template 2026-04-22 15:09:56 -07:00
Shane Smiskol
39d1eec575 Fix Tesla route spam (#37884)
bump
2026-04-22 15:05:07 -07:00
Adeeb Shihadeh
2266a9dd9c sensord: clean up SensorEventData struct (#37883) 2026-04-22 13:13:39 -07:00
Adeeb Shihadeh
f8372ccc4d sensord: remove mmc5603nj support (#37881)
* sensord: remove mmc5603nj support

* lil more

* lil more
2026-04-22 12:53:07 -07:00
Trey Moen
f8c45d307c esim: skip listing profiles on mutation ops (#37878) 2026-04-22 08:48:02 -07:00
ZwX1616
ca04b70d0a camerad: driver camera BPS magic (#37873)
* update blob

* fixed

* didnt catch this

* add back

* needs BLC built in

* for real

---------

Co-authored-by: Comma Device <device@comma.ai>
2026-04-21 21:04:06 -07:00
ZwX1616
571a547671 Fix driver preview alert text and sound (#37875)
* fix type and add text

* short

* fix sound too

---------

Co-authored-by: Comma Device <device@comma.ai>
2026-04-21 16:19:20 -07:00
Adeeb Shihadeh
b29d0a17af DM: readability, part 1 (#37872)
* spellings

* unused

* no roll

* lil more

* lil more

* one more

* policy enum

* better trans

* set_timer -> set_policy

* set_timer -> set_policy

* no yaonet

* del redundant code

---------

Co-authored-by: ZwX1616 <zwx1616@gmail.com>
2026-04-21 15:43:13 -07:00
Armand du Parc Locmaria
859bd215bf modeld: group npy -> qcom copies to avoid graph breaks (#37866)
* modeld: group npy -> qcom copies to avoid graph breaks

* batch realize

* dm as well
2026-04-21 13:50:20 -07:00
Harald Schäfer
4988a62b31 Revert "POP model (#37727)" (#37871)
This reverts commit 12f1be19cc.
2026-04-21 11:20:33 -07:00
Adeeb Shihadeh
e202bbe4aa monitoring: remove redundant README 2026-04-21 11:04:46 -07:00
Adeeb Shihadeh
4286a64083 jp: reduce y padding 2026-04-21 10:11:10 -07:00
Adeeb Shihadeh
341786acb5 jp: fix hidden plots unhiding on interaction (#37870) 2026-04-21 09:53:17 -07:00
Adeeb Shihadeh
04b23ff849 model replay: relax driverState timing (#37868) 2026-04-20 21:13:07 -07:00
Adeeb Shihadeh
6996e87f8d dm: helpers.py -> policy.py (#37864) 2026-04-20 19:20:48 -07:00
probablyanasian
b6432e705d Fix LSM6DS3 sensors test (#37855)
* fix temperature test + add std dev test for temp

* loosen gyro limit, no axis on temp mean

* whitespace fix

* add std_max to all sensors/tests
2026-04-18 20:27:58 -07:00
stef
5d7155fdda body ui c3 & c4 (#37794)
* c4 body ui

* clean up diff

* clean up

* default bodyview debug with True

* remove battery indicator and fix close settings bug

* organize debug file

* clean

* clean up frame index

* remove unneccessary is body check

* update bodyview

* remove joystick_debug_mode based events

* remove debug script

* apply suggestions

* clean diff

* clean diff

* move ignition message

* save a line

* remove visibility set and fix tici body face when sidebar open

* remove explicit textColor offroad message

* remove unused imports

* revert pairing dialog

* apply suggestions

* add body specific icon

* add body icon for homescreen

* set mode for state on tici after on body changed

* tiny

* tweak

* v

* tweaks

* icon ratio was wrong!

* same order

* rm

* apply suggestions

* remove commented lines in animation

* onroad click callback was on home bug and fix setup widget settings call back hack

* fix body changed

* one liner

* clean up

* formatting

* if false

* revert to master + reimplement

* close sidebar on body home tici

* make ignition message bigger on c3

* flip eye direction when turning

---------

Co-authored-by: Nick <nickorie@gmail.com>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
2026-04-18 13:00:05 -07:00
Trey Moen
b9986cae06 lpa: add is_euicc() (#37847) 2026-04-18 12:38:21 -07:00
Jason Wen
2c0903e45e tools: add retry mechanism for API requests (#36617) 2026-04-18 12:21:47 -07:00
ZwX1616
389b639ef2 DriverMonitoringState v2 (#37799)
* draft ds

* better names

* what is this

* build new

* better names2

* more

* bit more cleanup

* rm those

* .

* .2

* selfdrived

* depre

* hk

* fix test

* fix rest

* 1

* fix enum

* update cereal

* fix rest

* more

* add step

* fix all

* imports

* cant?

* .

* simplify

* bool

* fix some migrate

* cleanup

* fix fix

* Update cereal/log.capnp

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>

* touchup

* what

---------

Co-authored-by: Comma Device <device@comma.ai>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
2026-04-17 21:58:05 -07:00
stef
5624a4ccd6 bump teleoprtc_repo (#37848) 2026-04-17 15:41:47 -07:00
Armand du Parc Locmaria
d81d66193f modeld: single jit (#37758)
* compile_modeld.py

* update estimates

* missing image=2?

* Revert "missing image=2?"

This reverts commit 2f5952eb63ba1e3f24cbf5769e6b5e9170d7f0a6.

* Revert "update estimates"

This reverts commit 1f72feef2ffdec6126e3c941e899b46ace7b4b65.

* Revert "compile_modeld.py"

This reverts commit f10541502efca02725f368deda2a21d1f786f57d.

* load warp in ModelState init

* dead code

* prep

* compile modeld

* update SConscript

* tmp save plot locally

* Revert "tmp save plot locally"

This reverts commit ec22f15161ad3b0241a097546b35860f989219f5.

* openpilot hacks?

* no float16

* tmp more chunks

* Revert "tmp more chunks"

This reverts commit 9e1d9b4d0dc36ff530d2a70b565fbfabd7afb00d.

* Revert "no float16"

This reverts commit 6204956e98e3c0818ed1985ede8eeccb810f63e3.

* realize boundaries

* Revert "realize boundaries"

This reverts commit ffaa19259eba70944e7793e8f51a0f87089531b3.

* prune=False?

* Reapply "tmp more chunks"

This reverts commit 2599c41cea93b4a6b4e946cdffc6a617663a7d23.

* tg bug?

* load first?

* Revert "load first?"

This reverts commit f643d082d76a424b23295e254179eb111e936e61.

* revert

* Reapply "tmp save plot locally"

This reverts commit 1b95b82ee58654bd908b1cb04ab0ddbcd1a5955d.

* 0 tol pc

* warp -> modeld

* rename

* bypass chunking?

* dont chunk

* Revert "dont chunk"

This reverts commit cc97fc67b3203456e123f02babe5c83b87c7e264.

* dont chunk

* debug

* Revert "debug"

This reverts commit b3c2f2e7a095fd32f8d8562a68fd1cca42357eac.

* Revert "dont chunk"

This reverts commit 42bd9b6f6ad0722c50348ba11ba7e2a64fdf997d.

* Revert "bypass chunking?"

This reverts commit ad5422a93483ffd8a59ba62e5fb72ced3b5d04d0.

* corrupt model outputs

* Revert "corrupt model outputs"

This reverts commit 245feb94480e02f83a20b65a9488652bcbfc88b0.

* image=0 for warp, match master

* dedupe enqueue

* pass traffic convention

* tg buffer for desire

* dedupe buffer creation

* compile_modeld: nuke stale cached pkl before compiling

The UNSAFE CI checkout keeps gitignored files (.pkl, .sconsign.dblite),
so stale pkl files from previous commits can persist and be reused
instead of being recompiled. Delete them explicitly before compiling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test vs compile

* all outputs need to be different on different inputs

* randomize numpy inputs

* randomize on every step

* SConscript: nuke stale pkl+chunks before compile_modeld

Move the stale artifact cleanup from compile_modeld.py into the
SConscript build command. This ensures stale gitignored pkl and chunk
files are deleted even if scons decides to skip the compile step
(due to a stale .sconsign.dblite from UNSAFE CI checkout).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* compile_modeld: restore Context(IMAGE=0) for warp

The warp operations must run under IMAGE=0 to avoid QCOM image texture
optimizations that corrupt the output buffer after ~33 frames.
This was accidentally commented out in a855173.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* modeld: create SubMaster before model loading

Move PubMaster/SubMaster creation before the model loading step.
During model loading (3.5s+), process_replay may send liveCalibration.
If SubMaster doesn't exist yet, the message is dropped and the warp
transform stays as zeros, producing garbage warped images.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert "modeld: create SubMaster before model loading"

This reverts commit 968c987c2fbb3fce141c4e345d10ddea559b6c50.

* stale metadata?

* claude debug

* Revert "claude debug"

This reverts commit 49e754c6affa45a8ea8834588a00227b8090b17a.

* Revert "stale metadata?"

This reverts commit 870388513c0d4a67dcf970cd277b6db56cb2b478.

* modeld: realize jit outputs before parsing

* Update modeld.py

* modeld: fix NameError by removing redundant MODELS_DIR definition

* test buffers in test vs. compile

* 2x inputs before running

* fixup 2x inputs test

* realize onnx weights?

* Revert "realize onnx weights?"

This reverts commit 49c8b9a505db38ff22f342db011a3a6b6526d398.

* move openpilot_hacks flag to sconscript

* stricter test vs compile

* correct timings

* more run more fail?

* Revert "more run more fail?"

This reverts commit 9e94bb63940751ec29e81b634c42449113e1f2e5.

* numpy shenanigans

* correct shapes

* dont assert timings for now

* Revert "correct shapes"

This reverts commit 5b9ff6c84c0022327d21801d179e9e51c39e8f78.

* Revert "numpy shenanigans"

This reverts commit b4f6fb3078d7e9b09698895b88728fd8eea8c8a8.

* no need to nuke

* comment unused

* don't use NPY device

* copy instead of from_blob

* to device before jit

* Revert "to device before jit"

This reverts commit 7a59ed9b1ac88657b5a3917986b6ff92e59a2ee3.

* Revert "copy instead of from_blob"

This reverts commit 196c4892a06ffba89ef631876372cecf137cc1b4.

* Revert "don't use NPY device"

This reverts commit 18abf43bbac46ad47a60c03dd8d1ef40b3f59227.

* 3 runs is enough

* no_memory_planner=1

* lint

* restore model_replay.py

* on policy -> policy

* unused

* prepare only enqueues full images

* warp with image=2?

* unused args

* test vs compile, check different inputs different outputs

* avoid uop cache collision

* dont need realize here

* misc

* input queues diverged

* strict zip

* monkey patch for now

* memory planner

* prev desire correct order

* dedupe pkl paths / compile targets

* don't change behavior, warp and enqueue frames when skipping model eval

* actually prepare only

* warm up warp jit

* correct path

* oops

* explicit warmup

* need continuous + can't have dupplicate jit inputs

* whitespace

* bufs -> input_queues

* master tg

* /N_RUNS

* bump tg, remove uop cache patch

* more readable

* Revert "bump tg, remove uop cache patch"

This reverts commit 499acca2591becd389de4025943f9e776a5b337c.

* missing dep

---------

Co-authored-by: Bruce Wayne <harald.the.engineer@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 12:37:56 -07:00
85 changed files with 1908 additions and 1652 deletions

View File

@@ -1,8 +0,0 @@
---
name: Enhancement
about: For openpilot enhancement suggestions
title: ''
labels: 'enhancement'
assignees: ''
---

View File

@@ -273,11 +273,7 @@ struct GPSNMEAData {
nmea @2 :Text;
}
# android sensor_event_t
struct SensorEventData {
version @0 :Int32;
sensor @1 :Int32;
type @2 :Int32;
timestamp @3 :Int64;
union {
@@ -296,7 +292,10 @@ struct SensorEventData {
struct SensorVec {
v @0 :List(Float32);
status @1 :Int8;
deprecated :group {
status @1 :Int8;
}
}
enum SensorSource {
@@ -314,7 +313,11 @@ struct SensorEventData {
mmc5603nj @11;
}
# formerly based on android sensor_event_t
deprecated :group {
version @0 :Int32;
sensor @1 :Int32;
type @2 :Int32;
uncalibrated @10 :Bool;
}
}
@@ -457,10 +460,10 @@ struct DeviceState @0xa4d8b5af2aa492eb {
}
enum ThermalStatus {
green @0;
yellow @1;
red @2;
danger @3;
ok @0;
warmDEPRECATED @1;
overheated @2;
critical @3;
}
enum NetworkType {
@@ -2076,7 +2079,7 @@ struct DriverStateV2 {
}
}
struct DriverMonitoringState @0xb83cda094a1da284 {
struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
events @18 :List(OnroadEvent);
faceDetected @1 :Bool;
isDistracted @2 :Bool;
@@ -2104,6 +2107,75 @@ struct DriverMonitoringState @0xb83cda094a1da284 {
}
}
struct DriverMonitoringState {
lockout @0 :Bool;
alertCountLockoutPercent @1 :Int8;
alertTimeLockoutPercent @2 :Int8;
alwaysOn @3 :Bool;
alwaysOnLockout @4 :Bool;
alertLevel @5 :AlertLevel;
activePolicy @6 :MonitoringPolicy;
isRHD @7 :Bool;
rhdCalibration @8 :CalibrationState;
visionPolicyState @9 :VisionPolicyState;
wheeltouchPolicyState @10 :WheeltouchPolicyState;
enum AlertLevel {
# ordinal must match the name to prevent bugs
# comparing against the raw ordinal value
none @0;
one @1;
two @2;
three @3;
}
enum MonitoringPolicy {
wheeltouch @0;
vision @1;
}
struct VisionPolicyState {
awarenessPercent @0 :Int8;
awarenessStep @1 :Float32;
isDistracted @2 :Bool;
distractedTypes @3 :DistractedTypes;
faceDetected @4 :Bool;
pose @5 :Pose;
wheeltouchFallbackPercent @6 :Int8;
uncertainOffroadAlertPercent @7 :Int8;
struct DistractedTypes {
pose @0: Bool;
eye @1: Bool;
phone @2: Bool;
}
struct Pose {
pitch @0 :Float32;
yaw @1 :Float32;
pitchCalib @2 :CalibrationState;
yawCalib @3 :CalibrationState;
calibrated @4 :Bool;
uncertainty @5 :Float32;
}
}
struct WheeltouchPolicyState {
awarenessPercent @0 :Int8;
awarenessStep @1 :Float32;
driverInteracting @2 :Bool;
}
struct CalibrationState {
calibratedPercent @0 :Int8;
offset @1 :Float32;
}
}
struct Boot {
wallTimeNanos @0 :UInt64;
pstore @4 :Map(Text, Data);
@@ -2377,7 +2449,6 @@ struct Event {
boot @60 :Boot;
# ********** openpilot daemon msgs **********
gpsNMEA @3 :GPSNMEAData;
can @5 :List(CanData);
controlsState @7 :ControlsState;
selfdriveState @130 :SelfdriveState;
@@ -2402,7 +2473,6 @@ struct Event {
qcomGnss @31 :QcomGnss;
gpsLocationExternal @48 :GpsLocationData;
gpsLocation @21 :GpsLocationData;
gnssMeasurements @91 :GnssMeasurements;
liveParameters @61 :LiveParametersData;
liveTorqueParameters @94 :LiveTorqueParametersData;
liveDelay @146 : LiveDelayData;
@@ -2410,7 +2480,7 @@ struct Event {
thumbnail @66: Thumbnail;
onroadEvents @134: List(OnroadEvent);
carParams @69: Car.CarParams;
driverMonitoringState @71: DriverMonitoringState;
driverMonitoringState @151 :DriverMonitoringState;
livePose @129 :LivePose;
modelV2 @75 :ModelDataV2;
drivingModelData @128 :DrivingModelData;
@@ -2436,7 +2506,6 @@ struct Event {
# systems stuff
androidLog @20 :AndroidLogEntry;
managerState @78 :ManagerState;
uploaderState @79 :UploaderState;
procLog @33 :ProcLog;
clocks @35 :Clocks;
deviceState @6 :DeviceState;
@@ -2446,12 +2515,6 @@ struct Event {
# touch frame
touch @135 :List(Touch);
# navigation
navInstruction @82 :NavInstruction;
navRoute @83 :NavRoute;
navThumbnail @84: Thumbnail;
mapRenderState @105: MapRenderState;
# UI services
uiDebug @102 :UIDebug;
@@ -2553,5 +2616,13 @@ struct Event {
gyroscope2DEPRECATED @100 :SensorEventData;
accelerometer2DEPRECATED @101 :SensorEventData;
temperatureSensor2DEPRECATED @123 :SensorEventData;
driverMonitoringStateDEPRECATED @71 :DriverMonitoringStateDEPRECATED;
gpsNMEADEPRECATED @3 :GPSNMEAData;
uploaderStateDEPRECATED @79 :UploaderState;
navInstructionDEPRECATED @82 :NavInstruction;
navRouteDEPRECATED @83 :NavRoute;
navThumbnailDEPRECATED @84 :Thumbnail;
gnssMeasurementsDEPRECATED @91 :GnssMeasurements;
mapRenderStateDEPRECATED @105: MapRenderState;
}
}

View File

@@ -24,10 +24,7 @@ _services: dict[str, tuple] = {
# note: the "EncodeIdx" packets will still be in the log
"gyroscope": (True, 104., 104),
"accelerometer": (True, 104., 104),
"magnetometer": (True, 25.),
"lightSensor": (True, 100., 100),
"temperatureSensor": (True, 2., 200),
"gpsNMEA": (True, 9.),
"deviceState": (True, 2., 1),
"touch": (True, 20., 1),
"can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment
@@ -56,7 +53,6 @@ _services: dict[str, tuple] = {
"gpsLocation": (True, 1., 1),
"ubloxGnss": (True, 10.),
"qcomGnss": (True, 2.),
"gnssMeasurements": (True, 10., 10),
"clocks": (True, 0.1, 1),
"ubloxRaw": (True, 20.),
"livePose": (True, 20., 4),
@@ -75,10 +71,6 @@ _services: dict[str, tuple] = {
"drivingModelData": (True, 20., 10),
"modelV2": (True, 20., None, QueueSize.BIG),
"managerState": (True, 2., 1),
"uploaderState": (True, 0., 1),
"navInstruction": (True, 1., 10),
"navRoute": (True, 0.),
"navThumbnail": (True, 0.),
"qRoadEncodeIdx": (False, 20.),
"userBookmark": (True, 0., 1),
"soundPressure": (True, 10., 10),
@@ -114,8 +106,6 @@ _services: dict[str, tuple] = {
"livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
"livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM),
"customReservedRawData0": (True, 0.),
"customReservedRawData1": (True, 0.),
"customReservedRawData2": (True, 0.),
}
SERVICE_LIST = {name: Service(*vals) for
idx, (name, vals) in enumerate(_services.items())}

View File

@@ -1 +1 @@
#define DEFAULT_MODEL "POP model (Default)"
#define DEFAULT_MODEL "CD210 (Default)"

View File

@@ -8,7 +8,7 @@ from markdown.extensions import Extension
from markdown.preprocessors import Preprocessor
from markdown.treeprocessors import Treeprocessor
from zensical.extensions.links import LinksProcessor
from zensical.extensions.links import LinksTreeprocessor
GlossaryTerm = tuple[str, re.Pattern[str], str]
@@ -78,7 +78,7 @@ class GlossaryTreeprocessor(Treeprocessor):
def run(self, root: ET.Element) -> None:
at = self.md.treeprocessors.get_index_for_name("zrelpath")
processor = self.md.treeprocessors[at]
if not isinstance(processor, LinksProcessor):
if not isinstance(processor, LinksTreeprocessor):
raise TypeError("Links processor not registered")
if processor.path == GLOSSARY_PAGE:
return

View File

@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="17.2"
export AGNOS_VERSION="18.1"
fi
export STAGING_ROOT="/data/safe_staging"

2
panda

Submodule panda updated: 5a90799dac...0a9ef7ab54

Binary file not shown.

View File

@@ -94,7 +94,7 @@ class TestVCruiseHelper:
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False, False)
# Expected diff on enabling. Speed should not change on falling edge of pressed
assert not pressed == self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last
assert (not pressed) == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
def test_resume_in_standstill(self):
"""

View File

@@ -212,7 +212,7 @@ class Controls(ControlsExt):
cs.upAccelCmd = float(self.LoC.pid.p)
cs.uiAccelCmd = float(self.LoC.pid.i)
cs.ufAccelCmd = float(self.LoC.pid.f)
cs.forceDecel = bool((self.sm['driverMonitoringState'].awarenessStatus < 0.) or
cs.forceDecel = bool((self.sm['driverMonitoringState'].alertLevel == log.DriverMonitoringState.AlertLevel.three) or
(self.sm['selfdriveState'].state == State.softDisabling))
lat_tuning = self.CP.lateralTuning.which()

View File

@@ -1,15 +0,0 @@
# driver monitoring (DM)
Uploading driver-facing camera footage is opt-in, but it is encouraged to opt-in to improve the DM model. You can always change your preference using the "Record and Upload Driver Camera" toggle.
## Troubleshooting
Before creating a bug report, go through these troubleshooting steps.
* Ensure the driver-facing camera has a good view of the driver in normal driving positions.
* This can be checked in Settings -> Device -> Preview Driver Camera (when car is off).
* If the camera can't see the driver, the device should be re-mounted.
## Bug report
In order for us to look into DM bug reports, we'll need the driver-facing camera footage. If you don't normally have this enabled, simply enable the toggle for a single drive. Also ensure the "Upload Raw Logs" toggle is enabled before going for a drive.

View File

@@ -2,7 +2,7 @@
import cereal.messaging as messaging
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process
from openpilot.selfdrive.monitoring.helpers import DriverMonitoring
from openpilot.selfdrive.monitoring.policy import DriverMonitoring
def dmonitoringd_thread():
@@ -25,7 +25,7 @@ def dmonitoringd_thread():
valid = sm.all_checks()
if demo_mode and sm.valid['driverStateV2']:
DM.run_step(sm, demo=demo_mode)
DM.run_step(sm, demo=True)
elif valid:
DM.run_step(sm, demo=demo_mode)
@@ -40,8 +40,8 @@ def dmonitoringd_thread():
# save rhd virtual toggle every 5 mins
if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and
DM.wheelpos.prob_offseter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and
DM.wheel_on_right == (DM.wheelpos.prob_offseter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)):
DM.wheelpos_offsetter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and
DM.wheel_on_right == (DM.wheelpos_offsetter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)):
params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right)
def main():

View File

@@ -1,463 +0,0 @@
from math import atan2, radians
import numpy as np
from cereal import car, log
import cereal.messaging as messaging
from openpilot.selfdrive.selfdrived.events import Events
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.common.realtime import DT_DMON
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.common.stat_live import RunningStatFilter
from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.system.hardware import HARDWARE
EventName = log.OnroadEvent.EventName
# ******************************************************************************************
# NOTE: To fork maintainers.
# Disabling or nerfing safety features will get you and your users banned from our servers.
# We recommend that you do not change these numbers from the defaults.
# ******************************************************************************************
class DRIVER_MONITOR_SETTINGS:
def __init__(self, device_type):
self._DT_DMON = DT_DMON
# ref (page15-16): https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2
self._AWARENESS_TIME = 30. # passive wheeltouch total timeout
self._AWARENESS_PRE_TIME_TILL_TERMINAL = 15.
self._AWARENESS_PROMPT_TIME_TILL_TERMINAL = 6.
self._DISTRACTED_TIME = 11. # active monitoring total timeout
self._DISTRACTED_PRE_TIME_TILL_TERMINAL = 8.
self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6.
self._FACE_THRESHOLD = 0.7
self._EYE_THRESHOLD = 0.5
self._BLINK_THRESHOLD = 0.5
self._PHONE_THRESH = 0.5
self._POSE_PITCH_THRESHOLD = 0.3133
self._POSE_PITCH_THRESHOLD_SLACK = 0.3237
self._POSE_PITCH_THRESHOLD_STRICT = self._POSE_PITCH_THRESHOLD
self._POSE_YAW_THRESHOLD = 0.4020
self._POSE_YAW_THRESHOLD_SLACK = 0.5042
self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD
self._POSE_YAW_MIN_STEER_DEG = 30
self._POSE_YAW_STEER_FACTOR = 0.15
self._POSE_YAW_STEER_MAX_OFFSET = 0.3927
self._PITCH_NATURAL_OFFSET = 0.011 # initial value before offset is learned
self._PITCH_NATURAL_THRESHOLD = 0.449
self._YAW_NATURAL_OFFSET = 0.075 # initial value before offset is learned
self._PITCH_NATURAL_VAR = 3*0.01
self._YAW_NATURAL_VAR = 3*0.05
self._PITCH_MAX_OFFSET = 0.124
self._PITCH_MIN_OFFSET = -0.0881
self._YAW_MAX_OFFSET = 0.289
self._YAW_MIN_OFFSET = -0.0246
self._DCAM_UNCERTAIN_ALERT_THRESHOLD = 0.1
self._DCAM_UNCERTAIN_ALERT_COUNT = int(60 / self._DT_DMON)
self._DCAM_UNCERTAIN_RESET_COUNT = int(20 / self._DT_DMON)
self._POSESTD_THRESHOLD = 0.3
self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s
self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz
self._POSE_CALIB_MIN_SPEED = 13 # 30 mph
self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative
self._POSE_OFFSET_MAX_COUNT = int(360 / self._DT_DMON) # stop deweighting new data after 6 min, aka "short term memory"
self._WHEELPOS_CALIB_MIN_SPEED = 11
self._WHEELPOS_THRESHOLD = 0.5
self._WHEELPOS_FILTER_MIN_COUNT = int(15 / self._DT_DMON) # allow 15 seconds to converge wheel side
self._WHEELPOS_DATA_AVG = 0.03
self._WHEELPOS_DATA_VAR = 3*5.5e-5
self._WHEELPOS_MAX_COUNT = -1
self._RECOVERY_FACTOR_MAX = 5. # relative to minus step change
self._RECOVERY_FACTOR_MIN = 1.25 # relative to minus step change
self._MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts
self._MAX_TERMINAL_DURATION = int(30 / self._DT_DMON) # not allowed to engage after 30s of terminal alerts
class DistractedType:
NOT_DISTRACTED = 0
DISTRACTED_POSE = 1 << 0
DISTRACTED_BLINK = 1 << 1
DISTRACTED_PHONE = 1 << 2
class DriverPose:
def __init__(self, settings):
pitch_filter_raw_priors = (settings._PITCH_NATURAL_OFFSET, settings._PITCH_NATURAL_VAR, 2)
yaw_filter_raw_priors = (settings._YAW_NATURAL_OFFSET, settings._YAW_NATURAL_VAR, 2)
self.yaw = 0.
self.pitch = 0.
self.roll = 0.
self.yaw_std = 0.
self.pitch_std = 0.
self.roll_std = 0.
self.pitch_offseter = RunningStatFilter(raw_priors=pitch_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT)
self.yaw_offseter = RunningStatFilter(raw_priors=yaw_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT)
self.calibrated = False
self.low_std = True
self.cfactor_pitch = 1.
self.cfactor_yaw = 1.
self.steer_yaw_offset = 0.
class DriverProb:
def __init__(self, raw_priors, max_trackable):
self.prob = 0.
self.prob_offseter = RunningStatFilter(raw_priors=raw_priors, max_trackable=max_trackable)
self.prob_calibrated = False
# model output refers to center of undistorted+leveled image
EFL = 598.0 # focal length in K
cam = DEVICE_CAMERAS[("tici", "ar0231")] # corrected image has same size as raw
W, H = (cam.dcam.width, cam.dcam.height) # corrected image has same size as raw
def face_orientation_from_net(angles_desc, pos_desc, rpy_calib):
# the output of these angles are in device frame
# so from driver's perspective, pitch is up and yaw is right
pitch_net, yaw_net, roll_net = angles_desc
face_pixel_position = ((pos_desc[0]+0.5)*W, (pos_desc[1]+0.5)*H)
yaw_focal_angle = atan2(face_pixel_position[0] - W//2, EFL)
pitch_focal_angle = atan2(face_pixel_position[1] - H//2, EFL)
pitch = pitch_net + pitch_focal_angle
yaw = -yaw_net + yaw_focal_angle
# no calib for roll
pitch -= rpy_calib[1]
yaw -= rpy_calib[2]
return roll_net, pitch, yaw
class DriverMonitoring:
def __init__(self, rhd_saved=False, settings=None, always_on=False):
# init policy settings
self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type())
# init driver status
wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2)
self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT)
self.pose = DriverPose(settings=self.settings)
self.blink_prob = 0.
self.phone_prob = 0.
self.always_on = always_on
self.distracted_types = []
self.driver_distracted = False
self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON)
self.wheel_on_right = False
self.wheel_on_right_last = None
self.wheel_on_right_default = rhd_saved
self.face_detected = False
self.terminal_alert_cnt = 0
self.terminal_time = 0
self.step_change = 0.
self.active_monitoring_mode = True
self.is_model_uncertain = False
self.hi_stds = 0
self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME
self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME
self.dcam_uncertain_cnt = 0
self.dcam_uncertain_alerted = False # once per drive
self.dcam_reset_cnt = 0
self.params = Params()
self.too_distracted = self.params.get_bool("DriverTooDistracted")
self._reset_awareness()
self._set_timers(active_monitoring=True)
self._reset_events()
def _reset_awareness(self):
self.awareness = 1.
self.awareness_active = 1.
self.awareness_passive = 1.
def _reset_events(self):
self.current_events = Events()
def _set_timers(self, active_monitoring):
if self.active_monitoring_mode and self.awareness <= self.threshold_prompt:
if active_monitoring:
self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME
else:
self.step_change = 0.
return # no exploit after orange alert
elif self.awareness <= 0.:
return
if active_monitoring:
# when falling back from passive mode to active mode, reset awareness to avoid false alert
if not self.active_monitoring_mode:
self.awareness_passive = self.awareness
self.awareness = self.awareness_active
self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME
self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME
self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME
self.active_monitoring_mode = True
else:
if self.active_monitoring_mode:
self.awareness_active = self.awareness
self.awareness = self.awareness_passive
self.threshold_pre = self.settings._AWARENESS_PRE_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME
self.threshold_prompt = self.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME
self.step_change = self.settings._DT_DMON / self.settings._AWARENESS_TIME
self.active_monitoring_mode = False
def _set_policy(self, brake_disengage_prob, car_speed):
bp = brake_disengage_prob
k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2)
bp_normal = max(min(bp / k1, 0.5),0)
self.pose.cfactor_pitch = np.interp(bp_normal, [0, 0.5],
[self.settings._POSE_PITCH_THRESHOLD_SLACK,
self.settings._POSE_PITCH_THRESHOLD_STRICT]) / self.settings._POSE_PITCH_THRESHOLD
self.pose.cfactor_yaw = np.interp(bp_normal, [0, 0.5],
[self.settings._POSE_YAW_THRESHOLD_SLACK,
self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD
def _get_distracted_types(self):
distracted_types = []
if not self.pose.calibrated:
pitch_error = self.pose.pitch - self.settings._PITCH_NATURAL_OFFSET
yaw_error = self.pose.yaw - self.settings._YAW_NATURAL_OFFSET
else:
pitch_error = self.pose.pitch - min(max(self.pose.pitch_offseter.filtered_stat.mean(),
self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET)
yaw_error = self.pose.yaw - min(max(self.pose.yaw_offseter.filtered_stat.mean(),
self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET)
pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit
if yaw_error * self.pose.steer_yaw_offset > 0: # unidirectional
yaw_error = max(abs(yaw_error) - min(abs(self.pose.steer_yaw_offset), self.settings._POSE_YAW_STEER_MAX_OFFSET), 0.)
else:
yaw_error = abs(yaw_error)
pitch_threshold = self.settings._POSE_PITCH_THRESHOLD * self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD
yaw_threshold = self.settings._POSE_YAW_THRESHOLD * self.pose.cfactor_yaw
if pitch_error > pitch_threshold or yaw_error > yaw_threshold:
distracted_types.append(DistractedType.DISTRACTED_POSE)
if self.blink_prob > self.settings._BLINK_THRESHOLD:
distracted_types.append(DistractedType.DISTRACTED_BLINK)
if self.phone_prob > self.settings._PHONE_THRESH:
distracted_types.append(DistractedType.DISTRACTED_PHONE)
return distracted_types
def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill, demo_mode=False, steering_angle_deg=0.):
rhd_pred = driver_state.wheelOnRightProb
# calibrates only when there's movement and either face detected
if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or
driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD):
self.wheelpos.prob_offseter.push_and_update(rhd_pred)
self.wheelpos.prob_calibrated = self.wheelpos.prob_offseter.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT
if self.wheelpos.prob_calibrated or demo_mode:
self.wheel_on_right = self.wheelpos.prob_offseter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD
else:
self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished
# make sure no switching when engaged
if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right and not demo_mode:
self.wheel_on_right = self.wheel_on_right_last
driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData
if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition,
driver_data.faceOrientationStd, driver_data.facePositionStd)):
return
self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD
self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(driver_data.faceOrientation, driver_data.facePosition, cal_rpy)
steer_d = max(abs(steering_angle_deg) - self.settings._POSE_YAW_MIN_STEER_DEG, 0.)
self.pose.steer_yaw_offset = radians(steer_d) * -np.sign(steering_angle_deg) * self.settings._POSE_YAW_STEER_FACTOR
if self.wheel_on_right:
self.pose.yaw *= -1
self.pose.steer_yaw_offset *= -1
self.wheel_on_right_last = self.wheel_on_right
self.pose.pitch_std = driver_data.faceOrientationStd[0]
self.pose.yaw_std = driver_data.faceOrientationStd[1]
model_std_max = max(self.pose.pitch_std, self.pose.yaw_std)
self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD
self.blink_prob = driver_data.eyesClosedProb * (driver_data.eyesVisibleProb > self.settings._EYE_THRESHOLD)
self.phone_prob = driver_data.phoneProb
self.distracted_types = self._get_distracted_types()
self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types
or DistractedType.DISTRACTED_POSE in self.distracted_types
or DistractedType.DISTRACTED_BLINK in self.distracted_types) \
and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std
self.driver_distraction_filter.update(self.driver_distracted)
# update offseter
# only update when driver is actively driving the car above a certain speed
if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted):
self.pose.pitch_offseter.push_and_update(self.pose.pitch)
self.pose.yaw_offseter.push_and_update(self.pose.yaw)
self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \
self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT
if self.face_detected and not self.driver_distracted:
if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD:
if not standstill:
self.dcam_uncertain_cnt += 1
self.dcam_reset_cnt = 0
else:
self.dcam_reset_cnt += 1
if self.dcam_reset_cnt > self.settings._DCAM_UNCERTAIN_RESET_COUNT:
self.dcam_uncertain_cnt = 0
self.is_model_uncertain = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME
self._set_timers(self.face_detected and not self.is_model_uncertain)
if self.face_detected and not self.pose.low_std and not self.driver_distracted:
self.hi_stds += 1
elif self.face_detected and self.pose.low_std:
self.hi_stds = 0
def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car_speed):
self._reset_events()
# Block engaging until ignition cycle after max number or time of distractions
if self.terminal_alert_cnt >= self.settings._MAX_TERMINAL_ALERTS or \
self.terminal_time >= self.settings._MAX_TERMINAL_DURATION:
if not self.too_distracted:
self.params.put_bool_nonblocking("DriverTooDistracted", True)
self.too_distracted = True
# Always-on distraction lockout is temporary
if self.too_distracted or (self.always_on and self.awareness <= self.threshold_prompt):
self.current_events.add(EventName.tooDistracted)
always_on_valid = self.always_on and not wrong_gear
if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or \
(not always_on_valid and not op_engaged) or \
(always_on_valid and not op_engaged and self.awareness <= 0):
# always reset on disengage with normal mode; disengage resets only on red if always on
self._reset_awareness()
return
awareness_prev = self.awareness
_reaching_pre = self.awareness - self.step_change <= self.threshold_pre
_reaching_terminal = self.awareness - self.step_change <= 0
standstill_orange_exemption = standstill and _reaching_pre
always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal
if self.awareness > 0 and \
((self.driver_distraction_filter.x < 0.37 and self.face_detected and self.pose.low_std) or standstill_orange_exemption):
if driver_engaged:
self._reset_awareness()
return
# only restore awareness when paying attention and alert is not red
self.awareness = min(self.awareness + ((self.settings._RECOVERY_FACTOR_MAX-self.settings._RECOVERY_FACTOR_MIN)*
(1.-self.awareness)+self.settings._RECOVERY_FACTOR_MIN)*self.step_change, 1.)
if self.awareness == 1.:
self.awareness_passive = min(self.awareness_passive + self.step_change, 1.)
# don't display alert banner when awareness is recovering and has cleared orange
if self.awareness > self.threshold_prompt:
return
certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected
maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected
if certainly_distracted or maybe_distracted:
# should always be counting if distracted unless at standstill and reaching green
# also will not be reaching 0 if DM is active when not engaged
if not (standstill_orange_exemption or always_on_red_exemption):
self.awareness = max(self.awareness - self.step_change, -0.1)
alert = None
if self.awareness <= 0.:
# terminal red alert: disengagement required
alert = EventName.driverDistracted3 if self.active_monitoring_mode else EventName.driverUnresponsive3
self.terminal_time += 1
if awareness_prev > 0.:
self.terminal_alert_cnt += 1
elif self.awareness <= self.threshold_prompt:
# prompt orange alert
alert = EventName.driverDistracted2 if self.active_monitoring_mode else EventName.driverUnresponsive2
elif self.awareness <= self.threshold_pre:
# pre green alert
alert = EventName.driverDistracted1 if self.active_monitoring_mode else EventName.driverUnresponsive1
if alert is not None:
self.current_events.add(alert)
if self.dcam_uncertain_cnt > self.settings._DCAM_UNCERTAIN_ALERT_COUNT and not self.dcam_uncertain_alerted:
set_offroad_alert("Offroad_DriverMonitoringUncertain", True)
self.dcam_uncertain_alerted = True
def get_state_packet(self, valid=True):
# build driverMonitoringState packet
dat = messaging.new_message('driverMonitoringState', valid=valid)
dat.driverMonitoringState = {
"events": self.current_events.to_msg(),
"faceDetected": self.face_detected,
"isDistracted": self.driver_distracted,
"distractedType": sum(self.distracted_types),
"awarenessStatus": self.awareness,
"posePitchOffset": self.pose.pitch_offseter.filtered_stat.mean(),
"posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n,
"poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(),
"poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n,
"stepChange": self.step_change,
"awarenessActive": self.awareness_active,
"awarenessPassive": self.awareness_passive,
"isLowStd": self.pose.low_std,
"hiStdCount": self.hi_stds,
"isActiveMode": self.active_monitoring_mode,
"isRHD": self.wheel_on_right,
"uncertainCount": self.dcam_uncertain_cnt,
}
return dat
def run_step(self, sm, demo=False):
if demo:
highway_speed = 30
enabled = True
wrong_gear = False
standstill = False
driver_engaged = False
brake_disengage_prob = 1.0
rpyCalib = [0., 0., 0.]
else:
highway_speed = sm['carState'].vEgo
enabled = sm['selfdriveState'].enabled or sm['carControl'].latActive
wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low)
standstill = sm['carState'].standstill
driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed
brake_disengage_prob = sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s
rpyCalib = sm['liveCalibration'].rpyCalib
self._set_policy(
brake_disengage_prob=brake_disengage_prob,
car_speed=highway_speed,
)
# Parse data from dmonitoringmodeld
self._update_states(
driver_state=sm['driverStateV2'],
cal_rpy=rpyCalib,
car_speed=highway_speed,
op_engaged=enabled,
standstill=standstill,
demo_mode=demo,
steering_angle_deg=sm['carState'].steeringAngleDeg,
)
# Update distraction events
self._update_events(
driver_engaged=driver_engaged,
op_engaged=enabled,
standstill=standstill,
wrong_gear=wrong_gear,
car_speed=highway_speed
)

View File

@@ -0,0 +1,426 @@
from collections import defaultdict
from math import atan2, radians
import numpy as np
from cereal import car, log
import cereal.messaging as messaging
from openpilot.common.realtime import DT_DMON
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.common.stat_live import RunningStatFilter
from openpilot.common.transformations.camera import DEVICE_CAMERAS
AlertLevel = log.DriverMonitoringState.AlertLevel
MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy
def to_percent(v):
return int(min(max(v * 100., 0.), 100.))
# ******************************************************************************************
# NOTE: To fork maintainers.
# Disabling or nerfing safety features will get you and your users banned from our servers.
# We recommend that you do not change these numbers from the defaults.
# ******************************************************************************************
class DRIVER_MONITOR_SETTINGS:
def __init__(self):
# https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2
self._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT = 15.
self._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT = 24.
self._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT = 30.
# https://cdn.euroncap.com/cars/assets/euro_ncap_protocol_safe_driving_driver_engagement_v11_a30e874152.pdf
self._VISION_POLICY_ALERT_1_TIMEOUT = 3.
self._VISION_POLICY_ALERT_2_TIMEOUT = 5.
self._VISION_POLICY_ALERT_3_TIMEOUT = 11.
self._TIMEOUT_RECOVERY_FACTOR_MAX = 5.
self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25
self._MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts
self._MAX_TERMINAL_DURATION = int(30 / DT_DMON) # not allowed to engage after 30s of terminal alerts
self._FACE_THRESHOLD = 0.7
self._EYE_THRESHOLD = 0.5
self._BLINK_THRESHOLD = 0.5
self._PHONE_THRESH = 0.5
self._POSE_PITCH_THRESHOLD = 0.3133
self._POSE_PITCH_THRESHOLD_SLACK = 0.3237
self._POSE_PITCH_THRESHOLD_STRICT = self._POSE_PITCH_THRESHOLD
self._POSE_YAW_THRESHOLD = 0.4020
self._POSE_YAW_THRESHOLD_SLACK = 0.5042
self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD
self._POSE_YAW_MIN_STEER_DEG = 30
self._POSE_YAW_STEER_FACTOR = 0.15
self._POSE_YAW_STEER_MAX_OFFSET = 0.3927
self._PITCH_NATURAL_OFFSET = 0.011 # initial value before offset is learned
self._PITCH_NATURAL_THRESHOLD = 0.449
self._YAW_NATURAL_OFFSET = 0.075 # initial value before offset is learned
self._PITCH_NATURAL_VAR = 3*0.01
self._YAW_NATURAL_VAR = 3*0.05
self._PITCH_MAX_OFFSET = 0.124
self._PITCH_MIN_OFFSET = -0.0881
self._YAW_MAX_OFFSET = 0.289
self._YAW_MIN_OFFSET = -0.0246
self._DCAM_UNCERTAIN_ALERT_THRESHOLD = 0.1
self._DCAM_UNCERTAIN_ALERT_COUNT = int(60 / DT_DMON)
self._DCAM_UNCERTAIN_RESET_COUNT = int(20 / DT_DMON)
self._HI_STD_THRESHOLD = 0.3
self._HI_STD_FALLBACK_TIME = int(10 / DT_DMON) # fall back to wheel touch if model is uncertain for 10s
self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz
self._POSE_CALIB_MIN_SPEED = 13 # 30 mph
self._POSE_OFFSET_MIN_COUNT = int(60 / DT_DMON) # valid data counts before calibration completes, 1min cumulative
self._POSE_OFFSET_MAX_COUNT = int(360 / DT_DMON) # stop deweighting new data after 6 min, aka "short term memory"
self._WHEELPOS_CALIB_MIN_SPEED = 11
self._WHEELPOS_THRESHOLD = 0.5
self._WHEELPOS_FILTER_MIN_COUNT = int(15 / DT_DMON) # allow 15 seconds to converge wheel side
self._WHEELPOS_DATA_AVG = 0.03
self._WHEELPOS_DATA_VAR = 3*5.5e-5
self._WHEELPOS_MAX_COUNT = -1
class DriverPose:
def __init__(self, settings):
pitch_filter_raw_priors = (settings._PITCH_NATURAL_OFFSET, settings._PITCH_NATURAL_VAR, 2)
yaw_filter_raw_priors = (settings._YAW_NATURAL_OFFSET, settings._YAW_NATURAL_VAR, 2)
self.yaw = 0.
self.pitch = 0.
self.pitch_offsetter = RunningStatFilter(raw_priors=pitch_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT)
self.yaw_offsetter = RunningStatFilter(raw_priors=yaw_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT)
self.calibrated = False
self.low_std = True
self.cfactor_pitch = 1.
self.cfactor_yaw = 1.
self.steer_yaw_offset = 0.
# model output refers to center of undistorted+leveled image
ref_undistorted_cam = DEVICE_CAMERAS[("tici", "ar0231")].dcam
dcam_undistorted_FL = 598.0
dcam_undistorted_W, dcam_undistorted_H = (ref_undistorted_cam.width, ref_undistorted_cam.height)
def face_orientation_from_model(orient_model, pos_model, rpy_calib):
pitch_model = orient_model[0]
yaw_model = orient_model[1]
face_pixel_position = ((pos_model[0]+0.5)*dcam_undistorted_W, (pos_model[1]+0.5)*dcam_undistorted_H)
yaw_focal_angle = atan2(face_pixel_position[0] - dcam_undistorted_W//2, dcam_undistorted_FL)
pitch_focal_angle = atan2(face_pixel_position[1] - dcam_undistorted_H//2, dcam_undistorted_FL)
pitch = pitch_model + pitch_focal_angle
yaw = -yaw_model + yaw_focal_angle
pitch -= rpy_calib[1]
yaw -= rpy_calib[2]
return pitch, yaw
class DriverMonitoring:
def __init__(self, rhd_saved=False, settings=None, always_on=False):
# init policy settings
self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS()
# init driver status
wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2)
self.wheelpos_offsetter = RunningStatFilter(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT)
self.pose = DriverPose(settings=self.settings)
self.blink_prob = 0.
self.phone_prob = 0.
self.alert_level = AlertLevel.none
self.always_on = always_on
self.distracted_types = defaultdict(bool)
self.driver_distracted = False
self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, DT_DMON)
self.wheel_on_right = False
self.wheel_on_right_last = None
self.wheel_on_right_default = rhd_saved
self.face_detected = False
self.terminal_alert_cnt = 0
self.terminal_time = 0
self.step_change = 0.
self.active_policy = MonitoringPolicy.vision
self.driver_interacting = False
self.is_model_uncertain = False
self.hi_stds = 0
self.model_std_max = 0.
self.threshold_alert_1 = 0.
self.threshold_alert_2 = 0.
self.dcam_uncertain_cnt = 0
self.dcam_reset_cnt = 0
self.too_distracted = Params().get_bool("DriverTooDistracted")
self._reset_awareness()
self._set_policy(MonitoringPolicy.vision)
def _reset_awareness(self):
self.awareness = 1.
self.last_vision_awareness = 1.
self.last_wheeltouch_awareness = 1.
def _set_policy(self, target_policy):
if self.active_policy == MonitoringPolicy.vision and self.awareness <= self.threshold_alert_2:
if target_policy == MonitoringPolicy.vision:
self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT
else:
self.step_change = 0.
return # no exploit after orange alert
elif self.awareness <= 0.:
return
if target_policy == MonitoringPolicy.vision:
# when falling back from passive mode to active mode, reset awareness to avoid false alert
if self.active_policy != MonitoringPolicy.vision:
self.last_wheeltouch_awareness = self.awareness
self.awareness = self.last_vision_awareness
self.threshold_alert_1 = 1. - self.settings._VISION_POLICY_ALERT_1_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT
self.threshold_alert_2 = 1. - self.settings._VISION_POLICY_ALERT_2_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT
self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT
self.active_policy = MonitoringPolicy.vision
else:
if self.active_policy == MonitoringPolicy.vision:
self.last_vision_awareness = self.awareness
self.awareness = self.last_wheeltouch_awareness
self.threshold_alert_1 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT
self.threshold_alert_2 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT
self.step_change = DT_DMON / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT
self.active_policy = MonitoringPolicy.wheeltouch
def _set_pose_strictness(self, brake_disengage_prob, car_speed):
bp = brake_disengage_prob
k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2)
bp_normal = max(min(bp / k1, 0.5),0)
self.pose.cfactor_pitch = np.interp(bp_normal, [0, 0.5],
[self.settings._POSE_PITCH_THRESHOLD_SLACK,
self.settings._POSE_PITCH_THRESHOLD_STRICT]) / self.settings._POSE_PITCH_THRESHOLD
self.pose.cfactor_yaw = np.interp(bp_normal, [0, 0.5],
[self.settings._POSE_YAW_THRESHOLD_SLACK,
self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD
def _get_distracted_types(self):
self.distracted_types = defaultdict(bool)
if not self.pose.calibrated:
pitch_error = self.pose.pitch - self.settings._PITCH_NATURAL_OFFSET
yaw_error = self.pose.yaw - self.settings._YAW_NATURAL_OFFSET
else:
pitch_error = self.pose.pitch - min(max(self.pose.pitch_offsetter.filtered_stat.mean(),
self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET)
yaw_error = self.pose.yaw - min(max(self.pose.yaw_offsetter.filtered_stat.mean(),
self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET)
pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit
if yaw_error * self.pose.steer_yaw_offset > 0: # unidirectional
yaw_error = max(abs(yaw_error) - min(abs(self.pose.steer_yaw_offset), self.settings._POSE_YAW_STEER_MAX_OFFSET), 0.)
else:
yaw_error = abs(yaw_error)
pitch_threshold = self.settings._POSE_PITCH_THRESHOLD * self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD
yaw_threshold = self.settings._POSE_YAW_THRESHOLD * self.pose.cfactor_yaw
self.distracted_types['pose'] = bool((pitch_error > pitch_threshold) or (yaw_error > yaw_threshold))
self.distracted_types['eye'] = bool(self.blink_prob > self.settings._BLINK_THRESHOLD)
self.distracted_types['phone'] = bool(self.phone_prob > self.settings._PHONE_THRESH)
def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill, demo_mode=False, steering_angle_deg=0.):
rhd_pred = driver_state.wheelOnRightProb
# calibrates only when there's movement and either face detected
if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or
driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD):
self.wheelpos_offsetter.push_and_update(rhd_pred)
wheelpos_calibrated = self.wheelpos_offsetter.filtered_stat.n >= self.settings._WHEELPOS_FILTER_MIN_COUNT
if wheelpos_calibrated or demo_mode:
self.wheel_on_right = self.wheelpos_offsetter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD
else:
self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished
# make sure no switching when engaged
if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right and not demo_mode:
self.wheel_on_right = self.wheel_on_right_last
driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData
if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition,
driver_data.faceOrientationStd, driver_data.facePositionStd)):
return
self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD
self.pose.pitch, self.pose.yaw = face_orientation_from_model(driver_data.faceOrientation, driver_data.facePosition, cal_rpy)
steer_d = max(abs(steering_angle_deg) - self.settings._POSE_YAW_MIN_STEER_DEG, 0.)
self.pose.steer_yaw_offset = radians(steer_d) * -np.sign(steering_angle_deg) * self.settings._POSE_YAW_STEER_FACTOR
if self.wheel_on_right:
self.pose.yaw *= -1
self.pose.steer_yaw_offset *= -1
self.wheel_on_right_last = self.wheel_on_right
self.model_std_max = max(driver_data.faceOrientationStd[0], driver_data.faceOrientationStd[1])
self.pose.low_std = self.model_std_max < self.settings._HI_STD_THRESHOLD
self.blink_prob = driver_data.eyesClosedProb * (driver_data.eyesVisibleProb > self.settings._EYE_THRESHOLD)
self.phone_prob = driver_data.phoneProb
self._get_distracted_types()
self.driver_distracted = any(self.distracted_types.values()) and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std
self.driver_distraction_filter.update(self.driver_distracted)
# only update offsetter when driver is actively driving the car above a certain speed
if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted):
self.pose.pitch_offsetter.push_and_update(self.pose.pitch)
self.pose.yaw_offsetter.push_and_update(self.pose.yaw)
self.pose.calibrated = self.pose.pitch_offsetter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT and \
self.pose.yaw_offsetter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT
if self.face_detected and not self.driver_distracted:
dcam_uncertain = self.model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD
if dcam_uncertain and not standstill:
self.dcam_uncertain_cnt += 1
self.dcam_reset_cnt = 0
else:
self.dcam_reset_cnt += 1
if self.dcam_reset_cnt > self.settings._DCAM_UNCERTAIN_RESET_COUNT:
self.dcam_uncertain_cnt = 0
self.is_model_uncertain = self.hi_stds >= self.settings._HI_STD_FALLBACK_TIME
self._set_policy(MonitoringPolicy.vision if self.face_detected and not self.is_model_uncertain else MonitoringPolicy.wheeltouch)
if self.face_detected and not self.pose.low_std and not self.driver_distracted:
self.hi_stds += 1
elif self.face_detected and self.pose.low_std:
self.hi_stds = 0
def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear):
self.alert_level = AlertLevel.none
self.driver_interacting = driver_engaged
if self.terminal_alert_cnt >= self.settings._MAX_TERMINAL_ALERTS or \
self.terminal_time >= self.settings._MAX_TERMINAL_DURATION:
self.too_distracted = True
always_on_valid = self.always_on and not wrong_gear
if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \
(not always_on_valid and not op_engaged) or \
(always_on_valid and not op_engaged and self.awareness <= 0):
# always reset on disengage with normal mode; disengage resets only on red if always on
self._reset_awareness()
return
awareness_prev = self.awareness
_reaching_alert_1 = self.awareness - self.step_change <= self.threshold_alert_1
_reaching_alert_3 = self.awareness - self.step_change <= 0
standstill_exemption = standstill and _reaching_alert_1
always_on_exemption = always_on_valid and not op_engaged and _reaching_alert_3
if self.awareness > 0 and \
((self.driver_distraction_filter.x < 0.37 and self.face_detected and self.pose.low_std) or standstill_exemption):
if self.driver_interacting:
self._reset_awareness()
return
# only restore awareness when paying attention and alert is not red
self.awareness = min(self.awareness + ((self.settings._TIMEOUT_RECOVERY_FACTOR_MAX-self.settings._TIMEOUT_RECOVERY_FACTOR_MIN)*
(1.-self.awareness)+self.settings._TIMEOUT_RECOVERY_FACTOR_MIN)*self.step_change, 1.)
if self.awareness == 1.:
self.last_wheeltouch_awareness = min(self.last_wheeltouch_awareness + self.step_change, 1.)
# don't display alert banner when awareness is recovering and has cleared orange
if self.awareness > self.threshold_alert_2:
return
certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected
maybe_distracted = self.is_model_uncertain or not self.face_detected
if certainly_distracted or maybe_distracted:
# should always be counting if distracted unless at standstill and reaching green
# also will not be reaching 0 if DM is active when not engaged
if not (standstill_exemption or always_on_exemption):
self.awareness = max(self.awareness - self.step_change, -0.1)
if self.awareness <= 0.:
# terminal alert: disengagement required
self.alert_level = AlertLevel.three
self.terminal_time += 1
if awareness_prev > 0.:
self.terminal_alert_cnt += 1
elif self.awareness <= self.threshold_alert_2:
self.alert_level = AlertLevel.two
elif self.awareness <= self.threshold_alert_1:
self.alert_level = AlertLevel.one
def get_state_packet(self, valid=True):
# build driverMonitoringState packet
dat = messaging.new_message('driverMonitoringState', valid=valid)
dm = dat.driverMonitoringState
dm.lockout = self.too_distracted
dm.alertCountLockoutPercent = to_percent(self.terminal_alert_cnt / self.settings._MAX_TERMINAL_ALERTS)
dm.alertTimeLockoutPercent = to_percent(self.terminal_time / self.settings._MAX_TERMINAL_DURATION)
dm.alwaysOn = self.always_on
dm.alwaysOnLockout = self.always_on and self.awareness <= self.threshold_alert_2
dm.alertLevel = self.alert_level
dm.activePolicy = self.active_policy
dm.isRHD = self.wheel_on_right
dm.rhdCalibration.calibratedPercent = to_percent(self.wheelpos_offsetter.filtered_stat.n / self.settings._WHEELPOS_FILTER_MIN_COUNT)
dm.rhdCalibration.offset = self.wheelpos_offsetter.filtered_stat.M
dm.visionPolicyState.awarenessPercent = to_percent(self.last_vision_awareness if self.active_policy != MonitoringPolicy.vision else self.awareness)
dm.visionPolicyState.awarenessStep = self.step_change if self.active_policy == MonitoringPolicy.vision else 0.
dm.visionPolicyState.isDistracted = self.driver_distracted
dm.visionPolicyState.distractedTypes.pose = self.distracted_types['pose']
dm.visionPolicyState.distractedTypes.eye = self.distracted_types['eye']
dm.visionPolicyState.distractedTypes.phone = self.distracted_types['phone']
dm.visionPolicyState.faceDetected = self.face_detected
dm.visionPolicyState.pose.pitch = self.pose.pitch
dm.visionPolicyState.pose.yaw = self.pose.yaw
dm.visionPolicyState.pose.calibrated = self.pose.calibrated
dm.visionPolicyState.pose.pitchCalib.calibratedPercent = to_percent(self.pose.pitch_offsetter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT)
dm.visionPolicyState.pose.pitchCalib.offset = self.pose.pitch_offsetter.filtered_stat.M
dm.visionPolicyState.pose.yawCalib.calibratedPercent = to_percent(self.pose.yaw_offsetter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT)
dm.visionPolicyState.pose.yawCalib.offset = self.pose.yaw_offsetter.filtered_stat.M
dm.visionPolicyState.pose.uncertainty = self.model_std_max
dm.visionPolicyState.wheeltouchFallbackPercent = to_percent(self.hi_stds / self.settings._HI_STD_FALLBACK_TIME)
dm.visionPolicyState.uncertainOffroadAlertPercent = to_percent(self.dcam_uncertain_cnt / self.settings._DCAM_UNCERTAIN_ALERT_COUNT)
dm.wheeltouchPolicyState.awarenessPercent = to_percent(self.last_wheeltouch_awareness if self.active_policy == MonitoringPolicy.vision else self.awareness)
dm.wheeltouchPolicyState.awarenessStep = 0. if self.active_policy == MonitoringPolicy.vision else self.step_change
dm.wheeltouchPolicyState.driverInteracting = self.driver_interacting
return dat
def run_step(self, sm, demo=False):
if demo:
car_speed = 30
enabled = True
wrong_gear = False
standstill = False
driver_engaged = False
brake_disengage_prob = 1.0
steering_angle_deg = 0.0
rpyCalib = [0., 0., 0.]
else:
car_speed = sm['carState'].vEgo
enabled = sm['selfdriveState'].enabled or sm['carControl'].latActive
wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low)
standstill = sm['carState'].standstill
driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed
brake_disengage_prob = sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s
steering_angle_deg = sm['carState'].steeringAngleDeg
rpyCalib = sm['liveCalibration'].rpyCalib
self._set_pose_strictness(
brake_disengage_prob=brake_disengage_prob,
car_speed=car_speed,
)
# Parse data from dmonitoringmodeld
self._update_states(
driver_state=sm['driverStateV2'],
cal_rpy=rpyCalib,
car_speed=car_speed,
op_engaged=enabled,
standstill=standstill,
demo_mode=demo,
steering_angle_deg=steering_angle_deg,
)
# Update distraction events
self._update_events(
driver_engaged=driver_engaged,
op_engaged=enabled,
standstill=standstill,
wrong_gear=wrong_gear,
)

View File

@@ -3,17 +3,16 @@ import pytest
from cereal import log, car
from openpilot.common.realtime import DT_DMON
from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS
from openpilot.system.hardware import HARDWARE
from openpilot.selfdrive.monitoring.policy import DriverMonitoring, DRIVER_MONITOR_SETTINGS
EventName = log.OnroadEvent.EventName
dm_settings = DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type())
dm_settings = DRIVER_MONITOR_SETTINGS()
TEST_TIMESPAN = 120 # seconds
DISTRACTED_SECONDS_TO_ORANGE = dm_settings._DISTRACTED_TIME - dm_settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1
DISTRACTED_SECONDS_TO_RED = dm_settings._DISTRACTED_TIME + 1
INVISIBLE_SECONDS_TO_ORANGE = dm_settings._AWARENESS_TIME - dm_settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + 1
INVISIBLE_SECONDS_TO_RED = dm_settings._AWARENESS_TIME + 1
DISTRACTED_SECONDS_TO_ORANGE = dm_settings._VISION_POLICY_ALERT_2_TIMEOUT + 1
DISTRACTED_SECONDS_TO_RED = dm_settings._VISION_POLICY_ALERT_3_TIMEOUT + 1
INVISIBLE_SECONDS_TO_ORANGE = dm_settings._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT + 1
INVISIBLE_SECONDS_TO_RED = dm_settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + 1
def make_msg(face_detected, distracted=False, model_uncertain=False):
ds = log.DriverStateV2.new_message()
@@ -35,7 +34,7 @@ msg_ATTENTIVE = make_msg(True)
msg_DISTRACTED = make_msg(True, distracted=True)
msg_ATTENTIVE_UNCERTAIN = make_msg(True, model_uncertain=True)
msg_DISTRACTED_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=True)
msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=dm_settings._POSESTD_THRESHOLD*1.5)
msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=dm_settings._HI_STD_THRESHOLD*1.5)
# driver interaction with car
car_interaction_DETECTED = True
@@ -51,49 +50,49 @@ always_false = [False] * int(TEST_TIMESPAN / DT_DMON)
class TestMonitoring:
def _run_seq(self, msgs, interaction, engaged, standstill):
DM = DriverMonitoring()
events = []
alert_lvls = []
for idx in range(len(msgs)):
DM._update_states(msgs[idx], [0, 0, 0], 0, engaged[idx], standstill[idx])
# cal_rpy and car_speed don't matter here
# evaluate events at 10Hz for tests
DM._update_events(interaction[idx], engaged[idx], standstill[idx], 0, 0)
events.append(DM.current_events)
assert len(events) == len(msgs), f"got {len(events)} for {len(msgs)} driverState input msgs"
return events, DM
DM._update_events(interaction[idx], engaged[idx], standstill[idx], 0)
alert_lvls.append(DM.alert_level)
assert len(alert_lvls) == len(msgs), f"got {len(alert_lvls)} for {len(msgs)} driverState input msgs"
return alert_lvls, DM
def _assert_no_events(self, events):
assert all(not len(e) for e in events)
# engaged, driver is attentive all the time
def test_fully_aware_driver(self):
events, _ = self._run_seq(always_attentive, always_false, always_true, always_false)
self._assert_no_events(events)
alert_lvls, d_status = self._run_seq(always_attentive, always_false, always_true, always_false)
assert all(a == 0 for a in alert_lvls)
assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.vision
# engaged, driver is distracted and does nothing
def test_fully_distracted_driver(self):
events, d_status = self._run_seq(always_distracted, always_false, always_true, always_false)
assert len(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0
assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL + \
((d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \
EventName.driverDistracted1
assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + \
((d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.driverDistracted2
assert events[int((d_status.settings._DISTRACTED_TIME + \
((TEST_TIMESPAN-10-d_status.settings._DISTRACTED_TIME)/2))/DT_DMON)].names[0] == EventName.driverDistracted3
alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false)
s = d_status.settings
assert alert_lvls[int(s._VISION_POLICY_ALERT_1_TIMEOUT / 2 / DT_DMON)] == 0
assert alert_lvls[int((s._VISION_POLICY_ALERT_1_TIMEOUT + \
(s._VISION_POLICY_ALERT_2_TIMEOUT - s._VISION_POLICY_ALERT_1_TIMEOUT) / 2) / DT_DMON)] == 1
assert alert_lvls[int((s._VISION_POLICY_ALERT_2_TIMEOUT + \
(s._VISION_POLICY_ALERT_3_TIMEOUT - s._VISION_POLICY_ALERT_2_TIMEOUT) / 2) / DT_DMON)] == 2
assert alert_lvls[int((s._VISION_POLICY_ALERT_3_TIMEOUT + \
(TEST_TIMESPAN - 10 - s._VISION_POLICY_ALERT_3_TIMEOUT) / 2) / DT_DMON)] == 3
assert isinstance(d_status.awareness, float)
# engaged, no face detected the whole time, no action
def test_fully_invisible_driver(self):
events, d_status = self._run_seq(always_no_face, always_false, always_true, always_false)
assert len(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0
assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL + \
((d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \
EventName.driverUnresponsive1
assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + \
((d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive2
assert events[int((d_status.settings._AWARENESS_TIME + \
((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive3
alert_lvls, d_status = self._run_seq(always_no_face, always_false, always_true, always_false)
s = d_status.settings
assert alert_lvls[int(s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT / 2 / DT_DMON)] == 0
assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT + \
(s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT - s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT) / 2) / DT_DMON)] == 1
assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT + \
(s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT - s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT) / 2) / DT_DMON)] == 2
assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + \
(TEST_TIMESPAN - 10 - s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT) / 2) / DT_DMON)] == 3
assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch
# engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel
# - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention
@@ -104,13 +103,13 @@ class TestMonitoring:
[msg_ATTENTIVE] * (int(TEST_TIMESPAN/DT_DMON)-int((DISTRACTED_SECONDS_TO_ORANGE*3+2)/DT_DMON))
interaction_vector = [car_interaction_NOT_DETECTED] * int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON) + \
[car_interaction_DETECTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON))
events, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false)
assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0
assert events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverDistracted2
assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]) == 0
assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0] == EventName.driverDistracted2
assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0] == EventName.driverDistracted2
assert len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]) == 0
alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false)
assert alert_lvls[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)] == 0
assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2
assert alert_lvls[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)] == 0
assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)] == 2
assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)] == 2
assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)] == 0
# engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \
# driver dodges, and then touches wheel to no avail, disengages and reengages
@@ -128,11 +127,11 @@ class TestMonitoring:
= [True] * int(1/DT_DMON)
op_vector[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+2.5)/DT_DMON):int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3)/DT_DMON)] \
= [False] * int(0.5/DT_DMON)
events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false)
assert events[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted2
assert events[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted3
assert events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0] == EventName.driverDistracted3
assert len(events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)]) == 0
alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false)
assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)] == 2
assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)] == 3
assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)] == 3
assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)] == 0
# engaged, invisible driver, down to orange, driver touches wheel; then down to orange again, driver appears
# - both actions should clear the alert, but momentary appearance should not
@@ -143,16 +142,16 @@ class TestMonitoring:
ds_vector[int((2*INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON):int((2*INVISIBLE_SECONDS_TO_ORANGE+1+_visible_time)/DT_DMON)] = \
[msg_ATTENTIVE] * int(_visible_time/DT_DMON)
interaction_vector[int((INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON)
events, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false)
assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0
assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2
assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)]) == 0
alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false)
assert alert_lvls[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)] == 0
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)] == 0
if _visible_time == 0.5:
assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2
assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive1
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)] == 2
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)] == 1
elif _visible_time == 10:
assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2
assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)]) == 0
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)] == 2
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)] == 0
# engaged, invisible driver, down to red, driver appears and then touches wheel, then disengages/reengages
# - only disengage will clear the alert
@@ -164,19 +163,19 @@ class TestMonitoring:
ds_vector[int(INVISIBLE_SECONDS_TO_RED/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON)] = [msg_ATTENTIVE] * int(_visible_time/DT_DMON)
interaction_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON)] = [True] * int(1/DT_DMON)
op_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] = [False] * int(0.5/DT_DMON)
events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false)
assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0
assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2
assert events[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive3
assert events[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive3
assert events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0] == EventName.driverUnresponsive3
assert len(events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)]) == 0
alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false)
assert alert_lvls[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)] == 0
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2
assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)] == 3
assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)] == 3
assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] == 3
assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)] == 0
# disengaged, always distracted driver
# - dm should stay quiet when not engaged
def test_pure_dashcam_user(self):
events, _ = self._run_seq(always_distracted, always_false, always_false, always_false)
assert sum(len(event) for event in events) == 0
alert_lvls, _ = self._run_seq(always_distracted, always_false, always_false, always_false)
assert all(a == 0 for a in alert_lvls)
# engaged, car stops at traffic light, down to orange, no action, then car starts moving
# - should only reach green when stopped, but continues counting down on launch
@@ -184,11 +183,12 @@ class TestMonitoring:
_redlight_time = 60 # seconds
standstill_vector = always_true[:]
standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((TEST_TIMESPAN-_redlight_time)/DT_DMON)
events, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector)
assert len(events[int((_redlight_time-0.1)/DT_DMON)]) == 0
_pre_to_prompt = d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL - d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL
assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.driverDistracted1
assert events[int((_redlight_time+_pre_to_prompt+0.5)/DT_DMON)].names[0] == EventName.driverDistracted2
alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector)
s = d_status.settings
assert alert_lvls[int((_redlight_time-0.1)/DT_DMON)] == 0
_alert_1_to_2 = s._VISION_POLICY_ALERT_2_TIMEOUT - s._VISION_POLICY_ALERT_1_TIMEOUT
assert alert_lvls[int((_redlight_time+0.5)/DT_DMON)] == 1
assert alert_lvls[int((_redlight_time+_alert_1_to_2+0.5)/DT_DMON)] == 2
# engaged, distracted while moving, then car stops after reaching orange
# - should reset timer to pre green at standstill
@@ -196,23 +196,21 @@ class TestMonitoring:
_stop_time = DISTRACTED_SECONDS_TO_ORANGE + 1 # stop 1 second after reaching orange
standstill_vector = always_false[:]
standstill_vector[int(_stop_time/DT_DMON):] = [True] * int((TEST_TIMESPAN-_stop_time)/DT_DMON)
events, _ = self._run_seq(always_distracted, always_false, always_true, standstill_vector)
alert_lvls, _ = self._run_seq(always_distracted, always_false, always_true, standstill_vector)
# just before and briefly after stopping: orange alert; goes away quickly after stopped
assert events[int((_stop_time+0.1)/DT_DMON)].names[0] == EventName.driverDistracted2
assert len(events[int((_stop_time+0.5)/DT_DMON)]) == 0
assert alert_lvls[int((_stop_time+0.1)/DT_DMON)] == 2
assert alert_lvls[int((_stop_time+0.5)/DT_DMON)] == 0
# engaged, model is somehow uncertain and driver is distracted
# - should fall back to wheel touch after uncertain alert
def test_somehow_indecisive_model(self):
ds_vector = [msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN] * int(TEST_TIMESPAN/DT_DMON)
interaction_vector = always_false[:]
events, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false)
assert EventName.driverUnresponsive1 in \
events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)].names
assert EventName.driverUnresponsive2 in \
events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names
assert EventName.driverUnresponsive3 in \
events[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names
alert_lvls, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false)
s = d_status.settings
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*s._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)] == 1
assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*s._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)] == 2
assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*s._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)] == 3
@pytest.mark.parametrize("enabled_state, lat_active_state, expected", [
@@ -249,12 +247,12 @@ def test_enabled_states(enabled_state, lat_active_state, expected):
ds = make_msg(False)
sm = {
'carState': cs,
'selfdriveState': ss,
'carControl': cc,
'modelV2': mv2,
'liveCalibration': lc,
'driverStateV2': ds
'carState': cs,
'selfdriveState': ss,
'carControl': cc,
'modelV2': mv2,
'liveCalibration': lc,
'driverStateV2': ds
}
driver_monitoring = DriverMonitoring()
@@ -263,9 +261,9 @@ def test_enabled_states(enabled_state, lat_active_state, expected):
captured_args = []
original_update_events = driver_monitoring._update_events
def spy_update_events(driver_engaged, op_engaged, standstill, wrong_gear, car_speed):
captured_args.append(op_engaged)
return original_update_events(driver_engaged, op_engaged, standstill, wrong_gear, car_speed)
def spy_update_events(driver_engaged, op_engaged, standstill, wrong_gear):
captured_args.append(op_engaged)
return original_update_events(driver_engaged, op_engaged, standstill, wrong_gear)
driver_monitoring._update_events = spy_update_events

View File

@@ -45,6 +45,8 @@ LaneChangeDirection = log.LaneChangeDirection
EventName = log.OnroadEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
AlertLevel = log.DriverMonitoringState.AlertLevel
MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy
TurnDirection = custom.ModelDataV2SP.TurnDirection
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
@@ -140,6 +142,8 @@ class SelfdriveD(CruiseHelper):
self.params
)
self.recalibrating_seen = False
self.dm_lockout_set = False
self.dm_uncertain_alerted = False
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
@@ -216,8 +220,27 @@ class SelfdriveD(CruiseHelper):
if not self.CP.pcmCruise and CS.vCruise > 250 and resume_pressed:
self.events.add(EventName.resumeBlocked)
# Handle DM
if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
# Block engaging until ignition cycle after max number or time of distractions
if self.sm['driverMonitoringState'].lockout and not self.dm_lockout_set:
self.params.put_bool_nonblocking("DriverTooDistracted", True)
self.dm_lockout_set = True
# No entry conditions
if self.sm['driverMonitoringState'].lockout or self.sm['driverMonitoringState'].alwaysOnLockout:
self.events.add(EventName.tooDistracted)
# Alerts
vision_dm = self.sm['driverMonitoringState'].activePolicy == MonitoringPolicy.vision
if self.sm['driverMonitoringState'].alertLevel == AlertLevel.one:
self.events.add(EventName.driverDistracted1 if vision_dm else EventName.driverUnresponsive1)
elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.two:
self.events.add(EventName.driverDistracted2 if vision_dm else EventName.driverUnresponsive2)
elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.three:
self.events.add(EventName.driverDistracted3 if vision_dm else EventName.driverUnresponsive3)
# Warn consistent DM uncertainty
if self.sm['driverMonitoringState'].visionPolicyState.uncertainOffroadAlertPercent >= 100 and not self.dm_uncertain_alerted:
set_offroad_alert("Offroad_DriverMonitoringUncertain", True)
self.dm_uncertain_alerted = True
self.events_sp.add_from_msg(self.sm['longitudinalPlanSP'].events)
# Add car events, ignore if CAN isn't valid
@@ -241,7 +264,7 @@ class SelfdriveD(CruiseHelper):
self.events.add(EventName.pedalPressed)
# Create events for temperature, disk space, and memory
if self.sm['deviceState'].thermalStatus >= ThermalStatus.red:
if self.sm['deviceState'].thermalStatus >= ThermalStatus.overheated:
self.events.add(EventName.overheat)
if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION:
self.events.add(EventName.outOfSpace)

View File

@@ -449,9 +449,6 @@ def migrate_sensorEvents(msgs):
m.logMonoTime = msg.logMonoTime
m_dat = getattr(m, sensor_service)
m_dat.version = evt.version
m_dat.sensor = evt.sensor
m_dat.type = evt.type
m_dat.source = evt.source
m_dat.timestamp = evt.timestamp
setattr(m_dat, evt.which(), getattr(evt, evt.which()))
@@ -484,22 +481,41 @@ def migrate_onroadEvents(msgs):
return ops, [], []
@migration(inputs=["driverMonitoringState"])
@migration(inputs=["driverMonitoringStateDEPRECATED"])
def migrate_driverMonitoringState(msgs):
ops = []
for index, msg in msgs:
msg = msg.as_builder()
events = []
for event in msg.driverMonitoringState.deprecated.events:
try:
if not str(event.name).endswith('DEPRECATED'):
migrated_event = migrate_onroad_event(event)
if migrated_event is not None:
events.append(migrated_event)
except RuntimeError: # Member was null
traceback.print_exc()
old = msg.driverMonitoringStateDEPRECATED
new_msg = messaging.new_message('driverMonitoringState', valid=msg.valid, logMonoTime=msg.logMonoTime)
dm = new_msg.driverMonitoringState
dm.isRHD = old.isRHD
dm.activePolicy = log.DriverMonitoringState.MonitoringPolicy.vision if old.isActiveMode else \
log.DriverMonitoringState.MonitoringPolicy.wheeltouch
msg.driverMonitoringState.events = events
ops.append((index, msg.as_reader()))
AlertLevel = log.DriverMonitoringState.AlertLevel
event_to_alert_level = {
'driverDistracted1': AlertLevel.one, 'driverUnresponsive1': AlertLevel.one,
'driverDistracted2': AlertLevel.two, 'driverUnresponsive2': AlertLevel.two,
'driverDistracted3': AlertLevel.three, 'driverUnresponsive3': AlertLevel.three,
'tooDistracted': AlertLevel.three,
}
for event in old.events:
level = event_to_alert_level.get(str(event.name))
if level is not None:
dm.alertLevel = level
break
dm.visionPolicyState.awarenessPercent = int(max(0, min(100, (old.awarenessStatus if old.isActiveMode else old.awarenessActive) * 100)))
dm.visionPolicyState.awarenessStep = old.stepChange if old.isActiveMode else 0.
dm.visionPolicyState.isDistracted = old.isDistracted
dm.visionPolicyState.faceDetected = old.faceDetected
dm.visionPolicyState.pose.pitchCalib.offset = old.posePitchOffset
dm.visionPolicyState.pose.pitchCalib.calibratedPercent = int(min(100, old.posePitchValidCount / 600 * 100))
dm.visionPolicyState.pose.yawCalib.offset = old.poseYawOffset
dm.visionPolicyState.pose.yawCalib.calibratedPercent = int(min(100, old.poseYawValidCount / 600 * 100))
dm.visionPolicyState.pose.calibrated = old.posePitchValidCount >= 600 and old.poseYawValidCount >= 600
dm.wheeltouchPolicyState.awarenessPercent = int(max(0, min(100, (old.awarenessPassive if old.isActiveMode else old.awarenessStatus) * 100)))
dm.wheeltouchPolicyState.awarenessStep = 0. if old.isActiveMode else old.stepChange
ops.append((index, new_msg.as_reader()))
return ops, [], []

View File

@@ -35,7 +35,7 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN)
EXEC_TIMINGS = [
# model, instant max, average max
("modelV2", 0.05, 0.028),
("driverStateV2", 0.05, 0.016),
("driverStateV2", 0.05, 0.018),
]
def get_log_fn(test_route, ref="master"):

View File

View File

@@ -0,0 +1,278 @@
from dataclasses import dataclass
from enum import Enum
import time
class AnimationMode(Enum):
ONCE_FORWARD = 1
ONCE_FORWARD_BACKWARD = 2
REPEAT_FORWARD = 3
REPEAT_FORWARD_BACKWARD = 4
@dataclass
class Animation:
frames: list[list[tuple[int, int]]]
starting_frames: list[list[tuple[int, int]]] | None = None # played once before the main loop
frame_duration: float = 0.15 # seconds each frame is shown
mode: AnimationMode = AnimationMode.REPEAT_FORWARD_BACKWARD
repeat_interval: float = 5.0 # seconds between animation restarts (only for REPEAT modes)
hold_end: float = 0.0 # seconds to hold the last frame before playing backward (only for *_BACKWARD modes)
left_turn_remove: list[tuple[int, int]] | None = None # dots to remove from frame when turning left
right_turn_remove: list[tuple[int, int]] | None = None # dots to remove from frame when turning right
# --- Animation Helper Functions ---
def _mirror(dots: list[tuple[int, int]]) -> list[tuple[int, int]]:
"""Mirror a component from the left side of the face to the right"""
return [(r, 15 - c) for r, c in dots]
def _mirror_no_flip(dots: list[tuple[int, int]]) -> list[tuple[int, int]]:
"""Move a component to the mirrored position on the right half without flipping its shape."""
min_c = min(c for _, c in dots)
max_c = max(c for _, c in dots)
return [(r, 15 - max_c - min_c + c) for r, c in dots]
def _shift(dots: list[tuple[int, int]], rc: tuple[int, int]) -> list[tuple[int, int]]:
dr, dc = rc
return [(r + dr, c + dc) for r, c in dots]
def _make_frame(left_eye: list[tuple[int, int]], right_eye: list[tuple[int, int]],
left_brow: list[tuple[int, int]], right_brow: list[tuple[int, int]],
mouth: list[tuple[int, int]]) -> list[tuple[int, int]]:
return left_eye + left_brow + right_eye + right_brow + mouth
# --- Animation Helper Components ---
# Eyes (left side)
EYE_OPEN = [
(2, 2), (2, 3),
(3, 1), (3, 2), (3, 3), (3, 4),
(4, 1), (4, 2), (4, 3), (4, 4),
(5, 2), (5, 3)
]
EYE_HALF = [
(4, 1), (4, 2), (4, 3), (4, 4),
(5, 2), (5, 3)
]
EYE_CLOSED = [
(4, 1), (4, 4),
(5, 2), (5, 3),
]
EYE_LEFT_LOOK = [
(2, 2), (2, 3),
(3, 1), (3, 2),
(4, 1), (4, 2),
(5, 2), (5, 3),
]
EYE_RIGHT_LOOK = [
(2, 2), (2, 3),
(3, 3), (3, 4),
(4, 3), (4, 4),
(5, 2), (5, 3),
]
# Eyebrows (left side)
BROW_HIGH = [
(0, 1), (0, 2),
(1, 0),
]
BROW_LOWERED = [
(1, 1), (1, 2),
(2, 0)
]
BROW_STRAIGHT = [(1, 0), (1, 1), (1, 2)]
BROW_DOWN = [
(0, 1), (0, 2),
(1, 3)
]
# Mouths (centered, not mirrored)
MOUTH_SMILE = [
(6, 6), (6, 9),
(7, 7), (7, 8),
]
MOUTH_NORMAL = [(7, 7), (7, 8)]
MOUTH_SAD = [
(6, 7), (6, 8),
(7, 6), (7, 9)
]
# --- Animations ---
NORMAL = Animation(
frames=[
_make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_HALF, _mirror(EYE_HALF), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_CLOSED, _mirror(EYE_CLOSED), BROW_LOWERED, _mirror(BROW_LOWERED), MOUTH_SMILE),
],
left_turn_remove=[
(3, 3), (3, 4),
(4, 3), (4, 4),
] + _mirror_no_flip([
(3, 1), (3, 2),
(4, 1), (4, 2),
]),
right_turn_remove=[
(3, 1), (3, 2),
(4, 1), (4, 2),
] + _mirror_no_flip([
(3, 3), (3, 4),
(4, 3), (4, 4),
])
)
ASLEEP = Animation(
frames=[
_make_frame(EYE_CLOSED, _mirror(EYE_CLOSED), [], [], MOUTH_NORMAL),
],
)
SLEEPY = Animation(
frames=[
_make_frame(EYE_CLOSED, _mirror(EYE_CLOSED), _shift(BROW_STRAIGHT, (1, 0)), [], MOUTH_NORMAL),
_make_frame(EYE_HALF, _mirror(EYE_CLOSED), BROW_LOWERED, [], MOUTH_NORMAL),
_make_frame(EYE_OPEN, _mirror(EYE_CLOSED), BROW_HIGH, [], MOUTH_NORMAL)
],
frame_duration=0.25,
mode=AnimationMode.ONCE_FORWARD_BACKWARD,
repeat_interval=10,
hold_end=1.5,
)
INQUISITIVE = Animation(
frames=[
_make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_LEFT_LOOK, _mirror(EYE_RIGHT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(_shift(EYE_LEFT_LOOK, (0, -1)), _shift(_mirror(EYE_RIGHT_LOOK), (0, -1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(_shift(EYE_LEFT_LOOK, (0, -1)), _shift(_mirror(EYE_RIGHT_LOOK), (0, -1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(_shift(EYE_LEFT_LOOK, (0, -1)), _shift(_mirror(EYE_RIGHT_LOOK), (0, -1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_LEFT_LOOK, _mirror(EYE_RIGHT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_RIGHT_LOOK, _mirror(EYE_LEFT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(_shift(EYE_RIGHT_LOOK, (0, 1)), _shift(_mirror(EYE_LEFT_LOOK), (0, 1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(_shift(EYE_RIGHT_LOOK, (0, 1)), _shift(_mirror(EYE_LEFT_LOOK), (0, 1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(_shift(EYE_RIGHT_LOOK, (0, 1)), _shift(_mirror(EYE_LEFT_LOOK), (0, 1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_RIGHT_LOOK, _mirror(EYE_LEFT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
],
mode=AnimationMode.REPEAT_FORWARD,
frame_duration=0.15,
repeat_interval=10
)
WINK = Animation(
frames=[
_make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE),
_make_frame(EYE_OPEN, _mirror(EYE_CLOSED), BROW_HIGH, _mirror(_shift(BROW_DOWN, (0, 2))), MOUTH_SMILE),
],
mode=AnimationMode.ONCE_FORWARD_BACKWARD,
frame_duration=0.75,
)
# --- Face Animator Class ---
class FaceAnimator:
def __init__(self, animation: Animation):
self._animation = animation
self._next: Animation | None = None
self._start_time = time.monotonic()
self._rewinding = False
self._rewind_start: float = 0.0
self._rewind_from: int = 0
self._seen_nonzero = False
def set_animation(self, animation: Animation):
if animation is not self._animation:
self._next = animation
def get_dots(self) -> list[tuple[int, int]]:
now = time.monotonic()
elapsed = now - self._start_time
# Handle rewind for forward-only animations
if self._rewinding:
rewind_elapsed = now - self._rewind_start
frames_back = round(rewind_elapsed / self._animation.frame_duration)
frame_index = self._rewind_from - frames_back
if frame_index <= 0:
return self._switch_to_next(now)
return self._animation.frames[frame_index]
# Play starting frames first (once)
starting = self._animation.starting_frames or []
starting_duration = len(starting) * self._animation.frame_duration
if starting and elapsed < starting_duration:
frame_index = min(int(elapsed / self._animation.frame_duration), len(starting) - 1)
return starting[frame_index]
# Main loop
loop_elapsed = elapsed - starting_duration if starting else elapsed
frame_index = _get_frame_index(self._animation, loop_elapsed, gap_first=bool(starting))
if frame_index != 0:
self._seen_nonzero = True
if self._next is not None:
if frame_index == 0 and (len(self._animation.frames) == 1 or self._seen_nonzero):
return self._switch_to_next(now)
# No natural return to frame 0 — start rewinding
if self._animation.mode in (AnimationMode.ONCE_FORWARD, AnimationMode.REPEAT_FORWARD):
self._rewinding = True
self._rewind_start = now
self._rewind_from = frame_index
return self._animation.frames[frame_index]
def _switch_to_next(self, now: float) -> list[tuple[int, int]]:
self._animation = self._next
self._next = None
self._rewinding = False
self._seen_nonzero = False
self._start_time = now
return self._animation.frames[0]
def _get_frame_index(animation: Animation, elapsed: float, gap_first: bool = False) -> int:
"""Get the current frame index given elapsed time and animation mode."""
num_frames = len(animation.frames)
if num_frames == 1:
return 0
fd = animation.frame_duration
has_backward = animation.mode in (AnimationMode.ONCE_FORWARD_BACKWARD, AnimationMode.REPEAT_FORWARD_BACKWARD)
repeats = animation.mode in (AnimationMode.REPEAT_FORWARD, AnimationMode.REPEAT_FORWARD_BACKWARD)
forward_duration = num_frames * fd
backward_frames = max(num_frames - 2, 0) if has_backward else 0
hold = animation.hold_end if has_backward else 0.0
cycle_duration = forward_duration + hold + backward_frames * fd
if not repeats:
t = min(elapsed, cycle_duration)
else:
t = (elapsed + cycle_duration if gap_first else elapsed) % animation.repeat_interval
# Forward phase
if t < forward_duration:
return min(int(t / fd), num_frames - 1)
t -= forward_duration
# Hold at last frame
if t < hold:
return num_frames - 1
t -= hold
# Backward phase
if backward_frames and t < backward_frames * fd:
return num_frames - 2 - min(int(t / fd), backward_frames - 1)
return 0 if has_backward else num_frames - 1

View File

View File

@@ -0,0 +1,93 @@
import time
import pyray as rl
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.body.animations import FaceAnimator, ASLEEP, INQUISITIVE, NORMAL, SLEEPY
GRID_COLS = 16
GRID_ROWS = 8
DOT_RADIUS = 50 if gui_app.big_ui() else 10
IDLE_TIMEOUT = 30.0 # seconds of no joystick input before playing INQUISITIVE
IDLE_STEER_THRESH = 0.5 # degrees — below this counts as no input
IDLE_SPEED_THRESH = 0.01 # m/s — below this counts as no input
# This class is used both in BIG (tizi) and small (mici) UIs
class BodyLayout(Widget):
def __init__(self):
super().__init__()
self._animator = FaceAnimator(ASLEEP)
self._turning_left = False
self._turning_right = False
self._last_input_time = time.monotonic()
self._was_active = False
self._offroad_label = UnifiedLabel("turn on ignition to use", 95 if gui_app.big_ui() else 45, FontWeight.DISPLAY,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
def draw_dot_grid(self, rect: rl.Rectangle, dots: list[tuple[int, int]], color: rl.Color):
spacing = min(rect.height / GRID_ROWS, rect.width / GRID_COLS)
grid_w = (GRID_COLS - 1) * spacing
grid_h = (GRID_ROWS - 1) * spacing
offset_x = rect.x + (rect.width - grid_w) / 2
offset_y = rect.y + (rect.height - grid_h) / 2
for row, col in dots:
x = int(offset_x + col * spacing)
y = int(offset_y + row * spacing)
rl.draw_circle(x, y, DOT_RADIUS, color)
def _update_state(self):
super()._update_state()
sm = ui_state.sm
if ui_state.is_onroad():
if not self._was_active:
self._last_input_time = time.monotonic()
self._was_active = True
cs = sm['carState']
has_input = abs(cs.steeringAngleDeg) > IDLE_STEER_THRESH or abs(cs.vEgo) > IDLE_SPEED_THRESH
if has_input:
self._last_input_time = time.monotonic()
if time.monotonic() - self._last_input_time > IDLE_TIMEOUT:
self._animator.set_animation(INQUISITIVE)
else:
self._animator.set_animation(NORMAL)
else:
self._was_active = False
self._animator.set_animation(ASLEEP)
steer = sm['testJoystick'].axes[1] if len(sm['testJoystick'].axes) > 1 else 0
self._turning_left = steer <= -0.05
self._turning_right = steer >= 0.05
# play animation on screen tap
def _handle_mouse_release(self, mouse_pos):
super()._handle_mouse_release(mouse_pos)
if not self._was_active:
self._animator.set_animation(SLEEPY)
def _render(self, rect: rl.Rectangle):
dots = self._animator.get_dots()
animation = self._animator._animation
if self._turning_left and animation.left_turn_remove:
remove_set = set(animation.left_turn_remove)
dots = [d for d in dots if d not in remove_set]
elif self._turning_right and animation.right_turn_remove:
remove_set = set(animation.right_turn_remove)
dots = [d for d in dots if d not in remove_set]
self.draw_dot_grid(rect, dots, rl.WHITE)
if ui_state.is_offroad():
rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175))
upper_half = rl.Rectangle(rect.x, rect.y, rect.width, rect.height / 2)
self._offroad_label.render(upper_half)

View File

@@ -2,13 +2,14 @@ import pyray as rl
from enum import IntEnum
import cereal.messaging as messaging
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH
from openpilot.selfdrive.ui.layouts.home import HomeLayout
from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType
from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow
from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
@@ -31,7 +32,9 @@ class MainLayout(Widget):
self._prev_onroad = False
# Initialize layouts
self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
self._home_layout = HomeLayout()
self._home_body_layout = BodyLayout()
self._layouts = {MainState.HOME: self._home_layout, MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
self._sidebar_rect = rl.Rectangle(0, 0, 0, 0)
self._content_rect = rl.Rectangle(0, 0, 0, 0)
@@ -57,14 +60,18 @@ class MainLayout(Widget):
self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE))
self._layouts[MainState.HOME].set_settings_callback(lambda: self.open_settings(PanelType.TOGGLES))
self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked)
for layout in (self._layouts[MainState.ONROAD], self._home_body_layout):
layout.set_click_callback(self._on_onroad_clicked)
device.add_interactive_timeout_callback(self._set_mode_for_state)
ui_state.add_on_body_changed_callbacks(self._on_body_changed)
def _update_layout_rects(self):
self._sidebar_rect = rl.Rectangle(self._rect.x, self._rect.y, SIDEBAR_WIDTH, self._rect.height)
x_offset = SIDEBAR_WIDTH if self._sidebar.is_visible else 0
self._content_rect = rl.Rectangle(self._rect.y + x_offset, self._rect.y, self._rect.width - x_offset, self._rect.height)
self._content_rect = rl.Rectangle(self._rect.x + x_offset, self._rect.y, self._rect.width - x_offset, self._rect.height)
def _handle_onroad_transition(self):
if ui_state.started != self._prev_onroad:
@@ -73,6 +80,12 @@ class MainLayout(Widget):
self._set_mode_for_state()
def _set_mode_for_state(self):
# Don't go onroad if body, home is onroad
if ui_state.is_body:
self._set_current_layout(MainState.HOME)
self._sidebar.set_visible(not ui_state.ignition)
return
if ui_state.started:
# Don't hide sidebar from interactive timeout
if self._current_mode != MainState.ONROAD:
@@ -104,6 +117,10 @@ class MainLayout(Widget):
def _on_onroad_clicked(self):
self._sidebar.set_visible(not self._sidebar.is_visible)
def _on_body_changed(self):
self._layouts[MainState.HOME] = self._home_body_layout if ui_state.is_body else self._home_layout
self._set_mode_for_state()
def _render_main_content(self):
# Render sidebar
if self._sidebar.is_visible:

View File

@@ -135,12 +135,6 @@ class DeveloperLayout(Widget):
long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad()
self._long_maneuver_toggle.action_item.set_enabled(long_man_enabled)
if not long_man_enabled:
self._long_maneuver_toggle.action_item.set_state(False)
self._params.put_bool("LongitudinalManeuverMode", False)
lat_man_enabled = ui_state.is_offroad()
self._lat_maneuver_toggle.action_item.set_enabled(lat_man_enabled)
else:
self._long_maneuver_toggle.action_item.set_enabled(False)
self._lat_maneuver_toggle.action_item.set_enabled(False)

View File

@@ -1,7 +1,7 @@
import pyray as rl
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
from openpilot.system.ui.lib.multilang import tr, trn, tr_noop
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.wrap_text import wrap_text
@@ -65,12 +65,13 @@ class FirehoseLayout(FirehoseLayoutBase):
y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 60, status_color)
y += 20 + 20
# TODO: add back once reliable
# Contribution count (if available)
if self._segment_count > 0:
contrib_text = trn("{} segment of your driving is in the training dataset so far.",
"{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count)
y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE)
y += 20 + 20
#if self._segment_count > 0:
# contrib_text = trn("{} segment of your driving is in the training dataset so far.",
# "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count)
# y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE)
# y += 20 + 20
# Separator
rl.draw_rectangle(x, y, w, 2, self.GRAY)

View File

@@ -125,10 +125,8 @@ class Sidebar(Widget, SidebarSP):
def _update_temperature_status(self, device_state):
thermal_status = device_state.thermalStatus
if thermal_status == ThermalStatus.green:
if thermal_status == ThermalStatus.ok:
self._temp_status.update(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD)
elif thermal_status == ThermalStatus.yellow:
self._temp_status.update(tr_noop("TEMP"), tr_noop("OK"), Colors.WARNING)
else:
self._temp_status.update(tr_noop("TEMP"), tr_noop("HIGH"), Colors.DANGER)

View File

@@ -130,6 +130,7 @@ class MiciHomeLayout(Widget):
self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48))
self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46))
self._body_icon = IconWidget("icons_mici/body.png", (54, 37))
self._alerts_pill = AlertsPill()
@@ -137,6 +138,7 @@ class MiciHomeLayout(Widget):
IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9),
NetworkIcon(),
self._experimental_icon,
self._body_icon,
self._mic_icon,
], spacing=18)
@@ -247,6 +249,7 @@ class MiciHomeLayout(Widget):
# ***** Center-aligned bottom section icons *****
self._experimental_icon.set_visible(self._experimental_mode)
self._mic_icon.set_visible(ui_state.recording_audio)
self._body_icon.set_visible(ui_state.is_body)
footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48)
self._status_bar_layout.render(footer_rect)

View File

@@ -6,6 +6,7 @@ from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts
from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow
from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.scroller import Scroller
from openpilot.system.ui.lib.application import gui_app
@@ -31,22 +32,25 @@ class MiciMainLayout(Scroller):
self._home_layout = MiciHomeLayout()
self._alerts_layout = MiciOffroadAlerts()
self._settings_layout = SettingsLayout()
self._onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked)
self._car_onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked)
self._body_onroad_layout = BodyLayout()
# Initialize widget rects
for widget in (self._home_layout, self._settings_layout, self._alerts_layout, self._onroad_layout):
for widget in (self._home_layout, self._alerts_layout, self._settings_layout,
self._car_onroad_layout, self._body_onroad_layout):
# TODO: set parent rect and use it if never passed rect from render (like in Scroller)
widget.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
self._scroller.add_widgets([
self._alerts_layout,
self._home_layout,
self._onroad_layout,
self._car_onroad_layout,
self._body_onroad_layout,
])
self._scroller.set_reset_scroll_at_show(False)
# Disable scrolling when onroad is interacting with bookmark
self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left())
self._scroller.set_scrolling_enabled(lambda: not self._car_onroad_layout.is_swiping_left())
# Set callbacks
self._setup_callbacks()
@@ -59,14 +63,22 @@ class MiciMainLayout(Scroller):
if not self._onboarding_window.completed:
gui_app.push_widget(self._onboarding_window)
@property
def _onroad_layout(self) -> Widget:
# For scroll_to
return self._body_onroad_layout if ui_state.is_body else self._car_onroad_layout
def _setup_callbacks(self):
self._home_layout.set_callbacks(
on_settings=lambda: gui_app.push_widget(self._settings_layout),
on_alerts=lambda: self._scroll_to(self._alerts_layout),
alert_count_callback=self._alerts_layout.active_alerts,
)
self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout))
for layout in (self._car_onroad_layout, self._body_onroad_layout):
layout.set_click_callback(lambda: self._scroll_to(self._home_layout))
device.add_interactive_timeout_callback(self._on_interactive_timeout)
ui_state.add_on_body_changed_callbacks(self._on_body_changed)
def _scroll_to(self, layout: Widget):
layout_x = int(layout.rect.x)
@@ -132,3 +144,7 @@ class MiciMainLayout(Scroller):
user_bookmark = messaging.new_message('bookmarkButton')
user_bookmark.valid = True
self._pm.send('bookmarkButton', user_bookmark)
def _on_body_changed(self):
self._car_onroad_layout.set_visible(not ui_state.is_body)
self._body_onroad_layout.set_visible(ui_state.is_body)

View File

@@ -140,7 +140,7 @@ class TrainingGuideDMTutorial(NavWidget):
# stay at 100% once reached
in_bad_face = gui_app.get_active_widget() == self._bad_face_page
if ((dm_state.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face:
if ((dm_state.visionPolicyState.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face:
slow = self._progress.x < 0.25
duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION
self._progress.x += 1.0 / (duration * gui_app.target_fps)

View File

@@ -131,12 +131,6 @@ class DeveloperLayoutMici(NavScroller):
long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad()
self._long_maneuver_toggle.set_enabled(long_man_enabled)
if not long_man_enabled:
self._long_maneuver_toggle.set_checked(False)
ui_state.params.put_bool("LongitudinalManeuverMode", False)
lat_man_enabled = ui_state.is_offroad()
self._lat_maneuver_toggle.set_enabled(lat_man_enabled)
else:
self._long_maneuver_toggle.set_enabled(False)
self._lat_maneuver_toggle.set_enabled(False)

View File

@@ -1,20 +1,15 @@
import pyray as rl
from cereal import log, messaging
from cereal import car, log, messaging
from msgq.visionipc import VisionStreamType
from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView
from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer
from openpilot.selfdrive.ui.ui_state import ui_state, device
from openpilot.selfdrive.selfdrived.events import EVENTS, ET
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.nav_widget import NavWidget
from openpilot.system.ui.widgets.label import gui_label
EventName = log.OnroadEvent.EventName
EVENT_TO_INT = EventName.schema.enumerants
class DriverCameraView(CameraView):
def _calc_frame_matrix(self, rect: rl.Rectangle):
@@ -107,11 +102,14 @@ class BaseDriverCameraDialog(Widget):
if self._pm is None:
return
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
ALERT_SOUNDS = {
'two': AudibleAlert.promptDistracted,
'three': AudibleAlert.warningImmediate,
}
msg = messaging.new_message('selfdriveState')
if dm_state is not None and len(dm_state.events):
event_name = EVENT_TO_INT[dm_state.events[0].name]
if event_name is not None and event_name in EVENTS and ET.PERMANENT in EVENTS[event_name]:
msg.selfdriveState.alertSound = EVENTS[event_name][ET.PERMANENT].audible_alert
if dm_state is not None:
msg.selfdriveState.alertSound = ALERT_SOUNDS.get(str(dm_state.alertLevel), AudibleAlert.none)
self._pm.send('selfdriveState', msg)
def _render_dm_alerts(self, rect: rl.Rectangle):
@@ -119,29 +117,31 @@ class BaseDriverCameraDialog(Widget):
dm_state = ui_state.sm["driverMonitoringState"]
self._publish_alert_sound(dm_state)
is_vision = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision
awareness_pct = dm_state.visionPolicyState.awarenessPercent if is_vision else dm_state.wheeltouchPolicyState.awarenessPercent
gui_label(rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height),
f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
color=rl.Color(0, 0, 0, 180))
gui_label(rect, f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
gui_label(rect, f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
color=rl.Color(255, 255, 255, int(255 * 0.9)))
if not dm_state.events:
if dm_state.alertLevel == log.DriverMonitoringState.AlertLevel.none:
return
# Show first event (only one should be active at a time)
event_name_str = str(dm_state.events[0].name).split('.')[-1]
# Show alert level
alert_level_str = f"{'Pay Attention' if is_vision else 'Touch Wheel'} - level {dm_state.alertLevel}"
alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if self.driver_state_renderer.is_rhd else rl.GuiTextAlignment.TEXT_ALIGN_LEFT
shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height)
gui_label(shadow_rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD,
gui_label(shadow_rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD,
alignment=alignment,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM,
color=rl.Color(0, 0, 0, 180))
gui_label(rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD,
gui_label(rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD,
alignment=alignment,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM,
color=rl.Color(255, 255, 255, int(255 * 0.9)))
@@ -156,7 +156,7 @@ class BaseDriverCameraDialog(Widget):
def _draw_face_detection(self, rect: rl.Rectangle):
dm_state = ui_state.sm["driverMonitoringState"]
driver_data = self.driver_state_renderer.get_driver_data()
if not dm_state.faceDetected:
if not dm_state.visionPolicyState.faceDetected:
return
# Get face position and orientation

View File

@@ -6,7 +6,7 @@ from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.monitoring.helpers import face_orientation_from_net
AlertSize = log.SelfdriveState.AlertSize
@@ -35,6 +35,8 @@ class DriverStateRenderer(Widget):
self._is_active = False
self._is_rhd = False
self._face_detected = False
self._face_pitch = 0.
self._face_yaw = 0.
self._should_draw = False
self._force_active = False
self._looking_center = False
@@ -153,9 +155,11 @@ class DriverStateRenderer(Widget):
sm = ui_state.sm
dm_state = sm["driverMonitoringState"]
self._is_active = dm_state.isActiveMode
self._is_active = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision
self._is_rhd = dm_state.isRHD
self._face_detected = dm_state.faceDetected
self._face_detected = dm_state.visionPolicyState.faceDetected
self._face_pitch = dm_state.visionPolicyState.pose.pitch + math.radians(6) # calib or DM pose is not accurate, add a fake upward pitch to bias forward
self._face_yaw = -dm_state.visionPolicyState.pose.yaw # undo sign flip in face_orientation_from_model to match UI convention
driverstate = sm["driverStateV2"]
driver_data = driverstate.rightDriverData if self._is_rhd else driverstate.leftDriverData
@@ -163,26 +167,9 @@ class DriverStateRenderer(Widget):
def _update_state(self):
# Get monitoring state
driver_data = self.get_driver_data()
driver_orient = driver_data.faceOrientation
driver_position = driver_data.facePosition
if len(driver_orient) != 3:
return
# Calibrate orientation so looking straight ahead at road (instead of at device) is (0, 0, 0)
sm = ui_state.sm
if sm.valid['liveCalibration'] and len(sm['liveCalibration'].rpyCalib) == 3:
cal_rpy = sm['liveCalibration'].rpyCalib
else:
cal_rpy = [0.0, 0.0, 0.0]
_, pitch, yaw = face_orientation_from_net(driver_orient, driver_position, cal_rpy)
pitch += math.radians(6) # calib or DM pose is not accurate, add a fake upward pitch to bias forward
yaw = -yaw # undo sign flip in face_orientation_from_net to match UI convention
pitch = self._pitch_filter.update(pitch)
yaw = self._yaw_filter.update(yaw)
_ = self.get_driver_data()
pitch = self._pitch_filter.update(self._face_pitch)
yaw = self._yaw_filter.update(self._face_yaw)
# hysteresis on looking center
if abs(pitch) < LOOKING_CENTER_THRESHOLD_LOWER and abs(yaw) < LOOKING_CENTER_THRESHOLD_LOWER:

View File

@@ -114,7 +114,7 @@ class DriverStateRenderer(Widget):
# Get monitoring state
dm_state = sm["driverMonitoringState"]
self.is_active = dm_state.isActiveMode
self.is_active = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision
self.is_rhd = dm_state.isRHD
# Update fade state (smoother transition between active/inactive)

View File

@@ -34,7 +34,7 @@ class UIStateSP:
]
self.sunnylink_state = SunnylinkState()
self.update_params()
self.update_params_()
self.onroad_brightness_timer: int = 0
self.custom_interactive_timeout: int = self.params.get("InteractivityTimeout", return_default=True)
@@ -123,7 +123,7 @@ class UIStateSP:
return "disengaged"
def update_params(self) -> None:
def update_params_(self) -> None:
CP_SP_bytes = self.params.get("CarParamsSPPersistent")
if CP_SP_bytes is not None:
self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP)

View File

@@ -15,6 +15,7 @@ from openpilot.system.hardware import HARDWARE, PC
from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP, DeviceSP
BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50
PARAM_UPDATE_TIME = 5.0
class UIStatus(Enum):
@@ -59,6 +60,7 @@ class UIState(UIStateSP):
"carOutput",
"carControl",
"liveParameters",
"testJoystick",
"rawAudioData",
] + self.sm_services_ext
)
@@ -82,15 +84,15 @@ class UIState(UIStateSP):
self.panda_type: log.PandaState.PandaType = log.PandaState.PandaType.unknown
self.personality: log.LongitudinalPersonality = log.LongitudinalPersonality.standard
self.has_longitudinal_control: bool = False
self.is_body: bool | None = None
self.CP: car.CarParams | None = None
self.light_sensor: float = -1.0
self._param_update_time: float = 0.0
self._param_update_time: float = -PARAM_UPDATE_TIME
# Callbacks
self._offroad_transition_callbacks: list[Callable[[], None]] = []
self._engaged_transition_callbacks: list[Callable[[], None]] = []
self.update_params()
self._on_body_changed_callbacks: list[Callable[[], None]] = []
def add_offroad_transition_callback(self, callback: Callable[[], None]):
self._offroad_transition_callbacks.append(callback)
@@ -98,6 +100,9 @@ class UIState(UIStateSP):
def add_engaged_transition_callback(self, callback: Callable[[], None]):
self._engaged_transition_callbacks.append(callback)
def add_on_body_changed_callbacks(self, callback: Callable[[], None]):
self._on_body_changed_callbacks.append(callback)
@property
def engaged(self) -> bool:
return self.started and (self.sm["selfdriveState"].enabled or self.sm["selfdriveStateSP"].mads.enabled)
@@ -113,7 +118,7 @@ class UIState(UIStateSP):
self.sm.update(0)
self._update_state()
self._update_status()
if time.monotonic() - self._param_update_time > 5.0:
if time.monotonic() - self._param_update_time >= PARAM_UPDATE_TIME:
self.update_params()
device.update()
UIStateSP.update(self)
@@ -188,7 +193,13 @@ class UIState(UIStateSP):
self.has_longitudinal_control = self.params.get_bool("AlphaLongitudinalEnabled")
else:
self.has_longitudinal_control = self.CP.openpilotLongitudinalControl
UIStateSP.update_params(self)
if self.is_body != self.CP.notCar:
self.is_body = self.CP.notCar
for callback in self._on_body_changed_callbacks:
callback()
UIStateSP.update_params_(self)
self._param_update_time = time.monotonic()

View File

@@ -1 +1 @@
5d4d21f1899de21137f69d74a4602c44cc5a6b04cf4e4aa9d0ec9206f8c30350
32f57bdc91f910df1f48ddae7c59aaf6e751f9df6756da481a210577dbce8bcf

View File

@@ -255,7 +255,7 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData
}
// Gyro Uncalibrated
if (log.getSensor() == SENSOR_GYRO_UNCALIBRATED && log.getType() == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) {
if (log.which() == cereal::SensorEventData::GYRO_UNCALIBRATED) {
auto v = log.getGyroUncalibrated().getV();
auto meas = Vector3d(-v[2], -v[1], -v[0]);
@@ -273,7 +273,7 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData
}
// Accelerometer
if (log.getSensor() == SENSOR_ACCELEROMETER && log.getType() == SENSOR_TYPE_ACCELEROMETER) {
if (log.which() == cereal::SensorEventData::ACCELERATION) {
auto v = log.getAcceleration().getV();
// TODO: reduce false positives and re-enable this check

View File

@@ -19,7 +19,7 @@ if platform.system() == 'Darwin':
class TestLocationdProc:
LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'liveCalibration',
'accelerometer', 'gyroscope', 'magnetometer']
'accelerometer', 'gyroscope']
def setup_method(self):
self.pm = messaging.PubMaster(self.LLD_MSGS)

View File

@@ -71,9 +71,10 @@ bool BMX055_Accel::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initAccelerometer2();
event.setSource(cereal::SensorEventData::SensorSource::BMX055);
event.setVersion(1);
event.setSensor(SENSOR_ACCELEROMETER);
event.setType(SENSOR_TYPE_ACCELEROMETER);
auto deprecated = event.initDeprecated();
deprecated.setVersion(1);
deprecated.setSensor(SENSOR_ACCELEROMETER);
deprecated.setType(SENSOR_TYPE_ACCELEROMETER);
event.setTimestamp(start_time);
float xyz[] = {x, y, z};

View File

@@ -78,9 +78,10 @@ bool BMX055_Gyro::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initGyroscope2();
event.setSource(cereal::SensorEventData::SensorSource::BMX055);
event.setVersion(1);
event.setSensor(SENSOR_GYRO_UNCALIBRATED);
event.setType(SENSOR_TYPE_GYROSCOPE_UNCALIBRATED);
auto deprecated = event.initDeprecated();
deprecated.setVersion(1);
deprecated.setSensor(SENSOR_GYRO_UNCALIBRATED);
deprecated.setType(SENSOR_TYPE_GYROSCOPE_UNCALIBRATED);
event.setTimestamp(start_time);
float xyz[] = {x, y, z};

View File

@@ -229,9 +229,10 @@ bool BMX055_Magn::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initMagnetometer();
event.setSource(cereal::SensorEventData::SensorSource::BMX055);
event.setVersion(2);
event.setSensor(SENSOR_MAGNETOMETER_UNCALIBRATED);
event.setType(SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED);
auto deprecated = event.initDeprecated();
deprecated.setVersion(2);
deprecated.setSensor(SENSOR_MAGNETOMETER_UNCALIBRATED);
deprecated.setType(SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED);
event.setTimestamp(start_time);
// Move magnetometer into same reference frame as accel/gryo

View File

@@ -22,8 +22,9 @@ bool BMX055_Temp::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initTemperatureSensor();
event.setSource(cereal::SensorEventData::SensorSource::BMX055);
event.setVersion(1);
event.setType(SENSOR_TYPE_AMBIENT_TEMPERATURE);
auto deprecated = event.initDeprecated();
deprecated.setVersion(1);
deprecated.setType(SENSOR_TYPE_AMBIENT_TEMPERATURE);
event.setTimestamp(start_time);
event.setTemperature(temp);

View File

@@ -236,9 +236,10 @@ bool LSM6DS3_Accel::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initAccelerometer();
event.setSource(source);
event.setVersion(1);
event.setSensor(SENSOR_ACCELEROMETER);
event.setType(SENSOR_TYPE_ACCELEROMETER);
auto deprecated = event.initDeprecated();
deprecated.setVersion(1);
deprecated.setSensor(SENSOR_ACCELEROMETER);
deprecated.setType(SENSOR_TYPE_ACCELEROMETER);
event.setTimestamp(ts);
float xyz[] = {y, -x, z};

View File

@@ -219,9 +219,10 @@ bool LSM6DS3_Gyro::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initGyroscope();
event.setSource(source);
event.setVersion(2);
event.setSensor(SENSOR_GYRO_UNCALIBRATED);
event.setType(SENSOR_TYPE_GYROSCOPE_UNCALIBRATED);
auto deprecated = event.initDeprecated();
deprecated.setVersion(2);
deprecated.setSensor(SENSOR_GYRO_UNCALIBRATED);
deprecated.setType(SENSOR_TYPE_GYROSCOPE_UNCALIBRATED);
event.setTimestamp(ts);
float xyz[] = {y, -x, z};

View File

@@ -28,8 +28,9 @@ bool LSM6DS3_Temp::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initTemperatureSensor();
event.setSource(source);
event.setVersion(1);
event.setType(SENSOR_TYPE_AMBIENT_TEMPERATURE);
auto deprecated = event.initDeprecated();
deprecated.setVersion(1);
deprecated.setType(SENSOR_TYPE_AMBIENT_TEMPERATURE);
event.setTimestamp(start_time);
event.setTemperature(temp);

View File

@@ -91,9 +91,10 @@ bool MMC5603NJ_Magn::get_event(MessageBuilder &msg, uint64_t ts) {
auto event = msg.initEvent().initMagnetometer();
event.setSource(cereal::SensorEventData::SensorSource::MMC5603NJ);
event.setVersion(1);
event.setSensor(SENSOR_MAGNETOMETER_UNCALIBRATED);
event.setType(SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED);
auto deprecated = event.initDeprecated();
deprecated.setVersion(1);
deprecated.setSensor(SENSOR_MAGNETOMETER_UNCALIBRATED);
deprecated.setType(SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED);
event.setTimestamp(start_time);
float vals[] = {xyz[0], xyz[1], xyz[2], reset_xyz[0], reset_xyz[1], reset_xyz[2]};

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,9 @@
#include "cdm.h"
#include "stddef.h"
int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel) {
int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel, uint8_t opcode) {
struct cdm_dmi_cmd *cmd = (struct cdm_dmi_cmd*)dst;
cmd->cmd = CAM_CDM_CMD_DMI_32;
cmd->cmd = opcode;
cmd->length = length - 1;
cmd->reserved = 0;
cmd->addr = 0; // gets patched in

View File

@@ -6,11 +6,6 @@
#include <vector>
#include <memory>
// our helpers
int write_random(uint8_t *dst, const std::vector<uint32_t> &vals);
int write_cont(uint8_t *dst, uint32_t reg, const std::vector<uint32_t> &vals);
int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel);
// from drivers/media/platform/msm/camera/cam_cdm/cam_cdm_util.{c,h}
enum cam_cdm_command {
@@ -32,6 +27,11 @@ enum cam_cdm_command {
CAM_CDM_CMD_PRIVATE_BASE_MAX = 0x7F
};
// our helpers
int write_random(uint8_t *dst, const std::vector<uint32_t> &vals);
int write_cont(uint8_t *dst, uint32_t reg, const std::vector<uint32_t> &vals);
int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel, uint8_t opcode = CAM_CDM_CMD_DMI_32);
/**
* struct cdm_regrandom_cmd - Definition for CDM random register command.
* @count: Number of register writes

View File

@@ -466,8 +466,11 @@ void SpectraCamera::config_bps(int idx, int request_id) {
* BPS = Bayer Processing Segment
*/
int size = sizeof(struct cam_packet) + sizeof(struct cam_cmd_buf_desc)*2 + sizeof(struct cam_buf_io_cfg)*2;
size += sizeof(struct cam_patch_desc)*9;
bool needs_downscale = sensor->out_scale > 1;
int num_io_cfgs = needs_downscale ? 3 : 2;
int num_patches = needs_downscale ? 14 : 12;
int size = sizeof(struct cam_packet) + sizeof(struct cam_cmd_buf_desc)*2 + sizeof(struct cam_buf_io_cfg)*num_io_cfgs;
size += sizeof(struct cam_patch_desc)*num_patches;
uint32_t cam_packet_handle = 0;
auto pkt = m->mem_mgr.alloc<struct cam_packet>(size, &cam_packet_handle);
@@ -545,8 +548,15 @@ void SpectraCamera::config_bps(int idx, int request_id) {
int cdm_len = 0;
if (bps_lin_reg.size() == 0) {
// set first knee pt to do BLC
uint32_t new_knee[8];
new_knee[0] = sensor->black_level << (14 - sensor->bits_per_pixel);
for (int i = 0; i < 7; i++) {
uint32_t pts = sensor->linearization_pts[i / 2];
new_knee[i + 1] = (i % 2 == 0) ? (pts >> 16) : (pts & 0xffff);
}
for (int i = 0; i < 4; i++) {
bps_lin_reg.push_back(((sensor->linearization_pts[i] & 0xffff) << 0x10) | (sensor->linearization_pts[i] >> 0x10));
bps_lin_reg.push_back((new_knee[2*i + 1] << 16) | new_knee[2*i]);
}
}
@@ -569,20 +579,24 @@ void SpectraCamera::config_bps(int idx, int request_id) {
0x00000080,
0x00800066,
});
// linearization, EN=0
// linearization
cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1868, bps_lin_reg);
cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1878, bps_lin_reg);
cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1888, bps_lin_reg);
cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1898, bps_lin_reg);
/*
uint8_t *start = (unsigned char *)bps_cdm_program_array.ptr + cdm_len;
uint64_t addr;
cdm_len += write_dmi((unsigned char *)bps_cdm_program_array.ptr + cdm_len, &addr, sensor->linearization_lut.size()*sizeof(uint32_t), 0x1808, 1);
patches.push_back(addr - (uint64_t)start);
*/
cdm_len += write_dmi((unsigned char *)bps_cdm_program_array.ptr + cdm_len, &addr, sensor->linearization_lut.size()*sizeof(uint32_t), 0x1808, 1, CAM_CDM_CMD_DMI);
patches.push_back(addr - (uint64_t)bps_cdm_program_array.ptr);
// color correction
cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x2e68, bps_ccm_reg);
// gamma
for (uint8_t ch = 1; ch <= 3; ch++) {
cdm_len += write_dmi((unsigned char *)bps_cdm_program_array.ptr + cdm_len, &addr, sensor->gamma_lut_rgb.size()*sizeof(uint32_t), 0x3208, ch, CAM_CDM_CMD_DMI);
patches.push_back(addr - (uint64_t)bps_cdm_program_array.ptr);
}
cdm_len += build_common_ife_bps((unsigned char *)bps_cdm_program_array.ptr + cdm_len, cc, sensor.get(), patches, false);
pa->length = cdm_len - 1;
@@ -596,7 +610,7 @@ void SpectraCamera::config_bps(int idx, int request_id) {
tmp.header = CAM_ICP_CMD_GENERIC_BLOB_CLK;
tmp.header |= (sizeof(cam_icp_clk_bw_request)) << 8;
tmp.clk.budget_ns = 0x1fca058;
tmp.clk.frame_cycles = 2329024; // comes from the striping lib
tmp.clk.frame_cycles = sensor->frame_width * sensor->frame_height; // matches striping lib pixelCount
tmp.clk.rt_flag = 0x0;
tmp.clk.uncompressed_bw = 0x38512180;
tmp.clk.compressed_bw = 0x38512180;
@@ -611,7 +625,7 @@ void SpectraCamera::config_bps(int idx, int request_id) {
}
// *** io config ***
pkt->num_io_configs = 2;
pkt->num_io_configs = num_io_cfgs;
pkt->io_configs_offset = sizeof(struct cam_cmd_buf_desc)*pkt->num_cmd_buf;
struct cam_buf_io_cfg *io_cfg = (struct cam_buf_io_cfg *)((char*)&pkt->payload + pkt->io_configs_offset);
{
@@ -655,28 +669,69 @@ void SpectraCamera::config_bps(int idx, int request_id) {
io_cfg[1].format = CAM_FORMAT_NV12; // TODO: why is this 21 in the dump? should be 12
io_cfg[1].color_space = CAM_COLOR_SPACE_BT601_FULL;
io_cfg[1].resource_type = CAM_ICP_BPS_OUTPUT_IMAGE_FULL;
io_cfg[1].resource_type = needs_downscale ? CAM_ICP_BPS_OUTPUT_IMAGE_REG1 : CAM_ICP_BPS_OUTPUT_IMAGE_FULL;
io_cfg[1].fence = sync_objs_bps[idx];
io_cfg[1].direction = CAM_BUF_OUTPUT;
io_cfg[1].subsample_pattern = 0x1;
io_cfg[1].framedrop_pattern = 0x1;
if (needs_downscale) {
// downscaling needs a full res placeholder
uint32_t full_stride, full_y_h, full_uv_h, full_yuv_size;
std::tie(full_stride, full_y_h, full_uv_h, full_yuv_size) = get_nv12_info(sensor->frame_width, sensor->frame_height);
io_cfg[2].mem_handle[0] = bps_fullres_dummy.handle;
io_cfg[2].mem_handle[1] = bps_fullres_dummy.handle;
io_cfg[2].planes[0] = (struct cam_plane_cfg){
.width = sensor->frame_width,
.height = sensor->frame_height,
.plane_stride = full_stride,
.slice_height = full_y_h,
};
io_cfg[2].planes[1] = (struct cam_plane_cfg){
.width = sensor->frame_width,
.height = sensor->frame_height / 2,
.plane_stride = full_stride,
.slice_height = full_uv_h,
};
io_cfg[2].offsets[1] = ALIGNED_SIZE(full_stride * full_y_h, 0x1000);
io_cfg[2].format = CAM_FORMAT_NV12;
io_cfg[2].color_space = CAM_COLOR_SPACE_BT601_FULL;
io_cfg[2].resource_type = CAM_ICP_BPS_OUTPUT_IMAGE_FULL;
io_cfg[2].fence = sync_objs_bps[idx];
io_cfg[2].direction = CAM_BUF_OUTPUT;
io_cfg[2].subsample_pattern = 0x1;
io_cfg[2].framedrop_pattern = 0x1;
}
}
// *** patches ***
{
assert(patches.size() == 0 | patches.size() == 1);
assert(patches.size() == 0 || patches.size() == 4);
pkt->patch_offset = sizeof(struct cam_cmd_buf_desc)*pkt->num_cmd_buf + sizeof(struct cam_buf_io_cfg)*pkt->num_io_configs;
if (patches.size() > 0) {
add_patch(pkt.get(), bps_cmd.handle, patches[0], bps_linearization_lut.handle, 0);
// linearization LUT
add_patch(pkt.get(), bps_cdm_program_array.handle, patches[0], bps_linearization_lut.handle, 0);
// gamma LUTs
for (int i = 0; i < 3; i++) {
add_patch(pkt.get(), bps_cdm_program_array.handle, patches[i+1], bps_gamma_lut.handle, 0);
}
}
// input frame
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[0].ptr[0]), buf_handle_raw[idx], 0);
// output frame
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[1].ptr[0]), buf_handle_yuv[idx], 0);
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[1].ptr[1]), buf_handle_yuv[idx], io_cfg[1].offsets[1]);
if (needs_downscale) {
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[1].ptr[0]), bps_fullres_dummy.handle, 0);
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[1].ptr[1]), bps_fullres_dummy.handle, io_cfg[2].offsets[1]);
// output frame at REG1
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[7].ptr[0]), buf_handle_yuv[idx], 0);
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[7].ptr[1]), buf_handle_yuv[idx], io_cfg[1].offsets[1]);
} else {
// output frame at FULL
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[1].ptr[0]), buf_handle_yuv[idx], 0);
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, frames[1].ptr[1]), buf_handle_yuv[idx], io_cfg[1].offsets[1]);
}
// rest of buffers
add_patch(pkt.get(), bps_cmd.handle, buf_desc[0].offset + offsetof(bps_tmp, settings_addr), bps_iq.handle, 0);
@@ -987,9 +1042,6 @@ bool SpectraCamera::openSensor() {
LOGD("-- Probing sensor %d", cc.camera_num);
auto init_sensor_lambda = [this](SensorInfo *s) {
if (s->image_sensor == cereal::FrameData::ImageSensor::OS04C10 && cc.output_type == ISP_IFE_PROCESSED) {
((OS04C10*)s)->ife_downscale_configure();
}
sensor.reset(s);
return (sensors_init() == 0);
};
@@ -1167,13 +1219,39 @@ void SpectraCamera::configICP() {
// used internally by the BPS, we just allocate it.
// size comes from the BPSStripingLib
bps_cdm_striping_bl.init(m, 0xa100, 0x20, true, m->icp_device_iommu);
bps_cdm_striping_bl.init(m, 0xcfe0, 0x20, true, m->icp_device_iommu);
if (sensor->out_scale > 1) {
uint32_t full_stride, full_y_h, full_uv_h, full_yuv_size;
std::tie(full_stride, full_y_h, full_uv_h, full_yuv_size) = get_nv12_info(sensor->frame_width, sensor->frame_height);
bps_fullres_dummy.init(m, full_yuv_size, 0x1000, true, m->icp_device_iommu);
}
// LUTs
/*
assert(sensor->linearization_lut.size() == 36);
bps_linearization_lut.init(m, sensor->linearization_lut.size()*sizeof(uint32_t), 0x20, true, m->icp_device_iommu);
memcpy(bps_linearization_lut.ptr, sensor->linearization_lut.data(), bps_linearization_lut.size);
*/
// bit shift linearization_lut to bps specs, also compensate for black level here
uint32_t bl = sensor->black_level << (14 - sensor->bits_per_pixel);
uint32_t* bps_lut = (uint32_t*)bps_linearization_lut.ptr;
for (size_t i = 0; i < sensor->linearization_lut.size(); i++) {
size_t seg = i / 4;
size_t ch = i % 4;
if (seg == 0) {
bps_lut[i] = 0;
continue;
}
uint32_t e = sensor->linearization_lut[(seg - 1) * 4 + ch];
uint32_t base = e & 0x3fff;
uint32_t slope_q11 = (e >> 14) & 0x3fff;
uint32_t slope_q12 = std::min<uint32_t>(slope_q11 << 1, 0x3fff);
base = (base > bl) ? (base - bl) : 0;
bps_lut[i] = base | (slope_q12 << 14);
}
assert(sensor->gamma_lut_rgb.size() == 64);
bps_gamma_lut.init(m, sensor->gamma_lut_rgb.size()*sizeof(uint32_t), 0x20, true, m->icp_device_iommu);
memcpy(bps_gamma_lut.ptr, sensor->gamma_lut_rgb.data(), bps_gamma_lut.size);
}
void SpectraCamera::configCSIPHY() {

View File

@@ -173,6 +173,8 @@ public:
SpectraBuf bps_iq;
SpectraBuf bps_striping;
SpectraBuf bps_linearization_lut;
SpectraBuf bps_gamma_lut;
SpectraBuf bps_fullres_dummy;
std::vector<uint32_t> bps_lin_reg;
std::vector<uint32_t> bps_ccm_reg;

View File

@@ -21,27 +21,16 @@ const uint32_t os04c10_analog_gains_reg[] = {
} // namespace
void OS04C10::ife_downscale_configure() {
out_scale = 2;
pixel_size_mm = 0.002;
frame_width = 2688;
frame_height = 1520;
exposure_time_max = 2352;
init_reg_array.insert(init_reg_array.end(), std::begin(ife_downscale_override_array_os04c10), std::end(ife_downscale_override_array_os04c10));
}
OS04C10::OS04C10() {
image_sensor = cereal::FrameData::ImageSensor::OS04C10;
bayer_pattern = CAM_ISP_PATTERN_BAYER_BGBGBG;
pixel_size_mm = 0.004;
pixel_size_mm = 0.002;
data_word = false;
// hdr_offset = 64 * 2 + 8; // stagger
frame_width = 1344;
frame_height = 760; //760 * 2 + hdr_offset;
frame_stride = (frame_width * 12 / 8); // no alignment
out_scale = 2;
frame_width = 2688;
frame_height = 1520;
frame_stride = frame_width * 12 / 8;
extra_height = 0;
frame_offset = 0;
@@ -65,7 +54,7 @@ OS04C10::OS04C10() {
dc_gain_on_grey = 0.9;
dc_gain_off_grey = 1.0;
exposure_time_min = 2;
exposure_time_max = 1684;
exposure_time_max = 2352;
analog_gain_min_idx = 0x0;
analog_gain_rec_idx = 0x0; // 1x
analog_gain_max_idx = 0x28;

View File

@@ -4,7 +4,7 @@ const struct i2c_random_wr_payload start_reg_array_os04c10[] = {{0x100, 1}};
const struct i2c_random_wr_payload stop_reg_array_os04c10[] = {{0x100, 0}};
const struct i2c_random_wr_payload init_array_os04c10[] = {
// baseed on DP_2688X1520_NEWSTG_MIPI0776Mbps_30FPS_10BIT_FOURLANE
// OS04C10_AA_00_02_17_wAO_2688x1524_MIPI728Mbps_Linear12bit_20FPS_4Lane_MCLK24MHz
{0x0103, 0x01}, // software reset
// PLL + clocks
@@ -93,7 +93,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x388b, 0x00},
{0x3c80, 0x10},
{0x3c86, 0x00},
{0x3c8c, 0x20},
{0x3c8c, 0x40},
{0x3c9f, 0x01},
{0x3d85, 0x1b},
{0x3d8c, 0x71},
@@ -197,7 +197,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x370b, 0x48},
{0x370c, 0x01},
{0x370f, 0x00},
{0x3714, 0x28},
{0x3714, 0x24},
{0x3716, 0x04},
{0x3719, 0x11},
{0x371a, 0x1e},
@@ -229,7 +229,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x37bd, 0x01},
{0x37bf, 0x26},
{0x37c0, 0x11},
{0x37c2, 0x14},
{0x37c2, 0x04},
{0x37cd, 0x19},
{0x37e0, 0x08},
{0x37e6, 0x04},
@@ -239,14 +239,14 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x37d8, 0x02},
{0x37e2, 0x10},
{0x3739, 0x10},
{0x3662, 0x08},
{0x3662, 0x10},
{0x37e4, 0x20},
{0x37e3, 0x08},
{0x37d9, 0x04},
{0x37d9, 0x08},
{0x4040, 0x00},
{0x4041, 0x03},
{0x4008, 0x01},
{0x4009, 0x06},
{0x4041, 0x07},
{0x4008, 0x02},
{0x4009, 0x0d},
// FSIN - frame sync
{0x3002, 0x22},
@@ -267,20 +267,20 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x3802, 0x00}, {0x3803, 0x00},
{0x3804, 0x0a}, {0x3805, 0x8f},
{0x3806, 0x05}, {0x3807, 0xff},
{0x3808, 0x05}, {0x3809, 0x40},
{0x380a, 0x02}, {0x380b, 0xf8},
{0x3808, 0x0a}, {0x3809, 0x80},
{0x380a, 0x05}, {0x380b, 0xf0},
{0x3811, 0x08},
{0x3813, 0x08},
{0x3814, 0x03},
{0x3814, 0x01},
{0x3815, 0x01},
{0x3816, 0x03},
{0x3816, 0x01},
{0x3817, 0x01},
{0x380c, 0x0b}, {0x380d, 0xac}, // HTS (line length)
{0x380e, 0x06}, {0x380f, 0x9c}, // VTS (frame length)
{0x380c, 0x08}, {0x380d, 0x5c}, // HTS (line length)
{0x380e, 0x09}, {0x380f, 0x38}, // VTS (frame length)
{0x3820, 0xb3},
{0x3821, 0x01},
{0x3820, 0xb0},
{0x3821, 0x00},
{0x3880, 0x00},
{0x3882, 0x20},
{0x3c91, 0x0b},
@@ -330,23 +330,3 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x5104, 0x08}, {0x5105, 0xd6},
{0x5144, 0x08}, {0x5145, 0xd6},
};
const struct i2c_random_wr_payload ife_downscale_override_array_os04c10[] = {
// based on OS04C10_AA_00_02_17_wAO_2688x1524_MIPI728Mbps_Linear12bit_20FPS_4Lane_MCLK24MHz
{0x3c8c, 0x40},
{0x3714, 0x24},
{0x37c2, 0x04},
{0x3662, 0x10},
{0x37d9, 0x08},
{0x4041, 0x07},
{0x4008, 0x02},
{0x4009, 0x0d},
{0x3808, 0x0a}, {0x3809, 0x80},
{0x380a, 0x05}, {0x380b, 0xf0},
{0x3814, 0x01},
{0x3816, 0x01},
{0x380c, 0x08}, {0x380d, 0x5c}, // HTS
{0x380e, 0x09}, {0x380f, 0x38}, // VTS
{0x3820, 0xb0},
{0x3821, 0x00},
};

View File

@@ -98,7 +98,6 @@ public:
class OS04C10 : public SensorInfo {
public:
OS04C10();
void ife_downscale_configure();
std::vector<i2c_random_wr_payload> getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override;
float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override;
int getSlaveAddress(int port) const override;

View File

@@ -94,6 +94,10 @@ class LPABase(ABC):
def process_notifications(self) -> None:
pass
@abstractmethod
def is_euicc(self) -> bool:
pass
def is_comma_profile(self, iccid: str) -> bool:
return any(iccid.startswith(prefix) for prefix in ('8985235',))

View File

@@ -28,8 +28,7 @@ if __name__ == '__main__':
lpa.nickname_profile(args.nickname[0], args.nickname[1])
else:
parser.print_help()
profiles = lpa.list_profiles()
print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:')
for p in profiles:
print(f'- {p.iccid} (nickname: {p.nickname or "<none provided>"}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}')
profiles = lpa.list_profiles()
print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:')
for p in profiles:
print(f'- {p.iccid} (nickname: {p.nickname or "<none provided>"}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}')

View File

@@ -40,15 +40,21 @@ HardwareState = namedtuple("HardwareState", ['network_type', 'network_info', 'ne
# List of thermal bands. We will stay within this region as long as we are within the bounds.
# When exiting the bounds, we'll jump to the lower or higher band. Bands are ordered in the dict.
THERMAL_BANDS = OrderedDict({
ThermalStatus.green: ThermalBand(None, 80.0),
ThermalStatus.yellow: ThermalBand(75.0, 96.0),
ThermalStatus.red: ThermalBand(88.0, 107.),
ThermalStatus.danger: ThermalBand(94.0, None),
})
if HARDWARE.get_device_type() == "mici":
THERMAL_BANDS = OrderedDict({
ThermalStatus.ok: ThermalBand(None, 100.0),
ThermalStatus.overheated: ThermalBand(92.0, 107.),
ThermalStatus.critical: ThermalBand(98.0, None),
})
else:
THERMAL_BANDS = OrderedDict({
ThermalStatus.ok: ThermalBand(None, 96.0),
ThermalStatus.overheated: ThermalBand(88.0, 107.),
ThermalStatus.critical: ThermalBand(94.0, None),
})
# Override to highest thermal band when offroad and above this temp
OFFROAD_DANGER_TEMP = 75
OFFROAD_DANGER_TEMP = 85 if HARDWARE.get_device_type() == "mici" else 75
prev_offroad_states: dict[str, tuple[bool, str | None]] = {}
@@ -180,7 +186,7 @@ def hardware_thread(end_event, hw_queue) -> None:
started_ts: float | None = None
started_seen = False
startup_blocked_ts: float | None = None
thermal_status = ThermalStatus.yellow
thermal_status = ThermalStatus.ok
last_hw_state = HardwareState(
network_type=NetworkType.none,
@@ -288,7 +294,7 @@ def hardware_thread(end_event, hw_queue) -> None:
if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP:
# if device is offroad and already hot without the extra onroad load,
# we want to cool down first before increasing load
thermal_status = ThermalStatus.danger
thermal_status = ThermalStatus.critical
else:
current_band = THERMAL_BANDS[thermal_status]
band_idx = list(THERMAL_BANDS.keys()).index(thermal_status)
@@ -312,7 +318,7 @@ def hardware_thread(end_event, hw_queue) -> None:
startup_conditions["not_taking_snapshot"] = not params.get_bool("IsTakingSnapshot")
# must be at an engageable thermal band to go onroad
startup_conditions["device_temp_engageable"] = thermal_status < ThermalStatus.red
startup_conditions["device_temp_engageable"] = thermal_status < ThermalStatus.overheated
# ensure device is fully booted
startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted()
@@ -333,7 +339,7 @@ def hardware_thread(end_event, hw_queue) -> None:
set_offroad_alert("Offroad_TiciSupport", is_unsupported_combo, extra_text=build_metadata.channel)
# if the temperature enters the danger zone, go offroad to cool down
onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger
onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.critical
extra_text = f"{offroad_comp_temp:.1f}C"
show_alert = (not onroad_conditions["device_temp_good"] or not startup_conditions["device_temp_engageable"]) and onroad_conditions["ignition"]
set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", show_alert, extra_text=extra_text)

View File

@@ -56,28 +56,28 @@
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz",
"hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51",
"hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51",
"url": "https://commadist.azureedge.net/agnosupdate/boot-3a6285bc114d21cfdab312c42f5a76f81762328b41d404816ee46a2f83523f04.img.xz",
"hash": "3a6285bc114d21cfdab312c42f5a76f81762328b41d404816ee46a2f83523f04",
"hash_raw": "3a6285bc114d21cfdab312c42f5a76f81762328b41d404816ee46a2f83523f04",
"size": 17500160,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699"
"ondevice_hash": "4e12accbcbb03fd9eebc5c1231a7b79dc1d9e15341cce56dc88930832ee86207"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz",
"hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49",
"hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5",
"url": "https://commadist.azureedge.net/agnosupdate/system-82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2.img.xz",
"hash": "bb4ccf77037120ace841cbcd94387c0650b317595cd0eab555b3ba0beb74e840",
"hash_raw": "82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2",
"size": 4718592000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c",
"ondevice_hash": "f1505151cde236f0d3ccdb764e3c604761ad2c1beb40f2c340ea9470d0cecc0e",
"alt": {
"hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5",
"url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img",
"hash": "82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2",
"url": "https://commadist.azureedge.net/agnosupdate/system-82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2.img",
"size": 4718592000
}
}

View File

@@ -339,51 +339,51 @@
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz",
"hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51",
"hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51",
"url": "https://commadist.azureedge.net/agnosupdate/boot-3a6285bc114d21cfdab312c42f5a76f81762328b41d404816ee46a2f83523f04.img.xz",
"hash": "3a6285bc114d21cfdab312c42f5a76f81762328b41d404816ee46a2f83523f04",
"hash_raw": "3a6285bc114d21cfdab312c42f5a76f81762328b41d404816ee46a2f83523f04",
"size": 17500160,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699"
"ondevice_hash": "4e12accbcbb03fd9eebc5c1231a7b79dc1d9e15341cce56dc88930832ee86207"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz",
"hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49",
"hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5",
"url": "https://commadist.azureedge.net/agnosupdate/system-82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2.img.xz",
"hash": "bb4ccf77037120ace841cbcd94387c0650b317595cd0eab555b3ba0beb74e840",
"hash_raw": "82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2",
"size": 4718592000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c",
"ondevice_hash": "f1505151cde236f0d3ccdb764e3c604761ad2c1beb40f2c340ea9470d0cecc0e",
"alt": {
"hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5",
"url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img",
"hash": "82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2",
"url": "https://commadist.azureedge.net/agnosupdate/system-82ffa59e322c5abab18f2ec6fb83dce124f55c9658c5c673e9adbd4655d6e6a2.img",
"size": 4718592000
}
},
{
"name": "userdata_90",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634.img.xz",
"hash": "7ea9d7d4685ec36bbfdf06afe0b51650d567416c3092fef96bd97158ed322742",
"hash_raw": "a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-6bf2fb546944f6fc0cdf865f92ade316430a75a7b1680d3fe891b34921c29b64.img.xz",
"hash": "f3badccff4330301cdae10e072a460a331421715f71253ea3bdcbf708f61d012",
"hash_raw": "6bf2fb546944f6fc0cdf865f92ade316430a75a7b1680d3fe891b34921c29b64",
"size": 96636764160,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "79ed653c1679d84b13ee23083a511b0e668454e4af9b0db99a3279072ed041c1"
"ondevice_hash": "82e907612afc989363656c821b9cf2107cddcbdc5030cc9b5608a38855dd1c60"
},
{
"name": "userdata_89",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba.img.xz",
"hash": "7104cdb0384e4ecb1ebfa6136a2330251bc8aa829b9ec48c4b740f656252d382",
"hash_raw": "8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-575b01a0e84bff31180a1589b9d62810e2198e4012250e01fd80c2d5cd9ba9e1.img.xz",
"hash": "ebeebbe3b8e111fe807b88b993b2fa50c0ea15e838c531a3885dd3037338b1a4",
"hash_raw": "575b01a0e84bff31180a1589b9d62810e2198e4012250e01fd80c2d5cd9ba9e1",
"size": 95563022336,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "fbede3b0831dbc4a4edd336e5f547f4978902b9421fb1484e86c416192c59165"
"ondevice_hash": "2a9d00ad72978d94640e7fb6c4b20b398e1a1bbc7a0c32f7d02fa988f755bd12"
}
]

View File

@@ -716,22 +716,29 @@ class TiciLPA(LPABase):
atexit.register(self._client.close)
@contextmanager
def _acquire_channel(self):
def _acquire_lock(self):
fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR)
try:
fcntl.flock(fd, fcntl.LOCK_EX)
self._client.open_isdr()
yield
finally:
if self._client.channel:
try:
self._client.query(f"AT+CCHC={self._client.channel}")
except (RuntimeError, TimeoutError):
pass
self._client.channel = None
fcntl.flock(fd, fcntl.LOCK_UN)
os.close(fd)
@contextmanager
def _acquire_channel(self):
with self._acquire_lock():
try:
self._client.open_isdr()
yield
finally:
if self._client.channel:
try:
self._client.query(f"AT+CCHC={self._client.channel}")
except (RuntimeError, TimeoutError):
pass
self._client.channel = None
def list_profiles(self) -> list[Profile]:
with self._acquire_channel():
return [
@@ -792,3 +799,21 @@ class TiciLPA(LPABase):
if HARDWARE.get_device_type() == "mici":
self._client.send_raw(b'AT+CFUN=0\rAT+CFUN=1\r') # mici has no SIM presence pin; raw because CFUN=0 drops serial
self._client._ensure_serial(reconnect=True)
def is_euicc(self) -> bool:
# +CCHO:<n> -> eUICC; bare ERROR -> applet absent, non-eUICC; +CME ERROR -> applet
# exists but bus busy or modem in transient state, still eUICC.
with self._acquire_lock():
try:
lines = self._client.query(f'AT+CCHO="{ISDR_AID}"')
except RuntimeError as e:
return "+CME ERROR" in str(e)
for line in lines:
if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()):
try:
self._client.query(f"AT+CCHC={ch}")
except (RuntimeError, TimeoutError):
pass
self._client.channel = None
return True
return False

View File

@@ -11,8 +11,9 @@ if arch != "larch64":
libs += ['yuv']
if arch == "Darwin":
frameworks += ['VideoToolbox', 'CoreMedia', 'CoreFoundation', 'CoreVideo']
else:
libs += ['va', 'va-drm', 'drm']
if arch != "Darwin":
libs += ['va', 'va-drm', 'drm']
if arch == "Darwin":
# exclude v4l

View File

@@ -5,11 +5,8 @@ import signal
import itertools
import math
import time
import requests
import shutil
from serial import Serial
import datetime
from multiprocessing import Process, Event
from typing import NoReturn
from struct import unpack_from, calcsize, pack
@@ -30,9 +27,6 @@ from openpilot.system.qcomgpsd.structs import (dict_unpacker, position_report, r
LOG_GNSS_OEMDRE_SVPOLY_REPORT)
DEBUG = int(os.getenv("DEBUG", "0"))==1
ASSIST_DATA_FILE = '/tmp/xtra3grc.bin'
ASSIST_DATA_FILE_DOWNLOAD = ASSIST_DATA_FILE + '.download'
ASSISTANCE_URL = 'http://xtrapath3.izatcloud.net/xtra3grc.bin'
LOG_TYPES = [
LOG_GNSS_GPS_MEASUREMENT_REPORT,
@@ -112,49 +106,8 @@ def at_cmd(cmd: str) -> str:
def gps_enabled() -> bool:
return "QGPS: 1" in at_cmd("AT+QGPS?")
def download_assistance():
try:
response = requests.get(ASSISTANCE_URL, timeout=5, stream=True)
with open(ASSIST_DATA_FILE_DOWNLOAD, 'wb') as fp:
for chunk in response.iter_content(chunk_size=8192):
fp.write(chunk)
if fp.tell() > 1e5:
cloudlog.error("Qcom assistance data larger than expected")
return
os.rename(ASSIST_DATA_FILE_DOWNLOAD, ASSIST_DATA_FILE)
except requests.exceptions.RequestException:
cloudlog.exception("Failed to download assistance file")
return
def downloader_loop(event):
if os.path.exists(ASSIST_DATA_FILE):
os.remove(ASSIST_DATA_FILE)
alt_path = os.getenv("QCOM_ALT_ASSISTANCE_PATH", None)
if alt_path is not None and os.path.exists(alt_path):
shutil.copyfile(alt_path, ASSIST_DATA_FILE)
try:
while not os.path.exists(ASSIST_DATA_FILE) and not event.is_set():
download_assistance()
event.wait(timeout=10)
except KeyboardInterrupt:
pass
@retry(attempts=5, delay=0.2, ignore_failure=True)
def inject_assistance():
import subprocess
cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}"
subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True)
cloudlog.info("successfully loaded assistance data")
@retry(attempts=5, delay=1.0)
def setup_quectel(diag: ModemDiag) -> bool:
ret = False
def setup_quectel(diag: ModemDiag):
# enable OEMDRE in the NV
# TODO: it has to reboot for this to take effect
DIAG_NV_READ_F = 38
@@ -168,26 +121,11 @@ def setup_quectel(diag: ModemDiag) -> bool:
if gps_enabled():
at_cmd("AT+QGPSEND")
if "GPS_COLD_START" in os.environ:
# deletes all assistance
at_cmd("AT+QGPSDEL=0")
else:
# allow module to perform hot start
at_cmd("AT+QGPSDEL=1")
# disable DPO power savings for more accuracy
at_cmd("AT+QGPSCFG=\"dpoenable\",0")
# don't automatically turn on GNSS on powerup
at_cmd("AT+QGPSCFG=\"autogps\",0")
# Do internet assistance
at_cmd("AT+QGPSXTRA=1")
at_cmd("AT+QGPSSUPLURL=\"NULL\"")
if os.path.exists(ASSIST_DATA_FILE):
ret = True
inject_assistance()
os.remove(ASSIST_DATA_FILE)
#at_cmd("AT+QGPSXTRADATA?")
if system_time_valid():
time_str = datetime.datetime.now(datetime.UTC).replace(tzinfo=None).strftime("%Y/%m/%d,%H:%M:%S")
at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000")
@@ -214,7 +152,6 @@ def setup_quectel(diag: ModemDiag) -> bool:
0,0
))
return ret
def teardown_quectel(diag):
at_cmd("AT+QGPSCFG=\"outport\",\"none\"")
@@ -255,9 +192,6 @@ def main() -> NoReturn:
wait_for_modem()
stop_download_event = Event()
assist_fetch_proc = Process(target=downloader_loop, args=(stop_download_event,))
assist_fetch_proc.start()
def cleanup(sig, frame):
cloudlog.warning("caught sig disabling quectel gps")
@@ -268,18 +202,13 @@ def main() -> NoReturn:
except NameError:
cloudlog.warning('quectel not yet setup')
stop_download_event.set()
assist_fetch_proc.kill()
assist_fetch_proc.join()
sys.exit(0)
signal.signal(signal.SIGINT, cleanup)
signal.signal(signal.SIGTERM, cleanup)
# connect to modem
diag = ModemDiag()
r = setup_quectel(diag)
want_assistance = not r
setup_quectel(diag)
cloudlog.warning("quectel setup done")
gpio_init(GPIO.GNSS_PWR_EN, True)
gpio_set(GPIO.GNSS_PWR_EN, True)
@@ -287,10 +216,6 @@ def main() -> NoReturn:
pm = messaging.PubMaster(['qcomGnss', 'gpsLocation'])
while 1:
if os.path.exists(ASSIST_DATA_FILE) and want_assistance:
setup_quectel(diag)
want_assistance = False
opcode, payload = diag.recv()
if opcode != DIAG_LOG_F:
cloudlog.error(f"Unhandled opcode: {opcode}")
@@ -383,9 +308,6 @@ def main() -> NoReturn:
gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma]))
# quectel gps verticalAccuracy is clipped to 500, set invalid if so
gps.hasFix = gps.verticalAccuracy != 500
if gps.hasFix:
want_assistance = False
stop_download_event.set()
pm.send('gpsLocation', msg)
elif log_type == LOG_GNSS_OEMDRE_SVPOLY_REPORT:

View File

@@ -1,36 +1,29 @@
import os
import pytest
import time
import datetime
import cereal.messaging as messaging
from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem
from openpilot.system.manager.process_config import managed_processes
GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0')))
@pytest.mark.tici
class TestRawgpsd:
class TestQcomgpsd:
@classmethod
def setup_class(cls):
os.environ['GPS_COLD_START'] = '1'
os.system("sudo systemctl start systemd-resolved")
os.system("sudo systemctl restart ModemManager lte")
wait_for_modem()
@classmethod
def teardown_class(cls):
managed_processes['qcomgpsd'].stop()
os.system("sudo systemctl restart systemd-resolved")
os.system("sudo systemctl restart ModemManager lte")
def setup_method(self):
self.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation', 'gnssMeasurements'])
self.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation'])
def teardown_method(self):
managed_processes['qcomgpsd'].stop()
os.system("sudo systemctl restart systemd-resolved")
def _wait_for_output(self, t):
dt = 0.1
@@ -41,24 +34,10 @@ class TestRawgpsd:
time.sleep(dt)
return self.sm.updated['qcomGnss']
def test_no_crash_double_command(self):
wait_for_modem()
at_cmd("AT+QGPSDEL=0")
at_cmd("AT+QGPSDEL=0")
def test_wait_for_modem(self):
os.system("sudo systemctl stop ModemManager")
def test_startup_time(self):
managed_processes['qcomgpsd'].start()
assert self._wait_for_output(30)
def test_startup_time(self, subtests):
for internet in (True, False):
if not internet:
os.system("sudo systemctl stop systemd-resolved")
with subtests.test(internet=internet):
managed_processes['qcomgpsd'].start()
assert self._wait_for_output(30)
managed_processes['qcomgpsd'].stop()
managed_processes['qcomgpsd'].stop()
def test_turns_off_gnss(self, subtests):
for s in (0.1, 1, 5):
@@ -70,50 +49,3 @@ class TestRawgpsd:
wait_for_modem()
resp = at_cmd("AT+QGPS?")
assert "+QGPS: 0" in resp
def check_assistance(self, should_be_loaded):
# after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"'
# after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"'
wait_for_modem()
out = at_cmd("AT+QGPSXTRADATA?")
out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip()
valid_duration, injected_time_str = out.split(",", 1)
if should_be_loaded:
assert valid_duration == "10080" # should be max time
injected_time = datetime.datetime.strptime(injected_time_str.replace("\"", ""), "%Y/%m/%d,%H:%M:%S")
assert abs((datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - injected_time).total_seconds()) < 60*60*12
else:
valid_duration, injected_time_str = out.split(",", 1)
injected_time_str = injected_time_str.replace('\"', '').replace('\'', '')
assert injected_time_str[:] == '1980/01/05,19:00:00'[:]
assert valid_duration == '0'
@pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17")
def test_assistance_loading(self):
managed_processes['qcomgpsd'].start()
assert self._wait_for_output(30)
managed_processes['qcomgpsd'].stop()
self.check_assistance(True)
@pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17")
def test_no_assistance_loading(self):
os.system("sudo systemctl stop systemd-resolved")
managed_processes['qcomgpsd'].start()
assert self._wait_for_output(30)
managed_processes['qcomgpsd'].stop()
self.check_assistance(False)
@pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17")
def test_late_assistance_loading(self):
os.system("sudo systemctl stop systemd-resolved")
managed_processes['qcomgpsd'].start()
self._wait_for_output(17)
assert self.sm.updated['qcomGnss']
os.system("sudo systemctl restart systemd-resolved")
time.sleep(15)
managed_processes['qcomgpsd'].stop()
self.check_assistance(True)

View File

@@ -11,19 +11,21 @@ from openpilot.common.utils import sudo_write
from openpilot.common.realtime import config_realtime_process, Ratekeeper
from openpilot.common.swaglog import cloudlog
from openpilot.common.gpio import gpiochip_get_ro_value_fd, gpioevent_data
from openpilot.system.hardware import HARDWARE
from openpilot.system.sensord.sensors.i2c_sensor import Sensor
from openpilot.system.sensord.sensors.lsm6ds3_accel import LSM6DS3_Accel
from openpilot.system.sensord.sensors.lsm6ds3_gyro import LSM6DS3_Gyro
from openpilot.system.sensord.sensors.lsm6ds3_temp import LSM6DS3_Temp
from openpilot.system.sensord.sensors.mmc5603nj_magn import MMC5603NJ_Magn
I2C_BUS_IMU = 1
def interrupt_loop(sensors: list[tuple[Sensor, str, bool]], event) -> None:
pm = messaging.PubMaster([service for sensor, service, interrupt in sensors if interrupt])
# NOTE: the gyro and accelerometer share an IRQ due to the comma three
# routing only one GPIO from the LSM to the SOC, but comma 3X and four
# have two. if we want better timestamps in the future, we can use both.
# Requesting both edges as the data ready pulse from the lsm6ds sensor is
# very short (75us) and is mostly detected as falling edge instead of rising.
# So if it is detected as rising the following falling edge is skipped.
@@ -97,10 +99,6 @@ def main() -> None:
(LSM6DS3_Gyro(I2C_BUS_IMU), "gyroscope", True),
(LSM6DS3_Temp(I2C_BUS_IMU), "temperatureSensor", False),
]
if HARDWARE.get_device_type() == "tizi":
sensors_cfg.append(
(MMC5603NJ_Magn(I2C_BUS_IMU), "magnetometer", False),
)
# Reset sensors
for sensor, _, _ in sensors_cfg:

View File

@@ -77,13 +77,9 @@ class LSM6DS3_Accel(Sensor):
event = log.SensorEventData.new_message()
event.timestamp = ts
event.version = 1
event.sensor = 1 # SENSOR_ACCELEROMETER
event.type = 1 # SENSOR_TYPE_ACCELEROMETER
event.source = self.source
a = event.init('acceleration')
a.v = [y, -x, z]
a.status = 1
return event
def shutdown(self) -> None:

View File

@@ -73,13 +73,9 @@ class LSM6DS3_Gyro(Sensor):
event = log.SensorEventData.new_message()
event.timestamp = ts
event.version = 2
event.sensor = 5 # SENSOR_GYRO_UNCALIBRATED
event.type = 16 # SENSOR_TYPE_GYROSCOPE_UNCALIBRATED
event.source = self.source
g = event.init('gyroUncalibrated')
g.v = xyz
g.status = 1
return event
def shutdown(self) -> None:

View File

@@ -23,7 +23,6 @@ class LSM6DS3_Temp(Sensor):
def get_event(self, ts: int | None = None) -> log.SensorEventData:
event = log.SensorEventData.new_message()
event.version = 1
event.timestamp = int(time.monotonic() * 1e9)
event.source = self.source
event.temperature = self._read_temperature()

View File

@@ -1,76 +0,0 @@
import time
from cereal import log
from openpilot.system.sensord.sensors.i2c_sensor import Sensor
# https://www.mouser.com/datasheet/2/821/Memsic_09102019_Datasheet_Rev.B-1635324.pdf
# Register addresses
REG_ODR = 0x1A
REG_INTERNAL_0 = 0x1B
REG_INTERNAL_1 = 0x1C
# Control register settings
CMM_FREQ_EN = (1 << 7)
AUTO_SR_EN = (1 << 5)
SET = (1 << 3)
RESET = (1 << 4)
class MMC5603NJ_Magn(Sensor):
@property
def device_address(self) -> int:
return 0x30
def init(self):
self.verify_chip_id(0x39, [0x10, ])
self.writes((
(REG_ODR, 0),
# Set BW to 0b01 for 1-150 Hz operation
(REG_INTERNAL_1, 0b01),
))
def _read_data(self, cycle) -> list[float]:
# start measurement
self.write(REG_INTERNAL_0, cycle)
self.wait()
# read out XYZ
scale = 1.0 / 16384.0
b = self.read(0x00, 9)
return [
(self.parse_20bit(b[6], b[1], b[0]) * scale) - 32.0,
(self.parse_20bit(b[7], b[3], b[2]) * scale) - 32.0,
(self.parse_20bit(b[8], b[5], b[4]) * scale) - 32.0,
]
def get_event(self, ts: int | None = None) -> log.SensorEventData:
ts = time.monotonic_ns()
# SET - RESET cycle
xyz = self._read_data(SET)
reset_xyz = self._read_data(RESET)
vals = [*xyz, *reset_xyz]
event = log.SensorEventData.new_message()
event.timestamp = ts
event.version = 1
event.sensor = 3 # SENSOR_MAGNETOMETER_UNCALIBRATED
event.type = 14 # SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED
event.source = log.SensorEventData.SensorSource.mmc5603nj
m = event.init('magneticUncalibrated')
m.v = vals
m.status = int(all(int(v) != -32 for v in vals))
return event
def shutdown(self) -> None:
v = self.read(REG_INTERNAL_0, 1)[0]
self.writes((
# disable auto-reset of measurements
(REG_INTERNAL_0, (v & (~(CMM_FREQ_EN | AUTO_SR_EN)))),
# disable continuous mode
(REG_ODR, 0),
))

View File

@@ -5,44 +5,19 @@ import numpy as np
from collections import namedtuple, defaultdict
import cereal.messaging as messaging
from cereal import log
from cereal.services import SERVICE_LIST
from openpilot.common.gpio import get_irqs_for_action
from openpilot.common.timeout import Timeout
from openpilot.system.hardware import HARDWARE
from openpilot.system.manager.process_config import managed_processes
LSM = {
('lsm6ds3', 'acceleration'),
('lsm6ds3', 'gyroUncalibrated'),
('lsm6ds3', 'temperature'),
}
LSM_C = {(x[0]+'trc', x[1]) for x in LSM}
SensorConfig = namedtuple('SensorConfig', ['service', 'measurement', 'sanity_min', 'sanity_max', 'std_max'])
MMC = {
('mmc5603nj', 'magneticUncalibrated'),
}
SENSOR_CONFIGURATIONS: list[set] = {
"mici": [LSM, LSM_C],
"tizi": [MMC | LSM, MMC | LSM_C],
"tici": [LSM, LSM_C, MMC | LSM, MMC | LSM_C],
}.get(HARDWARE.get_device_type(), [])
Sensor = log.SensorEventData.SensorSource
SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max'])
ALL_SENSORS = {
Sensor.lsm6ds3trc: {
SensorConfig("acceleration", 5, 15),
SensorConfig("gyroUncalibrated", 0, .2),
SensorConfig("temperature", 10, 40), # set for max range of our office
},
Sensor.mmc5603nj: {
SensorConfig("magneticUncalibrated", 0, 300),
}
}
ALL_SENSORS[Sensor.lsm6ds3] = ALL_SENSORS[Sensor.lsm6ds3trc]
SENSOR_CONFIGS = (
SensorConfig("accelerometer", "acceleration", 5, 15, 5),
SensorConfig("gyroscope", "gyroUncalibrated", 0, .15, 0.5),
SensorConfig("temperatureSensor", "temperature", 10, 40, 0.5), # set for max range of our office
)
SENSOR_CONFIGS_BY_MEASUREMENT = {config.measurement: config for config in SENSOR_CONFIGS}
def get_irq_count(irq: int):
with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f:
@@ -50,12 +25,11 @@ def get_irq_count(irq: int):
return sum(per_cpu)
def read_sensor_events(duration_sec):
sensor_types = ['accelerometer', 'gyroscope', 'magnetometer', 'temperatureSensor',]
socks = {}
poller = messaging.Poller()
events = defaultdict(list)
for stype in sensor_types:
socks[stype] = messaging.sub_sock(stype, poller=poller, timeout=100)
for config in SENSOR_CONFIGS:
socks[config.service] = messaging.sub_sock(config.service, poller=poller, timeout=100)
# wait for sensors to come up
with Timeout(int(os.environ.get("SENSOR_WAIT", "5")), "sensors didn't come up"):
@@ -70,11 +44,15 @@ def read_sensor_events(duration_sec):
for s in socks:
events[s] += messaging.drain_sock(socks[s])
time.sleep(0.1)
assert sum(map(len, events.values())) != 0, "No sensor events collected!"
return {k: v for k, v in events.items() if len(v) > 0}
def iter_measurements(events):
for msgs in events.values():
for measurement in msgs:
yield measurement, getattr(measurement, measurement.which())
@pytest.mark.tici
class TestSensord:
@classmethod
@@ -102,31 +80,19 @@ class TestSensord:
def teardown_method(self):
managed_processes["sensord"].stop()
def test_sensors_present(self):
# verify correct sensors configuration
seen = set()
for etype in self.events:
for measurement in self.events[etype]:
m = getattr(measurement, measurement.which())
seen.add((str(m.source), m.which()))
assert seen in SENSOR_CONFIGURATIONS
def test_all_sensors_present(self):
missing = [config.service for config in SENSOR_CONFIGS if config.service not in self.events]
assert len(missing) == 0, f"missing sensors: {missing}"
def test_lsm6ds3_timing(self, subtests):
# verify measurements are sampled and published at 104Hz
sensor_t = {
1: [], # accel
5: [], # gyro
}
sensor_t = {service: [] for service in ('accelerometer', 'gyroscope')}
for measurement in self.events['accelerometer']:
m = getattr(measurement, measurement.which())
sensor_t[m.sensor].append(m.timestamp)
for measurement in self.events['gyroscope']:
m = getattr(measurement, measurement.which())
sensor_t[m.sensor].append(m.timestamp)
for service in sensor_t:
for measurement in self.events.get(service, []):
m = getattr(measurement, measurement.which())
sensor_t[service].append(m.timestamp)
for s, vals in sensor_t.items():
with subtests.test(sensor=s):
@@ -153,19 +119,16 @@ class TestSensord:
def test_logmonottime_timestamp_diff(self):
# ensure diff between the message logMonotime and sample timestamp is small
tdiffs = list()
for etype in self.events:
for measurement in self.events[etype]:
m = getattr(measurement, measurement.which())
tdiffs = []
for measurement, m in iter_measurements(self.events):
# check if gyro and accel timestamps are before logMonoTime
if str(m.source).startswith("lsm6ds3") and m.which() != 'temperature':
err_msg = f"Timestamp after logMonoTime: {m.timestamp} > {measurement.logMonoTime}"
assert m.timestamp < measurement.logMonoTime, err_msg
# check if gyro and accel timestamps are before logMonoTime
if str(m.source).startswith("lsm6ds3") and m.which() != 'temperature':
err_msg = f"Timestamp after logMonoTime: {m.timestamp} > {measurement.logMonoTime}"
assert m.timestamp < measurement.logMonoTime, err_msg
# negative values might occur, as non interrupt packages created
# before the sensor is read
tdiffs.append(abs(measurement.logMonoTime - m.timestamp) / 1e6)
# negative values might occur, as non interrupt packages created
# before the sensor is read
tdiffs.append(abs(measurement.logMonoTime - m.timestamp) / 1e6)
# some sensors have a read procedure that will introduce an expected diff on the order of 20ms
high_delay_diffs = set(filter(lambda d: d >= 25., tdiffs))
@@ -175,32 +138,29 @@ class TestSensord:
assert avg_diff < 4, f"Avg packet diff: {avg_diff:.1f}ms"
def test_sensor_values(self):
sensor_values = dict()
for etype in self.events:
for measurement in self.events[etype]:
m = getattr(measurement, measurement.which())
key = (m.source.raw, m.which())
values = getattr(m, m.which())
sensor_values = defaultdict(list)
for _, m in iter_measurements(self.events):
key = (m.source.raw, m.which())
values = getattr(m, m.which())
if hasattr(values, 'v'):
values = values.v
values = np.atleast_1d(values)
if key in sensor_values:
sensor_values[key].append(values)
else:
sensor_values[key] = [values]
if hasattr(values, 'v'):
values = values.v
sensor_values[key].append(np.atleast_1d(values))
# Sanity check sensor values
for sensor, stype in sensor_values:
for s in ALL_SENSORS[sensor]:
if s.type != stype:
continue
for (sensor, stype), values in sensor_values.items():
config = SENSOR_CONFIGS_BY_MEASUREMENT[stype]
key = (sensor, s.type)
mean_norm = np.mean(np.linalg.norm(sensor_values[key], axis=1))
err_msg = f"Sensor '{sensor} {s.type}' failed sanity checks {mean_norm} is not between {s.sanity_min} and {s.sanity_max}"
assert s.sanity_min <= mean_norm <= s.sanity_max, err_msg
if config.measurement == 'temperature':
measurement_stat = np.mean(values)
else:
measurement_stat = np.mean(np.linalg.norm(values, axis=1))
err_msg = f"Sensor '{sensor} {config.measurement}' failed sanity checks {measurement_stat} is not between {config.sanity_min} and {config.sanity_max}"
assert config.sanity_min <= measurement_stat <= config.sanity_max, err_msg
std_dev = np.std(values, axis=0)
err_msg = f"Sensor '{sensor} {config.measurement}' failed std dev test {std_dev} is not under {config.std_max}"
assert np.all(std_dev <= config.std_max), err_msg
def test_sensor_verify_no_interrupts_after_stop(self):
managed_processes["sensord"].start()
@@ -222,4 +182,3 @@ class TestSensord:
time.sleep(1)
state_two = get_irq_count(self.sensord_irq)
assert state_one == state_two, "Interrupts received after sensord stop!"

View File

@@ -3,6 +3,7 @@ jot_*.o
*.o
jotpluggler
car_fingerprint_to_dbc.h
generated_event_extractors.h
generated_dbcs/.stamp
generated_dbcs/*.dbc
layouts/.jotpluggler_autosave/

View File

@@ -1,4 +1,5 @@
import os
import subprocess
import imgui
import libusb
from opendbc import get_generated_dbcs
@@ -77,8 +78,24 @@ def write_car_fingerprint_to_dbc_header(target, source, env):
return None
def generate_event_extractors(target, source, env):
subprocess.check_call([
"python3",
"tools/jotpluggler/generate_event_extractors.py",
os.path.realpath(BASEDIR),
str(target[0]),
])
return None
generated_dbc_stamp = jot_env.Command(f"generated_dbcs/.stamp", [], materialize_generated_dbcs)
car_fingerprint_to_dbc = jot_env.Command("car_fingerprint_to_dbc.h", [], write_car_fingerprint_to_dbc_header)
event_extractors = jot_env.Command("generated_event_extractors.h", [
"generate_event_extractors.py",
jot_env.Glob("#cereal/*.capnp"),
jot_env.Glob("#cereal/include/*.capnp"),
],
generate_event_extractors,
)
libs = [replay_lib, common, messaging, visionipc, cereal, File(f"{imgui.LIB_DIR}/libimgui.a"), File(f"{imgui.LIB_DIR}/libglfw3.a"),
"avformat", "avcodec", "avutil", "x264", "yuv", "z", "bz2", "zstd", "m", "pthread", "usb-1.0"]
@@ -90,3 +107,4 @@ else:
program = jot_env.Program("jotpluggler", jot_env.Glob("*.cc"), LIBS=libs)
jot_env.Depends(program, generated_dbc_stamp)
jot_env.Depends(program, car_fingerprint_to_dbc)
jot_env.Depends(program, event_extractors)

View File

@@ -324,7 +324,7 @@ void configure_style() {
plot_style.LegendInnerPadding = ImVec2(6.0f, 3.0f);
plot_style.LegendSpacing = ImVec2(7.0f, 2.0f);
plot_style.PlotPadding = ImVec2(4.0f, 8.0f);
plot_style.FitPadding = ImVec2(0.02f, 0.4f);
plot_style.FitPadding = ImVec2(0.02f, static_cast<float>(PLOT_Y_PADDING_FRACTION));
ImPlot::MapInputDefault();
ImPlotInputMap &input_map = ImPlot::GetInputMap();

View File

@@ -0,0 +1,327 @@
import sys
import capnp
from pathlib import Path
NO_DISCRIMINANT = 65535
SCALAR_KINDS = {
"bool": "Bool",
"int8": "Int",
"int16": "Int",
"int32": "Int",
"int64": "Int",
"uint8": "UInt",
"uint16": "UInt",
"uint32": "UInt",
"uint64": "UInt",
"float32": "Float",
"float64": "Float",
"enum": "Enum",
}
NESTED_TYPE_KINDS = {"struct", "list"}
IGNORED_TYPE_KINDS = {"void", "text", "data", "interface", "anyPointer"}
def cxx_string(value):
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
def accessor(prefix, name):
return prefix + name[:1].upper() + name[1:]
def field_type(field):
if field.proto.which() == "group":
return "struct"
return field.proto.slot.type.which()
def field_type_proto(field):
return field.proto.slot.type if field.proto.which() == "slot" else None
def scalar_kind(type_proto):
if type_proto is None:
return None
return SCALAR_KINDS.get(type_proto.which())
def enum_names(schema):
if schema is None:
return []
names_by_ordinal = schema.enumerants
if not names_by_ordinal:
return []
max_ordinal = max(names_by_ordinal.values())
out = [""] * (max_ordinal + 1)
for name, ordinal in names_by_ordinal.items():
out[ordinal] = name
return out
class Generator:
def __init__(self, event_schema):
self.event_schema = event_schema
self.fixed_paths = []
self.tmp_index = 0
self.lines = []
self.emits_memo = {}
def tmp(self, prefix):
self.tmp_index += 1
return f"{prefix}_{self.tmp_index}"
def add_fixed_path(self, path):
slot = len(self.fixed_paths)
self.fixed_paths.append(path)
return slot
def emit(self, indent, text=""):
self.lines.append(" " * indent + text)
def scalar_double_expr(self, value_expr, kind):
if kind == "Bool":
return f"({value_expr} ? 1.0 : 0.0)"
if kind == "Enum":
return f"static_cast<double>(static_cast<uint16_t>({value_expr}))"
return f"static_cast<double>({value_expr})"
def emit_enum_capture(self, indent, path_expr, names):
if not names:
return
names_expr = "{" + ", ".join(cxx_string(name) for name in names) + "}"
self.emit(indent, f"capture_static_enum_info({path_expr}, {names_expr}, series);")
def emit_node(self, indent, type_kind, type_proto, schema, expr, path, path_expr, dynamic_path):
if not self.node_emits(type_kind, type_proto, schema):
return
kind = scalar_kind(type_proto)
if kind is not None:
double_expr = self.scalar_double_expr(expr, kind)
if dynamic_path:
if kind == "Enum":
self.emit_enum_capture(indent, path_expr, enum_names(schema))
self.emit(indent, f"append_dynamic_scalar_point({path_expr}, tm, {double_expr}, series);")
else:
slot = self.add_fixed_path(path)
if kind == "Enum":
self.emit_enum_capture(indent, cxx_string(path), enum_names(schema))
self.emit(indent, f"append_fixed_scalar_point(&series->fixed_series[{slot}], tm, {double_expr});")
return
if type_kind == "struct":
self.emit_struct(indent, schema, expr, path, path_expr, dynamic_path)
return
if type_kind == "list":
self.emit_list(indent, type_proto, schema, expr, path, path_expr, dynamic_path)
def emit_field(self, indent, struct_schema, reader_expr, field_name, base_path, base_path_expr, dynamic_path):
field = struct_schema.fields[field_name]
proto = field.proto
type_kind = field_type(field)
type_proto = field_type_proto(field)
kind = scalar_kind(type_proto)
value_schema = field.schema if kind == "Enum" or type_kind in NESTED_TYPE_KINDS else None
if not self.node_emits(type_kind, type_proto, value_schema):
return
field_path = f"{base_path}/{field_name}"
field_path_expr = None
if dynamic_path:
field_path_var = self.tmp("path")
self.emit(indent, f"const std::string {field_path_var} = {base_path_expr} + {cxx_string('/' + field_name)};")
field_path_expr = field_path_var
get_call = f"{reader_expr}.{accessor('get', field_name)}()"
has_call = f"{reader_expr}.{accessor('has', field_name)}()"
conditions = []
if proto.discriminantValue != NO_DISCRIMINANT:
conditions.append(f"{reader_expr}.which() == static_cast<decltype({reader_expr}.which())>({proto.discriminantValue})")
if proto.which() == "slot" and type_kind in NESTED_TYPE_KINDS:
conditions.append(has_call)
if conditions:
self.emit(indent, f"if ({' && '.join(conditions)}) {{")
indent += 2
value_var = self.tmp("value")
self.emit(indent, f"const auto {value_var} = {get_call};")
self.emit_node(indent, type_kind, type_proto, value_schema, value_var, field_path, field_path_expr, dynamic_path)
if conditions:
indent -= 2
self.emit(indent, "}")
def emit_struct(self, indent, schema, reader_expr, path, path_expr, dynamic_path):
if schema is None:
return
for field_name in schema.fieldnames:
self.emit_field(indent, schema, reader_expr, field_name, path, path_expr, dynamic_path)
def emit_list(self, indent, type_proto, schema, list_expr, path, path_expr, dynamic_path):
elem_type = type_proto.list.elementType
elem_kind = elem_type.which()
if elem_kind in IGNORED_TYPE_KINDS:
return
base_path_var = path_expr
if base_path_var is None:
base_path_var = self.tmp("base_path")
self.emit(indent, f"const std::string {base_path_var} = {cxx_string(path)};")
elem_scalar = scalar_kind(elem_type)
if elem_scalar is not None:
self.emit(indent, f"if ({list_expr}.size() <= 16) {{")
index_var = self.tmp("i")
self.emit(indent + 2, f"for (uint {index_var} = 0; {index_var} < {list_expr}.size(); ++{index_var}) {{")
item_series = self.tmp("item_series")
self.emit(indent + 4, f"RouteSeries *{item_series} = ensure_list_scalar_series({base_path_var}, {index_var}, series);")
if elem_scalar == "Enum":
self.emit_enum_capture(indent + 4, f"{item_series}->path", enum_names(schema.elementType))
self.emit(indent + 4, f"append_fixed_scalar_point({item_series}, tm, {self.scalar_double_expr(f'{list_expr}[{index_var}]', elem_scalar)});")
self.emit(indent + 2, "}")
self.emit(indent, "}")
return
if elem_kind in {"struct", "list"}:
index_var = self.tmp("i")
self.emit(indent, f"for (uint {index_var} = 0; {index_var} < {list_expr}.size(); ++{index_var}) {{")
item_path = self.tmp("item_path")
self.emit(indent + 2, f"const std::string {item_path} = {base_path_var} + \"/\" + std::to_string({index_var});")
item = self.tmp("item")
self.emit(indent + 2, f"const auto {item} = {list_expr}[{index_var}];")
if elem_kind == "struct":
self.emit_struct(indent + 2, schema.elementType, item, path, item_path, True)
else:
self.emit_list(indent + 2, elem_type, schema.elementType, item, path, item_path, True)
self.emit(indent, "}")
def node_emits(self, type_kind, type_proto, schema, seen=frozenset()):
if scalar_kind(type_proto) is not None:
return True
if type_kind == "struct":
if schema is None:
return False
schema_id = int(schema.node.id)
if schema_id in seen:
return False
if schema_id in self.emits_memo:
return self.emits_memo[schema_id]
next_seen = seen | {schema_id}
for field_name in schema.fieldnames:
field = schema.fields[field_name]
ft = field_type(field)
ftp = field_type_proto(field)
fkind = scalar_kind(ftp)
if ft in IGNORED_TYPE_KINDS:
continue
fschema = field.schema if fkind == "Enum" or ft in NESTED_TYPE_KINDS else None
if self.node_emits(ft, ftp, fschema, next_seen):
self.emits_memo[schema_id] = True
return True
self.emits_memo[schema_id] = False
return False
if type_kind == "list":
if type_proto is None or schema is None:
return False
elem_type = type_proto.list.elementType
elem_kind = elem_type.which()
if elem_kind in IGNORED_TYPE_KINDS:
return False
if scalar_kind(elem_type) is not None:
return True
if elem_kind == "struct":
return self.node_emits("struct", None, schema.elementType, seen)
if elem_kind == "list":
return self.node_emits("list", elem_type, schema.elementType, seen)
return False
def emit_can_special(self, indent, service_name):
service_kind = "CanServiceKind::Can" if service_name == "can" else "CanServiceKind::Sendcan"
self.emit(indent, f"const CanServiceKind can_service = {service_kind};")
self.emit(indent, f"for (const auto &msg : event.{accessor('get', service_name)}()) {{")
self.emit(indent + 2, "append_can_frame(can_service, static_cast<uint8_t>(msg.getSrc()), msg.getAddress(), msg.getDeprecated().getBusTime(), msg.getDat(), tm, series);") # noqa: E501
self.emit(indent + 2, "if (skip_raw_can) {")
self.emit(indent + 4, "const auto dat = msg.getDat();")
self.emit(indent + 4, f"decode_can_frame(can_dbc, {cxx_string(service_name)}, static_cast<uint8_t>(msg.getSrc()), msg.getAddress(), dat.begin(), dat.size(), tm, series);") # noqa: E501
self.emit(indent + 2, "}")
self.emit(indent, "}")
self.emit(indent, "if (skip_raw_can) {")
self.emit(indent + 2, "return true;")
self.emit(indent, "}")
def emit_event_case(self, field_name):
field = self.event_schema.fields[field_name]
proto = field.proto
type_kind = field_type(field)
type_proto = field_type_proto(field)
kind = scalar_kind(type_proto)
schema = field.schema if kind == "Enum" or type_kind in NESTED_TYPE_KINDS else None
self.emit(4, f"case static_cast<cereal::Event::Which>({proto.discriminantValue}): {{")
valid_slot = self.add_fixed_path(f"/{field_name}/valid")
mono_slot = self.add_fixed_path(f"/{field_name}/logMonoTime")
seconds_slot = self.add_fixed_path(f"/{field_name}/t")
self.emit(6, f"append_fixed_scalar_point(&series->fixed_series[{valid_slot}], tm, event.getValid() ? 1.0 : 0.0);")
self.emit(6, f"append_fixed_scalar_point(&series->fixed_series[{mono_slot}], tm, static_cast<double>(event.getLogMonoTime()));")
self.emit(6, f"append_fixed_scalar_point(&series->fixed_series[{seconds_slot}], tm, tm);")
if field_name in {"can", "sendcan"}:
self.emit_can_special(6, field_name)
if self.node_emits(type_kind, type_proto, schema):
payload = self.tmp("payload")
self.emit(6, f"const auto {payload} = event.{accessor('get', field_name)}();")
self.emit_node(6, type_kind, type_proto, schema, payload, f"/{field_name}", None, False)
self.emit(6, "return true;")
self.emit(4, "}")
def generate(self):
self.lines = []
self.emit(0, "// Generated by tools/jotpluggler/generate_event_extractors.py; do not edit.")
self.emit(0, "")
self.emit(0, "const std::vector<std::string> &static_event_fixed_paths() {")
self.emit(2, "static const std::vector<std::string> paths = {")
path_insert_at = len(self.lines)
self.emit(2, "};")
self.emit(2, "return paths;")
self.emit(0, "}")
self.emit(0, "")
self.emit(0, "void capture_static_enum_info(const std::string &path, std::initializer_list<std::string_view> names, SeriesAccumulator *series) {")
self.emit(2, "if (series->enum_info.find(path) != series->enum_info.end()) {")
self.emit(4, "return;")
self.emit(2, "}")
self.emit(2, "EnumInfo info;")
self.emit(2, "info.names.reserve(names.size());")
self.emit(2, "for (std::string_view name : names) {")
self.emit(4, "info.names.emplace_back(name);")
self.emit(2, "}")
self.emit(2, "if (!info.names.empty()) {")
self.emit(4, "series->enum_info.emplace(path, std::move(info));")
self.emit(2, "}")
self.emit(0, "}")
self.emit(0, "")
self.emit(0, "bool append_event_static_reader(cereal::Event::Which which, const cereal::Event::Reader &event, const dbc::Database *can_dbc, bool skip_raw_can, double time_offset, SeriesAccumulator *series) {") # noqa: E501
self.emit(2, "const double tm = static_cast<double>(event.getLogMonoTime()) / 1.0e9 - time_offset;")
self.emit(2, "switch (which) {")
for field_name in self.event_schema.union_fields:
self.emit_event_case(field_name)
self.emit(4, "default:")
self.emit(6, "return false;")
self.emit(2, "}")
self.emit(0, "}")
path_lines = [" " + cxx_string(path) + "," for path in self.fixed_paths]
self.lines[path_insert_at:path_insert_at] = path_lines
return "\n".join(self.lines) + "\n"
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"usage: {sys.argv[0]} <repo-root> <output>", file=sys.stderr)
sys.exit(2)
repo_root = Path(sys.argv[1]).resolve()
output = Path(sys.argv[2])
capnp.remove_import_hook()
log = capnp.load(str(repo_root / "cereal" / "log.capnp"))
generated = Generator(log.Event.schema).generate()
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(generated)

View File

@@ -58,6 +58,7 @@ inline constexpr float SIDEBAR_MAX_WIDTH = 520.0f;
inline constexpr float TIMELINE_BAR_HEIGHT = 14.0f;
inline constexpr float STATUS_BAR_HEIGHT = 52.0f;
inline constexpr double MIN_HORIZONTAL_ZOOM_SECONDS = 2.0;
inline constexpr double PLOT_Y_PADDING_FRACTION = 0.05;
struct UiMetrics {
float width = 0.0f;

View File

@@ -7,8 +7,6 @@
#include <cstdio>
#include <limits>
constexpr double PLOT_Y_PAD_FRACTION = 0.4;
struct PlotBounds {
double x_min = 0.0;
double x_max = 1.0;
@@ -484,7 +482,7 @@ PlotBounds compute_plot_bounds(const Pane &pane,
min_value = std::min(min_value, 0.0);
max_value = std::max(max_value, 1.0);
}
ensure_non_degenerate_range(&min_value, &max_value, PLOT_Y_PAD_FRACTION, 0.1);
ensure_non_degenerate_range(&min_value, &max_value, PLOT_Y_PADDING_FRACTION, 0.1);
if (pane.range.has_y_limit_min) {
min_value = pane.range.y_limit_min;
}
@@ -848,7 +846,7 @@ void draw_plot(const AppSession &session, Pane *pane, UiState *state) {
} else {
for (size_t i = 0; i < prepared_curves.size(); ++i) {
const PreparedCurve &curve = prepared_curves[i];
std::string series_id = curve_legend_label(curve, has_cursor_time, max_legend_label_width) + "##curve" + std::to_string(i);
std::string series_id = curve_legend_label(curve, has_cursor_time, max_legend_label_width) + "###curve" + std::to_string(curve.pane_curve_index);
ImPlotSpec spec;
spec.LineColor = color_rgb(curve.color);
spec.LineWeight = curve.line_weight;

View File

@@ -71,7 +71,6 @@ bool should_subscribe_stream_service(const std::string &name) {
"livestreamRoadEncodeIdx",
"livestreamDriverEncodeIdx",
"thumbnail",
"navThumbnail",
}};
if (name == "rawAudioData") return false;
for (std::string_view skipped : kSkippedServices) {

View File

@@ -2,7 +2,6 @@
#include "tools/jotpluggler/car_fingerprint_to_dbc.h"
#include "tools/jotpluggler/common.h"
#include <capnp/dynamic.h>
#include <kj/exception.h>
#include <chrono>
@@ -10,6 +9,7 @@
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <initializer_list>
#include <map>
#include <mutex>
#include <limits>
@@ -51,47 +51,7 @@ struct SegmentLogs {
std::string qcamera;
};
enum class ScalarKind {
None,
Bool,
Int,
UInt,
Float,
Enum,
};
enum class ResolvedNodeKind {
Ignore,
Scalar,
Struct,
List,
};
struct ResolvedNode {
ResolvedNodeKind kind = ResolvedNodeKind::Ignore;
ScalarKind scalar_kind = ScalarKind::None;
int fixed_slot = -1;
bool has_field = false;
capnp::StructSchema::Field field;
std::string segment;
std::string path;
bool skip_large_scalar_list = false;
std::vector<ResolvedNode> children;
std::unique_ptr<ResolvedNode> element;
};
struct ResolvedService {
uint16_t event_which = 0;
capnp::StructSchema::Field union_field;
std::string service_name;
int valid_slot = -1;
int log_mono_time_slot = -1;
int seconds_slot = -1;
ResolvedNode payload;
};
struct SchemaIndex {
std::vector<std::optional<ResolvedService>> by_which;
size_t fixed_series_count = 0;
std::vector<std::string> fixed_paths;
@@ -112,6 +72,27 @@ struct SeriesAccumulator {
std::unordered_map<std::string, EnumInfo> enum_info;
};
void append_fixed_scalar_point(RouteSeries *series, double tm, double value);
void append_dynamic_scalar_point(const std::string &path, double tm, double value, SeriesAccumulator *series);
RouteSeries *ensure_list_scalar_series(const std::string &base_path, size_t index, SeriesAccumulator *series);
void append_can_frame(CanServiceKind service,
uint8_t bus,
uint32_t address,
uint16_t bus_time,
capnp::Data::Reader dat,
double tm,
SeriesAccumulator *series);
void decode_can_frame(const dbc::Database *can_dbc,
const std::string &service_name,
uint8_t bus,
uint32_t address,
const uint8_t *raw,
size_t data_size,
double tm,
SeriesAccumulator *series);
#include "tools/jotpluggler/generated_event_extractors.h"
struct LoadedRouteArtifacts {
std::vector<RouteSeries> series;
std::vector<CanMessageData> can_messages;
@@ -898,125 +879,11 @@ SketchLayout parse_layout(const fs::path &layout_path) {
return layout;
}
ScalarKind scalar_kind_for_type(const capnp::Type &type) {
if (type.isBool()) return ScalarKind::Bool;
if (type.isInt8() || type.isInt16() || type.isInt32() || type.isInt64()) {
return ScalarKind::Int;
}
if (type.isUInt8() || type.isUInt16() || type.isUInt32() || type.isUInt64()) {
return ScalarKind::UInt;
}
if (type.isFloat32() || type.isFloat64()) {
return ScalarKind::Float;
}
if (type.isEnum()) return ScalarKind::Enum;
return ScalarKind::None;
}
ResolvedNode build_resolved_type(const capnp::Type &type,
bool has_field,
capnp::StructSchema::Field field,
std::string segment,
std::string path,
size_t *next_fixed_slot,
std::vector<std::string> *fixed_paths,
bool dynamic_path = false) {
ResolvedNode node;
node.has_field = has_field;
node.field = field;
node.segment = std::move(segment);
node.path = std::move(path);
node.scalar_kind = scalar_kind_for_type(type);
if (node.scalar_kind != ScalarKind::None) {
node.kind = ResolvedNodeKind::Scalar;
if (!dynamic_path) {
node.fixed_slot = static_cast<int>((*next_fixed_slot)++);
fixed_paths->push_back(node.path);
}
return node;
}
if (type.isStruct()) {
node.kind = ResolvedNodeKind::Struct;
for (auto child : type.asStruct().getFields()) {
const std::string child_segment = child.getProto().getName().cStr();
node.children.push_back(build_resolved_type(
child.getType(),
true,
child,
child_segment,
node.path + "/" + child_segment,
next_fixed_slot,
fixed_paths,
dynamic_path));
}
return node;
}
if (type.isList()) {
const capnp::Type element_type = type.asList().getElementType();
if (element_type.isText() || element_type.isData() || element_type.isInterface() || element_type.isAnyPointer()) {
node.kind = ResolvedNodeKind::Ignore;
return node;
}
node.kind = ResolvedNodeKind::List;
node.skip_large_scalar_list = scalar_kind_for_type(element_type) != ScalarKind::None;
node.element = std::make_unique<ResolvedNode>(
build_resolved_type(element_type,
false,
capnp::StructSchema::Field(),
"",
node.path,
next_fixed_slot,
fixed_paths,
true));
return node;
}
node.kind = ResolvedNodeKind::Ignore;
return node;
}
int register_fixed_series_path(const std::string &path,
size_t *next_fixed_slot,
std::vector<std::string> *fixed_paths) {
const int slot = static_cast<int>((*next_fixed_slot)++);
fixed_paths->push_back(path);
return slot;
}
const SchemaIndex &SchemaIndex::instance() {
static const SchemaIndex index = [] {
SchemaIndex out;
const auto event_schema = capnp::Schema::from<cereal::Event>().asStruct();
uint16_t max_discriminant = 0;
for (auto union_field : event_schema.getUnionFields()) {
max_discriminant = std::max<uint16_t>(max_discriminant, union_field.getProto().getDiscriminantValue());
}
out.by_which.resize(static_cast<size_t>(max_discriminant) + 1);
size_t next_fixed_slot = 0;
for (auto union_field : event_schema.getUnionFields()) {
ResolvedService service;
service.event_which = union_field.getProto().getDiscriminantValue();
service.union_field = union_field;
service.service_name = union_field.getProto().getName().cStr();
service.valid_slot = register_fixed_series_path(
"/" + service.service_name + "/valid", &next_fixed_slot, &out.fixed_paths);
service.log_mono_time_slot = register_fixed_series_path(
"/" + service.service_name + "/logMonoTime", &next_fixed_slot, &out.fixed_paths);
service.seconds_slot = register_fixed_series_path(
"/" + service.service_name + "/t", &next_fixed_slot, &out.fixed_paths);
service.payload = build_resolved_type(
union_field.getType(),
false,
capnp::StructSchema::Field(),
service.service_name,
"/" + service.service_name,
&next_fixed_slot,
&out.fixed_paths);
out.by_which[service.event_which] = std::move(service);
}
out.fixed_series_count = next_fixed_slot;
out.fixed_paths = static_event_fixed_paths();
out.fixed_series_count = out.fixed_paths.size();
return out;
}();
return index;
@@ -1026,45 +893,6 @@ bool is_absolute_curve(const std::string &name) {
return !name.empty() && name.front() == '/';
}
std::optional<double> scalar_value_to_double(const capnp::DynamicValue::Reader &value, ScalarKind kind) {
switch (kind) {
case ScalarKind::Bool:
return value.as<bool>() ? 1.0 : 0.0;
case ScalarKind::Int:
return static_cast<double>(value.as<int64_t>());
case ScalarKind::UInt:
return static_cast<double>(value.as<uint64_t>());
case ScalarKind::Float:
return value.as<double>();
case ScalarKind::Enum:
return static_cast<double>(value.as<capnp::DynamicEnum>().getRaw());
case ScalarKind::None:
return std::nullopt;
}
return std::nullopt;
}
void capture_enum_info(const std::string &path,
const capnp::DynamicValue::Reader &value,
SeriesAccumulator *series) {
if (series->enum_info.find(path) != series->enum_info.end()) {
return;
}
const auto dynamic_enum = value.as<capnp::DynamicEnum>();
EnumInfo info;
for (auto enumerant : dynamic_enum.getSchema().getEnumerants()) {
const uint16_t ordinal = enumerant.getOrdinal();
if (ordinal >= info.names.size()) {
info.names.resize(static_cast<size_t>(ordinal) + 1);
}
info.names[ordinal] = enumerant.getProto().getName().cStr();
}
if (!info.names.empty()) {
series->enum_info.emplace(path, std::move(info));
}
}
void append_scalar_point(RouteSeries *series,
const std::string &path,
double tm,
@@ -1195,156 +1023,9 @@ void append_dynamic_scalar_point(const std::string &path, double tm, double valu
append_scalar_point(ensure_dynamic_series(path, series), path, tm, value);
}
void append_scalar_value(const ResolvedNode &node,
const std::string *path_override,
const capnp::DynamicValue::Reader &raw_value,
double tm,
double value,
SeriesAccumulator *series) {
if (path_override == nullptr && node.fixed_slot >= 0) {
if (node.scalar_kind == ScalarKind::Enum) {
capture_enum_info(node.path, raw_value, series);
}
append_fixed_scalar_point(&series->fixed_series[static_cast<size_t>(node.fixed_slot)], tm, value);
return;
}
const std::string &path = path_override != nullptr ? *path_override : node.path;
if (node.scalar_kind == ScalarKind::Enum) {
capture_enum_info(path, raw_value, series);
}
append_dynamic_scalar_point(path, tm, value, series);
}
void append_fast_node(const ResolvedNode &node,
const capnp::DynamicValue::Reader &value,
double tm,
SeriesAccumulator *series,
const std::string *path_override = nullptr) {
switch (node.kind) {
case ResolvedNodeKind::Scalar: {
if (std::optional<double> scalar = scalar_value_to_double(value, node.scalar_kind); scalar.has_value()) {
append_scalar_value(node, path_override, value, tm, *scalar, series);
}
return;
}
case ResolvedNodeKind::Struct: {
const capnp::DynamicStruct::Reader reader = value.as<capnp::DynamicStruct>();
for (const ResolvedNode &child : node.children) {
if (!child.has_field || !reader.has(child.field)) continue;
if (path_override == nullptr) {
append_fast_node(child, reader.get(child.field), tm, series, nullptr);
} else {
const std::string child_path = child.segment.empty() ? *path_override : (*path_override + "/" + child.segment);
append_fast_node(child, reader.get(child.field), tm, series, &child_path);
}
}
return;
}
case ResolvedNodeKind::List: {
if (!node.element) {
return;
}
const capnp::DynamicList::Reader list = value.as<capnp::DynamicList>();
if (list.size() == 0) {
return;
}
if (node.skip_large_scalar_list && list.size() > 16) {
return;
}
const std::string &base_path = path_override != nullptr ? *path_override : node.path;
if (node.element->kind == ResolvedNodeKind::Scalar) {
for (uint i = 0; i < list.size(); ++i) {
if (std::optional<double> scalar = scalar_value_to_double(list[i], node.element->scalar_kind); scalar.has_value()) {
RouteSeries *item_series = ensure_list_scalar_series(base_path, i, series);
if (node.element->scalar_kind == ScalarKind::Enum && !item_series->path.empty()) {
capture_enum_info(item_series->path, list[i], series);
}
append_fixed_scalar_point(item_series, tm, *scalar);
}
}
return;
}
for (uint i = 0; i < list.size(); ++i) {
const std::string item_path = base_path + "/" + std::to_string(i);
append_fast_node(*node.element, list[i], tm, series, &item_path);
}
return;
}
case ResolvedNodeKind::Ignore:
return;
}
}
void append_event_fast_reader(cereal::Event::Which which,
const cereal::Event::Reader &event,
const SchemaIndex &schema,
const dbc::Database *can_dbc,
bool skip_raw_can,
double time_offset,
SeriesAccumulator *series) {
const uint16_t which_index = static_cast<uint16_t>(which);
if (which_index >= schema.by_which.size() || !schema.by_which[which_index].has_value()) {
return;
}
const ResolvedService &service = *schema.by_which[which_index];
const capnp::DynamicStruct::Reader dynamic_event(event);
const capnp::DynamicValue::Reader payload = dynamic_event.get(service.union_field);
const double tm = static_cast<double>(event.getLogMonoTime()) / 1.0e9 - time_offset;
append_fixed_scalar_point(&series->fixed_series[static_cast<size_t>(service.valid_slot)],
tm,
event.getValid() ? 1.0 : 0.0);
append_fixed_scalar_point(&series->fixed_series[static_cast<size_t>(service.log_mono_time_slot)],
tm,
static_cast<double>(event.getLogMonoTime()));
append_fixed_scalar_point(&series->fixed_series[static_cast<size_t>(service.seconds_slot)],
tm,
tm);
if (service.service_name == "can" || service.service_name == "sendcan") {
const CanServiceKind can_service = service.service_name == "can"
? CanServiceKind::Can
: CanServiceKind::Sendcan;
auto decode_message = [&](uint8_t bus, uint32_t address, const auto &dat_reader) {
const auto bytes = dat_reader.begin();
decode_can_frame(can_dbc, service.service_name, bus, address, bytes, dat_reader.size(), tm, series);
};
if (service.service_name == "can") {
for (const auto &msg : event.getCan()) {
append_can_frame(can_service,
static_cast<uint8_t>(msg.getSrc()),
msg.getAddress(),
msg.getDeprecated().getBusTime(),
msg.getDat(),
tm,
series);
if (!skip_raw_can) continue;
decode_message(static_cast<uint8_t>(msg.getSrc()), msg.getAddress(), msg.getDat());
}
} else {
for (const auto &msg : event.getSendcan()) {
append_can_frame(can_service,
static_cast<uint8_t>(msg.getSrc()),
msg.getAddress(),
msg.getDeprecated().getBusTime(),
msg.getDat(),
tm,
series);
if (!skip_raw_can) continue;
decode_message(static_cast<uint8_t>(msg.getSrc()), msg.getAddress(), msg.getDat());
}
}
if (skip_raw_can) {
return;
}
}
append_fast_node(service.payload, payload, tm, series);
}
void append_event_fast(cereal::Event::Which which,
int32_t eidx_segnum,
kj::ArrayPtr<const capnp::word> data,
const SchemaIndex &schema,
const dbc::Database *can_dbc,
bool skip_raw_can,
double time_offset,
@@ -1353,14 +1034,13 @@ void append_event_fast(cereal::Event::Which which,
return;
}
with_parseable_event(data, [&](const cereal::Event::Reader &event) {
append_event_fast_reader(which, event, schema, can_dbc, skip_raw_can, time_offset, series);
append_event_static_reader(which, event, can_dbc, skip_raw_can, time_offset, series);
});
}
void append_events_fast_range(const std::vector<Event> &events,
size_t begin,
size_t end,
const SchemaIndex &schema,
const dbc::Database *can_dbc,
bool skip_raw_can,
SeriesAccumulator *series) {
@@ -1369,7 +1049,6 @@ void append_events_fast_range(const std::vector<Event> &events,
append_event_fast(event_record.which,
event_record.eidx_segnum,
event_record.data,
schema,
can_dbc,
skip_raw_can,
0.0,
@@ -1802,7 +1481,7 @@ SeriesAccumulator extract_segment_series(const std::vector<Event> &events,
const size_t chunk_count = extract_chunk_count(events.size(), worker_budget, segment_workers);
if (chunk_count <= 1 || events.empty()) {
SeriesAccumulator series = make_series_accumulator(schema);
append_events_fast_range(events, 0, events.size(), schema, can_dbc, skip_raw_can, &series);
append_events_fast_range(events, 0, events.size(), can_dbc, skip_raw_can, &series);
return series;
}
@@ -1819,10 +1498,10 @@ SeriesAccumulator extract_segment_series(const std::vector<Event> &events,
workers.emplace_back([&, chunk]() {
const size_t begin = chunk * events_per_chunk;
const size_t end = std::min(events.size(), begin + events_per_chunk);
append_events_fast_range(events, begin, end, schema, can_dbc, skip_raw_can, &chunk_results[chunk]);
append_events_fast_range(events, begin, end, can_dbc, skip_raw_can, &chunk_results[chunk]);
});
}
append_events_fast_range(events, 0, std::min(events.size(), events_per_chunk), schema, can_dbc, skip_raw_can, &chunk_results[0]);
append_events_fast_range(events, 0, std::min(events.size(), events_per_chunk), can_dbc, skip_raw_can, &chunk_results[0]);
for (std::thread &worker : workers) {
worker.join();
}
@@ -2044,13 +1723,12 @@ void StreamAccumulator::appendEvent(kj::ArrayPtr<const capnp::word> data) {
}
}
append_event_fast_reader(which,
event,
impl_->schema,
impl_->can_dbc ? &*impl_->can_dbc : nullptr,
impl_->can_dbc.has_value(),
*impl_->time_offset,
&impl_->series);
append_event_static_reader(which,
event,
impl_->can_dbc ? &*impl_->can_dbc : nullptr,
impl_->can_dbc.has_value(),
*impl_->time_offset,
&impl_->series);
append_log_event(which, event, *impl_->time_offset, &impl_->logs, &impl_->last_alert_key);
if (which == cereal::Event::Which::SELFDRIVE_STATE) {
const auto sd = event.getSelfdriveState();

View File

@@ -13,7 +13,7 @@ from openpilot.tools.longitudinal_maneuvers.maneuversd import Action, Maneuver a
# thresholds for starting maneuvers
MAX_SPEED_DEV = 0.7 # deviation in m/s
MAX_CURV = 0.002 # 500 m radius
MAX_ROLL = 0.08 # 4.56°
MAX_ROLL = 0.12 # 6.8°
TIMER = 2.0 # sec stable conditions before starting maneuver
@dataclass

View File

@@ -19,7 +19,7 @@ function retry() {
return 1
}
function install_ubuntu_deps() {
function install_linux_deps() {
SUDO=""
if [[ ! $(id -u) -eq 0 ]]; then
@@ -30,61 +30,49 @@ function install_ubuntu_deps() {
SUDO="sudo"
fi
# Detect OS using /etc/os-release file
if [ -f "/etc/os-release" ]; then
source /etc/os-release
case "$VERSION_CODENAME" in
"jammy" | "kinetic" | "noble")
;;
*)
echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 24.04."
read -p "Would you like to attempt installation anyway? " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
;;
esac
# normal stuff, this mostly for bare docker images
if command -v apt-get > /dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y --no-install-recommends ca-certificates build-essential curl libcurl4-openssl-dev locales git xvfb
elif command -v dnf > /dev/null 2>&1; then
$SUDO dnf install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git xorg-x11-server-Xvfb
elif command -v yum > /dev/null 2>&1; then
$SUDO yum install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git xorg-x11-server-Xvfb
elif command -v pacman > /dev/null 2>&1; then
$SUDO pacman -Syu --noconfirm --needed base-devel ca-certificates curl git xorg-server-xvfb
elif command -v zypper > /dev/null 2>&1; then
$SUDO zypper --non-interactive refresh
$SUDO zypper --non-interactive install ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-locale git xorg-x11-server
elif command -v apk > /dev/null 2>&1; then
$SUDO apk add --no-cache ca-certificates build-base curl curl-dev musl-locales git xvfb
elif command -v xbps-install > /dev/null 2>&1; then
$SUDO xbps-install -Syu base-devel ca-certificates curl git libcurl-devel glibc-locales xorg-server-xvfb
else
echo "No /etc/os-release in the system. Make sure you're running on Ubuntu, or similar."
echo "Unsupported Linux distribution. Supported package managers: apt-get, dnf, yum, pacman, zypper, apk, xbps-install."
exit 1
fi
$SUDO apt-get update
# normal stuff, mostly for the bare docker image
$SUDO apt-get install -y --no-install-recommends \
ca-certificates \
build-essential \
curl \
libcurl4-openssl-dev \
locales \
git \
xvfb
if [[ -d "/etc/udev/rules.d/" ]]; then
# Setup jungle udev rules
$SUDO tee /etc/udev/rules.d/12-panda_jungle.rules > /dev/null <<EOF
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcf", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddef", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcf", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddef", MODE="0666"
$SUDO tee /etc/udev/rules.d/11-openpilot.rules > /dev/null <<-EOF
# Panda Jungle devices
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcf", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddef", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcf", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddef", MODE="0666"
EOF
# Panda devices
SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcc", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddee", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcc", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddee", MODE="0666"
# Setup panda udev rules
$SUDO tee /etc/udev/rules.d/11-panda.rules > /dev/null <<EOF
SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcc", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddee", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcc", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddee", MODE="0666"
EOF
# comma devices over ADB
SUBSYSTEM=="usb", ATTR{idVendor}=="04d8", ATTR{idProduct}=="1234", ENV{adb_user}="yes"
EOF
# Setup adb udev rules
$SUDO tee /etc/udev/rules.d/50-comma-adb.rules > /dev/null <<EOF
SUBSYSTEM=="usb", ATTR{idVendor}=="04d8", ATTR{idProduct}=="1234", ENV{adb_user}="yes"
EOF
# delete the old ones
$SUDO rm -f /etc/udev/rules.d/11-panda.rules /etc/udev/rules.d/12-panda_jungle.rules /etc/udev/rules.d/50-comma-adb.rules
$SUDO udevadm control --reload-rules && $SUDO udevadm trigger || true
fi
@@ -116,7 +104,7 @@ function install_python_deps() {
# --- Main ---
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
install_ubuntu_deps
install_linux_deps
echo "[ ] installed system dependencies t=$SECONDS"
elif [[ "$OSTYPE" == "darwin"* ]]; then
if [[ $SHELL == "/bin/zsh" ]]; then

View File

@@ -23,17 +23,12 @@ class SimulatedSensors:
def send_imu_message(self, simulator_state: 'SimulatorState'):
for _ in range(5):
dat = messaging.new_message('accelerometer', valid=True)
dat.accelerometer.sensor = 4
dat.accelerometer.type = 0x10
dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp
dat.accelerometer.init('acceleration')
dat.accelerometer.acceleration.v = [simulator_state.imu.accelerometer.x, simulator_state.imu.accelerometer.y, simulator_state.imu.accelerometer.z]
self.pm.send('accelerometer', dat)
# copied these numbers from locationd
dat = messaging.new_message('gyroscope', valid=True)
dat.gyroscope.sensor = 5
dat.gyroscope.type = 0x10
dat.gyroscope.timestamp = dat.logMonoTime # TODO: use the IMU timestamp
dat.gyroscope.init('gyroUncalibrated')
dat.gyroscope.gyroUncalibrated.v = [simulator_state.imu.gyroscope.x, simulator_state.imu.gyroscope.y, simulator_state.imu.gyroscope.z]
@@ -92,11 +87,12 @@ class SimulatedSensors:
# dmonitoringd output
dat = messaging.new_message('driverMonitoringState', valid=True)
dat.driverMonitoringState = {
"faceDetected": True,
"isDistracted": False,
"awarenessStatus": 1.,
}
dm = dat.driverMonitoringState
dm.alertLevel = log.DriverMonitoringState.AlertLevel.none
dm.activePolicy = log.DriverMonitoringState.MonitoringPolicy.vision
dm.visionPolicyState.faceDetected = True
dm.visionPolicyState.isDistracted = False
dm.visionPolicyState.awarenessPercent = 100
self.pm.send('driverMonitoringState', dat)
def send_camera_images(self, world: 'World'):