IQ.Pilot Release Commit @ bec7652
This commit is contained in:
89
.gitattributes
vendored
89
.gitattributes
vendored
@@ -2,56 +2,51 @@
|
||||
|
||||
# to move existing files into LFS:
|
||||
# git add --renormalize .
|
||||
selfdrive/assets/icons/iq/rotate-ccw.svg -filter -diff -merge -text
|
||||
selfdrive/assets/icons/iq/rotate-ccw.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons/iq/rotate-cw.svg -filter -diff -merge -text
|
||||
selfdrive/assets/icons/iq/rotate-cw.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons/iq/square-parking.svg -filter -diff -merge -text
|
||||
selfdrive/assets/icons/iq/square-parking.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/iq/rotate-ccw.svg -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/iq/rotate-ccw.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/iq/rotate-cw.svg -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/iq/rotate-cw.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/iq/square-parking.svg -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/iq/square-parking.png -filter -diff -merge -text
|
||||
|
||||
# Keep this icon in regular git (not LFS) for lightweight branding iteration.
|
||||
selfdrive/assets/icons_mici/experimental_mode_mici.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/experimental_mode_tizi.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/iqdynamic_mode_mici.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/iqdynamic_mode_tizi.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/iqstandard_mode_mici.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/iqstandard_mode_tizi.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_cone.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/konn3kt_icon.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/buttons/toggle_dot_enabled.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/experimental_mode_mici.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/experimental_mode_tizi.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/iqdynamic_mode_mici.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/iqdynamic_mode_tizi.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/iqstandard_mode_mici.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/iqstandard_mode_tizi.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_cone.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/konn3kt_icon.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/buttons/toggle_dot_enabled.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/buttons/toggle_pill_enabled.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/setup/green_button.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/setup/green_button_pressed.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/setup/green_dm.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/setup/green_info.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png -filter -diff -merge -text
|
||||
selfdrive/assets/images/spinner_comma.png -filter -diff -merge -text
|
||||
selfdrive/assets/images/k3_spinner.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons/camera.png -filter -diff -merge -text
|
||||
selfdrive/assets/fonts/Syncopate-Regular.ttf -filter -diff -merge -text
|
||||
selfdrive/assets/fonts/Tektur-Variable.ttf -filter -diff -merge -text
|
||||
tools/jotpluggler/assets/bootstrap-icons.ttf -filter -diff -merge -text
|
||||
|
||||
|
||||
|
||||
# IQ assets, including icon_longitudinal.png, should stay in normal git (not LFS).
|
||||
iqpilot/selfdrive/assets/** -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/buttons/toggle_pill_enabled.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/setup/green_button.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/setup/green_button_pressed.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/setup/green_dm.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/setup/green_info.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/images/spinner_comma.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/images/k3_spinner.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/camera.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/fonts/Syncopate-Regular.ttf -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/fonts/Tektur-Variable.ttf -filter -diff -merge -text
|
||||
iqpilot/tools/jotpluggler/assets/bootstrap-icons.ttf -filter -diff -merge -text
|
||||
|
||||
# mici UI icons stored as plain PNG — gitlab LFS server lacks these objects
|
||||
selfdrive/assets/icons_mici/buttons/button_circle_pressed.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/models.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/brightness.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/camera.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/keyboard/enter.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/sd_card.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/software.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/trips.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/settings/vehicle.png -filter -diff -merge -text
|
||||
selfdrive/assets/icons_mici/speedometer.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/models.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/brightness.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/camera.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/keyboard/enter.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/sd_card.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/software.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/trips.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/settings/vehicle.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons_mici/speedometer.png -filter -diff -merge -text
|
||||
|
||||
selfdrive/assets/icons/iq/*.png -filter -diff -merge -text
|
||||
iqpilot/selfdrive/assets/icons/iq/*.png -filter -diff -merge -text
|
||||
|
||||
58
.gitignore
vendored
58
.gitignore
vendored
@@ -7,6 +7,10 @@ venv/
|
||||
# perpetually dirty and sends the updater into an apply/restart loop that kills
|
||||
# ble-transportd (and comma.service) every few minutes.
|
||||
/.iqpilot/
|
||||
/.iqpilot-package-lock-sha256
|
||||
/gi
|
||||
/k3_log.txt
|
||||
/iqpilot/system/loggerd/encoder/v4l_decode
|
||||
.ci_cache
|
||||
.env
|
||||
.clang-format
|
||||
@@ -22,6 +26,13 @@ a.out
|
||||
.hypothesis
|
||||
.cache/
|
||||
|
||||
/iqdbc
|
||||
/msgq
|
||||
/openpilot
|
||||
/rednose
|
||||
/teleoprtc
|
||||
/tinygrad
|
||||
|
||||
/docs_site/
|
||||
|
||||
*.mp4
|
||||
@@ -46,6 +57,7 @@ a.out
|
||||
!artifacts/runtime/ble/rootfs/usr/local/venv/**/*.so
|
||||
artifacts/iqpilot_private/
|
||||
artifacts/iqpilot_*_private/python/iqpilot_private/**/*.cpython-*-darwin.so
|
||||
artifacts/test-results/
|
||||
*.a
|
||||
*.clb
|
||||
*.class
|
||||
@@ -59,14 +71,14 @@ clcache
|
||||
compile_commands.json
|
||||
compare_runtime*.html
|
||||
|
||||
selfdrive/pandad/pandad
|
||||
cereal/services.h
|
||||
cereal/gen
|
||||
cereal/messaging/bridge
|
||||
selfdrive/ui/translations/tmp
|
||||
selfdrive/car/tests/cars_dump
|
||||
system/camerad/camerad
|
||||
system/camerad/test/ae_gray_test
|
||||
iqpilot/selfdrive/pandad/pandad
|
||||
iqpilot/cereal/services.h
|
||||
iqpilot/cereal/gen
|
||||
iqpilot/cereal/messaging/bridge
|
||||
iqpilot/selfdrive/ui/translations/tmp
|
||||
iqpilot/selfdrive/car/tests/cars_dump
|
||||
iqpilot/system/camerad/camerad
|
||||
iqpilot/system/camerad/test/ae_gray_test
|
||||
|
||||
.coverage*
|
||||
coverage.xml
|
||||
@@ -79,13 +91,13 @@ flycheck_*
|
||||
cppcheck_report.txt
|
||||
comma*.sh
|
||||
|
||||
selfdrive/modeld/models/*.pkl
|
||||
!selfdrive/modeld/models/driving_vision_tinygrad.pkl
|
||||
!selfdrive/modeld/models/driving_policy_tinygrad.pkl
|
||||
!selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl
|
||||
!selfdrive/modeld/models/driving_vision_metadata.pkl
|
||||
!selfdrive/modeld/models/driving_policy_metadata.pkl
|
||||
!selfdrive/modeld/models/dmonitoring_model_metadata.pkl
|
||||
iqpilot/selfdrive/modeld/models/*.pkl
|
||||
!iqpilot/selfdrive/modeld/models/driving_vision_tinygrad.pkl
|
||||
!iqpilot/selfdrive/modeld/models/driving_policy_tinygrad.pkl
|
||||
!iqpilot/selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl
|
||||
!iqpilot/selfdrive/modeld/models/driving_vision_metadata.pkl
|
||||
!iqpilot/selfdrive/modeld/models/driving_policy_metadata.pkl
|
||||
!iqpilot/selfdrive/modeld/models/dmonitoring_model_metadata.pkl
|
||||
iqpilot/modeld*/thneed/compile
|
||||
iqpilot/modeld*/models/*.thneed
|
||||
iqpilot/modeld*/models/*.pkl
|
||||
@@ -124,10 +136,6 @@ Pipfile
|
||||
PLAN.md
|
||||
TASK.md
|
||||
|
||||
### JetBrains ###
|
||||
!.idea/customTargets.xml
|
||||
!.idea/tools/*
|
||||
!.run/*
|
||||
/.antigravitycli/
|
||||
/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/_iqcrypto.cpython-312-aarch64-linux-gnu.so
|
||||
/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc_handshake.cpython-312-aarch64-linux-gnu.so
|
||||
@@ -135,20 +143,21 @@ TASK.md
|
||||
/iqpilot/selfdrive/iqlocd/iqlocd
|
||||
/iqpilot/selfdrive/iqlocd/models/generated/car.cpp
|
||||
/iqpilot/selfdrive/iqlocd/models/generated/car.h
|
||||
/tools/scripts/debug/
|
||||
/iqpilot/tools/scripts/debug/
|
||||
!scripts/iqpilot/iqos_usrlocal_larch64.tar.zst
|
||||
|
||||
# pretty-build capture logs (model compile / mpc + dbc codegen stdout+stderr)
|
||||
*_tinygrad.pkl.log
|
||||
*_metadata.pkl.log
|
||||
selfdrive/controls/lib/*_mpc_lib/gen.log
|
||||
tools/cabana/dbc/*.json.log
|
||||
iqpilot/selfdrive/controls/lib/*_mpc_lib/gen.log
|
||||
iqpilot/tools/cabana/dbc/*.json.log
|
||||
iqpilot/selfdrive/iqlocd/models/generated/
|
||||
osmaps_build/
|
||||
|
||||
# --- local scratch/debug + large model artifacts (never commit) ---
|
||||
/infinite_driver/
|
||||
/konn3kt_private/
|
||||
/.mull/
|
||||
/_pq*.py
|
||||
/bus_topo.py
|
||||
/cluster_bus.py
|
||||
@@ -167,8 +176,7 @@ osmaps_build/
|
||||
/SUNNYPILOT_REMEDIATION_PLAN.md
|
||||
/prompt.md
|
||||
/iqos44_bluetooth_gamepad_spec.md
|
||||
tools/mac_egpu/*.pkl
|
||||
selfdrive/ui/tests/test_ui/nav_demo_report/
|
||||
iqpilot/selfdrive/ui/tests/test_ui/nav_demo_report/
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
*.mlpackage/
|
||||
@@ -176,5 +184,3 @@ selfdrive/ui/tests/test_ui/nav_demo_report/
|
||||
|
||||
# fetcher: closed-source precompiled component — ship only the obfuscated .so
|
||||
/iqpilot/models_private_src/fetcher.py
|
||||
|
||||
/tools/iqmacvisiond/macos/dist/
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[lfs]
|
||||
url = https://gitlab.com/commaai/openpilot-lfs.git/info/lfs
|
||||
pushurl = ssh://git@gitlab.com/commaai/openpilot-lfs.git
|
||||
locksverify = false
|
||||
230
CHANGELOG.md
230
CHANGELOG.md
@@ -1,230 +0,0 @@
|
||||
# IQ.Pilot User Changelog
|
||||
|
||||
This changelog is written for everyday drivers and focuses on what you will notice on the road, as well as changes under-the-hood.
|
||||
|
||||
## IQ.Pilot 1.0c
|
||||
|
||||
|
||||
**Navigate on IQ.Pilot**
|
||||
|
||||
Navigate on IQ.Pilot is here. Search for a destination, pick from route alternatives, and let IQ.Pilot guide you turn by turn with live rerouting when you miss an exit. Along with on-screen-maps, you can see your position, the route, and upcoming turns all at once without leaving the driving view. Speed, turn, and highway exit handling are route-aware, so IQ.Pilot knows what's coming before you do. Mapbox is included at no cost to you for enhanced online map data and routing. On supported vehicles, Navigate on IQ.Pilot will command turn signals automatically based on the route. Map Curve speed control pulls limit data from OpenStreetMaps when offline and Mapbox when available.
|
||||
|
||||
When approaching a highway exit, IQ.Pilot now initiates the lane change toward the exit. If your car has Blind Spot Monitoring (BSM), it can perform the lane change fully automatically, there's no blinker nudge required. Without BSM, you confirm with a brief blinker push and IQ.Pilot takes it from there.
|
||||
|
||||
**Speed Limit Control (SLC)**
|
||||
|
||||
IQ.Pilot can now read and act on speed limits from your car's dash, Mapbox, TomTom, HERE, (included at no cost to our users!) and offline maps. You pick what mode you want in settings: display only, warn you when you're over, or actually adjust your cruise speed. You also pick which source wins when they disagree (dash, Mapbox, map data, highest, or lowest reported limit). There's a look ahead setting so IQ.Pilot can start reacting to an upcoming speed change before you hit the sign to decelerate to the limit before crossing into the new speed limit.
|
||||
|
||||
SLC can now also raise your cruise speed automatically when the speed limit increases, not just lower it. Toggle "confirm higher speed limit" off to enable this, SLC will adjust up to a higher accepted limit with a small prompt, without any confirmation input needed.
|
||||
|
||||
**Camera Alerts (Speed Cameras, Red Light Cameras, and Surveillance / ALPR Cameras)**
|
||||
|
||||
IQ.Pilot now detects upcoming speed cameras, red light cameras, and ALPR/surveillance cameras (including Flock Safety cameras) sourced from OpenStreetMap's and alerts you before you reach them. Each camera type has its own toggle so you can pick what you want to be warned about. Speed cameras can also trigger a speed reduction to the limit when detected if enabled. Camera data is sourced from OSM and is updated periodically.
|
||||
|
||||
**Direct Flock / ALPR Camera Detection (Bluetooth & WiFi)**
|
||||
|
||||
Beyond map data, IQ.Pilot can now spot Flock Safety and similar ALPR cameras directly over the air by their Bluetooth and WiFi signatures as you approach them. Because it's sensing the actual hardware rather than relying on a map, this works anywhere, including fully offline and even for cameras that haven't been mapped yet, so you get a heads-up the moment one is nearby. When IQ.Pilot picks up a camera directly, that live detection takes priority over map data, so you see a single clear "Flock Camera Detected" alert instead of a duplicate. It shares the same Flock camera alert toggle, runs quietly in the background only when that's enabled, and is built to stay out of the way of your Bluetooth (phone link, game controllers) and WiFi connections.
|
||||
|
||||
**IQ.Dynamic and Driving Behavior**
|
||||
|
||||
In IQ.Dynamic blended mode, when IQ.Pilot sees a stop light ahead, and the model agrees you need to stop, and there's no lead car to track, it will now commit (force) to stopping on its own, no lead car required. Gas pedal overrides it instantly. The stop prediction horizon is adjustable in IQ.Dynamic settings. Behavior for curves, low-speed driving, stopped leads, speed-limit fallbacks, and vision-based stops is now configurable. On-device IQ.Dynamic tuning is accessible by double-tapping IQ.Dynamic in longitudinal mode selection.
|
||||
|
||||
Force Stops now include "Smooth Stops" under the same toggle thank's to SpysyWeeb! With Force Stops on, all stops including model predicted stops at signs and lights now use the smooth landing law, so every stop settles gently rather than dropping in hard. The minimum force stop distance slider enforces minimum distance when configured.
|
||||
|
||||
IQ.Dynamic on supported Volkswagen platforms including MQB, and PQ now support blending OEM Stock Radar ACC with IQ.Dynamic to allow for a blended longitudinal experience while maintaining E2E IQ.Pilot functionality.
|
||||
|
||||
**Driving Models**
|
||||
|
||||
IQ.Pilot updated to a new default driving model, `Pop!`
|
||||
|
||||
IQ.Pilot also has the latest bleeding-edge models, as always, including the latest TobyRL model, NoPP model, DeeperRL model, DeepRLv3/4/5 models, OP Model 16 Deep, and all future RL models as they are released.
|
||||
|
||||
IQ.Pilot maintains supports for all legacy models like `Notre Dame (v1/3)`, `FarmVille`, `WD40`, etc.
|
||||
|
||||
**Dashcam, Live View, and Alerts**
|
||||
|
||||
- WebSSH now connects in under 15 seconds and connects the first time, every time.
|
||||
- Live View in the Konn3kt app now processes full-resolution HDR input from the Comma 4 driver camera for a noticeably sharper, and more accurate picture. Live View performance was optimized, and microphone audio (one way) streaming is now included.
|
||||
- You can fully disable dashcam recording from the Konn3kt app. Turning it off stops all recording, no logs, no video, no audio, full stop.
|
||||
- Audible alerts now ramp in volume smoothly instead of cutting in abruptly for enhanced auditory alerts.
|
||||
- On-road live streaming, with Two-Way Audio streaming from your IQ.Pilot devices camera feed live through the Konn3kt app. Onroad choppiness fixed, keyframe-on-demand enabled for instant camera switches, and variable network-condition-adaptive bitrate.
|
||||
- Konn3kt can now take a snapshot from any camera (road, driver, or wide) on-demand, both onroad and offroad, and returns it as a JPEG instantly for a glance.
|
||||
|
||||
**Volkswagen**
|
||||
|
||||
Volkswagen support got a significant overhaul:
|
||||
|
||||
- Lateral and longitudinal tuning greatly improved.
|
||||
- Accelerator override behavior now matches stock feel.
|
||||
- MQB SnG handling improved for supported non-EPB ACC FtS vehicles.
|
||||
- IQ.Pilot now supports all VW PQ and MQB CC-only and (A)CC-less cars, including CC-only PQ cars without an ADAS gateway.
|
||||
- Volkswagen Passat B7 (PQ) with TRW450 now supports Stop-and-Go.
|
||||
- Volkswagen MEB/MQBevo now only go on-road when in Drive and no longer in Park for 15 minutes after parking the car.
|
||||
- Added Passat (PQ) model year ECU fingerprint.
|
||||
- Konn3kt can now check EPS compatibility and LKAS coding status on VW MQB vehicles with a comma power.
|
||||
- Konn3kt can enable LKAS coding on supported VW MQB vehicles with EPS that didn't ship with factory LKAS with a comma power.
|
||||
- Volkswagen MQB/PQ now supports full Radar Blending with IQ.Dynamic for an enhanced E2E + Highway experience. (limited by OEM ACC minimum speed)
|
||||
|
||||
**Volkswagen MEB and MQBevo**
|
||||
|
||||
IQ.Pilot now **officially** supports the Volkswagen MEB and MQBevo platforms! Including the ID.4, ID.3, ID.5, and Golf MK8 through model year 2025, as long as you have a compatible camera or gateway harness. Both LKAS and ACC are supported. This is the foundation of the `release-meb` branch, which is auto-synced from `release` and tailored specifically for these platforms.
|
||||
|
||||
**Toyota/Lexus**
|
||||
|
||||
Support added for Stop-and-Go and SDSU for Toyota/Lexus vehicles.
|
||||
|
||||
**Hyundai/Kia**
|
||||
|
||||
Fingerprint coverage expanded to cover more Hyundai/Kia variants that were previously unrecognized, and proper CAN-FD handling for newer HKG.
|
||||
|
||||
**UI — Comma 4 (mici)**
|
||||
|
||||
The Comma 4's offroad UI has been completely redesigned and has had major performance optimizations, it contains the same settings that BIG UI contains on Comma 3x/3 devices.
|
||||
|
||||
**UI — Comma 3x and Comma 3 (tizi/tici)**
|
||||
|
||||
The tizi/tici (Comma 3x and Comma 3) onroad and offroad UI has been fully redesigned as well, matching the new IQ.Pilot design language with a brand new home screen, status bar, settings menu's, and should be a much better experience.
|
||||
|
||||
**UI Improvements**
|
||||
|
||||
IQ.Pilot's on-road UI got a number of improvements:
|
||||
|
||||
- The Steering Assistance border now has a lower portion that distinguishes lateral only engagement from full engagement.
|
||||
- IQLong Personality can now be cycled on-road by tapping the driver-monitoring icon on BIG UI devices. The icon color reflects the currently selected personality as well as an on screen current profile confirmation.
|
||||
- IQLong mode IQ.Standard has been renamed to IQ.Chill to lessen confusion on long modes.
|
||||
- IQLong mode can now be cycled on-road by tapping the nucleus icon in the top right corner on BIG UI devices. The icon changes to reflect IQ.Chill/Dynamic/Pilot.
|
||||
- Fixed augmented road view calibration showing invalid calibration data on the model path / tracked lane lines by refreshing the matrix cache correctly for calibration to properly update on startup with Navigation enabled.
|
||||
- Live Konn3kt accent color sync: whatever color you pick in the Konn3kt app flows to your device UI instantly.
|
||||
- Revamped Branch switcher to properly switch branches, and updater has had bugfixes to fix install issues where the device claims to have updated but has not actually updated.
|
||||
|
||||
|
||||
**IQ.OS 4.9.1**
|
||||
|
||||
IQ.OS 4.9.1 is bundled with IQ.Pilot 1.0c. IQ.OS is available for all supported devices: Comma 3, Comma 3x, Comma 4, Konik A1/M, and Mr.One C3/C3 Lite. It's a lightweight OS based on Ubuntu 24.04, includes Bluetooth (BLE), has a much smaller install footprint, and is continuously optimized for IQ.Pilot.
|
||||
|
||||
- Konn3kt now stays online regardless of IQ.Pilot's status. If IQ.Pilot fails to boot, Konn3kt remains available so you can switch branches, SSH in, and recover remotely without a physical connection, including over cellular.
|
||||
- Bug causing konn3kt setup time on a fresh install dropped from ~30 minutes fixed, setup dropped down to ~10 seconds.
|
||||
- Automatic LocalAPI configuration in Konn3kt.
|
||||
- Fixed Upstream Comma 4/3x/3 AGNOS Wi-Fi driver crash causing random crashes while driving.
|
||||
- Fixed Comma 4 green-dot-matrix text aliasing issue.
|
||||
- Fixed Comma 4 display calibration not showing for accurate colors.
|
||||
|
||||
**Updater: Pre-download Mode**
|
||||
|
||||
A new "Update Install Mode" setting in Software settings gives you control over how updates are applied:
|
||||
|
||||
- **Predownload Only** — updates download in the background but wait for you to confirm before installing.
|
||||
- **Predownload + Preinstall** — downloads and installs automatically on next boot, as it was before.
|
||||
|
||||
**Konn3kt Services**
|
||||
|
||||
Konn3kt's WebApp has migrated to `app.konn3kt.com`
|
||||
|
||||
Connection stability between Konn3kt and IQ.Pilot (Konn3ktion) is greatly improved.
|
||||
|
||||
**eSIM**
|
||||
|
||||
eSIM detection, provisioning, and profile management groundwork is now built into the device. The app can detect whether your device has an embedded SIM, provision it, and manage profiles without a physical SIM swap. eSIM support requires a compatible data plan; Contact IQ.Pilot support in the discord for known working eSIM carriers and plans. Note: eSIM is experimental and generally requires a hotspot-style data plan or an MVNO that does not IMEI filter.
|
||||
|
||||
**Additional 1.0c Updates**
|
||||
|
||||
**Navigate on IQ.Pilot and Offline Maps**
|
||||
|
||||
- Added Home, Work, and Recent destination shortcuts, route cancellation, saved-destination management, and course-up map rotation.
|
||||
- Added an interactive off-road map with panning and current-location display, plus on-screen maneuver artwork for turns, merges, forks, off-ramps, continuation instructions, and arrival.
|
||||
- Added navigation-memory handling so important fork and exit guidance remains available when a model output briefly omits it.
|
||||
- Added full offline routing through a packaged Valhalla runtime. Navigation can now operate without Mapbox or an active internet connection.
|
||||
- Added simultaneous installation of multiple offline regions, including offline raster tiles for the displayed map as well as routing and road metadata.
|
||||
- Added separate Online On-Screen Maps and Offline On-Screen Maps controls, resumable regional downloads, combined routing-and-tile download progress, automatic mapd v2 region recognition and missing-data restoration.
|
||||
- Added a hosted regional tile-bundle service with a fallback source, background tile decoding, and bounded texture caching to keep map work off the UI render path.
|
||||
- Reduced navigation CPU, GPU, and memory overhead so maps can remain open for long drives without competing with the driving model.
|
||||
|
||||
**IQ Speed Assist and Camera Alerts**
|
||||
|
||||
- Added the new IQ Speed Assist architecture, with TomTom data alongside dashboard, Mapbox, and offline OpenStreetMap sources.
|
||||
- Added optional cruise-set-speed mirroring, percentage-based offset zones for low, medium, and higher speed ranges, and earlier, smoother upcoming-speed-limit reactions in the longitudinal cruise envelope.
|
||||
- Added clearer on-road SLC source, state, pending-limit, and adaptation feedback.
|
||||
- Added individual controls for speed, red-light, and Flock/ALPR alerts, plus optional speed-camera slowdown using the detected limit and a configurable safety factor.
|
||||
- Added haptic speed-camera feedback on supported Hyundai, Kia, and Genesis vehicles.
|
||||
|
||||
**Construction Zone Assist**
|
||||
|
||||
- Added optional camera-based Construction Zone Assist, which detects bright-orange work-zone barrels and markers.
|
||||
- Added an adjustable work-zone target speed (60 mph by default), plus daylight, road-speed, and active-zone checks to reduce unrelated reflective detections.
|
||||
- Added lightweight processing designed to run alongside the driving and driver-monitoring models.
|
||||
|
||||
**IQ.Dynamic, Stops, and Longitudinal Control**
|
||||
|
||||
- Added separate IQ.Dynamic road-speed and lead-speed thresholds, configurable curve, low-speed, slower-lead, stopped-lead, and model-predicted-stop activation, and an adjustable model-stop prediction horizon.
|
||||
- Added automatic Volkswagen stock-radar set-speed and following-gap synchronization when radar blending is enabled on supported PQ vehicles.
|
||||
- Added an independent custom stopping-distance adjustment, smoother stopped-lead pull-away, and end-to-end launch assistance when the model predicts an opening path.
|
||||
- Added dedicated departure chimes for an opening path and a pulling-away lead, Experimental Lead MPC, and end-to-end cruise convergence toward the selected speed as the road opens.
|
||||
- Added configurable end-to-end set-speed selection using either a preset or the driver’s current target, per-platform stopping overrides, accelerator-override longitudinal control on supported vehicles, and predictive reactions to speed limits and curves.
|
||||
|
||||
**Steering, Lane Changes, and Environment View**
|
||||
|
||||
- Added an on-road Always-On Lateral control path for supported Hyundai LFA buttons and Bluetooth-controller commands for testing or controlling it in Joystick Mode.
|
||||
- Added continuous lane changes while the blinker is held, while retaining driver confirmation for subsequent maneuvers.
|
||||
- Added configurable model-action smoothing, angle-based lateral control, optional VW ALC torque blending, a dedicated Volkswagen PQ HCA7 torque controller, and live-learned curvature correction for supported Volkswagen MEB configurations.
|
||||
- Added an optional Environment View that can replace the model scene and render 3D object boxes, a ground grid, lane lines, road edges, and the planned path.
|
||||
- Added a precompiled Qualcomm IQ.Vision model and bandwidth-efficient camera preprocessing to reduce contention with the primary driving model.
|
||||
|
||||
**Driving Models and IQModeld**
|
||||
|
||||
- Added the unified IQModeld runtime and native bridge for current combined models and legacy split models.
|
||||
- Added fused vision-and-policy execution for supported supercombo bundles, zero-copy camera-frame handling through the tinygrad runner, and unified combined-artifact, combined-split, fused, tinygrad, and ONNX runner support.
|
||||
- Added current-model redownload, manifest refresh when published models change, a clear Driving Model Updating state, and engagement gating until the selected model is ready.
|
||||
|
||||
**Home, On-Road UI, and Visuals**
|
||||
|
||||
- Added dedicated Routes, Navigation, Video, and status surfaces, a selectable home-panel widget, 60 fps off-road BIG UI presentation, and device-specific frame pacing.
|
||||
- Added Comma 4 anti-aliasing, augmented-road presentation, driver-camera orientation improvements, and an expanded status bar with temperature, vehicle state, Konn3kt status, and connected Wi-Fi network name.
|
||||
- Added the installed IQ.OS version to Software settings, tappable branch information in the BIG UI header, smooth edge-swipe navigation, transitions, parallax, shadows, animated controls, and a seamless build-spinner-to-UI transition.
|
||||
- Added a glowing primary-lead orb, IQ.Pilot teal and pink acceleration-bar styling, a top-bar Silent Mode bell, Night Mode display sleep after sunset, and screen recording through Konn3kt.
|
||||
|
||||
**Dashcam and Route Viewer**
|
||||
|
||||
- Increased qcamera resolution by 5× while balancing storage and upload use.
|
||||
- Added crash-safe route writes, frequent durable checkpoints, power-loss preservation, and boot-time recovery for incomplete recordings.
|
||||
- Added an on-device Routes screen with date, duration, camera availability, cloud-upload state, and local, uploading, uploaded, and cloud-only badges.
|
||||
- Added local playback of road, wide, and driver video with synchronized audio; cloud-only route discovery and streaming through Konn3kt; and synchronized telemetry, model path, steering angle, driver-monitoring, vehicle speed, and cruise-set-speed overlays.
|
||||
- Added qlog-only route visualization, fullscreen playback, auto-hiding controls, loading feedback, scrubbing, fast-forward speeds, hardware HEVC decode, local-time presentation, bounded playback buffering, and screen-awake behavior during playback.
|
||||
|
||||
**Live View, Audio, and WebSSH**
|
||||
|
||||
- Added a Live View indicator on the driving screen, dual-camera picture-in-picture streaming, and live model-path overlay while both the device and Konn3kt use cellular.
|
||||
- Added instant keyframe requests, network-adaptive encoder bitrate, and full-resolution HDR driver-camera input on Comma 4.
|
||||
|
||||
**Konn3kt Services, Bluetooth, and Backup**
|
||||
|
||||
- Added independent device services that stay available when IQ.Pilot is stopped or cannot enter its main UI, including OS-level recovery over Wi-Fi and cellular.
|
||||
- Added a dedicated `iquploaderd` route-and-log uploader, automatic IQ.Pilot crash-log uploads, Volkswagen and Tesla odometer display, backup-and-restore services, encrypted backup archives, backup status, and Konn3kt RPC support for compatible Volkswagen coding and diagnostics.
|
||||
- Added direct Bluetooth Low Energy control across comma 3, comma 3X, comma 4, Konik, and Mr.One hardware, including authenticated requests, replay protection, settings synchronization, automatic discovery and pairing, and fallback when internet connectivity is unavailable.
|
||||
- Added BLE control for supported IQ.Pilot, vehicle, display, network, navigation, and driving settings; live propagation to active services; and setup-stage Wi-Fi scanning, Wi-Fi connection, network status, channel selection, install start, setup progress, and installation status.
|
||||
|
||||
**Volkswagen PQ and MQB**
|
||||
|
||||
- Added Always-On Lateral for ECAN, non-ECAN/ACAN, CC-only, and CC-less PQ configurations; SEAT Alhambra TRW460i Stop-and-Go with automatic resume; and Stop-and-Go for additional electronic-parking-brake configurations.
|
||||
- Added MQB-A0 automatic resume and steering-lockout options, standstill handling for supported non-EPB ACC vehicles, and ACC-command suppression while braking.
|
||||
- Added dedicated PQ radar engagement, cancellation, set-speed, acceleration, and following-gap management; automatic PQ EPS-patch detection; and patch-aware minimum-steering-speed handling.
|
||||
- Added a PQ firmware tool with backup, dump upload, patch detection, programming, and recovery; persistent PQ LKAS coding over TP2.0/KWP; model-year ECU fingerprinting; expanded Passat identification; distance-button mapping; dashboard personality presentation; and continued MQB lateral control while cruise is faulted.
|
||||
- Added stock lateral handoff when IQ.Pilot lateral is inactive, explicit Volkswagen readiness handling, and Volkswagen odometer persistence with cloud backup.
|
||||
|
||||
**Platform Support**
|
||||
|
||||
- Added MEB/MQBevo steering ratios, steering behavior, zero-speed steering where supported, ACC-HUD handling, and Drive/Park state handling. Supported configurations include ID.3, ID.4, ID.5, and Golf Mk8 through model year 2025.
|
||||
- Added Honda-specific final-stopping deceleration control, Subaru Creep from Standstill, and guarded Tesla vehicle-bus parsing for supported harnesses.
|
||||
- Added more Hyundai/Kia/Genesis CAN-FD and HDA2 handling, radar-track and corner-radar controls, Camera SCC and platform-specific cruise controls, Auto Cruise Control and Auto Engage, custom steering and steering-rate controls, lane-change steering-rate controls, and expanded parameter/fingerprint diagnostics.
|
||||
|
||||
**IQ.OS, Power, Network, and Recovery**
|
||||
|
||||
- Updated the bundled platform to IQ.OS 4.9.1, with device-specific boot and display support, Comma 4 display calibration, HDR camera and display-color support, USB 3 logging, USB link-error recovery, 2 GB compressed zram swap, and consistent startup runtime packaging.
|
||||
- Added FastSleep deep standby, including five-minute idle entry after parking, low-voltage entry, staged voltage shutdown thresholds, suspension of high-power services, Konn3kt recovery connectivity during standby, voltage-aware power decisions, and immediate wake on ignition or charging.
|
||||
- Added zero-touch Bluetooth onboarding, Konn3kt pairing-code presentation, IQ.OS update confirmation, Bluetooth game-controller support for Joystick Mode, and Bluetooth operation independent of the main IQ.Pilot process.
|
||||
- Added a redesigned Network settings experience, explicit Wi-Fi Disconnect, cellular reconnection after APN changes, Comma 3X SIM electrical-probe recovery, and QR/manual eSIM activation with profile listing, refresh, enable, disable, and deletion.
|
||||
- Added Force On-Road for a temporary ten-minute parked diagnostic session, an Update & Reboot recovery-screen action, and USB Storage mode.
|
||||
|
||||
**Updater, Reliability, and Performance**
|
||||
|
||||
- Added Konn3kt confirmation before IQ.OS updates, interrupted submodule/artifact-preparation recovery, precompiled release preparation for comma 3, comma 3X, and comma 4, and crash-screen update-and-reboot without entering the full UI.
|
||||
- Added independent model, navigation, map, uploader, backup, and perception services; navigation/map recovery that does not block engagement; power-state coordination; startup parameter ownership and permission normalization; message-queue dead-reader cleanup; lower shared-memory use; and clearer build, process-state, and diagnostic output.
|
||||
54
Jenkinsfile
vendored
54
Jenkinsfile
vendored
@@ -93,7 +93,7 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
|
||||
retry (3) {
|
||||
def date = sh(script: 'date', returnStdout: true).trim();
|
||||
device(device_ip, "set time", "date -s '" + date + "'")
|
||||
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
|
||||
device(device_ip, "git checkout", extra + "\n" + readFile("iqpilot/selfdrive/test/setup_device_ci.sh"))
|
||||
}
|
||||
steps.each { item ->
|
||||
def name = item[0]
|
||||
@@ -202,61 +202,63 @@ node {
|
||||
parallel (
|
||||
'onroad tests': {
|
||||
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("build openpilot", "cd iqpilot/system/manager && ./build.py"),
|
||||
step("check dirty", "release/check-dirty.sh"),
|
||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||
step("onroad tests", "pytest iqpilot/selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||
])
|
||||
},
|
||||
'HW + Unit Tests': {
|
||||
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||
step("audit proprietary entrypoints", "python3 scripts/iqpilot/audit_proprietary_entrypoints.py --check"),
|
||||
step("audit proprietary runtime", "python3 scripts/iqpilot/audit_proprietary_runtime.py"),
|
||||
step("build", "cd iqpilot/system/manager && ./build.py"),
|
||||
step("test pandad", "pytest iqpilot/selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "iqpilot/selfdrive/pandad/"]]),
|
||||
step("test power draw", "pytest -s iqpilot/system/hardware/tici/tests/test_power_draw.py"),
|
||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest iqpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["iqpilot/system/loggerd/"]]),
|
||||
step("test manager", "pytest iqpilot/system/manager/test/test_manager.py"),
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
step("build openpilot", "cd iqpilot/system/manager && ./build.py"),
|
||||
step("test pandad loopback", "pytest iqpilot/selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
])
|
||||
},
|
||||
'camerad OX03C10': {
|
||||
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||
step("build", "cd iqpilot/system/manager && ./build.py"),
|
||||
step("test camerad", "pytest iqpilot/system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest iqpilot/system/camerad/test/test_exposure.py"),
|
||||
])
|
||||
},
|
||||
'camerad OS04C10': {
|
||||
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||
step("build", "cd iqpilot/system/manager && ./build.py"),
|
||||
step("test camerad", "pytest iqpilot/system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest iqpilot/system/camerad/test/test_exposure.py"),
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||
step("build", "cd iqpilot/system/manager && ./build.py"),
|
||||
step("test sensord", "pytest iqpilot/system/sensord/tests/test_sensord.py"),
|
||||
])
|
||||
},
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
step("build", "cd iqpilot/system/manager && ./build.py", [diffPaths: ["iqpilot/selfdrive/dmonitoringmodeld/", "iqpilot/selfdrive/iqmodeld/", "pyproject.toml", "iqpilot/selfdrive/test/process_replay/model_replay.py"]]),
|
||||
step("model replay", "iqpilot/selfdrive/test/process_replay/model_replay.py", [diffPaths: ["iqpilot/selfdrive/dmonitoringmodeld/", "iqpilot/selfdrive/iqmodeld/", "pyproject.toml", "iqpilot/selfdrive/test/process_replay/model_replay.py"]]),
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||
step("build openpilot", "cd iqpilot/system/manager && ./build.py"),
|
||||
step("test pandad loopback", "SINGLE_PANDA=1 pytest iqpilot/selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
step("test pandad spi", "pytest iqpilot/selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||
step("test amp", "pytest iqpilot/system/hardware/tici/tests/test_amplifier.py"),
|
||||
// TODO: enable once new AGNOS is available
|
||||
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
||||
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
||||
// step("test esim", "pytest iqpilot/system/hardware/tici/tests/test_esim.py"),
|
||||
step("test qcomgpsd", "pytest iqpilot/system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["iqpilot/system/qcomgpsd/"]]),
|
||||
])
|
||||
},
|
||||
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,4 +1,4 @@
|
||||
IQ.Lvbs License v0.1a
|
||||
IQ.Lvbs License v1.8.2
|
||||
|
||||
Copyright (c) 2026 IQ.Lvbs LLC, a part of Project Teal Lvbs Inc. All Rights Reserved.
|
||||
|
||||
@@ -91,7 +91,7 @@ GOVERNING LAW
|
||||
|
||||
This license shall be governed by the laws of the State of Illinois, United
|
||||
States of America. Any disputes arising under this license shall be subject
|
||||
to the exclusive jurisdiction of the courts located in The State of Illinois, United States.
|
||||
to the exclusive jurisdiction of the courts located in Henry County, Illinois.
|
||||
|
||||
---
|
||||
|
||||
|
||||
107
LICENSE.md
107
LICENSE.md
@@ -1,107 +0,0 @@
|
||||
IQ.Lvbs License v0.1a
|
||||
|
||||
Copyright (c) 2026 IQ.Lvbs LLC, a part of Project Teal Lvbs Inc. All Rights Reserved.
|
||||
|
||||
DEFINITIONS
|
||||
|
||||
"Software" refers to IQ.Pilot, konn3kt, and all associated source code,
|
||||
documentation, and assets owned by the Copyright Holder.
|
||||
|
||||
"Open Components" refers to portions of the Software explicitly marked as
|
||||
open source.
|
||||
|
||||
"Proprietary Components" refers to all portions of the Software not made
|
||||
available to the public in source form.
|
||||
|
||||
"Copyright Holder" refers to IQ.Lvbs LLC, a part of Project Teal Lvbs Inc.
|
||||
|
||||
GRANT OF LICENSE
|
||||
|
||||
Subject to the terms of this license, you are granted a limited,
|
||||
non-exclusive, revocable license to:
|
||||
|
||||
1. View, study, and learn from the Open Components
|
||||
2. Modify the Open Components for personal, internal, or open-source public use
|
||||
3. Run the Software for personal, non-commercial purposes
|
||||
|
||||
RESTRICTIONS
|
||||
|
||||
You may NOT:
|
||||
|
||||
1. Claim ownership of any part of the Software, excluding your own
|
||||
modifications that do not incorporate Proprietary Components.
|
||||
|
||||
2. Reverse engineer, decompile, disassemble, or in any way attempt to
|
||||
circumvent the obfuscation of the Proprietary Components.
|
||||
|
||||
3. Use the Software or any derivative for commercial purposes without
|
||||
explicit written permission from the Copyright Holder.
|
||||
|
||||
4. Remove or alter any copyright notices or this license.
|
||||
|
||||
5. Sublicense, sell, or transfer rights to the Software.
|
||||
|
||||
6. Use the Software and/or its source code to compete with or create a
|
||||
substantially similar product.
|
||||
|
||||
7. Use the Software in closed source software not licensed by IQ.Lvbs LLC.
|
||||
|
||||
CONSEQUENCES OF VIOLATION
|
||||
|
||||
In the event any Restriction is violated, any product created using inspiration from, or source code from, IQ.Pilot or Konn3kt shall be subject to a licensing fee determined solely by the Copyright Holder. Additionally, the violating party hereby grants IQ.Lvbs LLC an exclusive, irrevocable, worldwide, royalty-free license to use any and all assets from the infringing product on IQ.Lvbs webpages, advertising materials, and in any other manner IQ.Lvbs sees fit.
|
||||
|
||||
OWNERSHIP
|
||||
|
||||
All rights, title, and interest in the Software remain exclusively with the
|
||||
Copyright Holder. Any modifications, improvements, or derivative works you
|
||||
create based on the Software are owned by the Copyright Holder. By
|
||||
contributing modifications, you irrevocably assign all rights to the
|
||||
Copyright Holder.
|
||||
|
||||
PROPRIETARY COMPONENTS
|
||||
|
||||
The Proprietary Components are provided in binary or obfuscated form only.
|
||||
Reverse engineering, decompilation, or disassembly of Proprietary Components
|
||||
is strictly prohibited. Violation of this provision entitles IQ.Lvbs LLC to
|
||||
pursue all available legal remedies to protect its intellectual property and
|
||||
trade secrets.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT
|
||||
HOLDER DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
|
||||
NON-INFRINGEMENT.
|
||||
|
||||
LIMITATION OF LIABILITY
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR
|
||||
OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE. THE USER ACCEPTS FULL
|
||||
RESPONSIBILITY FOR ANY AND ALL LIABILITIES WHEN USING IQ.LVBS SOFTWARE.
|
||||
|
||||
TERMINATION
|
||||
|
||||
This license terminates automatically if you violate any of its terms. Upon
|
||||
termination, you must destroy all copies of the Software in your possession.
|
||||
|
||||
The Copyright Holder reserves the right to revoke this license at any time
|
||||
for any reason.
|
||||
|
||||
GOVERNING LAW
|
||||
|
||||
This license shall be governed by the laws of the State of Illinois, United
|
||||
States of America. Any disputes arising under this license shall be subject
|
||||
to the exclusive jurisdiction of the courts located in Henry County, Illinois.
|
||||
|
||||
---
|
||||
|
||||
For commercial licensing inquiries, contact: support@iqlvbs.com
|
||||
|
||||
IQ.Pilot is a fork of Stock OpenPilot maintained by Comma.ai, Inc. licensed under the following notice:
|
||||
Copyright (c) 2018, Comma.ai, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
## Running IQ.Pilot
|
||||
* A modern comma, or clone device to run this software (Comma 3, 3x, 4, Konik A1/M, Mr.One C3, C3 Lite)
|
||||
* One of [the supported cars](https://gitlvb.teallvbs.xyz/IQ.Lvbs/IQ.Pilot/src/branch/release/iqdbc_repo/docs/CARS.md).
|
||||
* One of [the supported cars](https://gitlvb.teallvbs.xyz/IQ.Lvbs/IQ.Pilot/src/branch/release/artifacts/package_sources/iqdbc/docs/CARS.md).
|
||||
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
|
||||
#### Wondering if IQ.Pilot supports your car? IQ.Pilot supports every car [stock openpilot](https://gitlvb.teallvbs.xyz/IQ.Lvbs/IQ.Pilot/src/branch/release/iqdbc_repo/docs/CARS.md) supports!
|
||||
#### Wondering if IQ.Pilot supports your car? IQ.Pilot supports every car [stock openpilot](https://gitlvb.teallvbs.xyz/IQ.Lvbs/IQ.Pilot/src/branch/release/artifacts/package_sources/iqdbc/docs/CARS.md) supports!
|
||||
## Installation
|
||||
#### Installing Via Installer URL:
|
||||
#### Enter the following into your device custom URL box to install IQ.Pilot:
|
||||
@@ -31,7 +31,7 @@
|
||||
#### If you'd like to backup your previous installation as well, paste the following command below to install IQ.Pilot:
|
||||
`cd .. && mv openpilot openpilot_backup_X && git clone https://git.konn3kt.com/IQ.Lvbs/IQ.Pilot -b release && cd openpilot && sudo reboot`
|
||||
#### Alternatively, you can use your existing fork's built in tools to switch your branch as well:
|
||||
`git remote add iqpilot https://git.konn3kt.com/IQ.Lvbs/IQ.Pilot && op switch iqpilot release`
|
||||
`git remote add iqpilot https://git.konn3kt.com/IQ.Lvbs/IQ.Pilot && iq switch iqpilot release`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
IQ.Pilot 1.0c
|
||||
========================
|
||||
* Navigation;)
|
||||
|
||||
IQ.Pilot 1.0b
|
||||
========================
|
||||
* Major Refactor!
|
||||
119
SConstruct
119
SConstruct
@@ -6,13 +6,19 @@ import platform
|
||||
import shlex
|
||||
import numpy as np
|
||||
|
||||
import iqdbc
|
||||
import msgq as msgq_package
|
||||
import panda
|
||||
import rednose as rednose_package
|
||||
import tinygrad
|
||||
|
||||
import SCons.Errors
|
||||
|
||||
SCons.Warnings.warningAsException(True)
|
||||
|
||||
# scons only auto-loads a site dir named site_scons at the repo root; ours lives under tools/,
|
||||
# so replicate what _load_site_scons_dir does (sys.path for site_tools imports + run site_init)
|
||||
SITE_DIR = Dir('#tools/scons').abspath
|
||||
SITE_DIR = Dir('#iqpilot/tools/scons').abspath
|
||||
if SITE_DIR not in sys.path:
|
||||
sys.path.insert(0, SITE_DIR)
|
||||
import site_init # noqa: F401
|
||||
@@ -32,9 +38,19 @@ AddOption('--minimal',
|
||||
action='store_false',
|
||||
dest='extras',
|
||||
default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS)
|
||||
help='the minimum build to run openpilot. no tests, tools, etc.')
|
||||
help='the minimum IQ.Pilot build. no tests, tools, etc.')
|
||||
AddOption('--verbose', action='store_true', help='show full compiler/linker command lines instead of short build lines')
|
||||
|
||||
python_paths = [
|
||||
Dir("#").abspath,
|
||||
]
|
||||
for p in reversed(python_paths):
|
||||
if p not in sys.path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
if external_pythonpath := os.environ.get("PYTHONPATH"):
|
||||
python_paths += [p for p in external_pythonpath.split(os.pathsep) if p and p not in python_paths]
|
||||
|
||||
# Detect platform
|
||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||
if platform.system() == "Darwin":
|
||||
@@ -43,7 +59,7 @@ if platform.system() == "Darwin":
|
||||
elif arch == "aarch64" and os.path.isfile('/TICI'):
|
||||
arch = "larch64"
|
||||
try:
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from iqpilot.system.hardware import HARDWARE
|
||||
HARDWARE.set_power_save(False)
|
||||
os.sched_setaffinity(0, range(8))
|
||||
except Exception:
|
||||
@@ -66,10 +82,10 @@ if arch != "Darwin":
|
||||
env = Environment(
|
||||
ENV={
|
||||
"PATH": os.environ['PATH'],
|
||||
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
||||
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
||||
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
||||
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
||||
"PYTHONPATH": os.pathsep.join(python_paths + [Dir(f"#iqpilot/third_party/acados").abspath]),
|
||||
"ACADOS_SOURCE_DIR": Dir("#iqpilot/third_party/acados").abspath,
|
||||
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#iqpilot/third_party/acados/acados_template").abspath,
|
||||
"TERA_PATH": Dir("#").abspath + f"/iqpilot/third_party/acados/{arch}/t_renderer"
|
||||
},
|
||||
CC='clang',
|
||||
CXX='clang++',
|
||||
@@ -90,37 +106,41 @@ env = Environment(
|
||||
CXXFLAGS=["-std=c++1z"],
|
||||
CPPPATH=[
|
||||
"#",
|
||||
"#msgq",
|
||||
"#third_party",
|
||||
"#third_party/json11",
|
||||
"#third_party/linux/include",
|
||||
"#third_party/acados/include",
|
||||
"#third_party/acados/include/blasfeo/include",
|
||||
"#third_party/acados/include/hpipm/include",
|
||||
"#third_party/catch2/include",
|
||||
"#third_party/libyuv/include",
|
||||
"#iqpilot",
|
||||
iqdbc.INCLUDE_PATH,
|
||||
msgq_package.INCLUDE_PATH,
|
||||
panda.INCLUDE_PATH,
|
||||
rednose_package.INCLUDE_PATH,
|
||||
os.path.dirname(rednose_package.__file__),
|
||||
"#iqpilot/cereal/gen/cpp",
|
||||
"#iqpilot/third_party",
|
||||
"#iqpilot/third_party/json11",
|
||||
"#iqpilot/third_party/linux/include",
|
||||
"#iqpilot/third_party/acados/include",
|
||||
"#iqpilot/third_party/acados/include/blasfeo/include",
|
||||
"#iqpilot/third_party/acados/include/hpipm/include",
|
||||
"#iqpilot/third_party/catch2/include",
|
||||
"#iqpilot/third_party/libyuv/include",
|
||||
],
|
||||
LIBPATH=[
|
||||
"#common",
|
||||
"#msgq_repo",
|
||||
"#third_party",
|
||||
"#selfdrive/pandad",
|
||||
"#rednose/helpers",
|
||||
f"#third_party/libyuv/{arch}/lib",
|
||||
f"#third_party/acados/{arch}/lib",
|
||||
"#iqpilot/common",
|
||||
"#iqpilot/third_party",
|
||||
"#iqpilot/selfdrive/pandad",
|
||||
f"#iqpilot/third_party/libyuv/{arch}/lib",
|
||||
f"#iqpilot/third_party/acados/{arch}/lib",
|
||||
],
|
||||
RPATH=[],
|
||||
CYTHONCFILESUFFIX=".cpp",
|
||||
COMPILATIONDB_USE_ABSPATH=True,
|
||||
REDNOSE_ROOT="#",
|
||||
REDNOSE_ROOT=rednose_package.INCLUDE_PATH,
|
||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||
toolpath=["#tools/scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||
toolpath=["#iqpilot/tools/scons/site_tools", rednose_package.SCONS_TOOL_PATH],
|
||||
)
|
||||
|
||||
# Arch-specific flags and paths
|
||||
if arch == "larch64":
|
||||
env.Append(CPPPATH=[
|
||||
"#third_party/opencl/include",
|
||||
"#iqpilot/third_party/opencl/include",
|
||||
"/usr/include/aarch64-linux-gnu",
|
||||
])
|
||||
env.Append(LIBPATH=[
|
||||
@@ -201,7 +221,8 @@ else:
|
||||
np_version = SCons.Script.Value(np.__version__)
|
||||
Export('envCython', 'np_version')
|
||||
|
||||
Export('env', 'arch', 'ffmpeg_libs')
|
||||
tinygrad_dir = os.path.dirname(tinygrad.__file__)
|
||||
Export('env', 'arch', 'ffmpeg_libs', 'tinygrad_dir')
|
||||
|
||||
# Setup cache dir
|
||||
default_cache_dir = os.environ.get('SCONS_CACHE_DIR') or ('/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache')
|
||||
@@ -212,53 +233,47 @@ Clean(["."], cache_dir)
|
||||
# ********** start building stuff **********
|
||||
|
||||
# Build common module
|
||||
SConscript(['common/SConscript'])
|
||||
SConscript(['iqpilot/common/SConscript'])
|
||||
Import('_common')
|
||||
common = [_common, 'json11', 'zmq']
|
||||
Export('common')
|
||||
|
||||
# Build messaging (cereal + msgq + socketmaster + their dependencies)
|
||||
# Enable swaglog include in submodules
|
||||
env_swaglog = env.Clone()
|
||||
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
||||
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
||||
SConscript(['iqdbc_repo/SConscript'], exports={'env': env_swaglog})
|
||||
msgq = File(msgq_package.LIB_PATH)
|
||||
visionipc = File(msgq_package.VISIONIPC_LIB_PATH)
|
||||
msgq_python = File(msgq_package.PYTHON_LIB_PATH)
|
||||
Export('msgq', 'visionipc', 'msgq_python')
|
||||
|
||||
SConscript(['cereal/SConscript'])
|
||||
SConscript(['iqpilot/cereal/SConscript'])
|
||||
|
||||
Import('socketmaster', 'msgq')
|
||||
Import('socketmaster')
|
||||
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
||||
Export('messaging')
|
||||
|
||||
|
||||
# Build other submodules
|
||||
SConscript(['panda/SConscript'])
|
||||
|
||||
# Build rednose library
|
||||
SConscript(['rednose/SConscript'])
|
||||
rednose = File(rednose_package.LIB_PATH)
|
||||
Export('rednose')
|
||||
|
||||
# Build system services
|
||||
SConscript([
|
||||
'system/loggerd/SConscript',
|
||||
'system/proprietary_runtime/SConscript',
|
||||
'iqpilot/system/loggerd/SConscript',
|
||||
'iqpilot/system/proprietary_runtime/SConscript',
|
||||
])
|
||||
|
||||
if arch == "larch64":
|
||||
SConscript(['system/camerad/SConscript'])
|
||||
SConscript(['iqpilot/system/camerad/SConscript'])
|
||||
|
||||
# Build openpilot
|
||||
SConscript(['third_party/SConscript'])
|
||||
SConscript(['iqpilot/third_party/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/SConscript'])
|
||||
SConscript(['iqpilot/selfdrive/SConscript'])
|
||||
|
||||
SConscript(['iqpilot/SConscript'])
|
||||
|
||||
if Dir('#tools/cabana/').exists() and GetOption('extras'):
|
||||
SConscript(['tools/replay/SConscript'])
|
||||
if Dir('#iqpilot/tools/cabana/').exists() and GetOption('extras'):
|
||||
SConscript(['iqpilot/tools/replay/SConscript'])
|
||||
if arch != "larch64":
|
||||
SConscript(['tools/cabana/SConscript'])
|
||||
if Dir('#tools/jotpluggler/').exists():
|
||||
SConscript(['tools/jotpluggler/SConscript'])
|
||||
SConscript(['iqpilot/tools/cabana/SConscript'])
|
||||
if Dir('#iqpilot/tools/jotpluggler/').exists():
|
||||
SConscript(['iqpilot/tools/jotpluggler/SConscript'])
|
||||
|
||||
|
||||
env.CompilationDatabase('compile_commands.json')
|
||||
|
||||
@@ -1,433 +0,0 @@
|
||||
# Upstream sync plan — `tools/cabana` and `tools/jotpluggler`
|
||||
|
||||
Hand-port the current upstream (commaai/openpilot) versions of Cabana and jotpluggler into
|
||||
iqpilot without losing konn3kt integration. iqpilot does not use comma connect: every route
|
||||
list, route-file listing, JWT and live-CAN path goes through `api-iqlabs.konn3kt.com`, and the
|
||||
DBC source is `iqdbc`, not `opendbc_repo`.
|
||||
|
||||
Reference clone used for this analysis: `/tmp/openpilot-upstream` @ `e7b1ee3a5`
|
||||
("jp: fix segment range parsing", 2026-08-05).
|
||||
|
||||
---
|
||||
|
||||
## 1. Where we actually are
|
||||
|
||||
Baselines were established by blob-matching every iqpilot file against upstream history, not by
|
||||
reading commit messages.
|
||||
|
||||
| Tool | iqpilot baseline | Upstream HEAD | Gap |
|
||||
|---|---|---|---|
|
||||
| `tools/cabana` | `bcdeec313` (2025-12-16, #36884) | `e7b1ee3a5` (2026-08-05) | **53 commits, ~8 months** |
|
||||
| `tools/jotpluggler` | `61608db78` (2026-07-15, #38353) | `e7b1ee3a5` | **6 commits, ~3 weeks** |
|
||||
| `tools/plotjuggler` | `a46ff01ca` (2026-01-19) | `e7b1ee3a5` | 14 commits (secondary scope) |
|
||||
|
||||
Cabana is the real work. jotpluggler was hand-ported in May–July 2026 (`4b68073c4`, `b26218e85`,
|
||||
`2cd89a198`) and is nearly current — most of its files are **byte-identical** to upstream HEAD.
|
||||
|
||||
A critical structural fact: upstream moved the whole tree under a nested `openpilot/` package
|
||||
(`#38219`/`#38220`/`#38223`, 2026-06-21) and moved the HAL to `common/hardware/` (`#38202`).
|
||||
iqpilot has **not** adopted either. Every ported file needs a de-prefix pass:
|
||||
|
||||
- `#include "openpilot/cereal/..."` → `"cereal/..."`
|
||||
- `#include "common/hardware/hw.h"` → `"system/hardware/hw.h"`
|
||||
- `#include "json11/json11.hpp"` → `"third_party/json11/json11.hpp"`
|
||||
- `repo_root() / "openpilot" / "tools" / ...` → `repo_root() / "tools" / ...`
|
||||
- SCons nodes `#openpilot/common`, `#openpilot/cereal` → `#common`, `#cereal`
|
||||
- `opendbc_repo/opendbc/dbc` → `iqdbc/dbc`; `#opendbc_repo` → `#iqdbc_repo`
|
||||
|
||||
---
|
||||
|
||||
## 2. konn3kt invariants — the contract the port must not break
|
||||
|
||||
These are the only genuinely IQ-owned surfaces. Everything else in both trees is upstream code we
|
||||
are behind on. **Anything not on this list should be taken from upstream verbatim** (modulo §1).
|
||||
|
||||
### Cabana
|
||||
|
||||
| File | What is IQ-owned | Disposition |
|
||||
|---|---|---|
|
||||
| `utils/api.{cc,h}` | `CommaApi::BASE_URL = API_HOST ?: https://api-iqlabs.konn3kt.com`; RSA-signed device JWT; Qt `HttpRequest` | **Delete** — fold into `tools/replay/api.{cc,h}` (§3.A) |
|
||||
| `streams/routes.{cc,h}` | konn3kt device list + route listing | Rewrite on upstream HEAD shape, konn3kt endpoints |
|
||||
| `konn3kt_canproxy.py` | `canlived → konn3kt relay → local ZMQ → Cabana` proxy | Keep; must stay wired to the Device/ZMQ stream (§3.C) |
|
||||
| `SConscript` | iqdbc paths, non-nested paths, `PrettyAction`, extra libs | Rebase onto upstream, re-apply IQ deltas |
|
||||
| `mainwin.cc` | "Load DBC from commaai/**iqdbc**" menu | Re-apply rename only |
|
||||
| `dbc/generate_dbc_json.py` | `from iqdbc.car import ...` | Re-apply rename only |
|
||||
| `assets/assets.cc` (tracked) | bootstrap-icons LFS bypass | See §3.K |
|
||||
| `dbc/car_fingerprint_to_dbc.json` (+`.log`) | tracked build product | Keep tracked |
|
||||
| `.gitignore`, `README.md` | binary name, iqdbc, konn3kt route examples | Re-apply |
|
||||
|
||||
### jotpluggler
|
||||
|
||||
All IQ deltas are already carrying `// IQ.Pilot patch:` comments — preserve that convention.
|
||||
|
||||
| File | IQ patch |
|
||||
|---|---|
|
||||
| `SConscript` | `.venv` site-packages injection; `iqdbc.dbc.generator.generator.create_all()` instead of upstream `get_generated_dbcs()`; no `bootstrap_icons` module; explicit `avformat/avcodec/avutil/x264/yuv/z/bz2/zstd/curl/ssl/crypto` + `OpenCL`/`va`/`va-drm`/`drm`; `PrettyAction`; guards `imgui.MESA_DIR` existence |
|
||||
| `icons.cc` | vendored `tools/jotpluggler/assets/bootstrap-icons.ttf` (third_party/bootstrap is LFS, TTF not checked in) |
|
||||
| `common.cc` | iqpilot `common/util.h` has no `check_system`; inline `std::system` + stderr |
|
||||
| `runtime.cc` | no `common/yuv.h`; vendored libyuv lacks `NV12ToABGR` → `NV12ToARGB` + `ARGBToABGR` |
|
||||
| `sketch_layout.cc` | `PyDownloader::getRouteFiles` → `CommaApi2::httpGet(BASE_URL + "/v1/route/<name>/files")`; `androidLog` (cereal not renamed); iqdbc paths |
|
||||
| `app.{cc,h}`, `layout.cc`, `layout_io.cc`, `map.cc`, `custom_series.cc` | path/include de-prefixing; `LogOrigin::Android`; iqdbc |
|
||||
| `dbc.h` | empty-multiplex guard (`mux.empty() ? 0 : std::stoi(mux)`) — a genuine bugfix, **upstream this** |
|
||||
| `generate_event_extractors.py` | `getBusTimeDEPRECATED()` (iqpilot cereal keeps a flat field, not upstream's `deprecated :group`) |
|
||||
| `pluggle.py` | iqpilot-only launcher (upstream has none) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Conflict matrix — upstream change vs konn3kt
|
||||
|
||||
### A. File downloading / API moved from C++ to Python (`#37497`, `#38430`) — **highest impact**
|
||||
|
||||
Upstream cabana HEAD calls `PyDownloader::getDevices()`, `getDeviceRoutes()`, `getRouteFiles()`
|
||||
(`tools/replay/py_downloader.h`), which shell into `openpilot/tools/lib/`. iqpilot's replay has
|
||||
**no** `py_downloader`; it keeps a C++/libcurl path in `tools/replay/api.{cc,h}`
|
||||
(`CommaApi2::BASE_URL`, `create_token(use_jwt)`, `httpGet`). jotpluggler already bridges this gap
|
||||
in `sketch_layout.cc` — **reuse that pattern**, don't invent a second one.
|
||||
|
||||
Resolution: extend `tools/replay/api.{cc,h}` with
|
||||
|
||||
```
|
||||
std::string getDevices(); // GET /v1/me/devices/
|
||||
std::string getDeviceRoutes(dongle_id, start_ms, end_ms, preserved);
|
||||
// GET /v1/devices/<id>/routes_segments?start=&end=
|
||||
// GET /v1/devices/<id>/routes/preserved
|
||||
std::string getRouteFiles(route); // GET /v1/route/<name>/files (already reachable via httpGet)
|
||||
```
|
||||
|
||||
returning the same JSON shapes upstream's `PyDownloader` returns, then port upstream's `routes.cc`
|
||||
verbatim with `PyDownloader::` → `CommaApi2::`. Upstream's HEAD `routes.cc` is already Qt-free
|
||||
(json11 + `strptime`/`strftime` for the preserved-route ISO timestamps) — that code ports as-is.
|
||||
|
||||
Auth semantics to preserve: `create_token(!Hardware::PC())` — device RSA-JWT on device,
|
||||
`~/.comma/auth.json` `access_token` on a PC. Upstream's Python path only ever does the latter.
|
||||
|
||||
Consequence: `tools/cabana/utils/api.{cc,h}` (Qt `QNetworkAccessManager` + duplicate JWT code)
|
||||
**gets deleted**, which drops `Qt5Network` from the cabana link and converges with §3.B.
|
||||
|
||||
### B. de-Qt: `#37519`, `#37521`, `#37522`, `#37523`, `#38357`, `#38359`, `#38360`
|
||||
|
||||
The bulk of the 53-commit gap. `QString`→`std::string` through the DBC core, new Qt-free
|
||||
`core/{can_data,color,message_id,settings}.h`, Qt/core split in `dbc/dbcqt.{cc,h}`, QtXml dropped
|
||||
(bootstrap SVG symbol extraction hand-rolled in `utils/util.cc`), QtConcurrent dropped, QtSerialBus
|
||||
dropped (SocketCAN now raw `linux/can` sockets, `#37553` excludes it on macOS).
|
||||
|
||||
**Zero konn3kt content.** Pure upstream adoption. It is also why almost every cabana file shows a
|
||||
large diff — those are not IQ changes, they are upstream changes we lack.
|
||||
|
||||
Net effect on `SConscript`: `qt_modules` goes from
|
||||
`["Widgets","Gui","Core","Network","Concurrent","DBus","Xml"]` → `["Widgets","Gui","Core"]`,
|
||||
and `Qt5SerialBus` / `QtSerialBus` framework drop out.
|
||||
|
||||
### C. `--zmq` bridge path (`#38484`) — **breaks konn3kt_canproxy if ported blind**
|
||||
|
||||
Upstream HEAD `DeviceStream::start()` forks `cereal/messaging/bridge <addr> /"can/"` and
|
||||
`streamThread()` then always subscribes on `127.0.0.1`. `bridge <ip> <whitelist>` is
|
||||
`zmq_to_msgq` (see `cereal/messaging/bridge.cc:60`) — it ZMQ-subscribes from `<ip>` and republishes
|
||||
to local msgq.
|
||||
|
||||
iqpilot's documented remote-CAN workflow is the opposite direction:
|
||||
`konn3kt_canproxy.py` sets `ZMQ=1` and **publishes** capnp `Event` frames on a local ZMQ `can`
|
||||
socket; the user then picks *Live → Device → ZMQ → 127.0.0.1*. Under upstream HEAD semantics that
|
||||
forks a bridge which subscribes to the same loopback endpoint the proxy publishes on and
|
||||
republishes into msgq, while cabana reads ZMQ — the proxy path stops being the thing cabana reads.
|
||||
|
||||
Resolution: keep the pre-`#38484` direct-attach semantics as an explicit IQ escape hatch. Port
|
||||
upstream's bridge-forking code, but subscribe to `zmq_address` directly (no bridge fork) when the
|
||||
stream is started in "attach to an existing ZMQ publisher" mode — either by treating loopback
|
||||
addresses as attach-mode, or by adding a third radio button next to MSGQ/ZMQ. Mark it
|
||||
`// IQ.Pilot patch:` and state why (konn3kt_canproxy). **This must be re-verified end-to-end on a
|
||||
real device before the sync is called done** (§5.3).
|
||||
|
||||
### D. libyuv removed, `common/yuv.h` added (`#38306`)
|
||||
|
||||
jotpluggler already patches around this. Cabana's `cameraview.cc` does NV12 conversion too.
|
||||
Decision: port `common/yuv.h` into iqpilot once, then **delete** the `runtime.cc` IQ patch and take
|
||||
upstream's `cameraview.cc` verbatim. One-time cost, removes a recurring patch site.
|
||||
|
||||
### E. Vendored native deps via `comma-deps-*` wheels (`#37327`, `#37994`, `#37681`, `#38308`)
|
||||
|
||||
Upstream `SConstruct` now exports `ffmpeg_libs` and pulls capnproto/ffmpeg/zstd/zeromq/json11/
|
||||
libusb/imgui/bootstrap-icons from pip wheels. iqpilot pins `imgui`/`libusb` from
|
||||
`git.konn3kt.com/IQ.Lvbs/dependencies`, vendors json11 in `third_party/json11`, and has **no**
|
||||
`bootstrap_icons` module and **no** `ffmpeg_libs` export.
|
||||
|
||||
Resolution: add an `ffmpeg_libs` list + `Export` to iqpilot's `SConstruct` mirroring upstream's
|
||||
(`avformat avcodec swresample avutil` [+ `x264 z` [+ `va va-drm drm`]]) so both tool SConscripts can
|
||||
take upstream's form nearly verbatim instead of hand-listing libs. Keep `third_party/json11`.
|
||||
Do **not** chase the wheels.
|
||||
|
||||
### F. `androidLog` → `operatingSystemLog` (`#38209`)
|
||||
|
||||
iqpilot cereal still has `androidLog`. Keep the existing `sketch_layout.cc` / `app.h` IQ patch.
|
||||
Renaming cereal is out of scope (it is a schema change with device-side blast radius).
|
||||
|
||||
### G. Tests: catch2 dropped (`#38408`), unittest conversion (`#38387`, `#38384`)
|
||||
|
||||
Upstream deleted `tools/cabana/tests/test_runner.cc` and split a deliberately Qt-free
|
||||
`tests/test_dbc_core` target (objects built from the **base** env, linking no Qt — it exists
|
||||
specifically to stop Qt creeping back into the DBC core). Adopt that shape; drop `test_runner.cc`.
|
||||
jotpluggler gains `test_jotpluggler.py`.
|
||||
|
||||
### H. `cabana` launcher script + `_cabana` binary (`#37814`, `c02cf706a`, `05cf8023a`)
|
||||
|
||||
Upstream builds `Program('_cabana')` and ships `tools/cabana/cabana` as a bash launcher that
|
||||
installs Qt if missing and runs `scons -u openpilot/tools/cabana/_cabana openpilot/cereal/messaging/bridge`.
|
||||
iqpilot builds `Program('cabana')` — so `tools/cabana/cabana` is currently a **committed binary**.
|
||||
Adopting upstream's split means: `.gitignore` `cabana` → `_cabana`, and the launcher's scons
|
||||
targets de-prefixed to `tools/cabana/_cabana cereal/messaging/bridge`.
|
||||
|
||||
### I. `assets.cc` / bootstrap icons (`#37994`, `71290f380`, `15267e408`)
|
||||
|
||||
Upstream generates `assets/assets.cc` at build time (`rcc` over an `assets.generated.qrc` that
|
||||
interpolates the packaged bootstrap-icons SVG path) and gitignores it. iqpilot tracks the generated
|
||||
14.9k-line `assets.cc` because the icons package isn't available.
|
||||
|
||||
Recommended: vendor the bootstrap-icons **SVG** next to the already-vendored TTF
|
||||
(`tools/jotpluggler/assets/bootstrap-icons.ttf`), add a tiny local shim module exposing
|
||||
`SVG_PATH`/`TTF_PATH`, adopt upstream's generation, and untrack `assets.cc`. This also lets
|
||||
jotpluggler's `icons.cc` IQ patch collapse back to upstream's `BOOTSTRAP_ICONS_TTF` define.
|
||||
Acceptable fallback: keep tracking `assets.cc` and keep both patches.
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
**Phases 0–2 are done and landed.** Both tools are at upstream `e7b1ee3a5` (2026-08-05) with
|
||||
konn3kt preserved. Departures from the plan as written, and what remains:
|
||||
|
||||
- §3.I resolved better than planned: `third_party/bootstrap/bootstrap-icons.svg` turned out to be
|
||||
**tracked already**, so upstream's build-time embedding was adopted as-is — no vendored SVG, and
|
||||
the 14.9k-line generated `assets.cc` is gone (it was never tracked; it was a gitignored build
|
||||
product).
|
||||
- §3.D done both ways: `common/yuv.{cc,h}` ported, and `tools/replay/framereader.cc` moved off
|
||||
libyuv too, so the jotpluggler `runtime.cc` patch disappeared entirely.
|
||||
- Unplanned dependency found during the port: upstream's `#38430` also added download/decompress/
|
||||
parse instrumentation to `LogReader`, which jotpluggler's load-stats panel reads. Ported into
|
||||
iqpilot's `LogReader` around its own FileReader/decompress steps (so decompress is timed
|
||||
separately, unlike upstream where the Python downloader does it inline).
|
||||
- §3.C landed as a **three-mode** `DeviceStream` (`Msgq` / `Zmq` / `Bridge`) rather than an
|
||||
escape hatch. Upstream's ZMQ path forks a bridge that republishes to **msgq** while still
|
||||
setting `ZMQ=1` and subscribing over **ZMQ** — that combination reads nothing. Keeping the modes
|
||||
explicit preserves upstream's bridge convenience *and* the direct attach `konn3kt_canproxy.py`
|
||||
needs, and fixes that inconsistency. `--bridge <ip>` is a new flag.
|
||||
- Also de-comma'd beyond the plan: jotpluggler's "Useradmin" / "comma connect" buttons became a
|
||||
single **konn3kt** link (`KONN3KT_APP_HOST`, default `https://konn3kt.com`, same
|
||||
`<dongle_id>/<log_id>` path shape `tools/lib/logreader.py` already parses).
|
||||
|
||||
### Phase 3 verification actually performed (macOS arm64, live konn3kt)
|
||||
|
||||
- **konn3kt API, live, real credentials** — every endpoint the port builds returns 200 with real
|
||||
data: `/v1/me/devices/` (2 devices), `/v1/devices/<id>/routes_segments?start=&end=` (1776
|
||||
routes), `/v1/devices/<id>/routes/preserved` (16), `/v1/route/<name>/files` (48 logs / 48 qlogs
|
||||
/ 48 qcameras).
|
||||
- **Real route in the real GUI** — `_cabana --qcam 0f53129ed44f6920|000001a5--07db1da02e` loaded
|
||||
**48 valid segments** and came up as a foreground Cocoa app (not offscreen).
|
||||
- **Live CAN over the ZMQ attach** — a local publisher mimicking `konn3kt_canproxy.py` fed
|
||||
`_cabana --zmq 127.0.0.1`; cabana ingested **2179 CAN frames** and wrote them to
|
||||
`~/cabana_live_stream/.../rlog`. This is the exact path `#38484` would have broken.
|
||||
- **Fixed while verifying:** `utils::icon()` painted a null QPixmap whenever an empty icon id was
|
||||
requested (`ToolButton("")` in `chartswidget.cc`), logging two `QPainter` warnings on every
|
||||
dark-theme macOS start. Pre-existing upstream bug, not a port regression; guarded here and worth
|
||||
upstreaming alongside the `dbc.h` mux fix.
|
||||
|
||||
**Still outstanding:**
|
||||
- **Phase 3.3 (device half):** the konn3kt relay accepts the websocket and authorizes the stream,
|
||||
but `0f53129ed44f6920` emits no frames because `canlived` only runs while the `CanLiveStreaming`
|
||||
param is set. Needs a device with that enabled to close the loop.
|
||||
- **Phase 3.5:** cabana has not been built for larch64.
|
||||
- Charts/video widgets were not visually inspected — `screencapture` is blocked by macOS
|
||||
screen-recording permission for the terminal, so GUI rendering is evidenced by process state and
|
||||
the ingested-frame log rather than by a screenshot.
|
||||
|
||||
Tests added: `tools/replay/tests/test_api.cc` (konn3kt endpoint paths + error envelope),
|
||||
`tools/cabana/test_cabana_konn3kt.py`, `tools/jotpluggler/test_jotpluggler.py`, and
|
||||
`tools/test_tools_local_route.py` (synthesizes a local route and drives both tools headlessly).
|
||||
|
||||
## 4. Execution plan
|
||||
|
||||
Land each phase as its own commit. Build after every sub-step — do not batch §4.2.
|
||||
|
||||
### Phase 0 — infrastructure (no tool code)
|
||||
|
||||
0.1 `SConstruct`: add and `Export` `ffmpeg_libs`.
|
||||
0.2 Port `common/yuv.h`; drop the `runtime.cc` libyuv patch.
|
||||
0.3 Vendor bootstrap-icons SVG + local `bootstrap_icons` shim (`SVG_PATH`, `TTF_PATH`).
|
||||
0.4 `tools/replay/api.{cc,h}`: add `getDevices()`, `getDeviceRoutes()`, `getRouteFiles()` against
|
||||
the konn3kt endpoints, matching upstream `PyDownloader` JSON shapes.
|
||||
|
||||
**Gate:** `scons -u -j8 tools/replay` clean; a scratch binary lists real devices from
|
||||
`api-iqlabs.konn3kt.com` with both a device JWT and a PC `auth.json` token.
|
||||
|
||||
### Phase 1 — jotpluggler (6 commits; do first, it validates the whole pattern cheaply)
|
||||
|
||||
1.1 `3f49e2d33` thumbnail source — add `thumbnail.{cc,h}`; `common.h` `kSpecialItemSpecs` 5→6;
|
||||
`common.cc` `PaneKind::Thumbnail`; `layout_io.cc` `"thumbnail"`; `app.{cc,h}` `ThumbnailView`
|
||||
+ `ThumbnailFrame`; `session.cc` `setThumbnails`. Re-apply the json11/path/`Android` patches.
|
||||
1.2 `576de9c7e` build speedup — take upstream `generate_event_extractors.py` **wholesale**
|
||||
(iqpilot's copy has diverged: `event_base_slots`, `static_enums`, single-use scalar getters)
|
||||
and re-apply only the `getBusTimeDEPRECATED()` patch.
|
||||
1.3 `e7b1ee3a5` segment-range parsing — empty-begin guard in `sketch_layout.cc`.
|
||||
1.4 `6b47a5b6b` decompress-in-downloader — iqpilot has no `PyDownloader`; confirm the replay-side
|
||||
decompression path is unaffected (expected no-op).
|
||||
1.5 `fef29ad22`/`911f07ee8` — add `test_jotpluggler.py`.
|
||||
|
||||
**Gate:** `scons -u -j8 tools/jotpluggler`; `./tools/jotpluggler/pluggle.py <konn3kt route>` opens,
|
||||
thumbnails render, CAN series decode against iqdbc, a saved layout round-trips.
|
||||
|
||||
### Phase 2 — cabana (53 commits). Bottom-up, one layer per build.
|
||||
|
||||
2.1 **DBC core** — `dbc/{dbc,dbcfile,dbcmanager}.{cc,h}`, new `core/{can_data,color,message_id,settings}.h`,
|
||||
new `dbc/dbcqt.{cc,h}`. Re-apply iqdbc rename in `dbc/generate_dbc_json.py`.
|
||||
2.2 **Streams** — `abstractstream`, `livestream`, `replaystream`, `pandastream`, `socketcanstream`
|
||||
(raw `linux/can`, macOS-excluded), `devicestream`. Re-apply the §3.C canproxy escape hatch.
|
||||
2.3 **Routes** — port upstream HEAD `streams/routes.{cc,h}`, swap `PyDownloader::` → `CommaApi2::`.
|
||||
**Delete `utils/api.{cc,h}`.**
|
||||
2.4 **Widgets** — `binaryview`, `historylog`, `videowidget`, `signalview`, `detailwidget`,
|
||||
`messageswidget`, `mainwin` (re-apply the iqdbc menu label), `streamselector`, `commands`,
|
||||
`settings`, `cameraview`, `chart/*`, `tools/*`, `utils/{util,export,elidedlabel}`.
|
||||
2.5 **SConscript** — rebase on upstream; re-apply: non-nested paths, `iqdbc/dbc` +
|
||||
`#iqdbc_repo`, `PrettyAction` wrappers, `third_party/json11`, `ffmpeg_libs` from Phase 0.1.
|
||||
Qt modules collapse to `Widgets/Gui/Core` (+Charts).
|
||||
2.6 **Tests** — adopt the Qt-free `tests/test_dbc_core` target; drop `tests/test_runner.cc`.
|
||||
2.7 **Launcher** — adopt `cabana` bash script + `Program('_cabana')`; de-prefix its scons targets;
|
||||
fix `.gitignore` (`cabana` → `_cabana`, `*.generated.qrc`, `bootstrap_icons.cc`).
|
||||
|
||||
**Gate (each sub-step):** `scons -u -j8 tools/cabana` clean, no new warnings.
|
||||
|
||||
### Phase 3 — konn3kt verification (the part that actually proves the sync)
|
||||
|
||||
3.1 Remote-routes dialog against `api-iqlabs.konn3kt.com`: device list populates; all five periods
|
||||
(7d / 14d / 30d / 6mo / **Preserved**) return routes with correct start times and durations.
|
||||
Preserved uses ISO timestamps, the rest use `*_utc_millis` — both paths must be exercised.
|
||||
3.2 Open a konn3kt route end-to-end: route-file listing, log download + decompression, qcam/fcam
|
||||
video, iqdbc DBC load, chart, CSV export.
|
||||
3.3 **Remote live CAN:** `canlived` on device → konn3kt relay → `konn3kt_canproxy.py` on laptop →
|
||||
Cabana *Live → Device → ZMQ → 127.0.0.1*. Frames must decode. Per prior work this is the
|
||||
untested leg of the canlive feature — run it on `gutek-a1`.
|
||||
3.4 Local live CAN: panda stream, MSGQ device stream, SocketCAN (Linux only).
|
||||
3.5 larch64 device build, if cabana is still expected to build on-device.
|
||||
|
||||
### Phase 4 — plotjuggler (secondary, 14 commits)
|
||||
|
||||
Only `juggle.py`, `layouts/`, `README.md` diverge, and the only IQ delta is the `iqdbc.car.fingerprints`
|
||||
import plus non-nested paths. Cheap; fold in after Phase 3 or skip.
|
||||
|
||||
---
|
||||
|
||||
## 5. Risks and rules
|
||||
|
||||
- **Don't batch Phase 2.** ~40 files change; a single "port everything then build" pass produces an
|
||||
unbisectable wall of link errors. One layer, one build.
|
||||
- **`git add` only ported files.** The tree currently contains built artifacts (`*.o`, `*.a`,
|
||||
`moc_*.cc`, `tools/cabana/cabana`, `tools/jotpluggler/jotpluggler`,
|
||||
`generated_event_extractors.h`, `generated_dbcs/`). Never `git add -A` here.
|
||||
- `dbc/car_fingerprint_to_dbc.json` is a tracked build product regenerated by scons — expect churn
|
||||
on every build; keep it tracked so cabana doesn't need iqdbc importable at runtime.
|
||||
- Dropping `Qt5Network/Concurrent/DBus/Xml/SerialBus` changes the larch64 link set; verify the
|
||||
device build before assuming the SConscript is done.
|
||||
- §3.C is the one place where a faithful upstream port actively regresses a konn3kt feature. If
|
||||
Phase 3.3 can't be run on hardware in this pass, **do not** land 2.2 with upstream's bridge fork
|
||||
as the only ZMQ path.
|
||||
- Upstream candidate: the `dbc.h` empty-multiplex-indicator guard is a real upstream bug fix worth
|
||||
sending back.
|
||||
|
||||
## 6. Explicit non-goals
|
||||
|
||||
- Adopting upstream's nested `openpilot/` tree layout.
|
||||
- Migrating iqpilot's replay to `PyDownloader` / `tools/lib` Python downloading.
|
||||
- Renaming cereal `androidLog` → `operatingSystemLog`.
|
||||
- Converging on the `comma-deps-*` pip wheels.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A — cabana commits to port (`bcdeec313..e7b1ee3a5`, 53)
|
||||
|
||||
```
|
||||
4cfdcea1d 2026-07-28 cabana: fix macOS build and --zmq bridge path (#38484) <- §3.C
|
||||
6b47a5b6b 2026-07-23 tools: decompress in the python downloader (#38430) <- §3.A
|
||||
75d590fb9 2026-07-21 replace catch2 tests for 20% faster builds (#38408) <- §3.G
|
||||
e124d6df9 2026-07-19 more dead code gc
|
||||
a04c045cd 2026-07-17 cabana: de-Qt, part 3 (#38360) <- §3.B
|
||||
5d23a78c7 2026-07-16 cabana: de-Qt, part 2 (#38359) <- §3.B
|
||||
06a73f538 2026-07-16 cabana: de-Qt, part 1 (#38357) <- §3.B
|
||||
39117a587 2026-07-08 remove submodule symlinks (#38312)
|
||||
f283f6703 2026-07-08 Revert "try removing submodule symlinks (#38310)"
|
||||
b827c0f55 2026-07-08 try removing submodule symlinks (#38310)
|
||||
1e49eac4d 2026-07-08 rm libyuv (#38306) <- §3.D
|
||||
9877f6ac0 2026-07-08 ffmpeg: use shared libraries (#38308) <- §3.E
|
||||
05cf8023a 2026-07-07 fix cabana <- §3.H
|
||||
c02cf706a 2026-07-07 fix cabana launch script <- §3.H
|
||||
5edc0bd89 2026-06-21 mv root dirs into nested openpilot (#38219) <- §1 (de-prefix)
|
||||
20e0f21b5 2026-06-21 prefix paths with openpilot (#38223) <- §1 (de-prefix)
|
||||
37eda06c9 2026-06-21 move cereal/ into nested openpilot (#38220) <- §1 (de-prefix)
|
||||
bfd8d4868 2026-06-06 cabana: fix "seperated" typo in findsignal placeholders (#38142)
|
||||
5408c86b7 2026-05-28 Cabana: Fixed internal typos and method casing (#38099)
|
||||
15267e408 2026-05-11 cabana: gitignore generated file <- §3.I
|
||||
bea893820 2026-05-10 use packaged bootstrap icons (#37994) <- §3.I
|
||||
bd1c7f39e 2026-05-07 scons build cleanups (#37981)
|
||||
0584a5f5e 2026-04-12 add bridge target to cabana run script (#37814) <- §3.H
|
||||
31e4fe55a 2026-03-22 tools: setup ffmpeg hwaccel (#37718)
|
||||
a8b5c7450 2026-03-21 prep for imgui tools (#37712)
|
||||
240e0036d 2026-03-20 macOS: fix build (#37686)
|
||||
a68ea44af 2026-03-14 cabana: use vendored libusb from commaai/dependencies (#37681) <- §3.E
|
||||
5e7f5dd84 2026-03-14 replay/cabana: remove unused openssl dependency (#37680)
|
||||
ee9da82aa 2026-03-13 cleanup build paths (#37667)
|
||||
71290f380 2026-03-08 cabana: gitignore assets.cc <- §3.I
|
||||
e42ee228c 2026-03-08 gitignore cleanups (#37615)
|
||||
5e1a576f3 2026-03-07 cabana: exclude SocketCAN on macOS (#37553) <- §3.B
|
||||
0c452dbaf 2026-03-03 cabana: fix right pane width limitation (#37527)
|
||||
06b2c68e0 2026-03-01 macOS: fix cabana builds (#37518)
|
||||
3478ac133 2026-03-01 cabana: remove QtSerialBus (#37523) <- §3.B
|
||||
ce04d25f7 2026-03-01 cabana: remove QtConcurrent (#37522) <- §3.B
|
||||
0c7abf385 2026-03-01 cabana: remove QtXml (#37521) <- §3.B
|
||||
0b9ab8bb9 2026-03-01 cabana: replace Qt types with stdlib (#37519) <- §3.B (largest)
|
||||
885658512 2026-02-28 new demo route (#37457)
|
||||
e7cc70f3f 2026-02-28 consolidate file downloading from C++ to Python (#37497) <- §3.A
|
||||
276713ddf 2026-02-27 add back bz2 support with vendored bzip2 (#37459)
|
||||
238fca233 2026-02-25 tools: fix darwin compile errors (#37399)
|
||||
8810948ec 2026-02-24 CI: ensure no brew (#37387)
|
||||
76d084d87 2026-02-23 switch to system compilers (GCC on Linux, Apple Clang on macOS) (#37355)
|
||||
f4a36f7f7 2026-02-22 rm cpp bz2 (#37332)
|
||||
4bffe422e 2026-02-22 vendor capnproto and ffmpeg via dependencies repo (#37327) <- §3.E
|
||||
c98ba4ff4 2026-02-20 Qt is optional (#37295)
|
||||
037e6e749 2026-02-17 cabana: fix crash when zmq address is used (#37222)
|
||||
af1583cdf 2026-02-12 Reapply tgwarp w NV12 fix (#37168)
|
||||
45099e7fc 2026-02-10 Revert tgwarp again (#37161)
|
||||
667f3bb32 2026-02-07 Revert "revert tg calib and opencl cleanup (#37113)" (#37115)
|
||||
51312afd3 2026-02-07 revert tg calib and opencl cleanup (#37113)
|
||||
d5cbb89d8 2026-02-06 Remove all the OpenCL (#37105)
|
||||
```
|
||||
|
||||
## Appendix B — jotpluggler commits to port (`61608db78..e7b1ee3a5`, 6)
|
||||
|
||||
```
|
||||
e7b1ee3a5 2026-08-05 jp: fix segment range parsing
|
||||
6b47a5b6b 2026-07-23 tools: decompress in the python downloader (#38430)
|
||||
576de9c7e 2026-07-21 speed up jotpluggler build (#38406)
|
||||
911f07ee8 2026-07-21 convert tests to unittest (#38387)
|
||||
fef29ad22 2026-07-19 start porting tests to unittest style (#38384)
|
||||
3f49e2d33 2026-07-18 jp: add thumbnail source (#38363)
|
||||
```
|
||||
|
||||
## Appendix C — how the baselines were established
|
||||
|
||||
Blob-hash matching, not commit archaeology: for each iqpilot file, walk upstream history
|
||||
newest-first and report the newest commit whose blob for that path is byte-identical. Files with no
|
||||
match are IQ-modified or IQ-new; the minimum over the untouched files bounds the fork point.
|
||||
|
||||
```
|
||||
cd /tmp/openpilot-upstream
|
||||
git log --format='%H %ad %s' --date=short -30 -- openpilot/tools/cabana tools/cabana > /tmp/cab_commits.txt
|
||||
for f in $(cd $LOCAL/tools/cabana && find . -type f \( -name '*.cc' -o -name '*.h' \) -not -name 'moc_*' | sed 's|^\./||' | sort); do
|
||||
h=$(git hash-object "$LOCAL/tools/cabana/$f") || continue
|
||||
while read -r c d rest; do
|
||||
b=$(git rev-parse "$c:openpilot/tools/cabana/$f" 2>/dev/null || git rev-parse "$c:tools/cabana/$f" 2>/dev/null)
|
||||
[ "$b" = "$h" ] && { echo "$f -> ${c:0:9} $d $rest"; break; }
|
||||
done < /tmp/cab_commits.txt
|
||||
done
|
||||
```
|
||||
|
||||
Cross-checked against the last upstream-authored commits in iqpilot's own history
|
||||
(`bcdeec313 Reduce pub-sub memory usage by 10x (#36884)` for cabana) and against feature markers
|
||||
(`virtual std::string routeName` first appears in `0b9ab8bb9`, 2026-03-01 — absent from iqpilot,
|
||||
confirming the fork predates it).
|
||||
1
artifacts/iqpilot_alc_private/.source_sha
Normal file
1
artifacts/iqpilot_alc_private/.source_sha
Normal file
@@ -0,0 +1 @@
|
||||
0a3bb124deb8a9fdc8cea76764c690c7673a597f
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"python/_iqclosure/_vendored.json": {
|
||||
"mode": 420,
|
||||
"sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
|
||||
"size": 3
|
||||
},
|
||||
"python/iqpilot_private/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
@@ -16,12 +21,12 @@
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "16990383c7ba1d2f3d33e5e4c1746019101bc79bed867721b51de853a68002ed",
|
||||
"size": 401688
|
||||
"sha256": "4d3eb2b63aa962bd764a9ee0086d3936846fb34a255321f7e95594412856f0fd",
|
||||
"size": 402072
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "bd060a8527d21501ce866f24d055f1c8bb03209b2730eb745b786780aace0aae",
|
||||
"sha256": "8e6829e7d8828e76258eba191144d70ae5b42dec620608dcf65431db25c0670f",
|
||||
"size": 69688
|
||||
},
|
||||
"runtime": {
|
||||
@@ -37,7 +42,7 @@
|
||||
}
|
||||
},
|
||||
"signatures": {
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": "IVz01J4HpdUHDrjPP9yB+9ksGFberB9MeFiCNLcv8hMzXCLIjIATl3JXTwpBqU8Kad/q683nkhgL5IJ3tCKdCw==",
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": "/aW/StEMRpge+zzihQDK7VPLmKhgwCucX8JielvPnPTud/mk0pmCy5pE8fB+TbwtWZCBfVhEd1bidRgsBqZfBA=="
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": "c/cZ53FdLJJLrMc8J40fW/CFkByPO0Dt2AMFIOY1AiT8/09piyULhhWAytgNaIejk8BP0aQhlk5xfaivI3bKCw==",
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": "bbcjBEVBBQskXGTqTTQmLAdjcgtoL45mwGhRUwchrEkMNNeMJrvSCQ7ZXf8KpNUgFaiSU7L/5tnqHsrtKvU8Dw=="
|
||||
}
|
||||
}
|
||||
|
||||
1
artifacts/iqpilot_alc_private/manifest.json.sig
Normal file
1
artifacts/iqpilot_alc_private/manifest.json.sig
Normal file
@@ -0,0 +1 @@
|
||||
M1n62PoNdW9Odr9dhsZgHsKupcmVorFMp8QU14sERQn2XkFWjOGDfjUX018m7IWOdxtbciDzTzHYOIHkBxqVAg==
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"python/_iqclosure/_vendored.json": {
|
||||
"mode": 420,
|
||||
"sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
|
||||
"size": 3
|
||||
},
|
||||
"python/iqpilot_private/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
@@ -16,7 +21,7 @@
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "384571df8380e5eef92806f3fdb6c9d1bc879af2ebdce9259bcfc486d506a4d0",
|
||||
"sha256": "9b48803b46906943d7cb10b7e4b4f38c880f06a5c281f254a89e2254965b025f",
|
||||
"size": 68080
|
||||
},
|
||||
"runtime": {
|
||||
@@ -28,6 +33,6 @@
|
||||
}
|
||||
},
|
||||
"signatures": {
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so": "cH88ob+s+ZRJaNgbrRN2CQqCiQMBlo9yXdBvN3gteSC7OYlJ5z8OZND7eZSEfeeq2O7E8ZeZDs9WDU0WfP8DCw=="
|
||||
"python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so": "RwstZkEGexV6fia/R9BRk+6CQ9Q6SGJopSJf04UpMpaw5fRtoPjLOrDFOt9kU8GPmCmW40gaXhJK+rimnBYnBw=="
|
||||
}
|
||||
}
|
||||
|
||||
1
artifacts/iqpilot_commander_private/manifest.json.sig
Normal file
1
artifacts/iqpilot_commander_private/manifest.json.sig
Normal file
@@ -0,0 +1 @@
|
||||
TV78NXdEYQiabAzLIqS2lsZbp5bO0y4EPGwLRUMyKeEH2BStZiFfpNPvlqSn5HGI+RjmTq+p5rRUDMNofbZGBw==
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
Binary file not shown.
@@ -1,4 +1,339 @@
|
||||
{
|
||||
"python/_iqclosure/_vendored.json": {
|
||||
"mode": 420,
|
||||
"sha256": "650f5873e58bb90fbe2429bb7e63eb7ac2145a39562b0780523835812b8baad3",
|
||||
"size": 1805
|
||||
},
|
||||
"python/_iqclosure/iqpilot/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/api/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "97d6e0d8cbd3e47797d734322b42d04efea4d95a2dae5a56f1baef3fd847aa0d",
|
||||
"size": 939
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/api/base.py": {
|
||||
"mode": 420,
|
||||
"sha256": "be55c83869bad911ee0204e8779b4673653f7d29ae4f524c978a280bd84c7777",
|
||||
"size": 3187
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/api/comma_connect.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e7c2dd0b56fb8533c2cbc614e728a9ffc5ce7360524cfa62a02f5235bc23b9a8",
|
||||
"size": 267
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/atlas_alerts.py": {
|
||||
"mode": 420,
|
||||
"sha256": "ef0d867f6213bb67ed6b7fd6ef4e694eb1705d9dc694163a80c8dd9572932954",
|
||||
"size": 9448
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/basedir.py": {
|
||||
"mode": 420,
|
||||
"sha256": "58cfa5d6405903ee1acecc4544463bb8f47a66921b120c382143aa80d8539b28",
|
||||
"size": 106
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/constants.py": {
|
||||
"mode": 420,
|
||||
"sha256": "fc439d59c24b95ed349b84f1a5862df3ea50e0891dbec4ea3cf5e1e0ccff6513",
|
||||
"size": 434
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/file_helpers.py": {
|
||||
"mode": 420,
|
||||
"sha256": "3c661d05dea697fae741b3e65a93385e4e0f5941eae8e8f4ea64da8da90e2f52",
|
||||
"size": 67
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/git_creds.py": {
|
||||
"mode": 420,
|
||||
"sha256": "aab57b08d1907d0a761111e2a9a415af990475a55307c0ced6f2573664abbc49",
|
||||
"size": 7327
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/gpio.py": {
|
||||
"mode": 420,
|
||||
"sha256": "01ba8cb32a644a2df545e764ba19bf1c7a320a217f983f8bac73b45d3a6891cf",
|
||||
"size": 2408
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/i2c.py": {
|
||||
"mode": 420,
|
||||
"sha256": "4af8f9da7504a548d15f18b91d71c1379cb7c05c84be031448764cc49920ce68",
|
||||
"size": 2449
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/issue_debug.py": {
|
||||
"mode": 420,
|
||||
"sha256": "73a10d846c735d4bcfda65f7828aa82192349b53920cb9d1b01cef8c211982fe",
|
||||
"size": 1128
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/logging_extra.py": {
|
||||
"mode": 420,
|
||||
"sha256": "ce9b3b3d8498d76e1c4c0352a3e98e72759bb7f367ff8629c1329c46040595c3",
|
||||
"size": 6628
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/realtime.py": {
|
||||
"mode": 420,
|
||||
"sha256": "95b2e4eb3d78b607945cc6fad29c97a2e133eff4c427620d5c04b044f78abe10",
|
||||
"size": 3763
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/spinner.py": {
|
||||
"mode": 420,
|
||||
"sha256": "33a2eb2aa3148492414035c5dc7150b3a69467905d7a3e58c8f65cfe70d3552c",
|
||||
"size": 1371
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/swaglog.py": {
|
||||
"mode": 420,
|
||||
"sha256": "0a133b4ef49f80b8890f115e81273f3f3e21351ae79f92dc02abc033e6d24027",
|
||||
"size": 5274
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/time_helpers.py": {
|
||||
"mode": 420,
|
||||
"sha256": "c14cda1f84149d5ffa63a2adec5ca24126f77c1412a78412e5e3e68a6fd12bcf",
|
||||
"size": 474
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/utils.py": {
|
||||
"mode": 420,
|
||||
"sha256": "6475ad94bc89252398c41e9eabf7929ba5eb8fa23a08ad7545e7a67b400af9f3",
|
||||
"size": 8090
|
||||
},
|
||||
"python/_iqclosure/iqpilot/common/version.py": {
|
||||
"mode": 420,
|
||||
"sha256": "c04a243c77a170b750bad283da5e92bc6b1072bb674ece20dfa4eb9ec2d90309",
|
||||
"size": 106
|
||||
},
|
||||
"python/_iqclosure/iqpilot/konn3kt/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/konn3kt/cloud_client.py": {
|
||||
"mode": 420,
|
||||
"sha256": "4d58e62f8d628a8570881f2b87c82c801b46928fbb84f8dab8a81a9b55ba4e15",
|
||||
"size": 493
|
||||
},
|
||||
"python/_iqclosure/iqpilot/konn3kt/registration.py": {
|
||||
"mode": 420,
|
||||
"sha256": "d8da8fa6373d54c3090b4466a975969317c9f5cd7c0ba20fd905e11ce9b95efd",
|
||||
"size": 7948
|
||||
},
|
||||
"python/_iqclosure/iqpilot/konn3kt/service_health.py": {
|
||||
"mode": 420,
|
||||
"sha256": "10fa7d8490322275963308a1162ac5a0d0e3a96ac4a4771884eb2d3ec69fb480",
|
||||
"size": 264
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/car/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/car/vehicle_catalog.py": {
|
||||
"mode": 420,
|
||||
"sha256": "8193cfcd9bda8651bf93f8d629996a22c760a42351c35a10c94329ab23fbf3f3",
|
||||
"size": 2380
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "1fe78b73b9f08b7081f28b80e01ca06b2ed423231459d30942184297ce1e50d7",
|
||||
"size": 632
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/config.py": {
|
||||
"mode": 420,
|
||||
"sha256": "056c744bbd9f1fe1053555d9b1fc1257d53093d69ddc94be3f05d840eb70781c",
|
||||
"size": 3590
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e5ddcbc7b4dc348957c3d141c6bf8e7c321067f2ac56aecec24f6de107f9c2d3",
|
||||
"size": 81
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/fetcher.py": {
|
||||
"mode": 420,
|
||||
"sha256": "0487789aec270f9a712a19afafe0b686f825f06f57006c2346c052d3622ac5f8",
|
||||
"size": 619
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py": {
|
||||
"mode": 420,
|
||||
"sha256": "5ddd486e47cdf811c0c293c460aba2d3b15bb30059ff6da8da775c5cd236465b",
|
||||
"size": 10945
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/locationd/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/locationd/calibrationd.py": {
|
||||
"mode": 420,
|
||||
"sha256": "6bceebb5a15895d2b6d20a28cbd14b9112f9422e0a0cf466314a7e8779da39dc",
|
||||
"size": 15698
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/selfdrived/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/selfdrived/alertmanager.py": {
|
||||
"mode": 420,
|
||||
"sha256": "c601bac30951a750d3ebf3d30f06c597bacd148210b1c405d5d3eca79bb7c4e3",
|
||||
"size": 2045
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py": {
|
||||
"mode": 420,
|
||||
"sha256": "7694cfb588e978a30c78a58039b087754e352215240b5e4bde953d848419122b",
|
||||
"size": 36515
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/ui/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/ui/feedback/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/selfdrive/ui/feedback/feedbackd.py": {
|
||||
"mode": 420,
|
||||
"sha256": "7c1e6cb2c3c2c6aae91210f6cbc5c372f33c28cc3edb292ef71439b83844615d",
|
||||
"size": 2615
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "889391bffca493e74bfc108cae2976a53b644caca13ab600061fd29e9622c627",
|
||||
"size": 695
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/base.py": {
|
||||
"mode": 420,
|
||||
"sha256": "a56293f7c23e503ff62d0d3ed39f1831b5544a65b37378165934361a5ddcd304",
|
||||
"size": 4966
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/hw.py": {
|
||||
"mode": 420,
|
||||
"sha256": "c69185d8f6482c8b021f687b793246d2e2ede853e89a88748c64b85f60711b59",
|
||||
"size": 3638
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/pc/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/pc/hardware.py": {
|
||||
"mode": 420,
|
||||
"sha256": "2520895597c375ae570bd04d5f189e874c89d642f3f07bdbeb20388cb6691433",
|
||||
"size": 258
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/tici/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
|
||||
"mode": 420,
|
||||
"sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
|
||||
"size": 7585
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/tici/esim_manager.py": {
|
||||
"mode": 420,
|
||||
"sha256": "ac8d6d33286a021b0e1591329b779742d73993279d3f4c56e933cd7efd3114c8",
|
||||
"size": 10930
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
|
||||
"mode": 420,
|
||||
"sha256": "f1ddffa9bdb5071a49fd65eb4b49958b60b1873e1ec70e6f3f2e2b2501abce2f",
|
||||
"size": 25584
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
|
||||
"mode": 420,
|
||||
"sha256": "d22858982705531732d5a20a45a73795496e5bbd9e23028c24609a7e58ac7aed",
|
||||
"size": 890
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/tici/lpa.py": {
|
||||
"mode": 420,
|
||||
"sha256": "2bffb84aef35892c4788e80b6d937bf2eebd15e226b72a82c4f39217667c1ca0",
|
||||
"size": 53692
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/hardware/tici/pins.py": {
|
||||
"mode": 420,
|
||||
"sha256": "b57ba4262e6a6a79f2db15c27356acf3e54f579a7edd13b483b2e16d140aca6d",
|
||||
"size": 678
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/loggerd/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/loggerd/xattr_cache.py": {
|
||||
"mode": 420,
|
||||
"sha256": "3aec77de0fe1ac5d32bef6cf59c4eeab3fcc37c86de42bdc1a9d730166225295",
|
||||
"size": 991
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/manager/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/manager/process.py": {
|
||||
"mode": 420,
|
||||
"sha256": "d036d82e2163a249186238375fb82f8dca250fae2272428ea47a46e4d6a8649d",
|
||||
"size": 11519
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/manager/process_config.py": {
|
||||
"mode": 420,
|
||||
"sha256": "810bbd9bf9d211b1c54bfaaf56992b52e9a08a4cd0d1919d43a0a3b907401313",
|
||||
"size": 11656
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/micd.py": {
|
||||
"mode": 420,
|
||||
"sha256": "1a59cbe6b72fcca5723ab56dc09f52039817299071a8d8da37f04acea58776bc",
|
||||
"size": 5977
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/sentry.py": {
|
||||
"mode": 420,
|
||||
"sha256": "c3da338feb533937b6a5258ee450b6a3ff7a77b69cc9e48e31d16b875855caf3",
|
||||
"size": 5229
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/ui/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/ui/lib/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"size": 0
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/ui/lib/networkmanager.py": {
|
||||
"mode": 420,
|
||||
"sha256": "5d239320755efad6e88df237af763ed206ee37c0c8200db93bbdb0e0c884eb56",
|
||||
"size": 1717
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/ui/lib/os_update.py": {
|
||||
"mode": 420,
|
||||
"sha256": "96760f2b5935a4429395628b64779efcfe80a7f50d98d57dda6c63855de1b2c6",
|
||||
"size": 4236
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/ui/lib/wifi_manager.py": {
|
||||
"mode": 420,
|
||||
"sha256": "c9fe91484344f154726b0cab62a19f58a172e4f06dfd6341ca9b179c0c4646a8",
|
||||
"size": 41618
|
||||
},
|
||||
"python/_iqclosure/iqpilot/system/version.py": {
|
||||
"mode": 420,
|
||||
"sha256": "b39a686854c9bfa3c6db9158f68734d7044760cf7ee79980102b7d23230836fd",
|
||||
"size": 5909
|
||||
},
|
||||
"python/iqpilot_private/__init__.py": {
|
||||
"mode": 420,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
@@ -16,28 +351,28 @@
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "e79be61e50cd0830197f44f8a6840b86a74b8059da45da65a4696587168ac466",
|
||||
"size": 135536
|
||||
"sha256": "80cd58a10f6356fcad68063a474458653acbae17dfc8a66791256ec1fc109036",
|
||||
"size": 135552
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "ebfeedba6cc5cac6dca1cf8125879fcb2d6afdbaec31a98dd46c3d346f55c982",
|
||||
"sha256": "81e12476cedca56039fe9bf194cc9fd1a80b223c987e212a3208c64ff1390b26",
|
||||
"size": 67664
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "11ffe8f261b78990aa743b43aed3a5534105da848be1450026090ae873ad0a2e",
|
||||
"size": 204624
|
||||
"sha256": "9d7e7fd9d1a3d2ff6ba8140fbe3743502151768f7d2c22625ffe2a78b3c8cd7b",
|
||||
"size": 204640
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "e62a6438c18b231f83cc873ac8170a64e670c8912ffba13edf47f163886aef19",
|
||||
"size": 69752
|
||||
"sha256": "c39b482f192406ab8f3ec4733f2da8131ec1d9d5c791189035e42504d8e6132f",
|
||||
"size": 69768
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "07195ca6d4e1ff26441e860d954e9c79e3da608244d110bfa62b3aa0818a79dc",
|
||||
"size": 203072
|
||||
"sha256": "5524a08ebc9918fabf6d8c7d2ccdb89ac67cbf8f017e4d00989bf88dc9720b12",
|
||||
"size": 203088
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/flockd/__init__.py": {
|
||||
"mode": 420,
|
||||
@@ -46,12 +381,12 @@
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "d18b8d67100f5906339dde577bc14f567168ff62e1b9166c011af853c016191e",
|
||||
"size": 202168
|
||||
"sha256": "364a06ba9338c61fe904c6c93e3be7bc567b0881cffaaa62b7cba0d67a5f3c04",
|
||||
"size": 202184
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "1a601a0de826fb41fadb3cef9e8b8645d99e41507befc36e87d077d49efd3025",
|
||||
"sha256": "08a7990bb84f88505eb5e58745b339b59ada3cc2b802c5f2396281cb03af1c84",
|
||||
"size": 68080
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/__init__.py": {
|
||||
@@ -61,68 +396,68 @@
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip": {
|
||||
"mode": 420,
|
||||
"sha256": "c3063f3d380627f604b0ae1cdd20fbfdfa7b64d212c86f20abd470230af8e169",
|
||||
"sha256": "ec5e60ae7ebed56e6613216115b25e3f7388afd374619a656642118b0506642a",
|
||||
"size": 2100238
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "57dbd02e130361d33c8a52635261ce85f3a91c577b70d5fad8a0e9e7557f9d46",
|
||||
"size": 268064
|
||||
"sha256": "b9d5402b525987bc9939bf30113debde997d07a7ab0a5922bf1954cea7cb7757",
|
||||
"size": 268080
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "1febeeb24421c82a64416a7ab04def21d57a39acf0caaf2103b4ebfbae655fc2",
|
||||
"size": 406216
|
||||
"sha256": "68787d021840f0e1ee1babd3e4e781324c9b4e1bb8de38e78ac9fb57d967ba7c",
|
||||
"size": 406176
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "42656c3b54f6392e2311e4039296e4906558fd8c63ba6be04b157445b96d38c9",
|
||||
"sha256": "405fbafd6e385b7f6fa5f20b1ef1fa38cbd04952edef764efffbecbbebcd8f14",
|
||||
"size": 68048
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "acdf51e90428e4487f0daf6e0e43110ef5fc1c7705d16aa3b9880618030f41f3",
|
||||
"size": 335728
|
||||
"sha256": "2449bf7779155e546ea14a5c10ee16be174da6993f6ab25e2aa59de007b5078b",
|
||||
"size": 335776
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "95afe64579cac0148c3de23867a333651dfed4606c218208dcf37b40a1cc6450",
|
||||
"size": 271680
|
||||
"sha256": "951fbff54e5fd1c667ca73359ea015e9cf79dcd55abb7f8b1bc857a38e11a54a",
|
||||
"size": 271696
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "fd983039accfaa30abf3d46f41c70241d1c3fcf4e21a1de7d3270ab9fce9b03c",
|
||||
"sha256": "2fa9a1e9352721a03f2eaa4ecf3719787baafc35fb37d71e0b6372656cde6d5f",
|
||||
"size": 134408
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "cc299f0d560bbd611cab099923bb1817229df3cbc15cef22d6909091e9de826b",
|
||||
"size": 3581928
|
||||
"sha256": "d342ef73b4fd9d60407c60d0c3768668e8bdbb0d9702f163aec4893f8d49af6c",
|
||||
"size": 3715880
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "0dbcb5d9ec1af832bcaceaa3ae96a4785a432778c1fb3cdc8b74e62b5d915158",
|
||||
"size": 136448
|
||||
"sha256": "cf6cc790e765adc5913c23ff32dea23588c1fb8608c6a8043fbc9b1bb2b662e8",
|
||||
"size": 136472
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "cabf37e151994a223f890627c0128990e56505bc704108f050d2422e118a273a",
|
||||
"size": 68136
|
||||
"sha256": "4d66dbd069d90ee956feac23130c5a14338f3d66492180dd3cd945f8a5c4ddad",
|
||||
"size": 68208
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "10af02f5daf288e465bcaa64509972a1a0b268f779b1c12c58bf63c79e52eb96",
|
||||
"sha256": "d5227394abae38d2118d7e1df8a142ad2137a28e02679d29d2426c3ac8a60ba8",
|
||||
"size": 67840
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "855f128d248c94fbd00f550f7dd388ccf1a487935d7f11cac00e5240b526d674",
|
||||
"size": 135648
|
||||
"sha256": "59cba66846b23370b9c7b20d108db8bf6f1c7dbc8a49958621905e0cb322fbe9",
|
||||
"size": 135664
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "8de50d50308777ff9dc22d4d5232cba38f2f332207313999ec8aaa67e5004f4e",
|
||||
"size": 269568
|
||||
"sha256": "cc1be0247694a84b596e1333d53fa23a44fa2353b8b635f95b0329a5ffef5585",
|
||||
"size": 269600
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/uploaderd/__init__.py": {
|
||||
"mode": 420,
|
||||
@@ -131,8 +466,8 @@
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "11d16de5d29a37f64351acabfa6372966447a88cd7d799580a6f2f1fa745c15a",
|
||||
"size": 204184
|
||||
"sha256": "8a4926ba1d3bcf80326f699b1f9c775cd1fbe9d4c7981ba47d8232a8d4aedf79",
|
||||
"size": 204296
|
||||
},
|
||||
"runtime": {
|
||||
"entries": {
|
||||
@@ -164,6 +499,10 @@
|
||||
"path": "python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so",
|
||||
"type": "python-module"
|
||||
},
|
||||
"iqpilot_private.konn3kt.hephaestus.vw_pq_flasher": {
|
||||
"path": "python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so",
|
||||
"type": "python-module"
|
||||
},
|
||||
"iqpilot_private.konn3kt.uploaderd.iquploaderd": {
|
||||
"path": "python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so",
|
||||
"type": "python-module"
|
||||
@@ -171,25 +510,25 @@
|
||||
}
|
||||
},
|
||||
"signatures": {
|
||||
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "1DjE26Z6yX6VuP+Jx/DxxS2PjWXSXLd+bS1RgYEo9WNOEHENVDv1xeoyXjFmrXe6/QTILJpv+LnqWegfVSnFBQ==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "ytsUI7NI3t/9m+zEfMCbXVCIggeKSk8lnTrVYWQr0uryX5tTtQXyR+f+rIPSZWzzHu80z3GE0jmnQEEN2m6XCA==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "Y57ZaVfeeB1Mnu4vFIQNSZIuob+jmBkuuZ86Zc0sOWvqgkE8gT41pivlipr/R/du5+HSqXvTv/iuQS8ekTBpCg==",
|
||||
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "PGjuQc316L/r8sSGF0Hg5EnFWj/Bhk27UYBgGvQ8xxLvoVJIkRKVJ/ZFLFZo6rdEWrd0GQECUYeR9ewU6ufFAA==",
|
||||
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "CVrgu0dEypYq6IIA38Lh/w4KKVNpP5+dfV0dkOpDA4UkyH2IoX3Wiywpz3o1jfjrSXE3oiPdk52qRE+pzLLxDQ==",
|
||||
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "+grQAam3UiFIBqGcvuAI+7aYW6UCU83YLlxCkPoo6YaX9SitDVqQk6bNQwqxYUzuUZ2s8BdhlbgseTuImRZMBw==",
|
||||
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "yhxeGAidy3Auu+i2wZgxD1bK18VeWknYuQQpeiylNyWNfzN/xPUDGFyyV0CpNY3FqEqH8LcHLzsa8Bz4JTbWDw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "OX/9GfQQf/krppHPChnXqvvHCs+8LqdpnRtRxJ9AYeqRLwNMrTgCSLOsU3YGlHiF78U5jIldNdkr4aOGBHRVAg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "n06LgyR6cPqHLaGsiEXlSFiNKFkT3S14mPEz+xtLw0y3bAuNod5Jpp6KiVuoTcVdzHjXPFWwYLgqbtDxSZe/BQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "JJ8eaFR2wkJW4/gw31a35LvyHLKTLfPwFwIC3cE5Y4AYbLkrVuVbizPefhUrcFMF0ZxzjQulU0YNAL7EMbzOAA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "Vr/pX91mN1D0Qcl9PNJiT2DUq9LxP13qjO5g7Ve/KDPwAqL9nKJXeAqbhKXIqFyRHp82PAHKaVhHsF39QtLRAA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "yjHeFmvqDWSEXIft0n93zTSZemP4L9hmXZtauDFL7bRnGHMnTVvrq7olJJoXvQITVGyGzDl+BuDF5pjOKau+AQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "7sVJ5qadF1T48RP8FlwynsJEC0w6OtN+BGBf93YABFXgYDPY/YAMm5aAicR6YmGvgkFqtfCf5g8ozdrz5fIuBg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "54+MaaeOIEbj+uZrcphTbHQ8K1jm4gRe2JYZyYNkwxPcT6dkcWLM6NZiNVzXiD6Ph87hTxUbecJnhVeT80pDAw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "xJnkGTaVVWne7p83UhTU2C/miZg4XR7pLrd6A7j7TvetRCFvQPxyuAeFi5lJsKKwhaWV3dInGZcVWrGV+kV0AA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "P+LVQJc3vD5Z9i/jxNxziNbgCiGrI+5P9saUGHMPMKUoWhVEBAAVU8xuVyd6GjSd8M5VrximPVrJyMCE9fEfAw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "vfDMFKWuTD2XSE7JF/qmHxcOXgdXRZN9fi3FTSffmr6t22Tz0N7v8MwVayiPKrYNt2gHixFZ9aAkvF2SC+yDBg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "Orgr5aTB5g3NRH8AdsuNkpcmIfSQDBFHzegDN875SxuvHuCOfsVVYgx2TtRiT0wqSR9hlQQwhelyRHRQjcMCAQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "tZNbbDyMxkFOcrvWCEBQz6ZTGdLZVesuz0dbUMO1OBaSYAG4MRBQ5y9fZwRQZAvcEa+NMlNZK2SGoC6r5BxgDA==",
|
||||
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "sgYjdE7ZRvdKlehs1/h3z/n14fksR+Z+KhCzi0BvFPcbMcx0Pod/L42IQeEDfcQfoFBURPNoI1OTwWc31H/NAQ=="
|
||||
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "tV3zvq8PZd4MgIU1hjzjpVxS8puOkVUUsJsSYb2fjeOqkBv6KG3oIlN+TPGiwwO7YX8dO8dFivZWGnktKWKPCg==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "kG8Ifpx65b0dhdTdfFhLr65vGa1O5enjiCI9N3aGd5+aOE7DdKJPID4Q3vIIjMZqMdoKPTdBLj3MaPEYXL3pCg==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "1Dh3vRWc91DfHWg7pw9GhZ6O5eT335f4utjxSzHh8Y1wvwButYfsEO5DUZ2KNss5IAKDy7O4/CpnACD/64UYDg==",
|
||||
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "6Fdo8Du4ZUMt8241uRDjLVoy1a2flzQNeXMfaVuRaMU8aYZZixqvaYPeanZDPOTvpy6PxaFVZYYnBR/cqokNCw==",
|
||||
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "YTL4x/JaFnZCALPoyET84LvOcISGB5vrpTYn5b87kSDdcWPDwiTyEAXUuueOQGRQXVzZJj4UC+zal2DEA2QlAw==",
|
||||
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "KHqjlPIClO9hg2LxyOzRismA38ysUPxci0KCiHrydE8W7Nbd3LMMR1/9/rUvPUfRYY/szpkfvkZTlMwo26R5BQ==",
|
||||
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "Uas3ReVuTgjJxr1elZ/26R8/6StRaFrMPD16t7GWGj6PTG+CiUOPn4oiiJgfumevnrYw4xGGLRsfZQhFHaUHAA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "39bSI5v9Mg9VQLo3PmfUBmfHE1hKjTD2Jfgak/W3m1FJ7eXROhUjts6bsY5pXsHDx6MTcEw35IUr7Q8v+3bQBg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "hB1aGUsLCYcFSLxGxg1nuRzbrA1Uv/StOQv0V/0L8v73Z7xakXYFW8h/C5xD2/hNIGFMaNc7GLOyc2GwN7tVAg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "LlBVzMoPeK8ypvzgXXYkqmooV6V1lHtS6Ym5VPj/fNFn79hWfhbsjwY9A463hs3QzaxGh5OWwcc1pmFc6USODw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "nq7AH2XYZuAxhGfHfgc3haBGbu5bDwOI15mI++Be3VYsVah0P+Fqd0F5G6dZjIFo5fbuO0FH7/LHOlEnxRQcBw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "m2y/Lozqs16IZsrpL+OOQ0MQCWEB+EuyacYa/TFch4KxwPHVrEnh+zKtGdeIhcd7pVkUd1IRkFVnWGGvBx+pCg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "+njDWbhjEsFL1WzGlL0fN5ygbqGbfwInwaAquDeC5aYgbrwvJOdnwonJgxV0fd0ABzIwfuUFzSMZg6F3qI84BA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "G8sfe52XB1m4Lc197oLD51bwbXOZXkpOtAsgiMHZHwbayEFsPeI2Ls/R4ZOcPYTjztXDKYt68Ig8QJblRanTCQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "jr8rsgKsxDYBuY58XqIqMkb9pW78Jb6rBGaEHLZRtWS6z58td37+PBesMzzg4J1GNov4RKcZfMbaANBui1OnAA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "QbprGqwfKC9bo4xs8pNieYI/TOwLOUNv4YVHhXPS/2xVU5bS+aDr71aD/o4PF3UmkJrMW/94b8X22Dy19SYEDQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "Iv9RL4rXHell9BfRp6PoWxD3TnT+WLugypT3aG4Ukf/iMqPVPizGtqfJY/axQY8jItBGTXcIyfZA9O6juGpfBg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "KKD5TdLEDBtDy+YoUgglFkpHVJjvn9K4xlzUF7doWIAo1+n3ySTYQ4V7+iJ04H0Axs3Gsp0d0ytDui5qw09+Cw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "C4po9GxTKUlww19PaUJUi+kUTBTTVYyUMjAvAhNYAVcOS6mpVNMIBX7UCt3XcTtlLU+q+goE0Mw68LvoMza4AQ==",
|
||||
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "l1lWA8GLEToT4GDa96aEt6TFO8RDP/WSHgXV+9mge8cHQ82UVrVJKX98andFvKDaNGPjZgR+gK5gslEMt2dVCg=="
|
||||
}
|
||||
}
|
||||
|
||||
1
artifacts/iqpilot_hephaestusd_private/manifest.json.sig
Normal file
1
artifacts/iqpilot_hephaestusd_private/manifest.json.sig
Normal file
@@ -0,0 +1 @@
|
||||
HlmJ/WkPaO0L0omQG7BWUtHYKzp2ZTFvaVu4BDpACrkluSoWeyX0suOZlU29fJvRI+nWhBEkRW7807toLyrNAQ==
|
||||
@@ -0,0 +1,53 @@
|
||||
[
|
||||
"iqpilot.common.api",
|
||||
"iqpilot.common.api.base",
|
||||
"iqpilot.common.api.comma_connect",
|
||||
"iqpilot.common.atlas_alerts",
|
||||
"iqpilot.common.basedir",
|
||||
"iqpilot.common.constants",
|
||||
"iqpilot.common.file_helpers",
|
||||
"iqpilot.common.git_creds",
|
||||
"iqpilot.common.gpio",
|
||||
"iqpilot.common.i2c",
|
||||
"iqpilot.common.issue_debug",
|
||||
"iqpilot.common.logging_extra",
|
||||
"iqpilot.common.realtime",
|
||||
"iqpilot.common.spinner",
|
||||
"iqpilot.common.swaglog",
|
||||
"iqpilot.common.time_helpers",
|
||||
"iqpilot.common.utils",
|
||||
"iqpilot.common.version",
|
||||
"iqpilot.konn3kt.cloud_client",
|
||||
"iqpilot.konn3kt.registration",
|
||||
"iqpilot.konn3kt.service_health",
|
||||
"iqpilot.selfdrive.car.vehicle_catalog",
|
||||
"iqpilot.selfdrive.iqmodeld",
|
||||
"iqpilot.selfdrive.iqmodeld.config",
|
||||
"iqpilot.selfdrive.iqmodeld.models",
|
||||
"iqpilot.selfdrive.iqmodeld.models.fetcher",
|
||||
"iqpilot.selfdrive.iqmodeld.models.helpers",
|
||||
"iqpilot.selfdrive.locationd.calibrationd",
|
||||
"iqpilot.selfdrive.selfdrived.alertmanager",
|
||||
"iqpilot.selfdrive.selfdrived.events",
|
||||
"iqpilot.selfdrive.ui.feedback.feedbackd",
|
||||
"iqpilot.system.hardware",
|
||||
"iqpilot.system.hardware.base",
|
||||
"iqpilot.system.hardware.hw",
|
||||
"iqpilot.system.hardware.pc.hardware",
|
||||
"iqpilot.system.hardware.tici",
|
||||
"iqpilot.system.hardware.tici.amplifier",
|
||||
"iqpilot.system.hardware.tici.esim_manager",
|
||||
"iqpilot.system.hardware.tici.hardware",
|
||||
"iqpilot.system.hardware.tici.iwlist",
|
||||
"iqpilot.system.hardware.tici.lpa",
|
||||
"iqpilot.system.hardware.tici.pins",
|
||||
"iqpilot.system.loggerd.xattr_cache",
|
||||
"iqpilot.system.manager.process",
|
||||
"iqpilot.system.manager.process_config",
|
||||
"iqpilot.system.micd",
|
||||
"iqpilot.system.sentry",
|
||||
"iqpilot.system.ui.lib.networkmanager",
|
||||
"iqpilot.system.ui.lib.os_update",
|
||||
"iqpilot.system.ui.lib.wifi_manager",
|
||||
"iqpilot.system.version"
|
||||
]
|
||||
@@ -1,9 +1,9 @@
|
||||
import openpilot.common.api.comma_connect
|
||||
import iqpilot.common.api.comma_connect
|
||||
|
||||
|
||||
class Api:
|
||||
def __init__(self, dongle_id):
|
||||
self.service = openpilot.common.api.comma_connect.CommaConnectApi(dongle_id)
|
||||
self.service = iqpilot.common.api.comma_connect.CommaConnectApi(dongle_id)
|
||||
|
||||
def request(self, method, endpoint, **params):
|
||||
return self.service.request(method, endpoint, **params)
|
||||
@@ -19,8 +19,8 @@ class Api:
|
||||
|
||||
|
||||
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
|
||||
return openpilot.common.api.comma_connect.CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
|
||||
return iqpilot.common.api.comma_connect.CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
|
||||
|
||||
|
||||
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
|
||||
return openpilot.common.api.comma_connect.CommaConnectApi(None).get_key_pair()
|
||||
return iqpilot.common.api.comma_connect.CommaConnectApi(None).get_key_pair()
|
||||
@@ -4,8 +4,8 @@ import requests
|
||||
import unicodedata
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from functools import lru_cache
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.system.version import get_version
|
||||
from iqpilot.system.hardware.hw import Paths
|
||||
from iqpilot.system.version import get_version
|
||||
|
||||
# name: jwt signature algorithm
|
||||
KEYS = {"id_rsa": "RS256",
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
|
||||
from openpilot.common.api.base import BaseApi
|
||||
from iqpilot.common.api.base import BaseApi
|
||||
|
||||
API_HOST = os.getenv('API_HOST', 'https://api-iqlabs.konn3kt.com')
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from bisect import insort
|
||||
from collections.abc import Callable, Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import IntEnum
|
||||
|
||||
import iqpilot.cereal.messaging as messaging
|
||||
from iqpilot.cereal import car, log
|
||||
from iqpilot.common.realtime import DT_CTRL
|
||||
from iqpilot.system.hardware import HARDWARE
|
||||
|
||||
AlertSize = log.SelfdriveState.AlertSize
|
||||
AlertStatus = log.SelfdriveState.AlertStatus
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
|
||||
|
||||
|
||||
def _frames_for(seconds: float) -> int:
|
||||
return int(seconds / DT_CTRL)
|
||||
|
||||
|
||||
class Tier(IntEnum):
|
||||
LOWEST = 0
|
||||
LOWER = 1
|
||||
LOW = 2
|
||||
MID = 3
|
||||
HIGH = 4
|
||||
HIGHEST = 5
|
||||
|
||||
|
||||
class Tags:
|
||||
ENABLE = "enable"
|
||||
PRE_ENABLE = "preEnable"
|
||||
OVERRIDE_LATERAL = "overrideLateral"
|
||||
OVERRIDE_LONGITUDINAL = "overrideLongitudinal"
|
||||
NO_ENTRY = "noEntry"
|
||||
WARNING = "warning"
|
||||
USER_DISABLE = "userDisable"
|
||||
SOFT_DISABLE = "softDisable"
|
||||
IMMEDIATE_DISABLE = "immediateDisable"
|
||||
PERMANENT = "permanent"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class AlertCard:
|
||||
alert_text_1: str
|
||||
alert_text_2: str
|
||||
alert_status: log.SelfdriveState.AlertStatus
|
||||
alert_size: log.SelfdriveState.AlertSize
|
||||
priority: Tier
|
||||
visual_alert: car.CarControl.HUDControl.VisualAlert
|
||||
audible_alert: car.CarControl.HUDControl.AudibleAlert
|
||||
duration: int
|
||||
creation_delay: float = 0.0
|
||||
alert_type: str = field(default="", init=False)
|
||||
event_type: str | None = field(default=None, init=False)
|
||||
|
||||
def __init__(self,
|
||||
alert_text_1: str,
|
||||
alert_text_2: str,
|
||||
alert_status: log.SelfdriveState.AlertStatus,
|
||||
alert_size: log.SelfdriveState.AlertSize,
|
||||
priority: Tier,
|
||||
visual_alert: car.CarControl.HUDControl.VisualAlert,
|
||||
audible_alert: car.CarControl.HUDControl.AudibleAlert,
|
||||
duration: float,
|
||||
creation_delay: float = 0.0):
|
||||
self.alert_text_1 = alert_text_1
|
||||
self.alert_text_2 = alert_text_2
|
||||
self.alert_status = alert_status
|
||||
self.alert_size = alert_size
|
||||
self.priority = priority
|
||||
self.visual_alert = visual_alert
|
||||
self.audible_alert = audible_alert
|
||||
self.duration = _frames_for(duration)
|
||||
self.creation_delay = creation_delay
|
||||
self.alert_type = ""
|
||||
self.event_type = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.alert_text_1}/{self.alert_text_2} {self.priority} {self.visual_alert} {self.audible_alert}"
|
||||
|
||||
|
||||
AlertFactory = Callable[[car.CarParams, car.CarState, messaging.SubMaster, bool, int, log.ControlsState], AlertCard]
|
||||
|
||||
|
||||
def car_mode_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> AlertCard:
|
||||
del CS, sm, metric, soft_disable_time, personality
|
||||
headline = "Enable Adaptive Cruise to Engage"
|
||||
if CP.brand == "honda":
|
||||
headline = "Enable Main Switch to Engage"
|
||||
return NoEntryCard(headline)
|
||||
|
||||
|
||||
class EventBook(ABC):
|
||||
def __init__(self):
|
||||
self._live_names: list[int] = []
|
||||
self._latched_names: list[int] = []
|
||||
self.event_counters: dict[int, int] = {}
|
||||
|
||||
@property
|
||||
def events(self) -> list[int]:
|
||||
return self._live_names
|
||||
|
||||
@events.setter
|
||||
def events(self, values: list[int]) -> None:
|
||||
self._live_names = values
|
||||
|
||||
@property
|
||||
def static_events(self) -> list[int]:
|
||||
return self._latched_names
|
||||
|
||||
@static_events.setter
|
||||
def static_events(self, values: list[int]) -> None:
|
||||
self._latched_names = values
|
||||
|
||||
@property
|
||||
def names(self) -> list[int]:
|
||||
return list(self._live_names)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._live_names)
|
||||
|
||||
def add(self, event_name: int, static: bool = False) -> None:
|
||||
if static:
|
||||
insort(self._latched_names, event_name)
|
||||
insort(self._live_names, event_name)
|
||||
|
||||
def clear(self) -> None:
|
||||
refreshed: dict[int, int] = {}
|
||||
for event_name, frames_seen in self.event_counters.items():
|
||||
refreshed[event_name] = frames_seen + 1 if event_name in self._live_names else 0
|
||||
self.event_counters = refreshed
|
||||
self._live_names = list(self._latched_names)
|
||||
|
||||
def contains(self, event_type: str) -> bool:
|
||||
board = self.get_events_mapping()
|
||||
return any(event_type in board.get(event_name, {}) for event_name in self._live_names)
|
||||
|
||||
def has(self, event_name: int) -> bool:
|
||||
return event_name in self._live_names
|
||||
|
||||
def contains_in_list(self, events_list: list[int]) -> bool:
|
||||
return any(event_name in self._live_names for event_name in events_list)
|
||||
|
||||
def remove(self, event_name: int, static: bool = False) -> None:
|
||||
if static and event_name in self._latched_names:
|
||||
self._latched_names.remove(event_name)
|
||||
|
||||
if event_name in self._live_names:
|
||||
self.event_counters[event_name] = self.event_counters.get(event_name, 0) + 1
|
||||
self._live_names.remove(event_name)
|
||||
|
||||
def add_from_msg(self, events: Iterable) -> None:
|
||||
for event in events:
|
||||
insort(self._live_names, event.name.raw)
|
||||
|
||||
def to_msg(self):
|
||||
board = self.get_events_mapping()
|
||||
outbound = []
|
||||
for event_name in self._live_names:
|
||||
msg = self.get_event_msg_type().new_message()
|
||||
msg.name = event_name
|
||||
for event_kind in board.get(event_name, {}):
|
||||
setattr(msg, event_kind, True)
|
||||
outbound.append(msg)
|
||||
return outbound
|
||||
|
||||
def create_alerts(self, event_types: list[str], callback_args=None):
|
||||
callback_args = [] if callback_args is None else callback_args
|
||||
board = self.get_events_mapping()
|
||||
spawned: list[AlertCard] = []
|
||||
for event_name in self._live_names:
|
||||
variants = board.get(event_name, {})
|
||||
for event_type in event_types:
|
||||
chosen = variants.get(event_type)
|
||||
if chosen is None:
|
||||
continue
|
||||
alert = self._realize(chosen, callback_args)
|
||||
age_frames = self.event_counters.get(event_name, 0) + 1
|
||||
if age_frames * DT_CTRL < alert.creation_delay:
|
||||
continue
|
||||
alert.alert_type = f"{self.get_event_name(event_name)}/{event_type}"
|
||||
alert.event_type = event_type
|
||||
spawned.append(alert)
|
||||
return spawned
|
||||
|
||||
@staticmethod
|
||||
def _realize(candidate: AlertCard | AlertFactory, callback_args: list) -> AlertCard:
|
||||
return candidate if isinstance(candidate, AlertCard) else candidate(*callback_args)
|
||||
|
||||
@abstractmethod
|
||||
def get_events_mapping(self) -> dict[int, dict[str, AlertCard | AlertFactory]]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_event_name(self, event: int) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_event_msg_type(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _mici_reframe(primary: str, secondary: str) -> tuple[str, str, log.SelfdriveState.AlertSize]:
|
||||
if HARDWARE.get_device_type() == "mici":
|
||||
return secondary, primary, AlertSize.small
|
||||
return primary, secondary, AlertSize.mid
|
||||
|
||||
|
||||
class NoEntryCard(AlertCard):
|
||||
def __init__(self,
|
||||
alert_text_2: str,
|
||||
alert_text_1: str = "IQ.Pilot Unavailable",
|
||||
visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none,
|
||||
priority: Tier = Tier.LOW):
|
||||
primary, secondary, size = _mici_reframe(alert_text_1, alert_text_2)
|
||||
super().__init__(primary, secondary, AlertStatus.normal, size, priority, visual_alert, AudibleAlert.refuse, 3.0)
|
||||
|
||||
|
||||
class GentleDisableCard(AlertCard):
|
||||
def __init__(self, alert_text_2: str):
|
||||
super().__init__(
|
||||
"TAKE CONTROL IMMEDIATELY",
|
||||
alert_text_2,
|
||||
AlertStatus.userPrompt,
|
||||
AlertSize.full,
|
||||
Tier.MID,
|
||||
VisualAlert.steerRequired,
|
||||
AudibleAlert.warningSoft,
|
||||
2.0,
|
||||
)
|
||||
|
||||
|
||||
class PendingDisableCard(GentleDisableCard):
|
||||
def __init__(self, alert_text_2: str):
|
||||
super().__init__(alert_text_2)
|
||||
self.alert_text_1 = "IQ.Pilot will disengage"
|
||||
|
||||
|
||||
class HardDisableCard(AlertCard):
|
||||
def __init__(self, alert_text_2: str):
|
||||
super().__init__(
|
||||
"TAKE CONTROL IMMEDIATELY",
|
||||
alert_text_2,
|
||||
AlertStatus.critical,
|
||||
AlertSize.full,
|
||||
Tier.HIGHEST,
|
||||
VisualAlert.steerRequired,
|
||||
AudibleAlert.warningImmediate,
|
||||
4.0,
|
||||
)
|
||||
|
||||
|
||||
class ChimeCard(AlertCard):
|
||||
def __init__(self, audible_alert: car.CarControl.HUDControl.AudibleAlert):
|
||||
super().__init__("", "", AlertStatus.normal, AlertSize.none, Tier.MID, VisualAlert.none, audible_alert, 0.2)
|
||||
|
||||
|
||||
class BannerCard(AlertCard):
|
||||
def __init__(self, alert_text_1: str, alert_text_2: str = "", duration: float = 0.2, priority: Tier = Tier.LOWER, creation_delay: float = 0.0):
|
||||
size = AlertSize.mid if alert_text_2 else AlertSize.small
|
||||
super().__init__(alert_text_1, alert_text_2, AlertStatus.normal, size, priority, VisualAlert.none, AudibleAlert.none, duration, creation_delay)
|
||||
|
||||
|
||||
class BootCard(AlertCard):
|
||||
def __init__(self, alert_text_1: str, alert_text_2: str = "Always keep hands on wheel and eyes on road", alert_status=AlertStatus.normal):
|
||||
if HARDWARE.get_device_type() == "mici":
|
||||
compact_secondary = "" if alert_text_2 == "Always keep hands on wheel and eyes on road" else alert_text_2
|
||||
super().__init__(alert_text_1, compact_secondary, alert_status, AlertSize.small, Tier.LOWER, VisualAlert.none, AudibleAlert.none, 5.0)
|
||||
else:
|
||||
super().__init__(alert_text_1, alert_text_2, alert_status, AlertSize.mid, Tier.LOWER, VisualAlert.none, AudibleAlert.none, 5.0)
|
||||
|
||||
|
||||
class AlertBase(AlertCard):
|
||||
pass
|
||||
|
||||
|
||||
NULL_ALERT = AlertCard("", "", AlertStatus.normal, AlertSize.none, Tier.LOWEST, VisualAlert.none, AudibleAlert.none, 0.0)
|
||||
@@ -1,4 +1,4 @@
|
||||
import os
|
||||
|
||||
|
||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
|
||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../.."))
|
||||
@@ -0,0 +1 @@
|
||||
from iqpilot.common.utils import CallbackReader, get_upload_stream
|
||||
@@ -3,13 +3,15 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from iqpilot.common.params import Params
|
||||
|
||||
PARAM = "GitAuthBlob"
|
||||
PARAMS_DIR = os.environ.get("PARAMS_DIR", "/data/params/d")
|
||||
KEY_DIR = "/data/konn3kt"
|
||||
KEY_PATH = os.path.join(KEY_DIR, "git_auth.key")
|
||||
HELPER_PATH = os.path.join(KEY_DIR, "git_credential_helper.py")
|
||||
DEFAULT_REPO_DIR = "/data/openpilot"
|
||||
CREDENTIAL_HOSTS = ("git.konn3kt.com", "gitlvb.teallvbs.xyz")
|
||||
|
||||
_HELPER_SCRIPT = '''#!/usr/bin/env python3
|
||||
import json
|
||||
@@ -50,6 +52,48 @@ if __name__ == "__main__":
|
||||
'''
|
||||
|
||||
|
||||
def _params_get(name: str) -> bytes | None:
|
||||
# Params -> cereal -> iqdbc: that chain is unavailable mid-bootstrap (this module's
|
||||
# callers install those very packages), so fall back to the params file directly,
|
||||
# exactly like the embedded credential helper does.
|
||||
try:
|
||||
return Params().get(name)
|
||||
except Exception:
|
||||
try:
|
||||
with open(os.path.join(PARAMS_DIR, name), "rb") as f:
|
||||
return f.read()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _params_put(name: str, value: bytes) -> None:
|
||||
try:
|
||||
Params().put(name, value)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
os.makedirs(PARAMS_DIR, exist_ok=True)
|
||||
tmp = os.path.join(PARAMS_DIR, f".tmp_{name}")
|
||||
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
|
||||
with os.fdopen(fd, "wb") as f:
|
||||
f.write(value)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp, os.path.join(PARAMS_DIR, name))
|
||||
|
||||
|
||||
def _params_remove(name: str) -> None:
|
||||
try:
|
||||
Params().remove(name)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
os.unlink(os.path.join(PARAMS_DIR, name))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _load_or_create_key() -> bytes:
|
||||
from cryptography.fernet import Fernet
|
||||
try:
|
||||
@@ -79,7 +123,7 @@ def set_credentials(username: str, token: str) -> None:
|
||||
blob = Fernet(_load_or_create_key()).encrypt(
|
||||
json.dumps({"u": username, "t": token}).encode()
|
||||
)
|
||||
Params().put(PARAM, blob)
|
||||
_params_put(PARAM, blob)
|
||||
try:
|
||||
install_credential_helper(DEFAULT_REPO_DIR)
|
||||
except Exception:
|
||||
@@ -88,7 +132,7 @@ def set_credentials(username: str, token: str) -> None:
|
||||
|
||||
def get_credentials() -> tuple[str, str] | None:
|
||||
"""Return (username, token), or None if unset / unreadable."""
|
||||
blob = Params().get(PARAM)
|
||||
blob = _params_get(PARAM)
|
||||
if not blob:
|
||||
return None
|
||||
try:
|
||||
@@ -100,7 +144,7 @@ def get_credentials() -> tuple[str, str] | None:
|
||||
|
||||
|
||||
def clear_credentials() -> None:
|
||||
Params().remove(PARAM)
|
||||
_params_remove(PARAM)
|
||||
|
||||
|
||||
def has_credentials() -> bool:
|
||||
@@ -135,21 +179,17 @@ def install_credential_helper(repo_dir: str = DEFAULT_REPO_DIR) -> None:
|
||||
if get_credentials() is None:
|
||||
return
|
||||
|
||||
scopes = {f"https://{host}" for host in CREDENTIAL_HOSTS}
|
||||
origin = subprocess.run(
|
||||
["git", "-C", repo_dir, "config", "--get", "remote.origin.url"],
|
||||
capture_output=True, text=True, check=False,
|
||||
).stdout.strip()
|
||||
if not origin:
|
||||
return
|
||||
https = ssh_to_https(origin)
|
||||
if not https.startswith("https://"):
|
||||
return
|
||||
|
||||
from urllib.parse import urlsplit
|
||||
parts = urlsplit(https)
|
||||
if not parts.hostname:
|
||||
return
|
||||
scope = f"{parts.scheme}://{parts.hostname}"
|
||||
if https.startswith("https://"):
|
||||
from urllib.parse import urlsplit
|
||||
parts = urlsplit(https)
|
||||
if parts.hostname:
|
||||
scopes.add(f"{parts.scheme}://{parts.hostname}")
|
||||
|
||||
try:
|
||||
os.makedirs(KEY_DIR, exist_ok=True)
|
||||
@@ -162,10 +202,11 @@ def install_credential_helper(repo_dir: str = DEFAULT_REPO_DIR) -> None:
|
||||
return
|
||||
|
||||
helper_cmd = f"!/usr/bin/env python3 {HELPER_PATH}"
|
||||
subprocess.run(
|
||||
["git", "-C", repo_dir, "config", f"credential.{scope}.helper", helper_cmd],
|
||||
check=False, capture_output=True,
|
||||
)
|
||||
for scope in scopes:
|
||||
subprocess.run(
|
||||
["git", "config", "--global", f"credential.{scope}.helper", helper_cmd],
|
||||
check=False, capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
def configure(repo_dir: str) -> None:
|
||||
@@ -4,8 +4,8 @@ import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from iqpilot.system.hardware import PC
|
||||
from iqpilot.system.hardware.hw import Paths
|
||||
|
||||
|
||||
DEBUG_FILENAME = "iqpilot_issue_debug.txt"
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Utilities for reading real time clocks and keeping soft real time constraints."""
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from setproctitle import getproctitle
|
||||
|
||||
from iqpilot.common.utils import MovingAverage
|
||||
from iqpilot.system.hardware import PC
|
||||
|
||||
|
||||
# time step for each process
|
||||
DT_CTRL = 0.01 # controlsd
|
||||
DT_MDL = 0.05 # model
|
||||
DT_HW = 0.5 # hardwared and manager
|
||||
DT_DMON = 0.05 # driver monitoring
|
||||
|
||||
|
||||
class Priority:
|
||||
# CORE 2
|
||||
# - modeld = 55
|
||||
# - camerad = 54
|
||||
CTRL_LOW = 51 # plannerd & radard
|
||||
|
||||
# CORE 3
|
||||
# - pandad = 55
|
||||
CTRL_HIGH = 53
|
||||
|
||||
|
||||
def set_core_affinity(cores: list[int]) -> None:
|
||||
if sys.platform == 'linux' and not PC:
|
||||
os.sched_setaffinity(0, cores)
|
||||
|
||||
|
||||
def config_realtime_process(cores: int | list[int], priority: int) -> None:
|
||||
gc.disable()
|
||||
if sys.platform == 'linux' and not PC:
|
||||
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(priority))
|
||||
c = cores if isinstance(cores, list) else [cores, ]
|
||||
set_core_affinity(c)
|
||||
|
||||
|
||||
def lock_memory() -> None:
|
||||
"""mlockall this process so memory reclaim/compaction can't stall it. RT control
|
||||
procs only (locking ui/modeld would worsen pressure). Best-effort."""
|
||||
if sys.platform != 'linux' or PC:
|
||||
return
|
||||
try:
|
||||
import ctypes
|
||||
import resource
|
||||
resource.setrlimit(resource.RLIMIT_MEMLOCK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
|
||||
MCL_CURRENT, MCL_FUTURE = 0x1, 0x2
|
||||
libc = ctypes.CDLL("libc.so.6", use_errno=True)
|
||||
if libc.mlockall(MCL_CURRENT | MCL_FUTURE) != 0:
|
||||
raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
|
||||
except Exception as e:
|
||||
try:
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
cloudlog.warning(f"lock_memory (mlockall) failed: {e}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class Ratekeeper:
|
||||
def __init__(self, rate: float, print_delay_threshold: float | None = 0.0) -> None:
|
||||
"""Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative."""
|
||||
self._interval = 1. / rate
|
||||
self._print_delay_threshold = print_delay_threshold
|
||||
self._frame = 0
|
||||
self._remaining = 0.0
|
||||
self._process_name = getproctitle()
|
||||
self._last_monitor_time = -1.
|
||||
self._next_frame_time = -1.
|
||||
|
||||
self.avg_dt = MovingAverage(100)
|
||||
self.avg_dt.add_value(self._interval)
|
||||
|
||||
def reset(self) -> None:
|
||||
self._remaining = 0.0
|
||||
self._last_monitor_time = -1.
|
||||
self._next_frame_time = -1.
|
||||
self.avg_dt = MovingAverage(100)
|
||||
self.avg_dt.add_value(self._interval)
|
||||
|
||||
@property
|
||||
def frame(self) -> int:
|
||||
return self._frame
|
||||
|
||||
@property
|
||||
def remaining(self) -> float:
|
||||
return self._remaining
|
||||
|
||||
@property
|
||||
def lag(self) -> float:
|
||||
return max(0., -self._remaining)
|
||||
|
||||
@property
|
||||
def lagging(self) -> bool:
|
||||
expected_dt = self._interval * (1 / 0.9)
|
||||
return self.avg_dt.get_average() > expected_dt
|
||||
|
||||
# Maintain loop rate by calling this at the end of each loop
|
||||
def keep_time(self) -> bool:
|
||||
lagged = self.monitor_time()
|
||||
if self._remaining > 0:
|
||||
time.sleep(self._remaining)
|
||||
return lagged
|
||||
|
||||
# Monitors the cumulative lag, but does not enforce a rate
|
||||
def monitor_time(self) -> bool:
|
||||
if self._last_monitor_time < 0:
|
||||
self._next_frame_time = time.monotonic() + self._interval
|
||||
self._last_monitor_time = time.monotonic()
|
||||
|
||||
prev = self._last_monitor_time
|
||||
self._last_monitor_time = time.monotonic()
|
||||
self.avg_dt.add_value(self._last_monitor_time - prev)
|
||||
|
||||
lagged = False
|
||||
remaining = self._next_frame_time - time.monotonic()
|
||||
self._next_frame_time += self._interval
|
||||
if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold:
|
||||
print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms")
|
||||
lagged = True
|
||||
self._frame += 1
|
||||
self._remaining = remaining
|
||||
return lagged
|
||||
4
common/spinner.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/spinner.py
Executable file → Normal file
4
common/spinner.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/spinner.py
Executable file → Normal file
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
import subprocess
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from iqpilot.common.basedir import BASEDIR
|
||||
|
||||
|
||||
class Spinner:
|
||||
@@ -8,7 +8,7 @@ class Spinner:
|
||||
try:
|
||||
self.spinner_proc = subprocess.Popen(["./spinner.py"],
|
||||
stdin=subprocess.PIPE,
|
||||
cwd=os.path.join(BASEDIR, "system", "ui"),
|
||||
cwd=os.path.join(BASEDIR, "iqpilot", "system", "ui"),
|
||||
close_fds=True)
|
||||
except OSError:
|
||||
self.spinner_proc = None
|
||||
@@ -8,8 +8,8 @@ from logging.handlers import BaseRotatingHandler
|
||||
|
||||
import zmq
|
||||
|
||||
from openpilot.common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from iqpilot.common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter
|
||||
from iqpilot.system.hardware.hw import Paths
|
||||
|
||||
|
||||
def get_file_handler():
|
||||
@@ -0,0 +1,5 @@
|
||||
from iqpilot.common.git import get_normalized_origin
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
return "IQ.Pilot 1.0c"
|
||||
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
from iqpilot.common.api.base import BaseApi
|
||||
API_HOST = os.getenv('KONN3KT_API_HOST', 'https://api-iqlabs.konn3kt.com')
|
||||
|
||||
class Konn3ktApi(BaseApi):
|
||||
|
||||
def __init__(self, dongle_id):
|
||||
super().__init__(dongle_id, API_HOST)
|
||||
self.user_agent = "konn3kt-device-"
|
||||
|
||||
def get_token(self, expiry_hours=1):
|
||||
return super()._get_token(expiry_hours=expiry_hours)
|
||||
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import jwt
|
||||
import re
|
||||
import secrets
|
||||
from typing import cast
|
||||
from pathlib import Path
|
||||
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from iqpilot.common.api import api_get, get_key_pair
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.spinner import Spinner
|
||||
from iqpilot.system.hardware import HARDWARE, PC
|
||||
from iqpilot.system.hardware.hw import Paths
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
|
||||
|
||||
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
|
||||
|
||||
_DONGLE_ID_RE = re.compile(r"^[a-fA-F0-9]{16}$")
|
||||
IMEI_WAIT_TIMEOUT = 15.0
|
||||
|
||||
|
||||
def _read_persist_dongle_id() -> str | None:
|
||||
p = Path(Paths.persist_root()) / "comma" / "dongle_id"
|
||||
try:
|
||||
if not p.is_file():
|
||||
return None
|
||||
s = p.read_text().strip()
|
||||
return s or None
|
||||
except Exception:
|
||||
cloudlog.exception("failed to read persist dongle_id")
|
||||
return None
|
||||
|
||||
|
||||
def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str | None:
|
||||
ro = _read_persist_dongle_id()
|
||||
if is_valid_dongle_id(ro):
|
||||
ro = ro.lower()
|
||||
if prefer_readonly and ro:
|
||||
return ro
|
||||
p = Params() if params is None else params
|
||||
v = p.get("DongleId")
|
||||
if v and v != UNREGISTERED_DONGLE_ID:
|
||||
return v.lower() if is_valid_dongle_id(v) else v
|
||||
return ro or None
|
||||
def is_valid_dongle_id(dongle_id: str | None) -> bool:
|
||||
return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
|
||||
def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str:
|
||||
p = Params() if params is None else params
|
||||
dongle_id = get_cached_dongle_id(p, prefer_readonly=prefer_readonly)
|
||||
if dongle_id and dongle_id != UNREGISTERED_DONGLE_ID:
|
||||
return dongle_id
|
||||
dongle_id = secrets.token_hex(8)
|
||||
p.put("DongleId", dongle_id)
|
||||
cloudlog.warning(f"generated new DongleId={dongle_id} (no readonly dongle_id found)")
|
||||
return dongle_id
|
||||
def ensure_dev_pairing_identity(params: Params | None = None, force_reset: bool = False) -> dict[str, str]:
|
||||
p = Params() if params is None else params
|
||||
|
||||
persist_dir = Path(Paths.persist_root()) / "comma"
|
||||
persist_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
dongle_path = persist_dir / "dongle_id"
|
||||
priv_path = persist_dir / "id_rsa"
|
||||
pub_path = persist_dir / "id_rsa.pub"
|
||||
|
||||
if force_reset:
|
||||
for fp in (dongle_path, priv_path, pub_path):
|
||||
try:
|
||||
fp.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
cloudlog.exception(f"failed to remove {fp}")
|
||||
try:
|
||||
(persist_dir / "konn3kt_prime_type").unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
p.remove("PrimeType")
|
||||
except Exception:
|
||||
pass
|
||||
forced_dongle = os.getenv("KONN3KT_DEV_DONGLE_ID")
|
||||
dongle_id = forced_dongle.strip().lower() if forced_dongle else None
|
||||
if dongle_id and not is_valid_dongle_id(dongle_id):
|
||||
cloudlog.error("KONN3KT_DEV_DONGLE_ID must be 16 hex chars")
|
||||
dongle_id = None
|
||||
if dongle_id is None:
|
||||
existing = None
|
||||
try:
|
||||
existing = dongle_path.read_text().strip().lower() if dongle_path.is_file() else None
|
||||
except Exception:
|
||||
cloudlog.exception("failed reading existing dev dongle_id")
|
||||
dongle_id = existing if is_valid_dongle_id(existing) else secrets.token_hex(8)
|
||||
try:
|
||||
dongle_path.write_text(dongle_id)
|
||||
except Exception:
|
||||
cloudlog.exception("failed writing dev dongle_id")
|
||||
p.put("DongleId", dongle_id)
|
||||
p.put("HardwareSerial", p.get("HardwareSerial") or f"DEV-{dongle_id}")
|
||||
if force_reset or (not priv_path.is_file()) or (not pub_path.is_file()):
|
||||
try:
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
priv_bytes = key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
pub_bytes = key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
priv_path.write_bytes(priv_bytes)
|
||||
pub_path.write_bytes(pub_bytes)
|
||||
except Exception:
|
||||
cloudlog.exception("failed generating dev RSA keys")
|
||||
raise
|
||||
return {
|
||||
"dongle_id": dongle_id,
|
||||
"serial": p.get("HardwareSerial") or f"DEV-{dongle_id}",
|
||||
"persist_dir": str(persist_dir),
|
||||
}
|
||||
def is_registered_device() -> bool:
|
||||
dongle = Params().get("DongleId")
|
||||
return dongle not in (None, UNREGISTERED_DONGLE_ID)
|
||||
|
||||
|
||||
def _normalize_imei(value: str | None) -> str:
|
||||
return value or ""
|
||||
|
||||
|
||||
def get_registration_identifiers(wait_timeout: float = IMEI_WAIT_TIMEOUT, show_spinner: bool = False) -> tuple[str, str, str]:
|
||||
serial = HARDWARE.get_serial()
|
||||
spinner = Spinner() if show_spinner else None
|
||||
start_time = time.monotonic()
|
||||
imei1: str | None = None
|
||||
imei2: str | None = None
|
||||
|
||||
while time.monotonic() - start_time < wait_timeout:
|
||||
try:
|
||||
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
|
||||
if imei1 or imei2:
|
||||
break
|
||||
except RuntimeError as e:
|
||||
if "no modems" in str(e).lower():
|
||||
cloudlog.warning("No cellular modem available, proceeding without IMEI")
|
||||
break
|
||||
cloudlog.exception("Error getting imei, trying again...")
|
||||
except Exception:
|
||||
cloudlog.exception("Error getting imei, trying again...")
|
||||
time.sleep(1)
|
||||
|
||||
imei1 = _normalize_imei(imei1)
|
||||
imei2 = _normalize_imei(imei2)
|
||||
|
||||
if not imei1 and not imei2:
|
||||
cloudlog.warning(f"proceeding with serial-only registration for serial={serial}")
|
||||
if spinner is not None:
|
||||
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
|
||||
spinner.close()
|
||||
|
||||
return serial, imei1, imei2
|
||||
|
||||
|
||||
def register(show_spinner=False) -> str | None:
|
||||
params = Params()
|
||||
|
||||
dongle_id: str | None = get_cached_dongle_id(params, prefer_readonly=True)
|
||||
if dongle_id in ("", UNREGISTERED_DONGLE_ID):
|
||||
dongle_id = None
|
||||
|
||||
jwt_algo, private_key, public_key = get_key_pair()
|
||||
|
||||
if not public_key:
|
||||
dongle_id = UNREGISTERED_DONGLE_ID
|
||||
cloudlog.warning("missing public key")
|
||||
elif dongle_id is None:
|
||||
if show_spinner:
|
||||
spinner = Spinner()
|
||||
spinner.update("registering device")
|
||||
|
||||
serial, imei1, imei2 = get_registration_identifiers(wait_timeout=IMEI_WAIT_TIMEOUT, show_spinner=False)
|
||||
|
||||
backoff = 0
|
||||
start_time = time.monotonic()
|
||||
while True:
|
||||
try:
|
||||
register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)},
|
||||
cast(str, private_key), algorithm=jwt_algo)
|
||||
cloudlog.info("getting pilotauth")
|
||||
cloudlog.info("getting pilotauth")
|
||||
resp = api_get("v2/pilotauth/", method='POST', timeout=15,
|
||||
imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
|
||||
|
||||
if resp.status_code in (400, 402, 403):
|
||||
cloudlog.info(f"Unable to register device, got {resp.status_code}")
|
||||
dongle_id = UNREGISTERED_DONGLE_ID
|
||||
else:
|
||||
dongleauth = json.loads(resp.text)
|
||||
dongle_id = dongleauth["dongle_id"]
|
||||
break
|
||||
except Exception:
|
||||
cloudlog.exception("failed to authenticate")
|
||||
backoff = min(backoff + 1, 15)
|
||||
time.sleep(backoff)
|
||||
|
||||
if time.monotonic() - start_time > 60 and show_spinner:
|
||||
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})")
|
||||
return UNREGISTERED_DONGLE_ID
|
||||
|
||||
if show_spinner:
|
||||
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
|
||||
spinner.close()
|
||||
|
||||
if dongle_id:
|
||||
params.put("DongleId", dongle_id)
|
||||
from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
|
||||
set_offroad_alert("Offroad_UnregisteredHardware", False)
|
||||
return dongle_id
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(register())
|
||||
@@ -1,4 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
"""
|
||||
|
||||
def hephaestus_ready(params=None) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def hephaestus_ready_shim():
|
||||
return hephaestus_ready()
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
from iqpilot.common.basedir import BASEDIR
|
||||
|
||||
SCHEMA = "iqlvbs/supported-vehicles"
|
||||
REV = 1
|
||||
|
||||
CATALOG_FILENAME = "vehicle_catalog.json"
|
||||
_CANDIDATE_PARTS = (
|
||||
("iqpilot", "selfdrive", "car", CATALOG_FILENAME),
|
||||
)
|
||||
|
||||
# in-memory (car-interface) field -> on-disk compact key
|
||||
_ATTR_TO_KEY = (
|
||||
("platform", "id"),
|
||||
("make", "mk"),
|
||||
("brand", "grp"),
|
||||
("model", "mdl"),
|
||||
("year", "yrs"),
|
||||
("package", "req"),
|
||||
)
|
||||
|
||||
|
||||
def _reference(platform: str, years: list[str], claimed: set[str]) -> str:
|
||||
span = f"{years[0]}-{years[-1]}" if len(years) > 1 else (years[0] if years else "na")
|
||||
stem = f"{platform}|{span}"
|
||||
ref, bump = stem, 2
|
||||
while ref in claimed:
|
||||
ref = f"{stem}#{bump}"
|
||||
bump += 1
|
||||
claimed.add(ref)
|
||||
return ref
|
||||
|
||||
|
||||
def encode(vehicles: dict[str, dict]) -> dict:
|
||||
records: dict[str, dict] = {}
|
||||
claimed: set[str] = set()
|
||||
for label, attrs in vehicles.items():
|
||||
years = list(attrs.get("year") or [])
|
||||
ref = _reference(attrs.get("platform", ""), years, claimed)
|
||||
record = {"label": label}
|
||||
for attr, key in _ATTR_TO_KEY:
|
||||
record[key] = attrs.get(attr)
|
||||
records[ref] = record
|
||||
return {"catalog": SCHEMA, "rev": REV, "vehicles": records}
|
||||
|
||||
|
||||
def decode(envelope: dict) -> dict[str, dict]:
|
||||
vehicles: dict[str, dict] = {}
|
||||
for record in (envelope.get("vehicles") or {}).values():
|
||||
attrs = {attr: record.get(key) for attr, key in _ATTR_TO_KEY}
|
||||
vehicles[record.get("label", "")] = attrs
|
||||
return vehicles
|
||||
|
||||
|
||||
def catalog_path(basedir: str = BASEDIR) -> str | None:
|
||||
for parts in _CANDIDATE_PARTS:
|
||||
candidate = os.path.join(basedir, *parts)
|
||||
if os.path.isfile(candidate):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def load_catalog(basedir: str = BASEDIR) -> dict[str, dict]:
|
||||
path = catalog_path(basedir)
|
||||
if path is None:
|
||||
return {}
|
||||
with open(path) as handle:
|
||||
return decode(json.load(handle))
|
||||
|
||||
|
||||
def _write(vehicles: dict[str, dict], basedir: str = BASEDIR) -> str:
|
||||
out = os.path.join(basedir, "iqpilot", "selfdrive", "car", CATALOG_FILENAME)
|
||||
with open(out, "w") as handle:
|
||||
json.dump(encode(vehicles), handle, indent=2, ensure_ascii=False)
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from iqdbc.lvbs.car.car_catalog import build_car_catalog
|
||||
print("wrote", _write(build_car_catalog()))
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _models_dir() -> Path:
|
||||
return Path(__file__).resolve().parent / "models"
|
||||
|
||||
|
||||
def _artifact_path(stem: str, suffix: str) -> Path:
|
||||
return _models_dir() / f"{stem}{suffix}"
|
||||
|
||||
|
||||
MODEL_ASSETS = {
|
||||
"onnx": _artifact_path("supercombo", ".onnx"),
|
||||
"tinygrad": _artifact_path("supercombo", "_tinygrad.pkl"),
|
||||
"metadata": _artifact_path("supercombo", "_metadata.pkl"),
|
||||
}
|
||||
|
||||
MODEL_PATH = MODEL_ASSETS["onnx"]
|
||||
MODEL_PKL_PATH = MODEL_ASSETS["tinygrad"]
|
||||
METADATA_PATH = MODEL_ASSETS["metadata"]
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def index_function(index: int, max_val: float = 192, max_idx: int = 32) -> float:
|
||||
return max_val * ((index / max_idx) ** 2)
|
||||
|
||||
|
||||
def _quadratic_series(limit: float, steps: int) -> list[float]:
|
||||
return [index_function(index, max_val=limit, max_idx=steps - 1) for index in range(steps)]
|
||||
|
||||
|
||||
def _probability_window(*values: float) -> np.ndarray:
|
||||
return np.asarray(values, dtype=np.float32)
|
||||
|
||||
|
||||
def _field_group(start: int, stop: int, stride: int) -> slice:
|
||||
return slice(start, stop, stride)
|
||||
|
||||
|
||||
_IDX_COUNT = 33
|
||||
_T_AXIS = _quadratic_series(10.0, _IDX_COUNT)
|
||||
_X_AXIS = _quadratic_series(192.0, _IDX_COUNT)
|
||||
|
||||
|
||||
class ModelConstants:
|
||||
IDX_N = _IDX_COUNT
|
||||
T_IDXS = _T_AXIS
|
||||
X_IDXS = _X_AXIS
|
||||
LEAD_T_IDXS = [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
|
||||
LEAD_T_OFFSETS = [0.0, 2.0, 4.0]
|
||||
META_T_IDXS = [2.0, 4.0, 6.0, 8.0, 10.0]
|
||||
|
||||
MODEL_FREQ = 20
|
||||
FEATURE_LEN = 512
|
||||
FULL_HISTORY_BUFFER_LEN = 99
|
||||
HISTORY_BUFFER_LEN = FULL_HISTORY_BUFFER_LEN
|
||||
DESIRE_LEN = 8
|
||||
TRAFFIC_CONVENTION_LEN = 2
|
||||
NAV_FEATURE_LEN = 256
|
||||
NAV_INSTRUCTION_LEN = 150
|
||||
LAT_PLANNER_STATE_LEN = 4
|
||||
LATERAL_CONTROL_PARAMS_LEN = 2
|
||||
PREV_DESIRED_CURV_LEN = 1
|
||||
|
||||
FCW_THRESHOLDS_5MS2 = _probability_window(0.05, 0.05, 0.15, 0.15, 0.15)
|
||||
FCW_THRESHOLDS_3MS2 = _probability_window(0.7, 0.7)
|
||||
FCW_5MS2_PROBS_WIDTH = 5
|
||||
FCW_3MS2_PROBS_WIDTH = 2
|
||||
|
||||
DISENGAGE_WIDTH = 5
|
||||
POSE_WIDTH = 6
|
||||
WIDE_FROM_DEVICE_WIDTH = 3
|
||||
SIM_POSE_WIDTH = 6
|
||||
LEAD_WIDTH = 4
|
||||
LANE_LINES_WIDTH = 2
|
||||
ROAD_EDGES_WIDTH = 2
|
||||
PLAN_WIDTH = 15
|
||||
DESIRE_PRED_WIDTH = 8
|
||||
LAT_PLANNER_SOLUTION_WIDTH = 4
|
||||
DESIRED_CURV_WIDTH = 1
|
||||
|
||||
NUM_LANE_LINES = 4
|
||||
NUM_ROAD_EDGES = 2
|
||||
LEAD_TRAJ_LEN = 6
|
||||
DESIRE_PRED_LEN = 4
|
||||
|
||||
PLAN_MHP_N = 5
|
||||
LEAD_MHP_N = 2
|
||||
PLAN_MHP_SELECTION = 1
|
||||
LEAD_MHP_SELECTION = 3
|
||||
|
||||
FCW_THRESHOLD_5MS2_HIGH = 0.15
|
||||
FCW_THRESHOLD_5MS2_LOW = 0.05
|
||||
FCW_THRESHOLD_3MS2 = 0.7
|
||||
|
||||
CONFIDENCE_BUFFER_LEN = 5
|
||||
RYG_GREEN = 0.01165
|
||||
RYG_YELLOW = 0.06157
|
||||
POLY_PATH_DEGREE = 4
|
||||
|
||||
|
||||
class Plan:
|
||||
POSITION = slice(0, 3)
|
||||
VELOCITY = slice(3, 6)
|
||||
ACCELERATION = slice(6, 9)
|
||||
T_FROM_CURRENT_EULER = slice(9, 12)
|
||||
ORIENTATION_RATE = slice(12, 15)
|
||||
|
||||
|
||||
class Meta:
|
||||
ENGAGED = _field_group(0, 1, 1)
|
||||
GAS_DISENGAGE = _field_group(1, 31, 6)
|
||||
BRAKE_DISENGAGE = _field_group(2, 31, 6)
|
||||
STEER_OVERRIDE = _field_group(3, 31, 6)
|
||||
HARD_BRAKE_3 = _field_group(4, 31, 6)
|
||||
HARD_BRAKE_4 = _field_group(5, 31, 6)
|
||||
HARD_BRAKE_5 = _field_group(6, 31, 6)
|
||||
GAS_PRESS = _field_group(31, 55, 4)
|
||||
BRAKE_PRESS = _field_group(32, 55, 4)
|
||||
LEFT_BLINKER = _field_group(33, 55, 4)
|
||||
RIGHT_BLINKER = _field_group(34, 55, 4)
|
||||
|
||||
|
||||
class MetaTombRaider:
|
||||
ENGAGED = _field_group(0, 1, 1)
|
||||
GAS_DISENGAGE = _field_group(1, 41, 8)
|
||||
BRAKE_DISENGAGE = _field_group(2, 41, 8)
|
||||
STEER_OVERRIDE = _field_group(3, 41, 8)
|
||||
HARD_BRAKE_3 = _field_group(4, 41, 8)
|
||||
HARD_BRAKE_4 = _field_group(5, 41, 8)
|
||||
HARD_BRAKE_5 = _field_group(6, 41, 8)
|
||||
GAS_PRESS = _field_group(7, 41, 8)
|
||||
BRAKE_PRESS = _field_group(8, 41, 8)
|
||||
LEFT_BLINKER = _field_group(41, 53, 2)
|
||||
RIGHT_BLINKER = _field_group(42, 53, 2)
|
||||
|
||||
|
||||
class MetaSimPose:
|
||||
ENGAGED = _field_group(0, 1, 1)
|
||||
GAS_DISENGAGE = _field_group(1, 36, 7)
|
||||
BRAKE_DISENGAGE = _field_group(2, 36, 7)
|
||||
STEER_OVERRIDE = _field_group(3, 36, 7)
|
||||
HARD_BRAKE_3 = _field_group(4, 36, 7)
|
||||
HARD_BRAKE_4 = _field_group(5, 36, 7)
|
||||
HARD_BRAKE_5 = _field_group(6, 36, 7)
|
||||
GAS_PRESS = _field_group(7, 36, 7)
|
||||
LEFT_BLINKER = _field_group(36, 48, 2)
|
||||
RIGHT_BLINKER = _field_group(37, 48, 2)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
IQ model selection and runner support that is actively used by iqmodeld.
|
||||
"""
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
|
||||
Public entry point for the model-manifest fetcher: prefers the compiled private
|
||||
bundle, falling back to the in-tree source. The default-runner fallback lives in
|
||||
ManifestDecoder now, so no post-import patching is needed.
|
||||
"""
|
||||
from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
|
||||
|
||||
try:
|
||||
load_private_module(__name__, "iqpilot_private.models.fetcher")
|
||||
except ProprietaryModuleMissing:
|
||||
from iqpilot.models_private_src.fetcher import * # noqa: F403
|
||||
@@ -0,0 +1,309 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from iqpilot.cereal import custom
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
|
||||
from iqpilot.system.hardware.hw import Paths
|
||||
|
||||
try:
|
||||
load_private_module(__name__, "iqpilot_private.models.helpers")
|
||||
except ProprietaryModuleMissing:
|
||||
try:
|
||||
from iqpilot.models_private_src.helpers import * # noqa: F403
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
ModelBundle = custom.IQModelManager.ModelBundle
|
||||
Runner = custom.IQModelManager.Runner
|
||||
_MODEL_ROOT = Path(Paths.model_root())
|
||||
_ACTIVE_BUNDLE_KEY = "ModelManager_ActiveBundle"
|
||||
_MODELS_CACHE_KEY = "ModelManager_ModelsCache"
|
||||
_RUNNER_CACHE_KEY = "ModelRunnerTypeCache"
|
||||
_DOWNLOAD_INDEX_KEY = "ModelManager_DownloadIndex"
|
||||
_PENDING_MODEL_RESTORE_FILE = "/data/k3_pending_model_restore"
|
||||
_STOCK_RUNNER = int(Runner.stock)
|
||||
_TINYGRAD_RUNNER = int(Runner.tinygrad)
|
||||
_SNPE_RUNNER = int(Runner.snpe)
|
||||
|
||||
_DEFAULT_MODEL_DIR = Path(__file__).resolve().parents[1] / "default_model"
|
||||
_DEFAULT_BUNDLE_JSON = _DEFAULT_MODEL_DIR / "bundle.json"
|
||||
_DEFAULT_BUNDLE_REF = "default"
|
||||
|
||||
|
||||
def get_default_model_bundle(_bundles):
|
||||
"""Legacy compatibility hook: stock default is preinstalled, not a manifest bundle."""
|
||||
return None
|
||||
|
||||
|
||||
def _coerce_runner_value(value) -> int | None:
|
||||
raw = getattr(value, "raw", value)
|
||||
try:
|
||||
return int(raw)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _bundle_models(bundle) -> list:
|
||||
models = getattr(bundle, "models", None)
|
||||
return list(models) if models is not None else []
|
||||
|
||||
|
||||
def _bundle_needs_runtime_upgrade(bundle) -> bool:
|
||||
if bundle is None:
|
||||
return False
|
||||
|
||||
if _coerce_runner_value(getattr(bundle, "runner", None)) == _SNPE_RUNNER:
|
||||
return True
|
||||
|
||||
for model in _bundle_models(bundle):
|
||||
file_name = getattr(getattr(model, "artifact", None), "fileName", "") or ""
|
||||
if file_name.endswith(".thneed"):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _load_cached_manifest_bundles(params: Params):
|
||||
cached = params.get(_MODELS_CACHE_KEY) or {}
|
||||
bundles = []
|
||||
for raw_bundle in cached.get("bundles", []):
|
||||
try:
|
||||
min_selector_version = int(raw_bundle.get("minimumSelectorVersion", raw_bundle.get("minimum_selector_version", 0)))
|
||||
compatibility_view = dict(raw_bundle)
|
||||
compatibility_view["minimumSelectorVersion"] = min_selector_version
|
||||
is_compatible = globals().get("is_bundle_version_compatible")
|
||||
if is_compatible is not None and not is_compatible(compatibility_view):
|
||||
continue
|
||||
|
||||
if "short_name" in raw_bundle:
|
||||
from iqpilot.selfdrive.iqmodeld.models.fetcher import ManifestDecoder
|
||||
bundles.append(ManifestDecoder._decode_bundle(raw_bundle))
|
||||
continue
|
||||
|
||||
if "internalName" in raw_bundle:
|
||||
bundles.append(ModelBundle(**raw_bundle))
|
||||
continue
|
||||
|
||||
bundle = ModelBundle()
|
||||
bundle.index = int(raw_bundle["index"])
|
||||
bundle.internalName = raw_bundle.get("short_name")
|
||||
bundle.displayName = raw_bundle.get("display_name")
|
||||
bundle.status = 0
|
||||
bundle.generation = int(raw_bundle["generation"])
|
||||
bundle.environment = raw_bundle["environment"]
|
||||
bundle.runner = raw_bundle.get("runner", Runner.tinygrad)
|
||||
bundle.is20hz = raw_bundle.get("is_20hz", False)
|
||||
bundle.minimumSelectorVersion = int(min_selector_version)
|
||||
bundle.ref = raw_bundle.get("ref")
|
||||
bundle.overrides = []
|
||||
for key, value in raw_bundle.get("overrides", {}).items():
|
||||
override = custom.IQModelManager.Override()
|
||||
override.key = key
|
||||
override.value = value
|
||||
bundle.overrides.append(override)
|
||||
|
||||
bundle.models = []
|
||||
for raw_model in raw_bundle.get("models", []):
|
||||
model = custom.IQModelManager.Model()
|
||||
model.type = raw_model.get("type")
|
||||
for attr_name in ("artifact", "metadata"):
|
||||
raw_artifact = raw_model.get(attr_name)
|
||||
if not raw_artifact:
|
||||
continue
|
||||
artifact = custom.IQModelManager.Artifact()
|
||||
artifact.fileName = raw_artifact.get("file_name")
|
||||
download_uri = custom.IQModelManager.DownloadUri()
|
||||
download_uri.uri = raw_artifact.get("download_uri", {}).get("url")
|
||||
download_uri.sha256 = raw_artifact.get("download_uri", {}).get("sha256")
|
||||
artifact.downloadUri = download_uri
|
||||
setattr(model, attr_name, artifact)
|
||||
bundle.models.append(model)
|
||||
|
||||
bundles.append(bundle)
|
||||
except Exception:
|
||||
continue
|
||||
return bundles
|
||||
|
||||
|
||||
def _bundle_match_key(bundle) -> tuple[str | None, str | None, str | None]:
|
||||
return (
|
||||
getattr(bundle, "ref", None),
|
||||
getattr(bundle, "internalName", None),
|
||||
getattr(bundle, "displayName", None),
|
||||
)
|
||||
|
||||
|
||||
def _find_runtime_upgrade(bundle, params: Params, available_bundles=None):
|
||||
if not _bundle_needs_runtime_upgrade(bundle):
|
||||
return bundle
|
||||
|
||||
candidate_bundles = available_bundles if available_bundles is not None else _load_cached_manifest_bundles(params)
|
||||
ref, internal_name, display_name = _bundle_match_key(bundle)
|
||||
|
||||
for candidate in candidate_bundles:
|
||||
if getattr(candidate, "ref", None) and getattr(candidate, "ref", None) == ref:
|
||||
return candidate
|
||||
|
||||
for candidate in candidate_bundles:
|
||||
if getattr(candidate, "internalName", None) == internal_name:
|
||||
return candidate
|
||||
|
||||
for candidate in candidate_bundles:
|
||||
if getattr(candidate, "displayName", None) == display_name:
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def bundle_files_ready(bundle) -> bool:
|
||||
if bundle is None:
|
||||
return False
|
||||
|
||||
for model in _bundle_models(bundle):
|
||||
artifact = getattr(model, "artifact", None)
|
||||
metadata = getattr(model, "metadata", None)
|
||||
for file_name in (getattr(metadata, "fileName", None), getattr(artifact, "fileName", None)):
|
||||
if file_name and not (_MODEL_ROOT / file_name).is_file():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def persist_active_bundle(params: Params, bundle) -> None:
|
||||
params.put(_ACTIVE_BUNDLE_KEY, bundle.to_dict())
|
||||
params.remove(_RUNNER_CACHE_KEY)
|
||||
|
||||
|
||||
def _load_default_bundle_dict() -> dict:
|
||||
return json.loads(_DEFAULT_BUNDLE_JSON.read_text())
|
||||
|
||||
|
||||
def _default_bundle_filenames(bundle_dict: dict) -> list[str]:
|
||||
names = []
|
||||
for model in bundle_dict.get("models", []):
|
||||
for artifact in (model.get("metadata"), model.get("artifact")):
|
||||
file_name = artifact.get("fileName", "") if isinstance(artifact, dict) else ""
|
||||
if file_name:
|
||||
names.append(file_name)
|
||||
return names
|
||||
|
||||
|
||||
def is_default_bundle(bundle) -> bool:
|
||||
return bool(bundle is not None and getattr(bundle, "ref", None) == _DEFAULT_BUNDLE_REF)
|
||||
|
||||
|
||||
def ensure_default_model_files(bundle_dict: dict = None) -> None:
|
||||
bundle_dict = bundle_dict if bundle_dict is not None else _load_default_bundle_dict()
|
||||
try:
|
||||
_MODEL_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
except OSError as e:
|
||||
cloudlog.exception(f"default_model: cannot create model root: {e}")
|
||||
return
|
||||
for file_name in _default_bundle_filenames(bundle_dict):
|
||||
src = _DEFAULT_MODEL_DIR / file_name
|
||||
dst = _MODEL_ROOT / file_name
|
||||
if not src.is_file():
|
||||
cloudlog.error(f"default_model: shipped asset missing {src}")
|
||||
continue
|
||||
if dst.is_file() and dst.stat().st_size == src.stat().st_size:
|
||||
continue
|
||||
try:
|
||||
shutil.copy2(src, dst)
|
||||
cloudlog.warning(f"default_model: staged {file_name} into model root")
|
||||
except OSError as e:
|
||||
cloudlog.exception(f"default_model: failed staging {file_name}: {e}")
|
||||
|
||||
|
||||
def select_default_model(params: Params = None) -> None:
|
||||
params = Params() if params is None else params
|
||||
bundle_dict = _load_default_bundle_dict()
|
||||
ensure_default_model_files(bundle_dict)
|
||||
params.remove(_DOWNLOAD_INDEX_KEY)
|
||||
params.put(_ACTIVE_BUNDLE_KEY, bundle_dict)
|
||||
params.remove(_RUNNER_CACHE_KEY)
|
||||
params.put(_RUNNER_CACHE_KEY, _TINYGRAD_RUNNER)
|
||||
try:
|
||||
if os.path.isfile(_PENDING_MODEL_RESTORE_FILE):
|
||||
os.remove(_PENDING_MODEL_RESTORE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def seed_default_bundle_if_unset(params: Params = None) -> None:
|
||||
params = Params() if params is None else params
|
||||
if params.get(_ACTIVE_BUNDLE_KEY) or params.get(_DOWNLOAD_INDEX_KEY) is not None:
|
||||
return
|
||||
try:
|
||||
select_default_model(params)
|
||||
cloudlog.warning("default_model: seeded Default (CD210) as active bundle")
|
||||
except Exception as e:
|
||||
cloudlog.exception(f"default_model: failed to seed default bundle: {e}")
|
||||
|
||||
|
||||
def get_runtime_bundle_upgrade(bundle, params: Params = None, available_bundles=None):
|
||||
params = Params() if params is None else params
|
||||
return _find_runtime_upgrade(bundle, params, available_bundles)
|
||||
|
||||
|
||||
def get_active_bundle(params: Params = None):
|
||||
params = Params() if params is None else params
|
||||
|
||||
try:
|
||||
active_bundle = params.get(_ACTIVE_BUNDLE_KEY) or {}
|
||||
if not active_bundle:
|
||||
return None
|
||||
is_compatible = globals().get("is_bundle_version_compatible")
|
||||
if is_compatible is not None and not is_compatible(active_bundle):
|
||||
return None
|
||||
bundle = ModelBundle(**active_bundle)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
replacement = _find_runtime_upgrade(bundle, params)
|
||||
if replacement is not None and replacement is not bundle and bundle_files_ready(replacement):
|
||||
persist_active_bundle(params, replacement)
|
||||
return replacement
|
||||
|
||||
return bundle
|
||||
|
||||
|
||||
def get_active_model_runner(params: Params = None, force_check=False):
|
||||
params = Params() if params is None else params
|
||||
|
||||
active_bundle = get_active_bundle(params)
|
||||
if not active_bundle:
|
||||
seed_default_bundle_if_unset(params)
|
||||
active_bundle = get_active_bundle(params)
|
||||
if not active_bundle:
|
||||
if params.get(_RUNNER_CACHE_KEY) != str(_TINYGRAD_RUNNER):
|
||||
params.put(_RUNNER_CACHE_KEY, _TINYGRAD_RUNNER)
|
||||
return _TINYGRAD_RUNNER
|
||||
|
||||
cached_runner_type = params.get(_RUNNER_CACHE_KEY)
|
||||
if cached_runner_type and not force_check and isinstance(cached_runner_type, str) and cached_runner_type.isdigit():
|
||||
return int(cached_runner_type)
|
||||
|
||||
runner_type = _coerce_runner_value(active_bundle.runner)
|
||||
if runner_type == _SNPE_RUNNER:
|
||||
replacement = _find_runtime_upgrade(active_bundle, params)
|
||||
if replacement is not None and replacement is not active_bundle and bundle_files_ready(replacement):
|
||||
persist_active_bundle(params, replacement)
|
||||
runner_type = _coerce_runner_value(replacement.runner)
|
||||
else:
|
||||
if replacement is not None and getattr(replacement, "index", None) is not None and params.get(_DOWNLOAD_INDEX_KEY) is None:
|
||||
params.put(_DOWNLOAD_INDEX_KEY, int(replacement.index))
|
||||
cloudlog.warning(f"Queued tinygrad migration for retired bundle {getattr(active_bundle, 'internalName', '<unknown>')}")
|
||||
runner_type = _TINYGRAD_RUNNER
|
||||
|
||||
if cached_runner_type != runner_type:
|
||||
params.put(_RUNNER_CACHE_KEY, int(runner_type))
|
||||
|
||||
return runner_type
|
||||
@@ -11,15 +11,15 @@ import capnp
|
||||
import numpy as np
|
||||
from typing import NoReturn
|
||||
|
||||
from cereal import log, car
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.issue_debug import log_issue_limited
|
||||
from openpilot.common.realtime import config_realtime_process
|
||||
from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from iqpilot.cereal import log, car
|
||||
import iqpilot.cereal.messaging as messaging
|
||||
from iqpilot.common.constants import CV
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.issue_debug import log_issue_limited
|
||||
from iqpilot.common.realtime import config_realtime_process
|
||||
from iqpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
from iqpilot.system.hardware import HARDWARE
|
||||
|
||||
MIN_SPEED_FILTER = 15 * CV.MPH_TO_MS
|
||||
MAX_VEL_ANGLE_STD = np.radians(0.25)
|
||||
@@ -38,6 +38,7 @@ TICI_FAMILY_PITCH_SPREAD_RESET = np.radians(3)
|
||||
RPY_INIT = np.array([0.0,0.0,0.0])
|
||||
WIDE_FROM_DEVICE_EULER_INIT = np.array([0.0, 0.0, 0.0])
|
||||
HEIGHT_INIT = np.array([1.22])
|
||||
HEIGHT_SANE_MIN, HEIGHT_SANE_MAX = 0.9, 2.0
|
||||
DEVICE_IS_TICI_FAMILY = HARDWARE.get_device_type() in ("tici", "tizi")
|
||||
|
||||
# These values are needed to accommodate the model frame in the narrow cam
|
||||
@@ -79,15 +80,15 @@ class Calibrator:
|
||||
wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
|
||||
height = HEIGHT_INIT
|
||||
valid_blocks = 0
|
||||
self.cal_status = log.LiveCalibrationData.Status.uncalibrated
|
||||
self.cal_status = log.ExtrinsicsCalibration.Status.uncalibrated
|
||||
|
||||
if param_put and calibration_params:
|
||||
try:
|
||||
with log.Event.from_bytes(calibration_params) as msg:
|
||||
rpy_init = np.array(msg.liveCalibration.rpyCalib)
|
||||
valid_blocks = msg.liveCalibration.validBlocks
|
||||
wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler)
|
||||
height = np.array(msg.liveCalibration.height)
|
||||
rpy_init = np.array(msg.extrinsicsCalibration.rpyCalib)
|
||||
valid_blocks = msg.extrinsicsCalibration.validBlocks
|
||||
wide_from_device_euler = np.array(msg.extrinsicsCalibration.wideFromDeviceEuler)
|
||||
height = np.array(msg.extrinsicsCalibration.height)
|
||||
except Exception:
|
||||
cloudlog.exception("Error reading cached CalibrationParams")
|
||||
|
||||
@@ -97,7 +98,7 @@ class Calibrator:
|
||||
# If saved calibration is immediately invalid (e.g. bad params from a previous
|
||||
# bootstrap bug or device remount), auto-clear it so we recalibrate from scratch
|
||||
# instead of getting permanently stuck in the "Calibration Invalid" state.
|
||||
if self.cal_status == log.LiveCalibrationData.Status.invalid:
|
||||
if self.cal_status == log.ExtrinsicsCalibration.Status.invalid:
|
||||
cloudlog.warning("calibrationd: saved CalibrationParams are invalid, clearing and starting fresh")
|
||||
if param_put:
|
||||
self.params.remove("CalibrationParams")
|
||||
@@ -170,24 +171,24 @@ class Calibrator:
|
||||
self.calib_spread = np.zeros(3)
|
||||
|
||||
if self.valid_blocks < INPUTS_NEEDED:
|
||||
if self.cal_status == log.LiveCalibrationData.Status.recalibrating:
|
||||
self.cal_status = log.LiveCalibrationData.Status.recalibrating
|
||||
if self.cal_status == log.ExtrinsicsCalibration.Status.recalibrating:
|
||||
self.cal_status = log.ExtrinsicsCalibration.Status.recalibrating
|
||||
else:
|
||||
self.cal_status = log.LiveCalibrationData.Status.uncalibrated
|
||||
self.cal_status = log.ExtrinsicsCalibration.Status.uncalibrated
|
||||
elif is_calibration_valid(self.rpy):
|
||||
self.cal_status = log.LiveCalibrationData.Status.calibrated
|
||||
self.cal_status = log.ExtrinsicsCalibration.Status.calibrated
|
||||
else:
|
||||
self.cal_status = log.LiveCalibrationData.Status.invalid
|
||||
self.cal_status = log.ExtrinsicsCalibration.Status.invalid
|
||||
|
||||
# If spread is too high, assume mounting was changed and reset to last block.
|
||||
# Make the transition smooth. Abrupt transitions are not good for feedback loop through supercombo model.
|
||||
# TODO: add height spread check with smooth transition too
|
||||
pitch_spread_limit = TICI_FAMILY_PITCH_SPREAD_RESET if DEVICE_IS_TICI_FAMILY else MAX_ALLOWED_PITCH_SPREAD
|
||||
spread_too_high = self.calib_spread[1] > pitch_spread_limit or self.calib_spread[2] > MAX_ALLOWED_YAW_SPREAD
|
||||
if self.cal_status == log.LiveCalibrationData.Status.calibrated and not spread_too_high:
|
||||
if self.cal_status == log.ExtrinsicsCalibration.Status.calibrated and not spread_too_high:
|
||||
self._remember_stable_solution()
|
||||
|
||||
if spread_too_high and self.cal_status == log.LiveCalibrationData.Status.calibrated:
|
||||
if spread_too_high and self.cal_status == log.ExtrinsicsCalibration.Status.calibrated:
|
||||
use_stable_snapshot = DEVICE_IS_TICI_FAMILY and self.has_stable_snapshot
|
||||
if use_stable_snapshot:
|
||||
reset_rpy = self.stable_rpy
|
||||
@@ -208,7 +209,7 @@ class Calibrator:
|
||||
)
|
||||
self.reset(reset_rpy, valid_blocks=1, wide_from_device_euler_init=reset_wide,
|
||||
height_init=reset_height, smooth_from=self.stable_rpy if use_stable_snapshot else self.rpy)
|
||||
self.cal_status = log.LiveCalibrationData.Status.recalibrating
|
||||
self.cal_status = log.ExtrinsicsCalibration.Status.recalibrating
|
||||
|
||||
write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5)
|
||||
if self.param_put and write_this_cycle:
|
||||
@@ -267,7 +268,7 @@ class Calibrator:
|
||||
else:
|
||||
new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
|
||||
|
||||
if (len(road_transform_trans) == 3):
|
||||
if len(road_transform_trans) == 3 and HEIGHT_SANE_MIN <= road_transform_trans[2] <= HEIGHT_SANE_MAX:
|
||||
new_height = np.array([road_transform_trans[2]])
|
||||
else:
|
||||
new_height = HEIGHT_INIT
|
||||
@@ -300,28 +301,28 @@ class Calibrator:
|
||||
def get_msg(self, valid: bool) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
smooth_rpy = self.get_smooth_rpy()
|
||||
|
||||
msg = messaging.new_message('liveCalibration')
|
||||
msg = messaging.new_message('extrinsicsCalibration')
|
||||
msg.valid = valid
|
||||
|
||||
liveCalibration = msg.liveCalibration
|
||||
liveCalibration.validBlocks = self.valid_blocks
|
||||
liveCalibration.calStatus = self.cal_status
|
||||
liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100)
|
||||
liveCalibration.rpyCalib = smooth_rpy.tolist()
|
||||
liveCalibration.rpyCalibSpread = self.calib_spread.tolist()
|
||||
liveCalibration.wideFromDeviceEuler = self.wide_from_device_euler.tolist()
|
||||
liveCalibration.height = self.height.tolist()
|
||||
extrinsicsCalibration = msg.extrinsicsCalibration
|
||||
extrinsicsCalibration.validBlocks = self.valid_blocks
|
||||
extrinsicsCalibration.calStatus = self.cal_status
|
||||
extrinsicsCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100)
|
||||
extrinsicsCalibration.rpyCalib = smooth_rpy.tolist()
|
||||
extrinsicsCalibration.rpyCalibSpread = self.calib_spread.tolist()
|
||||
extrinsicsCalibration.wideFromDeviceEuler = self.wide_from_device_euler.tolist()
|
||||
extrinsicsCalibration.height = self.height.tolist()
|
||||
|
||||
return msg
|
||||
|
||||
def send_data(self, pm: messaging.PubMaster, valid: bool) -> None:
|
||||
pm.send('liveCalibration', self.get_msg(valid))
|
||||
pm.send('extrinsicsCalibration', self.get_msg(valid))
|
||||
|
||||
|
||||
def main() -> NoReturn:
|
||||
config_realtime_process([0, 1, 2, 3], 5)
|
||||
|
||||
pm = messaging.PubMaster(['liveCalibration'])
|
||||
pm = messaging.PubMaster(['extrinsicsCalibration'])
|
||||
sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll='cameraOdometry')
|
||||
|
||||
params_reader = Params()
|
||||
@@ -4,13 +4,13 @@ import json
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.selfdrived.events import Alert
|
||||
from openpilot.iqpilot.common.atlas_alerts import NULL_ALERT as EmptyAlert
|
||||
from iqpilot.common.basedir import BASEDIR
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.selfdrive.selfdrived.events import Alert
|
||||
from iqpilot.common.atlas_alerts import NULL_ALERT as EmptyAlert
|
||||
|
||||
|
||||
with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json")) as f:
|
||||
with open(os.path.join(BASEDIR, "iqpilot/selfdrive/selfdrived/alerts_offroad.json")) as f:
|
||||
OFFROAD_ALERTS = json.load(f)
|
||||
|
||||
|
||||
45
selfdrive/selfdrived/events.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py
Executable file → Normal file
45
selfdrive/selfdrived/events.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py
Executable file → Normal file
@@ -1,16 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
import math
|
||||
|
||||
from cereal import log, car
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
|
||||
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
|
||||
from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from iqpilot.cereal import log, car
|
||||
import iqpilot.cereal.messaging as messaging
|
||||
from iqpilot.common.constants import CV
|
||||
from iqpilot.common.realtime import DT_CTRL
|
||||
from iqpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
|
||||
from iqpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
|
||||
from iqpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
|
||||
from iqpilot.system.hardware import HARDWARE
|
||||
|
||||
from openpilot.iqpilot.common.atlas_alerts import EventBook as EventsBase, Tier as Priority, Tags as ET, AlertCard as Alert, \
|
||||
from iqpilot.common.atlas_alerts import EventBook as EventsBase, Tier as Priority, Tags as ET, AlertCard as Alert, \
|
||||
NoEntryCard as NoEntryAlert, GentleDisableCard as SoftDisableAlert, PendingDisableCard as UserSoftDisableAlert, \
|
||||
HardDisableCard as ImmediateDisableAlert, ChimeCard as EngagementAlert, BannerCard as NormalPermanentAlert, \
|
||||
BootCard as StartupAlert, AlertFactory as AlertCallbackType, car_mode_entry_alert as wrong_car_mode_alert
|
||||
@@ -80,9 +80,9 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S
|
||||
|
||||
|
||||
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||
first_word = 'Recalibrating' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else 'Calibrating'
|
||||
first_word = 'Recalibrating' if sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.recalibrating else 'Calibrating'
|
||||
return Alert(
|
||||
f"{first_word}: {sm['liveCalibration'].calPerc:.0f}%",
|
||||
f"{first_word}: {sm['extrinsicsCalibration'].calPerc:.0f}%",
|
||||
f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}",
|
||||
AlertStatus.normal, AlertSize.mid,
|
||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
|
||||
@@ -131,7 +131,7 @@ def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.
|
||||
|
||||
|
||||
def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||
rpy = sm['liveCalibration'].rpyCalib
|
||||
rpy = sm['extrinsicsCalibration'].rpyCalib
|
||||
yaw = math.degrees(rpy[2] if len(rpy) == 3 else math.nan)
|
||||
pitch = math.degrees(rpy[1] if len(rpy) == 3 else math.nan)
|
||||
angles = f"Remount Device (Pitch: {pitch:.1f}°, Yaw: {yaw:.1f}°)"
|
||||
@@ -139,16 +139,16 @@ def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging
|
||||
|
||||
|
||||
def paramsd_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||
if not sm['liveParameters'].angleOffsetValid:
|
||||
angle_offset_deg = sm['liveParameters'].angleOffsetDeg
|
||||
if not sm['vehicleParameters'].angleOffsetValid:
|
||||
angle_offset_deg = sm['vehicleParameters'].angleOffsetDeg
|
||||
title = "Steering misalignment detected"
|
||||
text = f"Angle offset too high (Offset: {angle_offset_deg:.1f}°)"
|
||||
elif not sm['liveParameters'].steerRatioValid:
|
||||
steer_ratio = sm['liveParameters'].steerRatio
|
||||
elif not sm['vehicleParameters'].steerRatioValid:
|
||||
steer_ratio = sm['vehicleParameters'].steerRatio
|
||||
title = "Steer ratio mismatch"
|
||||
text = f"Steering rack geometry may be off (Ratio: {steer_ratio:.1f})"
|
||||
elif not sm['liveParameters'].stiffnessFactorValid:
|
||||
stiffness_factor = sm['liveParameters'].stiffnessFactor
|
||||
elif not sm['vehicleParameters'].stiffnessFactorValid:
|
||||
stiffness_factor = sm['vehicleParameters'].stiffnessFactor
|
||||
title = "Abnormal tire stiffness"
|
||||
text = f"Check tires, pressure, or alignment (Factor: {stiffness_factor:.1f})"
|
||||
else:
|
||||
@@ -226,7 +226,8 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin
|
||||
return NormalPermanentAlert(title, text)
|
||||
|
||||
|
||||
def invalid_lkas_setting_no_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||
def invalid_lkas_setting_no_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster,
|
||||
metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||
if CP.brand == "tesla":
|
||||
return NoEntryAlert("FSD / Autosteer is active", alert_text_1="Dashcam Mode")
|
||||
return NoEntryAlert("Invalid LKAS setting")
|
||||
@@ -275,7 +276,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
},
|
||||
|
||||
EventName.startupNoCar: {
|
||||
ET.PERMANENT: StartupAlert("IQ.Pilot Dashcam mode for unrecognized car"),
|
||||
ET.PERMANENT: StartupAlert("IQ.Pilot Dashcam mode: car unrecognized"),
|
||||
},
|
||||
|
||||
EventName.startupNoSecOcKey: {
|
||||
@@ -840,7 +841,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
|
||||
EventName.speedTooLow: {
|
||||
ET.IMMEDIATE_DISABLE: Alert(
|
||||
"openpilot Canceled",
|
||||
"IQ.Pilot Canceled",
|
||||
"Speed too low",
|
||||
AlertStatus.normal, AlertSize.mid,
|
||||
Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.),
|
||||
@@ -946,7 +947,7 @@ if HARDWARE.get_device_type() == 'mici':
|
||||
|
||||
if __name__ == '__main__':
|
||||
# print all alerts by type and priority
|
||||
from cereal.services import SERVICE_LIST
|
||||
from iqpilot.cereal.services import SERVICE_LIST
|
||||
from collections import defaultdict
|
||||
|
||||
event_names = {v: k for k, v in EventName.schema.enumerants.items()}
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from cereal import car
|
||||
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
|
||||
import iqpilot.cereal.messaging as messaging
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
from iqpilot.cereal import car
|
||||
from iqpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
|
||||
|
||||
FEEDBACK_MAX_DURATION = 10.0
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
@@ -12,8 +12,6 @@ ButtonType = car.CarState.ButtonEvent.Type
|
||||
def main():
|
||||
params = Params()
|
||||
pm = messaging.PubMaster(['userBookmark', 'audioFeedback'])
|
||||
# carState/iqState intentionally not subscribed: their only consumer is the
|
||||
# disabled LKAS-button block below; re-add them if that block is re-enabled
|
||||
sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton'])
|
||||
should_record_audio = False
|
||||
block_num = 0
|
||||
@@ -29,18 +27,18 @@ def main():
|
||||
if be.type == ButtonType.lkas:
|
||||
if be.pressed:
|
||||
if not should_record_audio:
|
||||
if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set
|
||||
if params.get_bool("RecordAudioFeedback"):
|
||||
should_record_audio = True
|
||||
block_num = 0
|
||||
waiting_for_release = False
|
||||
early_stop_triggered = False
|
||||
cloudlog.info("LKAS button pressed - starting 10-second audio feedback")
|
||||
else:
|
||||
should_send_bookmark = True # immediately send bookmark if toggle false
|
||||
should_send_bookmark = True
|
||||
cloudlog.info("LKAS button pressed - bookmarking")
|
||||
elif should_record_audio and not waiting_for_release: # Wait for release of second press to stop recording early
|
||||
elif should_record_audio and not waiting_for_release:
|
||||
waiting_for_release = True
|
||||
elif waiting_for_release: # Second press released
|
||||
elif waiting_for_release:
|
||||
waiting_for_release = False
|
||||
early_stop_triggered = True
|
||||
cloudlog.info("LKAS button released - ending recording early")
|
||||
@@ -52,8 +50,8 @@ def main():
|
||||
msg.audioFeedback.audio.sampleRate = raw_audio.sampleRate
|
||||
msg.audioFeedback.blockNum = block_num
|
||||
block_num += 1
|
||||
if (block_num * SAMPLE_BUFFER / SAMPLE_RATE) >= FEEDBACK_MAX_DURATION or early_stop_triggered: # Check for timeout or early stop
|
||||
should_send_bookmark = True # send bookmark at end of audio segment
|
||||
if (block_num * SAMPLE_BUFFER / SAMPLE_RATE) >= FEEDBACK_MAX_DURATION or early_stop_triggered:
|
||||
should_send_bookmark = True
|
||||
should_record_audio = False
|
||||
early_stop_triggered = False
|
||||
cloudlog.info("10-second recording completed or second button press - stopping audio feedback")
|
||||
@@ -1,9 +1,9 @@
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from openpilot.system.hardware.base import HardwareBase
|
||||
from openpilot.system.hardware.tici.hardware import Tici
|
||||
from openpilot.system.hardware.pc.hardware import Pc
|
||||
from iqpilot.system.hardware.base import HardwareBase
|
||||
from iqpilot.system.hardware.tici.hardware import Tici
|
||||
from iqpilot.system.hardware.pc.hardware import Pc
|
||||
|
||||
TICI = os.path.isfile('/TICI')
|
||||
AGNOS = os.path.isfile('/AGNOS')
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
from abc import abstractmethod, ABC
|
||||
from dataclasses import dataclass, fields
|
||||
|
||||
from cereal import log
|
||||
from iqpilot.cereal import log
|
||||
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
NetworkStrength = log.DeviceState.NetworkStrength
|
||||
@@ -0,0 +1,137 @@
|
||||
import os
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
from iqpilot.system.hardware import PC
|
||||
|
||||
DEFAULT_DOWNLOAD_CACHE_ROOT = "/tmp/comma_download_cache"
|
||||
|
||||
class Paths:
|
||||
_persist_root_cache: str | None = None
|
||||
|
||||
@staticmethod
|
||||
def _is_writable_persist_root(path: str) -> bool:
|
||||
try:
|
||||
os.makedirs(path, exist_ok=True)
|
||||
comma_dir = os.path.join(path, "comma")
|
||||
os.makedirs(comma_dir, exist_ok=True)
|
||||
|
||||
probe_path = os.path.join(comma_dir, ".rw_probe")
|
||||
with open(probe_path, "w") as f:
|
||||
f.write("1")
|
||||
os.remove(probe_path)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def comma_home() -> str:
|
||||
return os.path.join(str(Path.home()), ".comma" + os.environ.get("OPENPILOT_PREFIX", ""))
|
||||
|
||||
@staticmethod
|
||||
def params() -> str:
|
||||
if os.environ.get("PARAMS_ROOT"):
|
||||
return os.environ["PARAMS_ROOT"]
|
||||
return os.path.join(Paths.comma_home(), "params") if PC else "/data/params"
|
||||
|
||||
@staticmethod
|
||||
def log_root() -> str:
|
||||
if os.environ.get('LOG_ROOT', False):
|
||||
return os.environ['LOG_ROOT']
|
||||
elif PC:
|
||||
return str(Path(Paths.comma_home()) / "media" / "0" / "realdata")
|
||||
else:
|
||||
return '/data/media/0/realdata/'
|
||||
|
||||
@staticmethod
|
||||
def log_root_external() -> str:
|
||||
return '/mnt/external_realdata/'
|
||||
|
||||
@staticmethod
|
||||
def swaglog_root() -> str:
|
||||
if PC:
|
||||
return os.path.join(Paths.comma_home(), "log")
|
||||
else:
|
||||
return "/data/log/"
|
||||
|
||||
@staticmethod
|
||||
def swaglog_ipc() -> str:
|
||||
return "ipc:///tmp/logmessage" + os.environ.get("OPENPILOT_PREFIX", "")
|
||||
|
||||
@staticmethod
|
||||
def download_cache_root() -> str:
|
||||
if os.environ.get('COMMA_CACHE', False):
|
||||
return os.environ['COMMA_CACHE'] + "/"
|
||||
return DEFAULT_DOWNLOAD_CACHE_ROOT + os.environ.get("OPENPILOT_PREFIX", "") + "/"
|
||||
|
||||
@staticmethod
|
||||
def persist_root() -> str:
|
||||
if PC:
|
||||
return os.path.join(Paths.comma_home(), "persist")
|
||||
|
||||
if Paths._persist_root_cache is not None:
|
||||
return Paths._persist_root_cache
|
||||
|
||||
for candidate in ("/persist", "/data/persist"):
|
||||
if Paths._is_writable_persist_root(candidate):
|
||||
Paths._persist_root_cache = candidate
|
||||
return candidate
|
||||
|
||||
# Keep previous behavior as a last resort.
|
||||
Paths._persist_root_cache = "/persist"
|
||||
return Paths._persist_root_cache
|
||||
|
||||
@staticmethod
|
||||
def stats_root() -> str:
|
||||
if PC:
|
||||
return str(Path(Paths.comma_home()) / "stats")
|
||||
else:
|
||||
return "/data/stats/"
|
||||
|
||||
@staticmethod
|
||||
def stats_iq_root() -> str:
|
||||
if PC:
|
||||
return str(Path(Paths.comma_home()) / "stats")
|
||||
else:
|
||||
return "/data/stats_iq/"
|
||||
|
||||
@staticmethod
|
||||
def config_root() -> str:
|
||||
if PC:
|
||||
return Paths.comma_home()
|
||||
else:
|
||||
return "/tmp/.comma"
|
||||
|
||||
@staticmethod
|
||||
def shm_path() -> str:
|
||||
if PC and platform.system() == "Darwin":
|
||||
return "/tmp" # This is not really shared memory on macOS, but it's the closest we can get
|
||||
return "/dev/shm"
|
||||
|
||||
@staticmethod
|
||||
def model_root() -> str:
|
||||
if PC:
|
||||
return str(Path(Paths.comma_home()) / "media" / "0" / "models")
|
||||
else:
|
||||
return "/data/media/0/models"
|
||||
|
||||
@staticmethod
|
||||
def crash_log_root() -> str:
|
||||
if PC:
|
||||
return str(Path(Paths.comma_home()) / "community" / "crashes")
|
||||
else:
|
||||
return "/data/community/crashes"
|
||||
|
||||
@staticmethod
|
||||
def mapd_root() -> str:
|
||||
if PC:
|
||||
return str(Path(Paths.comma_home()) / "media" / "0" / "osm")
|
||||
else:
|
||||
return "/data/media/0/osm"
|
||||
|
||||
@staticmethod
|
||||
def screen_recordings_root() -> str:
|
||||
if PC:
|
||||
return str(Path(Paths.comma_home()) / "media" / "0" / "screen_recordings")
|
||||
else:
|
||||
return "/data/media/0/screen_recordings"
|
||||
@@ -1,5 +1,5 @@
|
||||
from cereal import log
|
||||
from openpilot.system.hardware.base import HardwareBase
|
||||
from iqpilot.cereal import log
|
||||
from iqpilot.system.hardware.base import HardwareBase
|
||||
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import time
|
||||
from collections import namedtuple
|
||||
|
||||
from openpilot.common.i2c import SMBus
|
||||
from iqpilot.common.i2c import SMBus
|
||||
|
||||
# https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf
|
||||
|
||||
@@ -6,9 +6,9 @@ from enum import Enum
|
||||
from queue import Queue, Empty
|
||||
from typing import Callable
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.hardware.base import LPAError, LPAProfileNotFoundError, Profile
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.system.hardware import HARDWARE
|
||||
from iqpilot.system.hardware.base import LPAError, LPAProfileNotFoundError, Profile
|
||||
|
||||
|
||||
class EsimOperationState(Enum):
|
||||
@@ -0,0 +1,740 @@
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import tempfile
|
||||
from enum import IntEnum
|
||||
from functools import cached_property, lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from iqpilot.cereal import log
|
||||
from iqpilot.common.utils import sudo_read, sudo_write
|
||||
from iqpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action
|
||||
from iqpilot.system.hardware.base import HardwareBase, LPABase, ThermalConfig, ThermalZone
|
||||
from iqpilot.system.hardware.tici import iwlist
|
||||
from iqpilot.system.hardware.tici.lpa import TiciLPA
|
||||
from iqpilot.system.hardware.tici.pins import GPIO
|
||||
from iqpilot.system.hardware.tici.amplifier import Amplifier
|
||||
|
||||
NM = 'org.freedesktop.NetworkManager'
|
||||
NM_CON_ACT = NM + '.Connection.Active'
|
||||
NM_DEV = NM + '.Device'
|
||||
NM_DEV_WL = NM + '.Device.Wireless'
|
||||
NM_DEV_STATS = NM + '.Device.Statistics'
|
||||
NM_AP = NM + '.AccessPoint'
|
||||
DBUS_PROPS = 'org.freedesktop.DBus.Properties'
|
||||
|
||||
MM = 'org.freedesktop.ModemManager1'
|
||||
MM_MODEM = MM + ".Modem"
|
||||
MM_MODEM_SIMPLE = MM + ".Modem.Simple"
|
||||
MM_SIM = MM + ".Sim"
|
||||
|
||||
class MM_MODEM_STATE(IntEnum):
|
||||
FAILED = -1
|
||||
UNKNOWN = 0
|
||||
INITIALIZING = 1
|
||||
LOCKED = 2
|
||||
DISABLED = 3
|
||||
DISABLING = 4
|
||||
ENABLING = 5
|
||||
ENABLED = 6
|
||||
SEARCHING = 7
|
||||
REGISTERED = 8
|
||||
DISCONNECTING = 9
|
||||
CONNECTING = 10
|
||||
CONNECTED = 11
|
||||
|
||||
class NMActiveConnectionState(IntEnum):
|
||||
UNKNOWN = 0
|
||||
ACTIVATING = 1
|
||||
ACTIVATED = 2
|
||||
DEACTIVATING = 3
|
||||
DEACTIVATED = 4
|
||||
|
||||
class NMMetered(IntEnum):
|
||||
NM_METERED_UNKNOWN = 0
|
||||
NM_METERED_YES = 1
|
||||
NM_METERED_NO = 2
|
||||
NM_METERED_GUESS_YES = 3
|
||||
NM_METERED_GUESS_NO = 4
|
||||
|
||||
TIMEOUT = 0.1
|
||||
REFRESH_RATE_MS = 1000
|
||||
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
NetworkStrength = log.DeviceState.NetworkStrength
|
||||
|
||||
# https://developer.gnome.org/ModemManager/unstable/ModemManager-Flags-and-Enumerations.html#MMModemAccessTechnology
|
||||
MM_MODEM_ACCESS_TECHNOLOGY_UMTS = 1 << 5
|
||||
MM_MODEM_ACCESS_TECHNOLOGY_LTE = 1 << 14
|
||||
|
||||
# MMModemStateFailedReason
|
||||
MM_MODEM_STATE_FAILED_REASON_SIM_MISSING = 2
|
||||
|
||||
|
||||
def affine_irq(val, action):
|
||||
irqs = get_irqs_for_action(action)
|
||||
if len(irqs) == 0:
|
||||
return
|
||||
|
||||
for i in irqs:
|
||||
sudo_write(str(val), f"/proc/irq/{i}/smp_affinity_list")
|
||||
|
||||
@lru_cache
|
||||
def get_device_type():
|
||||
# lru_cache and cache can cause memory leaks when used in classes
|
||||
try:
|
||||
with open("/sys/firmware/devicetree/base/model") as f:
|
||||
model = f.read().strip('\x00')
|
||||
except FileNotFoundError:
|
||||
# off-device (e.g. the prebuilt build container fakes /TICI but has no
|
||||
# devicetree); import must not crash. Not a real device type.
|
||||
return "unknown"
|
||||
return model.split('comma ')[-1]
|
||||
|
||||
class Tici(HardwareBase):
|
||||
@staticmethod
|
||||
def _ensure_system_python_path() -> None:
|
||||
system_site = "/usr/lib/python3/dist-packages"
|
||||
if system_site not in sys.path and os.path.isdir(system_site):
|
||||
sys.path.append(system_site)
|
||||
|
||||
@staticmethod
|
||||
def _run_direct_modem_command(command: str) -> None:
|
||||
import serial
|
||||
|
||||
last_error: Exception | None = None
|
||||
for device in ("/dev/ttyUSB2", "/dev/ttyUSB3"):
|
||||
if not os.path.exists(device):
|
||||
continue
|
||||
|
||||
try:
|
||||
with serial.Serial(device, baudrate=9600, timeout=2) as modem:
|
||||
modem.reset_input_buffer()
|
||||
modem.write((command + "\r").encode("ascii"))
|
||||
|
||||
deadline = time.monotonic() + 3.0
|
||||
while time.monotonic() < deadline:
|
||||
line = modem.readline().decode(errors="ignore").strip()
|
||||
if not line:
|
||||
continue
|
||||
if line == "OK":
|
||||
return
|
||||
if line == "ERROR" or "ERROR" in line:
|
||||
raise RuntimeError(f"{device}: {line}")
|
||||
raise TimeoutError(f"{device}: timed out waiting for modem response")
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
|
||||
if last_error is not None:
|
||||
raise last_error
|
||||
raise RuntimeError("No modem AT port available")
|
||||
|
||||
@cached_property
|
||||
def bus(self):
|
||||
try:
|
||||
import dbus
|
||||
except ModuleNotFoundError:
|
||||
self._ensure_system_python_path()
|
||||
import dbus
|
||||
return dbus.SystemBus()
|
||||
|
||||
@cached_property
|
||||
def nm(self):
|
||||
return self.bus.get_object(NM, '/org/freedesktop/NetworkManager')
|
||||
|
||||
@property # this should not be cached, in case the modemmanager restarts
|
||||
def mm(self):
|
||||
return self.bus.get_object(MM, '/org/freedesktop/ModemManager1')
|
||||
|
||||
@cached_property
|
||||
def amplifier(self):
|
||||
if self.get_device_type() == "mici":
|
||||
return None
|
||||
if os.path.exists('/tmp/lite_hw') or os.environ.get('LITE') == '1':
|
||||
return None
|
||||
return Amplifier()
|
||||
|
||||
def get_os_version(self):
|
||||
with open("/VERSION") as f:
|
||||
return f.read().strip()
|
||||
|
||||
def get_device_type(self):
|
||||
return get_device_type()
|
||||
|
||||
def reboot(self, reason=None):
|
||||
subprocess.check_output(["sudo", "reboot"])
|
||||
|
||||
def uninstall(self):
|
||||
Path("/data/__system_reset__").touch()
|
||||
os.sync()
|
||||
self.reboot()
|
||||
|
||||
def get_serial(self):
|
||||
return self.get_cmdline()['androidboot.serialno']
|
||||
|
||||
def get_voltage(self):
|
||||
with open("/sys/class/hwmon/hwmon1/in1_input") as f:
|
||||
return int(f.read())
|
||||
|
||||
def get_current(self):
|
||||
with open("/sys/class/hwmon/hwmon1/curr1_input") as f:
|
||||
return int(f.read())
|
||||
|
||||
def set_ir_power(self, percent: int):
|
||||
if self.get_device_type() in ("tici", "tizi"):
|
||||
return
|
||||
|
||||
value = int((percent / 100) * 300)
|
||||
with open("/sys/class/leds/led:switch_2/brightness", "w") as f:
|
||||
f.write("0\n")
|
||||
with open("/sys/class/leds/led:torch_2/brightness", "w") as f:
|
||||
f.write(f"{value}\n")
|
||||
with open("/sys/class/leds/led:switch_2/brightness", "w") as f:
|
||||
f.write(f"{value}\n")
|
||||
|
||||
def get_network_type(self):
|
||||
try:
|
||||
primary_connection = self.nm.Get(NM, 'PrimaryConnection', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
primary_connection = self.bus.get_object(NM, primary_connection)
|
||||
primary_type = primary_connection.Get(NM_CON_ACT, 'Type', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
if primary_type == '802-3-ethernet':
|
||||
return NetworkType.ethernet
|
||||
elif primary_type == '802-11-wireless':
|
||||
return NetworkType.wifi
|
||||
else:
|
||||
active_connections = self.nm.Get(NM, 'ActiveConnections', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
for conn in active_connections:
|
||||
c = self.bus.get_object(NM, conn)
|
||||
tp = c.Get(NM_CON_ACT, 'Type', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if tp == 'gsm':
|
||||
modem = self.get_modem()
|
||||
modem_state = modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if modem_state < MM_MODEM_STATE.REGISTERED:
|
||||
return NetworkType.none
|
||||
access_t = modem.Get(MM_MODEM, 'AccessTechnologies', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if access_t >= MM_MODEM_ACCESS_TECHNOLOGY_LTE:
|
||||
return NetworkType.cell4G
|
||||
elif access_t >= MM_MODEM_ACCESS_TECHNOLOGY_UMTS:
|
||||
return NetworkType.cell3G
|
||||
else:
|
||||
return NetworkType.cell2G
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return NetworkType.none
|
||||
|
||||
def get_modem(self):
|
||||
objects = self.mm.GetManagedObjects(dbus_interface="org.freedesktop.DBus.ObjectManager", timeout=TIMEOUT)
|
||||
if not objects:
|
||||
raise RuntimeError("ModemManager returned no modems")
|
||||
modem_path = next(iter(objects))
|
||||
return self.bus.get_object(MM, modem_path)
|
||||
|
||||
def get_wlan(self):
|
||||
wlan_path = self.nm.GetDeviceByIpIface('wlan0', dbus_interface=NM, timeout=TIMEOUT)
|
||||
return self.bus.get_object(NM, wlan_path)
|
||||
|
||||
def get_wwan(self):
|
||||
wwan_path = self.nm.GetDeviceByIpIface('wwan0', dbus_interface=NM, timeout=TIMEOUT)
|
||||
return self.bus.get_object(NM, wwan_path)
|
||||
|
||||
def get_sim_info(self):
|
||||
modem = self.get_modem()
|
||||
sim_path = modem.Get(MM_MODEM, 'Sim', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
if sim_path == "/":
|
||||
return {
|
||||
'sim_id': '',
|
||||
'mcc_mnc': None,
|
||||
'network_type': ["Unknown"],
|
||||
'sim_state': ["ABSENT"],
|
||||
'data_connected': False
|
||||
}
|
||||
else:
|
||||
sim = self.bus.get_object(MM, sim_path)
|
||||
return {
|
||||
'sim_id': str(sim.Get(MM_SIM, 'SimIdentifier', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)),
|
||||
'mcc_mnc': str(sim.Get(MM_SIM, 'OperatorIdentifier', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)),
|
||||
'network_type': ["Unknown"],
|
||||
'sim_state': ["READY"],
|
||||
'data_connected': modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) == MM_MODEM_STATE.CONNECTED,
|
||||
}
|
||||
|
||||
def get_sim_lpa(self) -> LPABase:
|
||||
return TiciLPA()
|
||||
|
||||
def get_imei(self, slot):
|
||||
if slot != 0:
|
||||
return ""
|
||||
|
||||
return str(self.get_modem().Get(MM_MODEM, 'EquipmentIdentifier', dbus_interface=DBUS_PROPS, timeout=TIMEOUT))
|
||||
|
||||
def get_network_info(self):
|
||||
if self.get_device_type() == "mici":
|
||||
return None
|
||||
try:
|
||||
modem = self.get_modem()
|
||||
info = modem.Command("AT+QNWINFO", math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
extra = modem.Command('AT+QENG="servingcell"', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
state = modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
if info and info.startswith('+QNWINFO: '):
|
||||
info = info.replace('+QNWINFO: ', '').replace('"', '').split(',')
|
||||
extra = "" if extra is None else extra.replace('+QENG: "servingcell",', '').replace('"', '')
|
||||
state = "" if state is None else MM_MODEM_STATE(state).name
|
||||
|
||||
if len(info) != 4:
|
||||
return None
|
||||
|
||||
technology, operator, band, channel = info
|
||||
|
||||
return({
|
||||
'technology': technology,
|
||||
'operator': operator,
|
||||
'band': band,
|
||||
'channel': int(channel),
|
||||
'extra': extra,
|
||||
'state': state,
|
||||
})
|
||||
else:
|
||||
return None
|
||||
|
||||
def parse_strength(self, percentage):
|
||||
if percentage < 25:
|
||||
return NetworkStrength.poor
|
||||
elif percentage < 50:
|
||||
return NetworkStrength.moderate
|
||||
elif percentage < 75:
|
||||
return NetworkStrength.good
|
||||
else:
|
||||
return NetworkStrength.great
|
||||
|
||||
def get_network_strength(self, network_type):
|
||||
network_strength = NetworkStrength.unknown
|
||||
|
||||
try:
|
||||
if network_type == NetworkType.none:
|
||||
pass
|
||||
elif network_type == NetworkType.wifi:
|
||||
wlan = self.get_wlan()
|
||||
active_ap_path = wlan.Get(NM_DEV_WL, 'ActiveAccessPoint', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if active_ap_path != "/":
|
||||
active_ap = self.bus.get_object(NM, active_ap_path)
|
||||
strength = int(active_ap.Get(NM_AP, 'Strength', dbus_interface=DBUS_PROPS, timeout=TIMEOUT))
|
||||
network_strength = self.parse_strength(strength)
|
||||
else: # Cellular
|
||||
modem = self.get_modem()
|
||||
strength = int(modem.Get(MM_MODEM, 'SignalQuality', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)[0])
|
||||
network_strength = self.parse_strength(strength)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return network_strength
|
||||
|
||||
def get_network_metered(self, network_type) -> bool:
|
||||
try:
|
||||
primary_connection = self.nm.Get(NM, 'PrimaryConnection', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
primary_connection = self.bus.get_object(NM, primary_connection)
|
||||
primary_devices = primary_connection.Get(NM_CON_ACT, 'Devices', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
for dev in primary_devices:
|
||||
dev_obj = self.bus.get_object(NM, str(dev))
|
||||
metered_prop = dev_obj.Get(NM_DEV, 'Metered', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
if network_type == NetworkType.wifi:
|
||||
if metered_prop in [NMMetered.NM_METERED_YES, NMMetered.NM_METERED_GUESS_YES]:
|
||||
return True
|
||||
elif network_type in [NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G]:
|
||||
if metered_prop == NMMetered.NM_METERED_NO:
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return super().get_network_metered(network_type)
|
||||
|
||||
def get_modem_version(self):
|
||||
try:
|
||||
modem = self.get_modem()
|
||||
return modem.Get(MM_MODEM, 'Revision', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_modem_temperatures(self):
|
||||
timeout = 0.2 # Default timeout is too short
|
||||
try:
|
||||
modem = self.get_modem()
|
||||
temps = modem.Command("AT+QTEMP", math.ceil(timeout), dbus_interface=MM_MODEM, timeout=timeout)
|
||||
return list(filter(lambda t: t != 255, map(int, temps.split(' ')[1].split(','))))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def get_current_power_draw(self):
|
||||
return (self.read_param_file("/sys/class/hwmon/hwmon1/power1_input", int) / 1e6)
|
||||
|
||||
def get_som_power_draw(self):
|
||||
return (self.read_param_file("/sys/class/power_supply/bms/voltage_now", int) * self.read_param_file("/sys/class/power_supply/bms/current_now", int) / 1e12)
|
||||
|
||||
def shutdown(self):
|
||||
os.system("sudo poweroff")
|
||||
|
||||
def get_thermal_config(self):
|
||||
intake, exhaust, case = None, None, None
|
||||
if self.get_device_type() == "mici":
|
||||
case = ThermalZone("case")
|
||||
intake = ThermalZone("intake")
|
||||
exhaust = ThermalZone("exhaust")
|
||||
return ThermalConfig(cpu=[ThermalZone(f"cpu{i}-silver-usr") for i in range(4)] +
|
||||
[ThermalZone(f"cpu{i}-gold-usr") for i in range(4)],
|
||||
gpu=[ThermalZone("gpu0-usr"), ThermalZone("gpu1-usr")],
|
||||
dsp=ThermalZone("compute-hvx-usr"),
|
||||
memory=ThermalZone("ddr-usr"),
|
||||
pmic=[ThermalZone("pm8998_tz"), ThermalZone("pm8005_tz")],
|
||||
intake=intake,
|
||||
exhaust=exhaust,
|
||||
case=case)
|
||||
|
||||
def set_display_power(self, on):
|
||||
try:
|
||||
with open("/sys/class/backlight/panel0-backlight/bl_power", "w") as f:
|
||||
f.write("0" if on else "4")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def set_screen_brightness(self, percentage):
|
||||
try:
|
||||
with open("/sys/class/backlight/panel0-backlight/max_brightness") as f:
|
||||
max_brightness = float(f.read().strip())
|
||||
|
||||
val = int(percentage * (max_brightness / 100.))
|
||||
with open("/sys/class/backlight/panel0-backlight/brightness", "w") as f:
|
||||
f.write(str(val))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_screen_brightness(self):
|
||||
try:
|
||||
with open("/sys/class/backlight/panel0-backlight/max_brightness") as f:
|
||||
max_brightness = float(f.read().strip())
|
||||
|
||||
with open("/sys/class/backlight/panel0-backlight/brightness") as f:
|
||||
return int(float(f.read()) / (max_brightness / 100.))
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def set_power_save(self, powersave_enabled):
|
||||
# amplifier, 100mW at idle
|
||||
if self.amplifier is not None:
|
||||
self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled)
|
||||
if not powersave_enabled:
|
||||
self.amplifier.initialize_configuration(self.get_device_type())
|
||||
|
||||
# *** CPU config ***
|
||||
|
||||
# offline big cluster
|
||||
for i in range(4, 8):
|
||||
val = '0' if powersave_enabled else '1'
|
||||
sudo_write(val, f'/sys/devices/system/cpu/cpu{i}/online')
|
||||
|
||||
for n in ('0', '4'):
|
||||
if powersave_enabled and n == '4':
|
||||
continue
|
||||
gov = 'ondemand' if powersave_enabled else 'performance'
|
||||
sudo_write(gov, f'/sys/devices/system/cpu/cpufreq/policy{n}/scaling_governor')
|
||||
|
||||
# *** IRQ config ***
|
||||
|
||||
# GPU, modeld core
|
||||
affine_irq(7, "kgsl-3d0")
|
||||
|
||||
# camerad core
|
||||
camera_irqs = ("a5", "cci", "cpas_camnoc", "cpas-cdm", "csid", "ife", "csid-lite", "ife-lite")
|
||||
for n in camera_irqs:
|
||||
affine_irq(6, n)
|
||||
|
||||
def get_gpu_usage_percent(self):
|
||||
try:
|
||||
with open('/sys/class/kgsl/kgsl-3d0/gpubusy') as f:
|
||||
used, total = f.read().strip().split()
|
||||
return 100.0 * int(used) / int(total)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def initialize_hardware(self):
|
||||
if self.amplifier is not None:
|
||||
self.amplifier.initialize_configuration(self.get_device_type())
|
||||
|
||||
# Allow hardwared to write engagement status to kmsg
|
||||
os.system("sudo chmod a+w /dev/kmsg")
|
||||
|
||||
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
|
||||
gpio_init(GPIO.SOM_ST_IO, True)
|
||||
gpio_set(GPIO.SOM_ST_IO, 1)
|
||||
|
||||
# *** IRQ config ***
|
||||
|
||||
# mask off big cluster from default affinity
|
||||
sudo_write("f", "/proc/irq/default_smp_affinity")
|
||||
|
||||
# move these off the default core
|
||||
affine_irq(1, "msm_vidc") # encoders
|
||||
affine_irq(1, "i2c_geni") # sensors
|
||||
|
||||
# *** GPU config ***
|
||||
# https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216
|
||||
affine_irq(5, "fts_ts") # touch
|
||||
affine_irq(5, "msm_drm") # display
|
||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel")
|
||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/max_pwrlevel")
|
||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_bus_on")
|
||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_clk_on")
|
||||
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_rail_on")
|
||||
sudo_write("1000", "/sys/class/kgsl/kgsl-3d0/idle_timer")
|
||||
sudo_write("performance", "/sys/class/kgsl/kgsl-3d0/devfreq/governor")
|
||||
sudo_write("710", "/sys/class/kgsl/kgsl-3d0/max_clock_mhz")
|
||||
|
||||
# setup governors
|
||||
sudo_write("performance", "/sys/class/devfreq/soc:qcom,cpubw/governor")
|
||||
sudo_write("performance", "/sys/class/devfreq/soc:qcom,memlat-cpu0/governor")
|
||||
sudo_write("performance", "/sys/class/devfreq/soc:qcom,memlat-cpu4/governor")
|
||||
|
||||
# *** VIDC (encoder) config ***
|
||||
sudo_write("N", "/sys/kernel/debug/msm_vidc/clock_scaling")
|
||||
sudo_write("Y", "/sys/kernel/debug/msm_vidc/disable_thermal_mitigation")
|
||||
|
||||
# pandad core
|
||||
affine_irq(3, "spi_geni") # SPI
|
||||
if "tici" in self.get_device_type():
|
||||
affine_irq(3, "xhci-hcd:usb3")
|
||||
affine_irq(3, "xhci-hcd:usb1")
|
||||
try:
|
||||
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
|
||||
subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
|
||||
subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except subprocess.CalledProcessException as e:
|
||||
print(str(e))
|
||||
|
||||
def configure_modem(self):
|
||||
from iqpilot.common.params import Params
|
||||
|
||||
sim_info = self.get_sim_info()
|
||||
sim_id = sim_info.get('sim_id', '')
|
||||
params = Params()
|
||||
manual_apn = params.get("GsmApn", encoding="utf-8") or ""
|
||||
metered_enabled = params.get_bool("GsmMetered")
|
||||
|
||||
modem = self.get_modem()
|
||||
try:
|
||||
manufacturer = str(modem.Get(MM_MODEM, 'Manufacturer', dbus_interface=DBUS_PROPS, timeout=TIMEOUT))
|
||||
except Exception:
|
||||
manufacturer = None
|
||||
|
||||
cmds = []
|
||||
is_comma_profile = self.get_sim_lpa().is_comma_profile(sim_id)
|
||||
roaming_enabled = params.get_bool("GsmRoaming")
|
||||
initial_eps_apn = "" if is_comma_profile else manual_apn
|
||||
|
||||
if not is_comma_profile and params.get("GsmRoaming") is None:
|
||||
params.put_bool("GsmRoaming", True)
|
||||
roaming_enabled = True
|
||||
|
||||
subprocess.call([
|
||||
"nmcli", "connection", "modify", "lte",
|
||||
"gsm.auto-config", "no" if manual_apn else "yes",
|
||||
"gsm.apn", manual_apn,
|
||||
"gsm.home-only", "no" if roaming_enabled else "yes",
|
||||
"gsm.network-id", "",
|
||||
"gsm.initial-eps-bearer-configure", "yes" if initial_eps_apn else "no",
|
||||
"gsm.initial-eps-bearer-apn", initial_eps_apn,
|
||||
"connection.metered", "unknown" if metered_enabled else "no",
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
if self.get_device_type() in ("tici", "tizi"):
|
||||
if initial_eps_apn:
|
||||
subprocess.call(["mmcli", "-m", "any", f'--3gpp-set-initial-eps-bearer-settings=apn={initial_eps_apn}'])
|
||||
else:
|
||||
subprocess.call(["mmcli", "-m", "any", '--3gpp-set-initial-eps-bearer-settings=apn='])
|
||||
|
||||
cmds += [
|
||||
# configure modem as data-centric
|
||||
'AT+QNVW=5280,0,"0102000000000000"',
|
||||
'AT+QNVFW="/nv/item_files/ims/IMS_enable",00',
|
||||
'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01',
|
||||
]
|
||||
if self.get_device_type() == "tizi":
|
||||
cmds += [
|
||||
'AT+QSIMDET=1,0',
|
||||
'AT+QSIMSTAT=1',
|
||||
]
|
||||
elif manufacturer == 'Cavli Inc.':
|
||||
cmds += [
|
||||
'AT^SIMSWAP=1', # use SIM slot, instead of internal eSIM
|
||||
'AT$QCSIMSLEEP=0', # disable SIM sleep
|
||||
'AT$QCSIMCFG=SimPowerSave,0', # more sleep disable
|
||||
|
||||
# ethernet config
|
||||
'AT$QCPCFG=usbNet,0',
|
||||
'AT$QCNETDEVCTL=3,1',
|
||||
]
|
||||
else:
|
||||
# this modem gets upset with too many AT commands
|
||||
if sim_id is None or len(sim_id) == 0:
|
||||
cmds += [
|
||||
# SIM sleep disable
|
||||
'AT$QCSIMSLEEP=0',
|
||||
'AT$QCSIMCFG=SimPowerSave,0',
|
||||
|
||||
# ethernet config
|
||||
'AT$QCPCFG=usbNet,1',
|
||||
]
|
||||
|
||||
for cmd in cmds:
|
||||
try:
|
||||
modem.Command(cmd, math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# eSIM prime
|
||||
dest = "/etc/NetworkManager/system-connections/esim.nmconnection"
|
||||
if self.get_sim_lpa().is_comma_profile(sim_id) and not os.path.exists(dest):
|
||||
with open(Path(__file__).parent/'esim.nmconnection') as f, tempfile.NamedTemporaryFile(mode='w') as tf:
|
||||
dat = f.read()
|
||||
dat = dat.replace("sim-id=", f"sim-id={sim_id}")
|
||||
tf.write(dat)
|
||||
tf.flush()
|
||||
|
||||
# needs to be root
|
||||
os.system(f"sudo cp {tf.name} {dest}")
|
||||
os.system(f"sudo nmcli con load {dest}")
|
||||
|
||||
def recover_sim_detection(self) -> bool:
|
||||
# A worn SIM-tray presence switch can read "removed" while the SIM pads still make
|
||||
# contact; with hot-swap detect armed (AT+QSIMDET=1) the modem never powers the SIM
|
||||
# and lands in failed/sim-missing. Disabling detect and rebooting the modem makes it
|
||||
# probe the SIM electrically. Safe to retry on failure: firing disarms the QSIMDET
|
||||
# gate, so a genuinely SIM-less device gets at most one extra modem reboot per boot.
|
||||
if self.get_device_type() not in ("tici", "tizi"):
|
||||
return False
|
||||
|
||||
try:
|
||||
modem = self.get_modem()
|
||||
state = modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if state != MM_MODEM_STATE.FAILED:
|
||||
return False
|
||||
reason = modem.Get(MM_MODEM, 'StateFailedReason', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if reason != MM_MODEM_STATE_FAILED_REASON_SIM_MISSING:
|
||||
return False
|
||||
detect = str(modem.Command('AT+QSIMDET?', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)).strip()
|
||||
if not detect.startswith('+QSIMDET: 1'):
|
||||
return False
|
||||
modem.Command('AT+QSIMDET=0,0', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
modem.Command('AT+CFUN=1,1', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def reboot_modem(self):
|
||||
modem = None
|
||||
try:
|
||||
modem = self.get_modem()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if modem is not None:
|
||||
for state in (0, 1):
|
||||
try:
|
||||
modem.Command(f'AT+CFUN={state}', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
for state in (0, 1):
|
||||
try:
|
||||
self._run_direct_modem_command(f"AT+CFUN={state}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_networks(self):
|
||||
r = {}
|
||||
|
||||
wlan = iwlist.scan()
|
||||
if wlan is not None:
|
||||
r['wlan'] = wlan
|
||||
|
||||
lte_info = self.get_network_info()
|
||||
if lte_info is not None:
|
||||
extra = lte_info['extra']
|
||||
|
||||
# <state>,"LTE",<is_tdd>,<mcc>,<mnc>,<cellid>,<pcid>,<earfcn>,<freq_band_ind>,
|
||||
# <ul_bandwidth>,<dl_bandwidth>,<tac>,<rsrp>,<rsrq>,<rssi>,<sinr>,<srxlev>
|
||||
if 'LTE' in extra:
|
||||
extra = extra.split(',')
|
||||
try:
|
||||
r['lte'] = [{
|
||||
"mcc": int(extra[3]),
|
||||
"mnc": int(extra[4]),
|
||||
"cid": int(extra[5], 16),
|
||||
"nmr": [{"pci": int(extra[6]), "earfcn": int(extra[7])}],
|
||||
}]
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
return r
|
||||
|
||||
def get_modem_data_usage(self):
|
||||
try:
|
||||
wwan = self.get_wwan()
|
||||
|
||||
# Ensure refresh rate is set so values don't go stale
|
||||
refresh_rate = wwan.Get(NM_DEV_STATS, 'RefreshRateMs', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if refresh_rate != REFRESH_RATE_MS:
|
||||
u = type(refresh_rate)
|
||||
wwan.Set(NM_DEV_STATS, 'RefreshRateMs', u(REFRESH_RATE_MS), dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
tx = wwan.Get(NM_DEV_STATS, 'TxBytes', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
rx = wwan.Get(NM_DEV_STATS, 'RxBytes', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
return int(tx), int(rx)
|
||||
except Exception:
|
||||
return -1, -1
|
||||
|
||||
def has_internal_panda(self):
|
||||
return True
|
||||
|
||||
def reset_internal_panda(self):
|
||||
gpio_init(GPIO.STM_RST_N, True)
|
||||
gpio_init(GPIO.STM_BOOT0, True)
|
||||
|
||||
gpio_set(GPIO.STM_RST_N, 1)
|
||||
gpio_set(GPIO.STM_BOOT0, 0)
|
||||
time.sleep(1)
|
||||
gpio_set(GPIO.STM_RST_N, 0)
|
||||
|
||||
def recover_internal_panda(self):
|
||||
gpio_init(GPIO.STM_RST_N, True)
|
||||
gpio_init(GPIO.STM_BOOT0, True)
|
||||
|
||||
gpio_set(GPIO.STM_RST_N, 1)
|
||||
gpio_set(GPIO.STM_BOOT0, 1)
|
||||
time.sleep(0.5)
|
||||
gpio_set(GPIO.STM_RST_N, 0)
|
||||
time.sleep(0.5)
|
||||
gpio_set(GPIO.STM_BOOT0, 0)
|
||||
|
||||
def booted(self):
|
||||
# this normally boots within 8s, but on rare occasions takes 30+s
|
||||
encoder_state = sudo_read("/sys/kernel/debug/msm_vidc/core0/info")
|
||||
if "Core state: 0" in encoder_state and (time.monotonic() < 60*2):
|
||||
return False
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
t = Tici()
|
||||
t.configure_modem()
|
||||
t.initialize_hardware()
|
||||
t.set_power_save(False)
|
||||
print(t.get_sim_info())
|
||||
6
system/hardware/tici/lpa.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/lpa.py
Executable file → Normal file
6
system/hardware/tici/lpa.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/lpa.py
Executable file → Normal file
@@ -19,8 +19,8 @@ from collections.abc import Generator
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.system.hardware.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
|
||||
from iqpilot.common.time_helpers import system_time_valid
|
||||
from iqpilot.system.hardware.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
|
||||
|
||||
GSMA_CI_BUNDLE = str(Path(__file__).parent / 'gsma_ci_bundle.pem')
|
||||
|
||||
@@ -936,7 +936,7 @@ class TiciLPA(LPABase):
|
||||
self.verbose = verbose
|
||||
self._last_good_device = device
|
||||
self._euicc_present_cache: bool | None = None
|
||||
from openpilot.system.hardware.tici.hardware import get_device_type
|
||||
from iqpilot.system.hardware.tici.hardware import get_device_type
|
||||
self._is_eg25 = get_device_type() in ("tizi",)
|
||||
self._client: AtClient | None = None
|
||||
self._initialized = True
|
||||
@@ -1,12 +1,16 @@
|
||||
import errno
|
||||
import os
|
||||
|
||||
import xattr
|
||||
|
||||
_cached_attributes: dict[tuple, bytes | None] = {}
|
||||
_cached_attributes: dict[tuple[str, str], tuple[tuple[int, int, int], bytes | None]] = {}
|
||||
|
||||
def getxattr(path: str, attr_name: str) -> bytes | None:
|
||||
key = (path, attr_name)
|
||||
if key not in _cached_attributes:
|
||||
st = os.stat(path)
|
||||
identity = (st.st_dev, st.st_ino, st.st_ctime_ns)
|
||||
cached = _cached_attributes.get(key)
|
||||
if cached is None or cached[0] != identity:
|
||||
try:
|
||||
response = xattr.getxattr(path, attr_name)
|
||||
except OSError as e:
|
||||
@@ -15,9 +19,10 @@ def getxattr(path: str, attr_name: str) -> bytes | None:
|
||||
response = None
|
||||
else:
|
||||
raise
|
||||
_cached_attributes[key] = response
|
||||
return _cached_attributes[key]
|
||||
_cached_attributes[key] = (identity, response)
|
||||
return _cached_attributes[key][1]
|
||||
|
||||
def setxattr(path: str, attr_name: str, attr_value: bytes) -> None:
|
||||
_cached_attributes.pop((path, attr_name), None)
|
||||
xattr.setxattr(path, attr_name, attr_value)
|
||||
st = os.stat(path)
|
||||
_cached_attributes[(path, attr_name)] = ((st.st_dev, st.st_ino, st.st_ctime_ns), attr_value)
|
||||
@@ -10,22 +10,22 @@ from multiprocessing import Process
|
||||
|
||||
from setproctitle import setproctitle
|
||||
|
||||
from cereal import car, log
|
||||
import cereal.messaging as messaging
|
||||
import openpilot.system.sentry as sentry
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from iqpilot.cereal import car, log
|
||||
import iqpilot.cereal.messaging as messaging
|
||||
import iqpilot.system.sentry as sentry
|
||||
from iqpilot.common.basedir import BASEDIR
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
|
||||
MAX_CRASH_BACKOFF = 300.0
|
||||
CRASH_RESET_TIME = 60.0
|
||||
CRASH_LOOP_THRESHOLD = 6
|
||||
|
||||
try:
|
||||
from openpilot.system.proprietary_runtime.runtime_paths import preferred_runner_path
|
||||
from iqpilot.system.proprietary_runtime.runtime_paths import preferred_runner_path
|
||||
except ModuleNotFoundError:
|
||||
_VERIFIED_RUNNER_PATH = Path("/usr/libexec/iqpilot/iqpilot_bundle_runner")
|
||||
_FALLBACK_RUNNER_PATH = Path("/data/openpilot/system/proprietary_runtime/iqpilot_bundle_runner")
|
||||
_FALLBACK_RUNNER_PATH = Path("/data/openpilot/iqpilot/system/proprietary_runtime/iqpilot_bundle_runner")
|
||||
|
||||
def preferred_runner_path() -> Path:
|
||||
if _VERIFIED_RUNNER_PATH.is_file() and os.access(_VERIFIED_RUNNER_PATH, os.X_OK):
|
||||
@@ -66,6 +66,7 @@ def nativelauncher(pargs: list[str], cwd: str, name: str) -> None:
|
||||
|
||||
# exec the process
|
||||
os.chdir(cwd)
|
||||
os.environ['PWD'] = cwd
|
||||
os.execvp(pargs[0], pargs)
|
||||
|
||||
|
||||
@@ -103,7 +104,7 @@ class ManagerProcess(ABC):
|
||||
self.stop(sig=signal.SIGKILL)
|
||||
self.start()
|
||||
|
||||
def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals | None = None) -> int | None:
|
||||
def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals | None = None, timeout: float = 5) -> int | None:
|
||||
if self.proc is None:
|
||||
return None
|
||||
|
||||
@@ -118,7 +119,7 @@ class ManagerProcess(ABC):
|
||||
if not block:
|
||||
return None
|
||||
|
||||
join_process(self.proc, 5)
|
||||
join_process(self.proc, timeout)
|
||||
|
||||
# If process failed to die send SIGKILL
|
||||
if self.proc.exitcode is None and retry:
|
||||
@@ -223,7 +224,7 @@ class BundleProcess(NativeProcess):
|
||||
self.restart_if_crash = restart_if_crash
|
||||
runner_path = preferred_runner_path()
|
||||
runner_cmd = str(runner_path) if runner_path.is_absolute() else "./iqpilot_bundle_runner"
|
||||
runner_cwd = "" if runner_path.is_absolute() else "system/proprietary_runtime"
|
||||
runner_cwd = ".iqpilot/runtime_root" if runner_path.is_absolute() else "system/proprietary_runtime"
|
||||
super().__init__(
|
||||
name=name,
|
||||
cwd=runner_cwd,
|
||||
@@ -244,6 +245,9 @@ class BundleProcess(NativeProcess):
|
||||
_normalize_bundle_modes(self.bundle)
|
||||
super().start()
|
||||
|
||||
def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals | None = None, timeout: float = 5) -> int | None:
|
||||
return super().stop(retry=retry, block=block, sig=signal.SIGTERM if sig is None else sig, timeout=timeout)
|
||||
|
||||
|
||||
class PythonProcess(ManagerProcess):
|
||||
def __init__(self, name, module, should_run, enabled=True, sigkill=False, restart_if_crash=False):
|
||||
@@ -2,17 +2,15 @@ import os
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import car, custom
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.hardware import HARDWARE, PC, TICI
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.system.manager.process import PythonProcess, NativeProcess, BundleProcess
|
||||
from iqpilot.cereal import car, custom
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.system.hardware import HARDWARE, PC, TICI
|
||||
from iqpilot.system.hardware.hw import Paths
|
||||
from iqpilot.system.manager.process import PythonProcess, NativeProcess, BundleProcess
|
||||
|
||||
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_model_runner
|
||||
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_model_runner
|
||||
from iqpilot.konn3kt.service_health import hephaestus_ready
|
||||
|
||||
WEBCAM = os.getenv("USE_WEBCAM") is not None
|
||||
|
||||
def driverview(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
return started or params.get_bool("IsDriverViewEnabled")
|
||||
|
||||
@@ -77,6 +75,11 @@ def navd_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
def navrenderd_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
return started and params.get_bool("NavigationEnabled") and params.get_bool("OnScreenNavigation")
|
||||
|
||||
def navincidentd_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
return started and params.get_bool("NavigationEnabled") and bool(params.get("WazePoliceApiKey")) and (
|
||||
params.get_int("WazePoliceAlertMode") > 0 or params.get_bool("WazePoliceShadow")
|
||||
)
|
||||
|
||||
def iqmapd_needed(params: Params) -> bool:
|
||||
return (
|
||||
params.get_bool("IQRoadNameOverlay")
|
||||
@@ -141,64 +144,63 @@ def and_(*fns):
|
||||
return lambda *args: all(fn(*args) for fn in fns)
|
||||
|
||||
procs = [
|
||||
NativeProcess("loggerd", "system/loggerd", ["./loggerd"], logging),
|
||||
NativeProcess("encoderd", "system/loggerd", ["./encoderd"], only_onroad),
|
||||
NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], or_(notcar, livestream)),
|
||||
PythonProcess("logmessaged", "system.logmessaged", always_run, restart_if_crash=True),
|
||||
NativeProcess("loggerd", "iqpilot/system/loggerd", ["./loggerd"], logging),
|
||||
NativeProcess("encoderd", "iqpilot/system/loggerd", ["./encoderd"], only_onroad),
|
||||
NativeProcess("stream_encoderd", "iqpilot/system/loggerd", ["./encoderd", "--stream"], or_(notcar, livestream)),
|
||||
PythonProcess("logmessaged", "iqpilot.system.logmessaged", always_run, restart_if_crash=True),
|
||||
|
||||
NativeProcess("camerad", "system/camerad", ["./camerad"], or_(driverview, livestream), enabled=not WEBCAM),
|
||||
PythonProcess("webcamerad", "tools.webcam.camerad", driverview, enabled=WEBCAM),
|
||||
PythonProcess("proclogd", "system.proclogd", only_onroad, enabled=platform.system() != "Darwin"),
|
||||
PythonProcess("journald", "system.journald", only_onroad, platform.system() != "Darwin"),
|
||||
PythonProcess("micd", "system.micd", or_(iscar, livestream)),
|
||||
PythonProcess("timed", "system.timed", always_run, enabled=not PC),
|
||||
NativeProcess("camerad", "iqpilot/system/camerad", ["./camerad"], or_(driverview, livestream), restart_if_crash=True),
|
||||
PythonProcess("proclogd", "iqpilot.system.proclogd", only_onroad, enabled=platform.system() != "Darwin"),
|
||||
PythonProcess("journald", "iqpilot.system.journald", only_onroad, platform.system() != "Darwin"),
|
||||
PythonProcess("micd", "iqpilot.system.micd", or_(iscar, livestream)),
|
||||
PythonProcess("timed", "iqpilot.system.timed", always_run, enabled=not PC),
|
||||
|
||||
PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driver_monitoring, enabled=(WEBCAM or not PC)),
|
||||
PythonProcess("dmonitoringmodeld", "iqpilot.selfdrive.dmonitoringmodeld.dmonitoringmodeld", driver_monitoring, enabled=not PC),
|
||||
|
||||
PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC),
|
||||
PythonProcess("ui", "selfdrive.ui.ui", not_low_power, restart_if_crash=True),
|
||||
PythonProcess("soundd", "selfdrive.ui.soundd", driverview),
|
||||
PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad),
|
||||
NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False),
|
||||
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad),
|
||||
PythonProcess("torqued", "selfdrive.locationd.torqued", only_onroad),
|
||||
PythonProcess("controlsd", "selfdrive.controls.controlsd", and_(not_joystick, iscar)),
|
||||
PythonProcess("joystickd", "tools.joystick.joystickd", or_(joystick, notcar)),
|
||||
PythonProcess("selfdrived", "selfdrive.selfdrived.selfdrived", only_onroad),
|
||||
PythonProcess("card", "selfdrive.car.card", only_onroad),
|
||||
PythonProcess("deleter", "system.loggerd.deleter", always_run),
|
||||
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driver_monitoring, enabled=(WEBCAM or not PC)),
|
||||
PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
|
||||
PythonProcess("pandad", "selfdrive.pandad.pandad", always_run),
|
||||
PythonProcess("paramsd", "selfdrive.locationd.paramsd", only_onroad),
|
||||
PythonProcess("lagd", "selfdrive.locationd.lagd", only_onroad),
|
||||
PythonProcess("ubloxd", "system.ubloxd.ubloxd", ublox, enabled=TICI),
|
||||
PythonProcess("pigeond", "system.ubloxd.pigeond", ublox, enabled=TICI),
|
||||
PythonProcess("plannerd", "selfdrive.controls.plannerd", not_long_maneuver),
|
||||
PythonProcess("maneuversd", "tools.longitudinal_maneuvers.maneuversd", long_maneuver),
|
||||
PythonProcess("lateral_maneuversd", "tools.lateral_maneuvers.lateral_maneuversd", lat_maneuver),
|
||||
PythonProcess("radard", "selfdrive.controls.radard", only_onroad),
|
||||
PythonProcess("hardwared", "system.hardware.hardwared", always_run, restart_if_crash=True),
|
||||
PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC),
|
||||
PythonProcess("updated", "system.updated.updated", and_(only_offroad, not_low_power), enabled=not PC),
|
||||
BundleProcess("iquploaderd", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.uploaderd.iquploaderd", and_(iquploaderd_ready, not_low_power), restart_if_crash=True),
|
||||
PythonProcess("feedbackd", "selfdrive.ui.feedback.feedbackd", and_(only_onroad, not_lat_maneuver)),
|
||||
PythonProcess("sensord", "iqpilot.system.sensord.sensord", only_onroad, enabled=not PC),
|
||||
PythonProcess("ui", "iqpilot.selfdrive.ui.ui", not_low_power, restart_if_crash=True),
|
||||
PythonProcess("soundd", "iqpilot.selfdrive.ui.soundd", driverview),
|
||||
PythonProcess("locationd", "iqpilot.selfdrive.locationd.locationd", only_onroad),
|
||||
NativeProcess("_pandad", "iqpilot/selfdrive/pandad", ["./pandad"], always_run, enabled=False),
|
||||
PythonProcess("calibrationd", "iqpilot.selfdrive.locationd.calibrationd", only_onroad),
|
||||
PythonProcess("controlsd", "iqpilot.selfdrive.controls.controlsd", and_(not_joystick, iscar)),
|
||||
PythonProcess("joystickd", "iqpilot.tools.joystick.joystickd", or_(joystick, notcar)),
|
||||
PythonProcess("selfdrived", "iqpilot.selfdrive.selfdrived.selfdrived", only_onroad),
|
||||
PythonProcess("card", "iqpilot.selfdrive.car.card", only_onroad),
|
||||
PythonProcess("deleter", "iqpilot.system.loggerd.deleter", always_run),
|
||||
PythonProcess("dmonitoringd", "iqpilot.selfdrive.monitoring.dmonitoringd", driver_monitoring, enabled=not PC),
|
||||
PythonProcess("qcomgpsd", "iqpilot.system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
|
||||
PythonProcess("pandad", "iqpilot.selfdrive.pandad.pandad", always_run),
|
||||
PythonProcess("estimatord", "iqpilot.selfdrive.locationd.estimatord", only_onroad),
|
||||
PythonProcess("ubloxd", "iqpilot.system.ubloxd.ubloxd", ublox, enabled=TICI),
|
||||
PythonProcess("pigeond", "iqpilot.system.ubloxd.pigeond", ublox, enabled=TICI),
|
||||
PythonProcess("plannerd", "iqpilot.selfdrive.controls.plannerd", not_long_maneuver),
|
||||
PythonProcess("maneuversd", "iqpilot.tools.maneuvers.longitudinal_maneuversd", long_maneuver),
|
||||
PythonProcess("lateral_maneuversd", "iqpilot.tools.maneuvers.lateral_maneuversd", lat_maneuver),
|
||||
PythonProcess("radard", "iqpilot.selfdrive.controls.radard", only_onroad),
|
||||
PythonProcess("hardwared", "iqpilot.system.hardware.hardwared", always_run, restart_if_crash=True),
|
||||
PythonProcess("tombstoned", "iqpilot.system.tombstoned", always_run, enabled=not PC),
|
||||
PythonProcess("updated", "iqpilot.system.updated.updated", and_(only_offroad, not_low_power), enabled=not PC),
|
||||
BundleProcess("iquploaderd", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.uploaderd.iquploaderd",
|
||||
and_(iquploaderd_ready, not_low_power), restart_if_crash=True),
|
||||
PythonProcess("feedbackd", "iqpilot.selfdrive.ui.feedback.feedbackd", and_(only_onroad, not_lat_maneuver)),
|
||||
|
||||
# debug procs
|
||||
NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar),
|
||||
PythonProcess("webrtcd", "system.webrtc.webrtcd", or_(iscar, livestream)),
|
||||
NativeProcess("bridge", "iqpilot/cereal/messaging", ["./bridge"], notcar),
|
||||
PythonProcess("webrtcd", "iqpilot.system.webrtc.webrtcd", or_(iscar, livestream)),
|
||||
PythonProcess("canlived", "iqpilot.konn3kt.canlive.canlived", canlive),
|
||||
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
|
||||
]
|
||||
|
||||
# iqpilot
|
||||
procs += [
|
||||
# Models
|
||||
PythonProcess("models_manager", "iqpilot.selfdrive.iqmodeld.models.manager", and_(only_offroad, not_low_power)),
|
||||
BundleProcess("models_manager", "iqpilot_model_selector_private", "iqpilot_private.models.manager", and_(only_offroad, not_low_power)),
|
||||
NativeProcess("iqmodeld", "iqpilot/selfdrive/iqmodeld", ["./iqmodeld"], and_(only_onroad, is_tinygrad_model)),
|
||||
|
||||
BundleProcess("backup_manager_k3", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.backups.backup_orchestrator", and_(only_offroad, hephaestus_ready_shim, not_low_power)),
|
||||
BundleProcess("backup_manager_k3", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.backups.backup_orchestrator",
|
||||
and_(only_offroad, hephaestus_ready_shim, not_low_power)),
|
||||
BundleProcess("navd", "iqpilot_navd_private", "iqpilot_private.navd.navd", navd_onroad, restart_if_crash=True),
|
||||
BundleProcess("navincidentd", "iqpilot_navd_private", "iqpilot_private.navd.navincidentd", navincidentd_onroad, restart_if_crash=True),
|
||||
BundleProcess("navrenderd", "iqpilot_navd_private", "iqpilot_private.navd.navrenderd", navrenderd_onroad, restart_if_crash=True),
|
||||
BundleProcess("iqmapd", "iqpilot_navd_private", "iqpilot_private.navd.iqmapd", iqmapd_onroad, restart_if_crash=True),
|
||||
|
||||
@@ -209,7 +211,7 @@ procs += [
|
||||
BundleProcess("iqvd", "iqpilot_iqvd_private", "iqpilot_private.iqvd.iqvd", iqvd_onroad, restart_if_crash=True),
|
||||
|
||||
# mapd
|
||||
NativeProcess("mapd", "third_party/mapd_pfeiferj", ["./mapd"], mapd_onroad, restart_if_crash=True),
|
||||
NativeProcess("mapd", "iqpilot/third_party/mapd_pfeiferj", ["./mapd"], mapd_onroad, restart_if_crash=True),
|
||||
PythonProcess("mapd_manager", "iqpilot.iq_maps.orchestrator", and_(only_offroad, not_low_power)),
|
||||
|
||||
# locationd
|
||||
10
system/micd.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/micd.py
Executable file → Normal file
10
system/micd.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/micd.py
Executable file → Normal file
@@ -5,11 +5,11 @@ import time
|
||||
from functools import cache
|
||||
import threading
|
||||
|
||||
from cereal import messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
from openpilot.common.utils import retry
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from iqpilot.cereal import messaging
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.realtime import Ratekeeper
|
||||
from iqpilot.common.utils import retry
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
|
||||
RATE = 10
|
||||
FFT_SAMPLES = 1600 # 100ms
|
||||
@@ -7,12 +7,12 @@ import sentry_sdk
|
||||
from enum import Enum
|
||||
from sentry_sdk.integrations.threading import ThreadingIntegration
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.iqpilot.konn3kt.registration import UNREGISTERED_DONGLE_ID
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.system.version import get_build_metadata, get_version
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.konn3kt.registration import UNREGISTERED_DONGLE_ID
|
||||
from iqpilot.system.hardware import HARDWARE
|
||||
from iqpilot.system.hardware.hw import Paths
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
from iqpilot.system.version import get_build_metadata, get_version
|
||||
|
||||
CRASHES_DIR = Paths.crash_log_root()
|
||||
CRASH_UPLOADS_DIR = os.path.join(Paths.log_root(), "crash")
|
||||
@@ -20,9 +20,9 @@ from jeepney.io.threading import DBusRouter, open_dbus_connection as open_dbus_c
|
||||
from jeepney.low_level import MessageType
|
||||
from jeepney.wrappers import Properties
|
||||
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_802_11_AP_SEC_PAIR_WEP40,
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
from iqpilot.system.hardware import PC
|
||||
from iqpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_802_11_AP_SEC_PAIR_WEP40,
|
||||
NM_802_11_AP_SEC_PAIR_WEP104, NM_802_11_AP_SEC_GROUP_WEP40,
|
||||
NM_802_11_AP_SEC_GROUP_WEP104, NM_802_11_AP_SEC_KEY_MGMT_PSK,
|
||||
NM_802_11_AP_SEC_KEY_MGMT_802_1X, NM_802_11_AP_SEC_KEY_MGMT_SAE,
|
||||
@@ -35,7 +35,7 @@ from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_80
|
||||
NM_IP4_CONFIG_IFACE, NMDeviceState)
|
||||
|
||||
try:
|
||||
from openpilot.common.params import Params
|
||||
from iqpilot.common.params import Params
|
||||
except Exception:
|
||||
Params = None
|
||||
|
||||
20
system/version.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/version.py
Executable file → Normal file
20
system/version.py → artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/version.py
Executable file → Normal file
@@ -6,9 +6,9 @@ import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date
|
||||
from iqpilot.common.basedir import BASEDIR
|
||||
from iqpilot.common.swaglog import cloudlog
|
||||
from iqpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date
|
||||
|
||||
RELEASE_IQ_BRANCHES = ['release', 'release-tici', 'release-new']
|
||||
TESTED_BRANCHES = RELEASE_IQ_BRANCHES
|
||||
@@ -20,14 +20,20 @@ training_version: str = "0.2.0"
|
||||
terms_version: str = "2"
|
||||
|
||||
|
||||
UNKNOWN_VERSION = "0.0.0"
|
||||
|
||||
|
||||
@cache
|
||||
def get_version(path: str = BASEDIR) -> str:
|
||||
with open(os.path.join(path, "iqpilot", "common", "version.h")) as _versionf:
|
||||
version = _versionf.read().split('"')[1]
|
||||
return version
|
||||
try:
|
||||
with open(os.path.join(path, "iqpilot", "common", "version.h")) as _versionf:
|
||||
return _versionf.read().split('"')[1]
|
||||
except (OSError, IndexError):
|
||||
return UNKNOWN_VERSION
|
||||
|
||||
|
||||
def get_release_notes(path: str = BASEDIR) -> str:
|
||||
with open(os.path.join(path, "CHANGELOG.md")) as f:
|
||||
with open(os.path.join(path, "docs", "CHANGELOG.md")) as f:
|
||||
return f.read().split('\n\n', 1)[0]
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user