IQ.Pilot Release Commit @ 661a2de

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-08 14:37:51 -05:00
parent a6c27ac169
commit a1ef7d6c80
211 changed files with 7332 additions and 2756 deletions
+2
View File
@@ -176,3 +176,5 @@ 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/
+106 -3
View File
@@ -67,7 +67,7 @@ Volkswagen support got a significant overhaul:
**Volkswagen MEB and MQBevo**
IQ.Pilot now **officially** supports the Volkswagen MEB and MQBevo platforms! Including the ID.4, ID.3, ID.5, Golf MK8, and Tiguan 2024+, up to model year 2026, 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.
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**
@@ -98,9 +98,9 @@ IQ.Pilot's on-road UI got a number of improvements:
- 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 3.14**
**IQ.OS 4.9.1**
IQ.OS 3.14 is bundeled 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(X)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.
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.
@@ -125,3 +125,106 @@ Connection stability between Konn3kt and IQ.Pilot (Konn3ktion) is greatly improv
**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 drivers 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.
+8 -1
View File
@@ -56,6 +56,13 @@ assert arch in [
"Darwin", # macOS arm64 (x86 not supported)
]
# ffmpeg comes from the system (brew on macOS, distro packages elsewhere) rather than
# a vendored wheel, so it always needs the static-link deps. Exported so tools/ can
# take upstream's `ffmpeg_libs` form instead of hand-listing codecs per SConscript.
ffmpeg_libs = ['avformat', 'avcodec', 'avutil', 'x264', 'z']
if arch != "Darwin":
ffmpeg_libs += ['va', 'va-drm', 'drm']
env = Environment(
ENV={
"PATH": os.environ['PATH'],
@@ -194,7 +201,7 @@ else:
np_version = SCons.Script.Value(np.__version__)
Export('envCython', 'np_version')
Export('env', 'arch')
Export('env', 'arch', 'ffmpeg_libs')
# Setup cache dir
default_cache_dir = os.environ.get('SCONS_CACHE_DIR') or ('/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache')
+433
View File
@@ -0,0 +1,433 @@
# 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 MayJuly 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 02 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).
@@ -16,27 +16,27 @@
},
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "a46608f5471360235edb40285873868b3fdd11f2eaa03f68aa1446433fe849ba",
"sha256": "e79be61e50cd0830197f44f8a6840b86a74b8059da45da65a4696587168ac466",
"size": 135536
},
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "db877b087839a133457b83ee01774a3cc6da58e184c5fb072533eae960f7a128",
"sha256": "ebfeedba6cc5cac6dca1cf8125879fcb2d6afdbaec31a98dd46c3d346f55c982",
"size": 67664
},
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "063b95415029a679e6f730a26b3d29e466fdaad99a92ab8cbfca92b72c30faf2",
"sha256": "11ffe8f261b78990aa743b43aed3a5534105da848be1450026090ae873ad0a2e",
"size": 204624
},
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "67d6f58f5019d267253fe0091eccf40e3f2b1179c203eb8cb9154fe533177354",
"sha256": "e62a6438c18b231f83cc873ac8170a64e670c8912ffba13edf47f163886aef19",
"size": 69752
},
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "7fb204692c1b5ab529a81cd345b115115a06077e9f890e52ddabbdf08bee5ff3",
"sha256": "07195ca6d4e1ff26441e860d954e9c79e3da608244d110bfa62b3aa0818a79dc",
"size": 203072
},
"python/iqpilot_private/konn3kt/flockd/__init__.py": {
@@ -46,12 +46,12 @@
},
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "7f6ee4b3df65f08d12b869ea645ea24e3a634459df5fa1f5109a8bc993818979",
"sha256": "d18b8d67100f5906339dde577bc14f567168ff62e1b9166c011af853c016191e",
"size": 202168
},
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "fbe39c9452aa5856b661a98fef006dc121412245fea94492cfc9e847fa9746dd",
"sha256": "1a601a0de826fb41fadb3cef9e8b8645d99e41507befc36e87d077d49efd3025",
"size": 68080
},
"python/iqpilot_private/konn3kt/hephaestus/__init__.py": {
@@ -61,67 +61,67 @@
},
"python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip": {
"mode": 420,
"sha256": "7ba36031ba73ce90a9171663cf2e912264923151b3727224b6ac98a77cf7eabd",
"sha256": "c3063f3d380627f604b0ae1cdd20fbfdfa7b64d212c86f20abd470230af8e169",
"size": 2100238
},
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "c81a547e5fc9db0a1bc87e4b120c877f60a1458817a7279af6509494a4d25d55",
"sha256": "57dbd02e130361d33c8a52635261ce85f3a91c577b70d5fad8a0e9e7557f9d46",
"size": 268064
},
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "ec9ce5cff734f4d9e63c3e128615eea7abf6332f706b947c0d1144e9d4833b4f",
"size": 340584
"sha256": "1febeeb24421c82a64416a7ab04def21d57a39acf0caaf2103b4ebfbae655fc2",
"size": 406216
},
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "d197938697bf0fc3f07f0635ccb0073581400b32c4c659166f5d2b59ede82f40",
"sha256": "42656c3b54f6392e2311e4039296e4906558fd8c63ba6be04b157445b96d38c9",
"size": 68048
},
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "db333f1c2ca337a0481bfc8ff6e6dcc5500d7046f9f812e71cf75c24e242b1fa",
"sha256": "acdf51e90428e4487f0daf6e0e43110ef5fc1c7705d16aa3b9880618030f41f3",
"size": 335728
},
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "7a86f26a85c418ab740b8bde8c8690edd2e0f002def8ff5a226b102bd09ea0c7",
"sha256": "95afe64579cac0148c3de23867a333651dfed4606c218208dcf37b40a1cc6450",
"size": 271680
},
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "907d1c81687a9829ef427e6c5d2f3a9814269cc328a261bdaaa4c0896ab87b8b",
"sha256": "fd983039accfaa30abf3d46f41c70241d1c3fcf4e21a1de7d3270ab9fce9b03c",
"size": 134408
},
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "bc8996624e05f52547fde24c8f13074ad3fc8fc66a6f4c9dac48b0b5536eec8e",
"size": 3581864
"sha256": "cc299f0d560bbd611cab099923bb1817229df3cbc15cef22d6909091e9de826b",
"size": 3581928
},
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "db4c2d54c7ed02990208fbf50652d9ea66d4ee56369ad5b5876407e20ca7080a",
"sha256": "0dbcb5d9ec1af832bcaceaa3ae96a4785a432778c1fb3cdc8b74e62b5d915158",
"size": 136448
},
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "94ebeccf5b102b898e0ac27cfb9abb32f71d66eafadc42c674c4d835c81f708e",
"sha256": "cabf37e151994a223f890627c0128990e56505bc704108f050d2422e118a273a",
"size": 68136
},
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "518d5a8d62dfe7b2e384cbfc91d042c4c85c20747b3b366d366ed0a44183b81a",
"sha256": "10af02f5daf288e465bcaa64509972a1a0b268f779b1c12c58bf63c79e52eb96",
"size": 67840
},
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "caea6a9d72c825d0612dcc588fcd0b10a6379f7b8239e3573ee3031be07f2d50",
"sha256": "855f128d248c94fbd00f550f7dd388ccf1a487935d7f11cac00e5240b526d674",
"size": 135648
},
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "c5ba0105725860ed2ad5323790a0896c568cee3d20e0f1ea3f34cffd22741bb9",
"sha256": "8de50d50308777ff9dc22d4d5232cba38f2f332207313999ec8aaa67e5004f4e",
"size": 269568
},
"python/iqpilot_private/konn3kt/uploaderd/__init__.py": {
@@ -131,7 +131,7 @@
},
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "c63c9aa7894162630c1cba513e2a2b3ecbd56170c733056f55423a61a77daf3d",
"sha256": "11d16de5d29a37f64351acabfa6372966447a88cd7d799580a6f2f1fa745c15a",
"size": 204184
},
"runtime": {
@@ -171,25 +171,25 @@
}
},
"signatures": {
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "0VbxHAh2mTri3IYid3vuJRr2djX5Bs+cuqrz3PHP5IHK7pWguSdEgzW3tUXx6VDykG2OEHy+wTJsIOL+X5imCw==",
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "tjABsrrulzA4MYSTuXT0EbMaNnFKhsLW4ET5uOBFx4INXvodCgxWWKuvP1yiPizunMbdf+ifaHtU1Y5O/02BCw==",
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "dhlRKpxt1CGAqjoc513NEBJbXLbZlAJ+cmCK/Ch7TNVl9hkWH0VfIiC+o5h+/H/FviAxlUPKOjI4rvmODywzAw==",
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "yESLGmZMIVOZxz1lfasGz+T1uz32VhdHC+MwFz1T941BNkwhrbSJruHCpJcagU1XLBGmDkRJ87XRuR6ahhQhAw==",
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "BjAU29gJrL95toiQc/QsoZl9OXV7Iw9T+u7FUsRUCDDcbSzpRk2X6kNZ0e3EDi0vjj/xDTWXRZPvO47DoeguAQ==",
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "7HPKLT0HrABmVBKCSTYoJdLsMHE+92SiefR+AzwUU6V6V8iXqvJ1ew+6k8q42ONuNSjjzPHQPyz9pdfdOV0oCQ==",
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "R5WPYpdyhaaf05W96Doxr0rOra7eNTOu7Lpu+a07HUuzmZx4f1oRp0QJD8Nk1YB5W180X925FJjx5dquo6RiAg==",
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "vra3W6bA1veYNJE/Tz7Ck6q5y2+51E8/ypdIUYFQCT1lEQE99xdCiMao/UupeQtz8NCWtdz/hHXAZiHvwPkAAQ==",
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "ALFEQV2hIqlcvaEcC0UwFMCVL2NXS2DWuh7UfwVgNahxLbNtIpjogWUyrzSgJZSXwAg5Y/KnbgxWgxmQ+N8+Cg==",
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "8fBrfWgtNy8p07ggeyxo9QbcCl+N5W8gGIhLJJEuLalZzqRkb2/rS2xNad2BQCoF4r5jR5BfsmzfQDjvVOQ2DQ==",
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "ZEgb4IMjvp6d9HCEggvuL4o5U4YvQBw2cUKD+EKxdb5VmAzzfXp/ekov1jJqbub+/PVYCryVgn9xRpplzWY2CA==",
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "cIUZNPCdDGDHJwHwgqrqmCV+Qwf1k19cz9JkA1I8rqt7eniOcjeAB8Z+RpL6WzZzWBlYTqGcCrUMTZeFpw47BQ==",
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "qFG60RaEJezzZgMBeIzSHCxBl6Ko4i4zMudg0y6bTXJAlqSw7jLz3FqaZ/z4BiqkooUKqPba9mp/gD0+XBVOCQ==",
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "meaDONzNucZ7mOxTgP8iSwqv0gpxwwgJf3R2f2V/PVYLpSyfU8GRrm81knEsqjC6K9boeWtX+L/xPhyvngHKCQ==",
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "kluaZXQIafY5MNn5G0Gd3AN3WPdnqMm/uMP7FVn3iSB8mJJWMPyQY1DdSO3mn4iEf2J+tVQcx+bwSQA1OwlyAw==",
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "Yt+FlpcONGVsklwY3/g/9lTW7/iekPUkY9b7/1uhh/5YopS7oGe3qBkNDThmgWADEm9oJBqHHShosQqDmzkPAA==",
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "Y5pEAvGmZCLi0w1c8eQXakzyDUNDUVl31tdtEugHXTdqivCDuS9n09q32nwAZlqb4rR8Bn/a1ngy1Bl3zCprDQ==",
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "PkkPxek8gAOexvlUz6YYihTt6IPwznAIjeAYBwBN3EIvxnzTts4cvZ6u0FGR6LLjBPmCmTMsHcQpmfzuom0nCA==",
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "dbr4WtuAXTu/IC5C+dQQEgDl/tGP3abpeJ+JX/H2+rFvFe3IakpEbZ2azcu6rVN7aDH/qRfw7WfasgZYAjdYAA==",
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "ImpCbB9fX10Ax/rmczgYoujMOzEaGgr4BgUkz9l+IbNRduVcbF6avrVk5qfGbg9ZoX3k/vPwiwRAXpUk+5GADA=="
"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=="
}
}
+42 -7
View File
@@ -6,7 +6,7 @@
},
"python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "d36af86bf1fe721522e729b6a48e3a962ce033dcfe89f9e00e3d6208b1801591",
"sha256": "48bad6268fabc23a9be62fcd7f61240ebcbabd5e3c6afcf7d3328bd33d93d3a2",
"size": 67616
},
"python/iqpilot_private/iqvd/__init__.py": {
@@ -16,12 +16,42 @@
},
"python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "f81036080eb7bc0c91bae10b11f52064041311a5fc67e7dc0fba32f585318e41",
"size": 201584
"sha256": "674c4ecd09e36af6c2b9a41285e78914ee94a0e8c4de728904171dd59e02edda",
"size": 135576
},
"python/iqpilot_private/iqvd/offload/__init__.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "7ce45dfb9a6ffe8e5426f261cf7e9b3a3825e3d284a0675013d43697e73d56d9",
"size": 67616
},
"python/iqpilot_private/iqvd/offload/__init__.py": {
"mode": 420,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
"python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "439f88b68d220912db2f7e9a43d41827d298f68b113e7c5e5858adea3a05e963",
"size": 135296
},
"python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "0199f81ce365fb956d729c61c20c8eaa0120448171db6b83544f3dd1a0861877",
"size": 68232
},
"python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "678ebcdd9a97fe527970bbb93021d2ae8f4f9935444b6558dd61b399ab3fe3e9",
"size": 135976
},
"python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "a8d24107433a037d19876b2240e980b09c89f560ba960dda5e28c16665a03ca6",
"size": 68120
},
"python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "976be8806f006a175a14b0c63e9a1c2c2b845ee2dab77a41eeb6759c7feab641",
"sha256": "943e0ef218eed109289d208ea76407e223a4e8b81659f0ec79dc090fbd7ed329",
"size": 204568
},
"runtime": {
@@ -33,8 +63,13 @@
}
},
"signatures": {
"python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": "bMboPEsPzZjh0Y1Zk3Brvc6fyWFF5CxuzN9KQr2RK0Hn+l8XiD6l/ym2Y10nFxvj8+uLikIZ0xSAovJlJcRUAA==",
"python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": "seyE6m33mqlKZ4H7b1JL6pxxz+r/+dKP1PzcfXxhdhHvsl8Ka8Noi6+ln5z+QSvBgt5ulxQeBJMBP5qdu6lOCw==",
"python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": "RvzkJLNxPA+u7yEnH5jd2jecT02PJCQkIDj/LiWvBCE4lFmFFzOf1Q+ZCGGjrFQzhLQZlm75SvW80mlqJ9BJCw=="
"python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": "YPDPJiSbuWXyg3MpOJXzZtejR5sazfra6c/AFVm+tzE+i9OA9/OPS5qvpFSaHOBPaVZYtsmxkOFq4Q5GJlYOAg==",
"python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": "H+2OPwV0XS7biX05P6fBzRBCN1Cjnq9eH6YSLk+9BX8OPbdiyJTbDpt8YZYk4nU0LO5FF4dMkEXzFRsewB7BAw==",
"python/iqpilot_private/iqvd/offload/__init__.cpython-312-aarch64-linux-gnu.so": "vbv8RlHm7LGdrojZuRhs+2StjASHEC1GbkZspXh6FCbmyKmW7F9B5GzHjIwr7HVFSNX6u+ELPPKebfnIsOkmAQ==",
"python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so": "pfDu4/OifTpFWqMD/hyQN+p+9syWaiAavHCkvxG9++0VvHDwKsBPJFxRk8r3vBdylSz4zkIFXlqPPt/hNuzJAg==",
"python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so": "Ll6azxnD3jGmyPPjd+yTmAVCl+CwM2iSdSqpYtTtzqPIb6GFBp0FfF5w+FTpacKjoQ4i3cXV4gdUVOf3qRWzCg==",
"python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so": "ww5BVRLiGB+LkqREN1f7njOrrgOcWghyTXNXTJTJHYeLx5AgIrvsqg1PH7+nq1LuSvk+LHDTOSUAcs7JIDh3DQ==",
"python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so": "3OzAWo66E+qe/H+HtGEP1i73WLW9SPa9+zeb8fOWBe7Nlsx7WrgGcURBq0VTycv2v3QLNLEluei/rnQEYBRMCA==",
"python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": "C64cBnuSM2wm6vCEO9W3cNqbB4P7Yuy78pFUXxn8iesCQD0hbZ1+w8IuH4VuxB88ubLRLQt2GZ8LYIESb+HYAg=="
}
}
+28 -1
View File
@@ -333,6 +333,7 @@ struct IQCarParams @0xd4189b5c8aca9f78 {
iqLateralNet @4 :LateralNet;
longitudinalStoppingSpeedOverride @5 :Float32; # m/s; zero keeps the upstream default
stoppingDecelRateOverride @6 :Float32; # m/s^3; zero keeps the upstream default
longActiveWithGasOverride @7 :Bool; # keep long control active while the driver is on the gas
struct LateralNet {
fuzzyFingerprint @0 :Bool;
@@ -762,7 +763,33 @@ struct IQVehicleTracks @0xb877ef4b20a4ae22 {
}
}
struct CustomReserved13 @0xfd960244a79e2804 {
struct IQEnvironment @0xfd960244a79e2804 {
frameId @0 :UInt32;
offloaded @1 :Bool;
modelValid @2 :Bool;
objects @3 :List(Object);
struct Object {
x @0 :Float32;
y @1 :Float32;
z @2 :Float32;
width @3 :Float32;
height @4 :Float32;
length @5 :Float32;
prob @6 :Float32;
label @7 :Label;
enum Label {
car @0;
motorcycle @1;
bus @2;
truck @3;
person @4;
bicycle @5;
stopSign @6;
trafficLight @7;
}
}
}
struct CustomReserved14 @0xa6e5a1ce8ca5258e {
+10 -1
View File
@@ -177,6 +177,15 @@ struct InitData {
wallTimeNanos @20 :UInt64;
ufsHealth @25 :UfsHealth;
struct UfsHealth {
preEolInfo @0 :UInt8;
lifeTimeEstimateA @1 :UInt8;
lifeTimeEstimateB @2 :UInt8;
vendorHealthReport @3 :Data;
}
enum DeviceType {
unknown @0;
neo @1;
@@ -2670,7 +2679,7 @@ struct Event {
iqPerfTrace @136 :Custom.IQPerfTrace;
iqConstructionZone @137 :Custom.IQConstructionZone;
iqVehicleTracks @138 :Custom.IQVehicleTracks;
customReserved13 @139 :Custom.CustomReserved13;
iqEnvironment @139 :Custom.IQEnvironment;
customReserved14 @140 :Custom.CustomReserved14;
customReserved15 @141 :Custom.CustomReserved15;
customReserved16 @142 :Custom.CustomReserved16;
+1
View File
@@ -100,6 +100,7 @@ _services: dict[str, tuple] = {
"iqLiveData": (True, 1., 1),
"iqConstructionZone": (True, 2., 2),
"iqVehicleTracks": (True, 4., 4),
"iqEnvironment": (True, 4., 4),
"mapdOut": (True, 20., 20, QueueSize.MEDIUM),
"mapdExtendedOut": (False, 1., -1, QueueSize.MEDIUM),
"mapdIn": (False, 1., -1, QueueSize.MEDIUM),
+1
View File
@@ -6,6 +6,7 @@ common_libs = [
'util.cc',
'ratekeeper.cc',
'clutil.cc',
'yuv.cc',
]
_common = env.Library('common', common_libs, LIBS="json11")
+4
View File
@@ -21,6 +21,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CalibrationParams", {PERSISTENT, BYTES}},
{"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}},
{"CameraDebugExpTime", {CLEAR_ON_MANAGER_START, STRING}},
{"CanLiveStreaming", {CLEAR_ON_MANAGER_START, BOOL}},
{"CarBatteryCapacity", {PERSISTENT, INT}},
{"CarParams", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
{"CarParamsCache", {CLEAR_ON_MANAGER_START, BYTES}},
@@ -199,6 +200,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OSMapsHeadingUp", {PERSISTENT, BOOL, "1"}},
{"OfflineTilesBaseUrl", {PERSISTENT, STRING}},
{"OnroadUploads", {PERSISTENT, BOOL, "1"}},
{"IQAutoUnits", {PERSISTENT, BOOL, "1"}},
{"IQAutoUnitsRegion", {PERSISTENT, STRING}},
{"IQAlertSilence", {PERSISTENT, BOOL, "0"}},
{"IQAccelMeter", {PERSISTENT, BOOL, "0"}},
{"IQBlinkerIndicators", {PERSISTENT, BOOL, "0"}},
@@ -349,6 +352,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ConstructionZoneAssist", {PERSISTENT, BOOL, "0"}},
{"VisionVehicleTracks", {PERSISTENT, BOOL, "0"}},
{"AmbientTrackDots", {PERSISTENT, BOOL, "1"}},
{"EnvironmentView", {PERSISTENT, INT, "0"}},
{"ConstructionZoneSpeed", {PERSISTENT, INT, "60"}},
{"ShowSpeedLimits", {PERSISTENT, BOOL, "0"}},
{"SLCPolicy", {PERSISTENT, INT, "1"}},
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <iostream>
#include <stdexcept>
#include <string>
inline void native_test_check(bool condition, const char *expression, const char *file, int line) {
if (!condition) {
throw std::runtime_error(std::string(file) + ":" + std::to_string(line) + ": check failed: " + expression);
}
}
#define CHECK(condition) native_test_check(static_cast<bool>(condition), #condition, __FILE__, __LINE__)
#define REQUIRE(...) CHECK((__VA_ARGS__))
template <typename Function>
int run_native_test(Function &&function) {
try {
function();
return 0;
} catch (const std::exception &error) {
std::cerr << error.what() << '\n';
return 1;
}
}
+132
View File
@@ -0,0 +1,132 @@
#include "common/yuv.h"
#include <algorithm>
#include <cstring>
namespace yuv {
namespace {
inline uint8_t clamp_u8(int v) {
return static_cast<uint8_t>(std::clamp(v, 0, 255));
}
void copy_plane(const uint8_t *src, int src_stride,
uint8_t *dst, int dst_stride,
int width, int height) {
if (src_stride == width && dst_stride == width) {
std::memcpy(dst, src, static_cast<size_t>(width) * height);
return;
}
for (int y = 0; y < height; ++y) {
std::memcpy(dst + y * dst_stride, src + y * src_stride, width);
}
}
void scale_plane_point(const uint8_t *src, int src_stride, int src_width, int src_height,
uint8_t *dst, int dst_stride, int dst_width, int dst_height) {
if (src_width == dst_width && src_height == dst_height) {
copy_plane(src, src_stride, dst, dst_stride, dst_width, dst_height);
return;
}
for (int y = 0; y < dst_height; ++y) {
const int sy = y * src_height / dst_height;
const uint8_t *src_row = src + sy * src_stride;
uint8_t *dst_row = dst + y * dst_stride;
for (int x = 0; x < dst_width; ++x) {
dst_row[x] = src_row[x * src_width / dst_width];
}
}
}
// BT.601 limited range → RGB (integer form used widely, incl. similar to libyuv).
inline void yuv_to_rgb(int y, int u, int v, uint8_t *r, uint8_t *g, uint8_t *b) {
const int c = (y - 16) * 298;
const int d = u - 128;
const int e = v - 128;
*r = clamp_u8((c + 409 * e + 128) >> 8);
*g = clamp_u8((c - 100 * d - 208 * e + 128) >> 8);
*b = clamp_u8((c + 516 * d + 128) >> 8);
}
} // namespace
void nv12_to_i420(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_uv, int src_stride_uv,
uint8_t *dst_y, int dst_stride_y,
uint8_t *dst_u, int dst_stride_u,
uint8_t *dst_v, int dst_stride_v,
int width, int height) {
copy_plane(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
const int uv_width = width / 2;
const int uv_height = height / 2;
for (int y = 0; y < uv_height; ++y) {
const uint8_t *uv = src_uv + y * src_stride_uv;
uint8_t *u = dst_u + y * dst_stride_u;
uint8_t *v = dst_v + y * dst_stride_v;
for (int x = 0; x < uv_width; ++x) {
u[x] = uv[2 * x];
v[x] = uv[2 * x + 1];
}
}
}
void i420_to_nv12(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_u, int src_stride_u,
const uint8_t *src_v, int src_stride_v,
uint8_t *dst_y, int dst_stride_y,
uint8_t *dst_uv, int dst_stride_uv,
int width, int height) {
copy_plane(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
const int uv_width = width / 2;
const int uv_height = height / 2;
for (int y = 0; y < uv_height; ++y) {
const uint8_t *u = src_u + y * src_stride_u;
const uint8_t *v = src_v + y * src_stride_v;
uint8_t *uv = dst_uv + y * dst_stride_uv;
for (int x = 0; x < uv_width; ++x) {
uv[2 * x] = u[x];
uv[2 * x + 1] = v[x];
}
}
}
void i420_scale(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_u, int src_stride_u,
const uint8_t *src_v, int src_stride_v,
int src_width, int src_height,
uint8_t *dst_y, int dst_stride_y,
uint8_t *dst_u, int dst_stride_u,
uint8_t *dst_v, int dst_stride_v,
int dst_width, int dst_height) {
scale_plane_point(src_y, src_stride_y, src_width, src_height,
dst_y, dst_stride_y, dst_width, dst_height);
scale_plane_point(src_u, src_stride_u, src_width / 2, src_height / 2,
dst_u, dst_stride_u, dst_width / 2, dst_height / 2);
scale_plane_point(src_v, src_stride_v, src_width / 2, src_height / 2,
dst_v, dst_stride_v, dst_width / 2, dst_height / 2);
}
void nv12_to_rgba(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_uv, int src_stride_uv,
uint8_t *dst_rgba, int dst_stride_rgba,
int width, int height) {
for (int y = 0; y < height; ++y) {
const uint8_t *y_row = src_y + y * src_stride_y;
const uint8_t *uv_row = src_uv + (y / 2) * src_stride_uv;
uint8_t *dst = dst_rgba + y * dst_stride_rgba;
for (int x = 0; x < width; ++x) {
const int uv_x = (x & ~1);
uint8_t r, g, b;
yuv_to_rgb(y_row[x], uv_row[uv_x], uv_row[uv_x + 1], &r, &g, &b);
dst[4 * x + 0] = r;
dst[4 * x + 1] = g;
dst[4 * x + 2] = b;
dst[4 * x + 3] = 255;
}
}
}
} // namespace yuv
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
// NV12: Y plane + interleaved UV. I420: planar Y, U, V.
namespace yuv {
// Deinterleave NV12 UV into planar I420.
void nv12_to_i420(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_uv, int src_stride_uv,
uint8_t *dst_y, int dst_stride_y,
uint8_t *dst_u, int dst_stride_u,
uint8_t *dst_v, int dst_stride_v,
int width, int height);
// Interleave planar I420 UV into NV12.
void i420_to_nv12(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_u, int src_stride_u,
const uint8_t *src_v, int src_stride_v,
uint8_t *dst_y, int dst_stride_y,
uint8_t *dst_uv, int dst_stride_uv,
int width, int height);
// Point-sample scale I420 (equivalent to libyuv::I420Scale + kFilterNone).
void i420_scale(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_u, int src_stride_u,
const uint8_t *src_v, int src_stride_v,
int src_width, int src_height,
uint8_t *dst_y, int dst_stride_y,
uint8_t *dst_u, int dst_stride_u,
uint8_t *dst_v, int dst_stride_v,
int dst_width, int dst_height);
// Convert NV12 to packed RGBA (R,G,B,A bytes — suitable for GL_RGBA).
// BT.601 limited-range, matching common libyuv defaults.
void nv12_to_rgba(const uint8_t *src_y, int src_stride_y,
const uint8_t *src_uv, int src_stride_uv,
uint8_t *dst_rgba, int dst_stride_rgba,
int width, int height);
}
+58
View File
@@ -0,0 +1,58 @@
import time
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.iqpilot.common.geo_regions import UNKNOWN_REGION, region_for_position, region_is_metric
CHECK_INTERVAL = 10.0
CONFIRMATIONS = 3
class AutoUnits:
def __init__(self, params: Params | None = None):
self.params = params or Params()
self._next_check = 0.0
self._candidate = UNKNOWN_REGION
self._confirmations = 0
def _position(self) -> tuple[float, float, bool]:
from openpilot.selfdrive.ui.lib.nav_helpers import current_or_last_gps_position
lat, lon, _, valid = current_or_last_gps_position(self.params)
return lat, lon, valid
def update(self, now: float | None = None) -> None:
if not self.params.get_bool("IQAutoUnits"):
self._candidate = UNKNOWN_REGION
self._confirmations = 0
return
now = time.monotonic() if now is None else now
if now < self._next_check:
return
self._next_check = now + CHECK_INTERVAL
lat, lon, valid = self._position()
region = region_for_position(lat, lon) if valid else UNKNOWN_REGION
if region == UNKNOWN_REGION:
self._confirmations = 0
return
if region != self._candidate:
self._candidate = region
self._confirmations = 1
return
self._confirmations += 1
if self._confirmations < CONFIRMATIONS:
return
if region == self.params.get("IQAutoUnitsRegion"):
return
self.params.put("IQAutoUnitsRegion", region)
metric = region_is_metric(region)
if metric != self.params.get_bool("IsMetric"):
self.params.put_bool("IsMetric", metric)
cloudlog.warning(f"auto units: {region} detected, switching to {'km/h' if metric else 'mph'}")
+140
View File
@@ -0,0 +1,140 @@
MPH_REGIONS = ("US", "GB", "LR")
METRIC_REGION = "METRIC"
UNKNOWN_REGION = ""
_US_CONUS = [
(-123.32, 49.00), (-117.03, 49.00), (-110.00, 49.00), (-104.05, 49.00), (-97.23, 49.00), (-95.15, 49.00),
(-95.15, 49.38), (-94.82, 49.30), (-94.68, 48.77), (-93.85, 48.63), (-93.35, 48.62), (-92.72, 48.54),
(-92.30, 48.24), (-91.55, 48.10), (-90.84, 48.24), (-89.99, 48.02), (-89.60, 48.02), (-89.10, 48.32),
(-88.40, 48.30), (-87.00, 47.80), (-85.60, 47.15), (-84.60, 46.75), (-84.42, 46.56), (-84.30, 46.49),
(-84.12, 46.28), (-83.90, 46.05), (-83.40, 45.75), (-82.90, 45.05), (-82.55, 44.00), (-82.42, 43.00),
(-82.70, 42.47), (-82.93, 42.34), (-83.00, 42.33), (-83.05, 42.32), (-83.075, 42.312), (-83.13, 42.25),
(-83.15, 42.18), (-83.11, 42.10), (-83.09, 42.02),
(-82.50, 41.70), (-81.50, 42.00), (-80.20, 42.40), (-79.06, 42.85), (-79.05, 43.27), (-78.00, 43.45),
(-77.00, 43.65), (-76.40, 44.10), (-75.80, 44.50), (-74.75, 45.00), (-73.35, 45.01), (-71.50, 45.01),
(-71.29, 45.30), (-70.90, 45.30), (-70.72, 45.42), (-70.31, 45.86), (-70.05, 46.44), (-69.99, 46.70),
(-69.24, 47.46), (-68.90, 47.20), (-68.38, 47.29), (-67.79, 47.07), (-67.78, 45.94), (-67.42, 45.60),
(-67.03, 44.80), (-68.00, 44.30), (-69.06, 43.80), (-70.20, 43.60), (-70.80, 42.85), (-70.00, 41.90),
(-70.00, 41.55), (-71.20, 41.30), (-72.00, 41.05), (-73.90, 40.55), (-74.20, 39.60), (-75.05, 38.45),
(-75.90, 37.05), (-75.50, 35.20), (-78.50, 33.85), (-80.90, 32.00), (-81.40, 30.70), (-80.03, 26.80),
(-80.15, 25.15), (-81.20, 24.55), (-82.00, 26.40), (-82.80, 27.80), (-83.00, 29.15), (-84.30, 29.90),
(-85.30, 29.65), (-87.50, 30.25), (-89.00, 29.15), (-89.40, 28.95), (-91.30, 29.10), (-93.80, 29.65),
(-95.00, 29.10), (-97.10, 27.80), (-97.14, 25.96), (-98.30, 26.05), (-99.10, 26.40), (-99.50, 27.60),
(-100.40, 28.50), (-101.40, 29.77), (-102.30, 29.88), (-102.90, 29.30), (-103.30, 29.00), (-104.37, 29.56),
(-104.68, 30.13), (-105.30, 30.80), (-105.85, 31.30), (-106.15, 31.50), (-106.30, 31.68), (-106.45, 31.755),
(-106.53, 31.786), (-108.21, 31.783), (-108.21, 31.33), (-111.07, 31.33), (-114.72, 32.72),
(-117.13, 32.53), (-118.40, 33.75), (-119.80, 34.40), (-120.65, 35.10), (-121.90, 36.60), (-122.52, 37.78),
(-123.75, 39.40), (-124.20, 40.45), (-124.15, 42.00), (-124.05, 43.35), (-123.95, 46.25), (-124.75, 48.40),
(-123.30, 48.25), (-123.15, 48.70),
]
_US_ALASKA = [
(-141.00, 70.20), (-141.00, 60.30), (-139.05, 60.35), (-137.45, 58.95), (-136.47, 59.63), (-135.03, 59.57),
(-134.30, 58.90), (-133.40, 58.20), (-132.20, 56.90), (-130.60, 56.20), (-130.01, 54.80), (-131.80, 54.70),
(-133.80, 55.90), (-136.60, 58.20), (-140.00, 59.70), (-145.00, 60.00), (-149.20, 59.10), (-152.30, 57.30),
(-155.20, 55.60), (-160.00, 54.60), (-164.50, 54.40), (-162.00, 57.50), (-165.00, 60.20), (-167.50, 62.50),
(-164.00, 64.50), (-168.10, 65.60), (-166.00, 68.30), (-161.00, 70.30), (-156.50, 71.40), (-150.00, 70.50),
]
_US_ALEUTIANS_EAST = [(-180.00, 51.00), (-158.50, 51.00), (-158.50, 56.00), (-180.00, 56.00)]
_US_ALEUTIANS_WEST = [(172.00, 51.00), (180.00, 51.00), (180.00, 54.00), (172.00, 54.00)]
_US_HAWAII = [(-160.50, 18.80), (-154.70, 18.80), (-154.70, 22.30), (-160.50, 22.30)]
_US_PUERTO_RICO = [(-67.35, 17.85), (-64.55, 17.85), (-64.55, 18.55), (-67.35, 18.55)]
_US_MARIANAS = [(144.50, 13.10), (146.20, 13.10), (146.20, 20.60), (144.50, 20.60)]
_US_SAMOA = [(-171.20, -14.60), (-168.10, -14.60), (-168.10, -11.00), (-171.20, -11.00)]
_GB_BRITAIN = [
(-5.72, 50.07), (-4.20, 50.32), (-3.41, 50.62), (-2.45, 50.52), (-1.80, 50.72), (-0.90, 50.77),
(0.58, 50.85), (1.35, 51.13), (1.38, 51.38), (1.15, 51.79), (1.35, 51.95), (1.75, 52.48),
(1.30, 52.94), (0.49, 52.94), (0.34, 53.15), (-0.08, 53.57), (-0.08, 54.12), (-0.61, 54.49),
(-1.18, 54.69), (-1.38, 54.91), (-1.50, 55.13), (-2.00, 55.77), (-2.52, 56.00), (-2.62, 56.28),
(-2.47, 56.55), (-2.21, 56.96), (-2.08, 57.14), (-1.77, 57.50), (-2.00, 57.70), (-2.96, 57.68),
(-3.90, 57.60), (-4.22, 57.48), (-4.05, 57.81), (-3.85, 58.01), (-3.65, 58.12), (-3.09, 58.44),
(-3.01, 58.67), (-3.35, 58.62), (-3.52, 58.60), (-4.99, 58.62), (-5.05, 58.45), (-5.16, 57.90), (-5.70, 57.72),
(-5.72, 57.28), (-5.83, 57.00), (-5.72, 56.65), (-5.47, 56.41), (-5.79, 55.60), (-5.62, 55.31),
(-4.82, 55.64), (-4.63, 55.46), (-4.85, 55.24), (-5.12, 54.84), (-4.86, 54.63), (-4.44, 54.87),
(-4.05, 54.83), (-3.26, 54.98), (-3.05, 54.90), (-3.50, 54.72), (-3.23, 54.07), (-3.05, 53.82),
(-3.40, 53.34), (-3.83, 53.33), (-4.63, 53.42), (-4.72, 53.28), (-4.35, 53.12), (-4.76, 52.80),
(-4.06, 52.72), (-4.09, 52.41), (-4.66, 52.09), (-5.31, 51.88), (-5.06, 51.70), (-4.70, 51.67),
(-4.30, 51.62), (-3.95, 51.56), (-3.70, 51.48), (-3.17, 51.45), (-2.99, 51.55), (-2.67, 51.62),
(-2.48, 51.72), (-2.30, 51.85), (-2.70, 51.50), (-2.98, 51.35), (-3.00, 51.20), (-3.47, 51.21),
(-4.12, 51.21), (-4.55, 50.83), (-5.08, 50.42), (-5.48, 50.21),
]
_GB_NORTHERN_IRELAND = [
(-6.03, 54.05), (-6.28, 54.10), (-6.65, 54.17), (-6.86, 54.33), (-7.16, 54.34), (-7.31, 54.12),
(-7.62, 54.14), (-8.00, 54.31), (-8.18, 54.47), (-8.20, 54.52), (-7.90, 54.55), (-7.85, 54.72), (-7.55, 54.75),
(-7.44, 54.94), (-7.25, 55.06), (-6.95, 55.22), (-6.50, 55.25), (-6.25, 55.31), (-6.03, 55.22),
(-5.43, 54.62), (-5.53, 54.24),
]
_GB_ISLE_OF_MAN = [(-4.85, 54.03), (-4.30, 54.03), (-4.30, 54.42), (-4.85, 54.42)]
_GB_CHANNEL_ISLANDS = [(-2.75, 49.15), (-1.95, 49.15), (-1.95, 49.80), (-2.75, 49.80)]
_GB_ISLE_OF_WIGHT = [(-1.60, 50.55), (-1.05, 50.55), (-1.05, 50.80), (-1.60, 50.80)]
_GB_OUTER_HEBRIDES = [(-7.75, 56.75), (-6.05, 56.75), (-6.05, 58.55), (-7.75, 58.55)]
_GB_INNER_HEBRIDES = [(-7.00, 55.45), (-5.55, 55.45), (-5.55, 57.85), (-7.00, 57.85)]
_GB_ORKNEY = [(-3.50, 58.70), (-2.35, 58.70), (-2.35, 59.45), (-3.50, 59.45)]
_GB_SHETLAND = [(-1.85, 59.80), (-0.65, 59.80), (-0.65, 60.90), (-1.85, 60.90)]
_LR_LIBERIA = [
(-11.46, 6.77), (-11.30, 6.95), (-11.16, 7.15), (-11.05, 7.40), (-10.85, 7.75), (-10.60, 8.00),
(-10.28, 8.49), (-9.70, 8.54), (-9.35, 7.80),
(-8.85, 7.40), (-8.48, 7.55), (-8.30, 6.90), (-7.95, 6.20), (-7.60, 5.20), (-7.40, 4.55),
(-7.74, 4.33), (-8.46, 4.61), (-9.06, 4.97), (-9.52, 5.36), (-10.08, 5.85), (-10.40, 6.11),
(-10.83, 6.27),
]
_REGION_RINGS = {
"US": (_US_CONUS, _US_ALASKA, _US_ALEUTIANS_EAST, _US_ALEUTIANS_WEST, _US_HAWAII, _US_PUERTO_RICO,
_US_MARIANAS, _US_SAMOA),
"GB": (_GB_BRITAIN, _GB_NORTHERN_IRELAND, _GB_ISLE_OF_MAN, _GB_CHANNEL_ISLANDS, _GB_ISLE_OF_WIGHT,
_GB_OUTER_HEBRIDES, _GB_INNER_HEBRIDES, _GB_ORKNEY, _GB_SHETLAND),
"LR": (_LR_LIBERIA,),
}
def _bounded(rings):
out = []
for ring in rings:
lons = [p[0] for p in ring]
lats = [p[1] for p in ring]
out.append(((min(lons), min(lats), max(lons), max(lats)), ring))
return tuple(out)
_REGIONS = tuple((region, _bounded(rings)) for region, rings in _REGION_RINGS.items())
def _point_in_ring(lat: float, lon: float, ring) -> bool:
inside = False
count = len(ring)
j = count - 1
for i in range(count):
lon_i, lat_i = ring[i]
lon_j, lat_j = ring[j]
if (lat_i > lat) != (lat_j > lat):
crossing = (lon_j - lon_i) * (lat - lat_i) / (lat_j - lat_i) + lon_i
if lon < crossing:
inside = not inside
j = i
return inside
def valid_position(lat: float, lon: float) -> bool:
return abs(lat) <= 90.0 and abs(lon) <= 180.0 and (abs(lat) > 1e-4 or abs(lon) > 1e-4)
def region_for_position(lat: float, lon: float) -> str:
if not valid_position(lat, lon):
return UNKNOWN_REGION
for region, rings in _REGIONS:
for (min_lon, min_lat, max_lon, max_lat), ring in rings:
if min_lon <= lon <= max_lon and min_lat <= lat <= max_lat and _point_in_ring(lat, lon, ring):
return region
return METRIC_REGION
def region_is_metric(region: str) -> bool:
return bool(region) and region not in MPH_REGIONS
+4 -10
View File
@@ -121,19 +121,13 @@ def _install_signal_handlers() -> None:
signal.signal(signal.SIGTERM, _handle_shutdown_signal)
class _QuietSpinner:
def update(self, *args, **kwargs) -> None:
pass
def close(self, *args, **kwargs) -> None:
pass
def ensure_vendor_runtime() -> None:
# verify-only: a hash-mismatched binary is quarantined, never replaced from
# the network — the updater restores the checked-in one
try:
VendorMapdInstaller(_QuietSpinner()).check_and_download()
VendorMapdInstaller().verify()
except Exception:
cloudlog.exception("iq_maps: vendor runtime install/download failed")
cloudlog.exception("iq_maps: vendor runtime verification failed")
params = Params()
mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else params
+4 -2
View File
@@ -2,8 +2,10 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Maintainer utility: pin a new pfeiferj/mapd release tag and refresh the checked-in
binary hash. Not used at runtime.
Maintainer utility: pin a new mapd release tag and refresh the checked-in binary
hash. Not used at runtime. Binaries come from the gitlvb teal/mapd CI (built
against teal/gomsgq) — drop the artifact at third_party/mapd_pfeiferj/mapd, then
run this so the hash pin moves in the same commit.
"""
import argparse
import os
+59 -127
View File
@@ -2,36 +2,30 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Provisions the `mapd` routing binary authored by Jacob Pfeifer (github.com/pfeiferj/mapd).
The binary itself is his work; this module only fetches, verifies and stages it on-device.
Verifies the vendored `mapd` routing binary authored by Jacob Pfeifer
(github.com/pfeiferj/mapd), built from the gitlvb teal/mapd fork against
teal/gomsgq. The only accepted binary is the checked-in one matching the pinned
hash; nothing is ever downloaded at runtime. Jacob's stock release build embeds
a 15-reader msgq header layout — on this fork (NUM_READERS=32) its registration
writes land inside other processes' reader slots, so a wrong binary is
quarantined rather than left where manager could start it.
"""
import hashlib
import logging
import os
import stat
import time
from pathlib import Path
import sys
import requests
from cereal import messaging
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.spinner import Spinner
from openpilot.system.hardware.hw import Paths
from openpilot.system.version import is_prebuilt
from openpilot.iqpilot.iq_maps import VENDOR_MAPD_BIN_DIR, VENDOR_MAPD_PATH
from openpilot.common.swaglog import cloudlog
from openpilot.iqpilot.iq_maps import VENDOR_MAPD_PATH
import openpilot.system.sentry as sentry
VENDOR_RELEASE_TAG = "v2.0.6"
VENDOR_RELEASE_URL = f"https://github.com/pfeiferj/mapd/releases/download/{VENDOR_RELEASE_TAG}/mapd"
VENDOR_RELEASE_TAG = "v2.0.6-iq1"
_VERSION_PARAM = "MapdVersion"
_HASH_FILE = os.path.join(BASEDIR, "iqpilot", "iq_maps", "tests", "mapd_hash")
_HTTP_TIMEOUT_S = 60
_FETCH_ATTEMPTS = 5
_NET_PROBE_ATTEMPTS = 10
_NET_PROBE_INTERVAL_S = 2
QUARANTINE_PATH = VENDOR_MAPD_PATH + ".quarantined"
def sha256_of_file(path: str) -> str:
@@ -48,45 +42,58 @@ def stamp_vendor_version(version: str, params: Params | None = None) -> None:
class VendorMapdInstaller:
def __init__(self, spinner_ref: Spinner):
def __init__(self, spinner_ref: Spinner | None = None, params: Params | None = None):
self._spinner = spinner_ref
self._params = Params()
self._params = params if params is not None else Params()
# --- externally consumed surface -----------------------------------------
def get_installed_version(self) -> str:
return str(self._params.get(_VERSION_PARAM) or "")
@staticmethod
def ensure_directories_exist() -> None:
for directory in (Paths.mapd_root(), VENDOR_MAPD_BIN_DIR):
os.makedirs(directory, exist_ok=True)
def verify(self) -> bool:
"""True iff the on-disk binary matches the pinned hash; quarantines a wrong one."""
expected = self._expected_hash()
if not expected:
cloudlog.error("iq_maps: pinned mapd hash missing, vendor binary cannot be verified")
return False
def check_and_download(self) -> None:
if not self._binary_up_to_date():
self._provision()
def non_prebuilt_install(self) -> None:
if self._on_metered_link():
self._say("Metered connection detected — offline maps engine will not download here.")
time.sleep(5)
return
if not os.path.isfile(VENDOR_MAPD_PATH):
# the binary is a tracked file: the updater/bundle restores it
self._say("Offline maps engine missing; it will be restored by the next update.")
self._params.remove(_VERSION_PARAM)
return False
try:
self.ensure_directories_exist()
if self._binary_up_to_date():
self._say("Offline maps engine already present and current.")
time.sleep(0.1)
return
current = sha256_of_file(VENDOR_MAPD_PATH)
except OSError:
cloudlog.exception("iq_maps: vendor mapd unreadable")
return False
if self._block_until_online():
self._say(f"Retrieving offline maps engine [{self.get_installed_version() or 'none'}] -> [{VENDOR_RELEASE_TAG}]")
time.sleep(0.1)
self._provision()
self._spinner.close()
except Exception as exc: # noqa: BLE001
self._announce_failure(exc)
if current == expected:
stamp_vendor_version(VENDOR_RELEASE_TAG, self._params)
try:
os.remove(QUARANTINE_PATH)
except OSError:
pass
self._say(f"Offline maps engine verified [{VENDOR_RELEASE_TAG}]")
return True
# a foreign binary — e.g. a stock release download from the retired fetch
# path — must never run: quarantine it where manager can't start it
cloudlog.error(f"iq_maps: vendor mapd hash {current[:12]} != pinned {expected[:12]}, quarantining")
self._say("Offline maps engine failed verification; quarantined until the next update.")
try:
os.replace(VENDOR_MAPD_PATH, QUARANTINE_PATH)
except OSError:
cloudlog.exception("iq_maps: vendor mapd quarantine failed")
return False
self._params.remove(_VERSION_PARAM)
try:
raise RuntimeError(f"vendor mapd hash mismatch quarantined: {current}")
except RuntimeError as exc:
sentry.init(sentry.SentryProject.SELFDRIVE)
sentry.capture_exception(exc)
return False
# --- internal ------------------------------------------------------------
def _expected_hash(self) -> str:
try:
with open(_HASH_FILE) as f:
@@ -94,88 +101,13 @@ class VendorMapdInstaller:
except OSError:
return ""
def _binary_up_to_date(self) -> bool:
if not os.path.exists(VENDOR_MAPD_PATH):
return False
if self.get_installed_version() != VENDOR_RELEASE_TAG:
return False
reference = self._expected_hash()
if not reference:
return True
try:
return sha256_of_file(VENDOR_MAPD_PATH) == reference
except OSError:
return False
def _provision(self) -> None:
self.ensure_directories_exist()
if self._retrieve_binary():
stamp_vendor_version(VENDOR_RELEASE_TAG, self._params)
def _retrieve_binary(self) -> bool:
staging = Path(f"{VENDOR_MAPD_PATH}.part")
last_error: Exception | None = None
for attempt in range(1, _FETCH_ATTEMPTS + 1):
try:
with requests.get(VENDOR_RELEASE_URL, stream=True, timeout=_HTTP_TIMEOUT_S) as resp:
resp.raise_for_status()
with open(staging, "wb") as out:
for chunk in resp.iter_content(chunk_size=1 << 16):
out.write(chunk)
out.flush()
os.fsync(out.fileno())
os.chmod(staging, os.lstat(staging).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
staging.replace(VENDOR_MAPD_PATH)
return True
except requests.exceptions.RequestException as exc:
last_error = exc
self._say(f"offline maps fetch attempt {attempt}/{_FETCH_ATTEMPTS} did not complete ({exc})")
time.sleep(0.5)
staging.unlink(missing_ok=True)
logging.error("offline maps engine could not be fetched after %d attempts: %s", _FETCH_ATTEMPTS, last_error)
return False
def _on_metered_link(self) -> bool:
sm = messaging.SubMaster(["deviceState"])
return bool(sm["deviceState"].networkMetered)
def _block_until_online(self) -> bool:
for i in range(1, _NET_PROBE_ATTEMPTS + 1):
self._say(f"Waiting for a usable network connection... [{i}/{_NET_PROBE_ATTEMPTS}]")
if self._link_reachable():
return True
time.sleep(_NET_PROBE_INTERVAL_S)
return False
@staticmethod
def _link_reachable() -> bool:
try:
requests.head(VENDOR_RELEASE_URL, timeout=10, allow_redirects=True)
return True
except requests.exceptions.RequestException as exc:
logging.debug("network probe failed: %s", exc)
return False
def _announce_failure(self, exc: Exception) -> None:
for remaining in range(5, 0, -1):
self._say(f"Offline maps engine unavailable; navigation stays online-only. Boot continues in {remaining}s...")
time.sleep(1)
logging.exception("vendor mapd install failed")
sentry.init(sentry.SentryProject.SELFDRIVE)
sentry.capture_exception(exc)
def _say(self, text: str) -> None:
self._spinner.update(text)
if self._spinner is not None:
self._spinner.update(text)
if __name__ == "__main__":
spinner = Spinner()
installer = VendorMapdInstaller(spinner)
installer.ensure_directories_exist()
if is_prebuilt():
spinner.update(f"[DEBUG] Prebuilt build; vendor mapd install skipped. "
f"target [{VENDOR_RELEASE_TAG}], param [{installer.get_installed_version()}]")
stamp_vendor_version(VENDOR_RELEASE_TAG)
else:
spinner.update(f"Verifying vendor mapd install. prebuilt [{is_prebuilt()}]")
installer.non_prebuilt_install()
ok = VendorMapdInstaller(spinner).verify()
spinner.close()
sys.exit(0 if ok else 1)
View File
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
canlived — live CAN bridge to konn3kt.
Streams the device's live CAN bus to the konn3kt server so it can be viewed remotely
in Cabana (via a local ZMQ proxy on the laptop). This is the remote analogue of running
`./cereal/messaging/bridge` locally: instead of re-publishing CAN over a LAN ZMQ socket,
canlived opens its OWN websocket to konn3kt and forwards the raw capnp `Event` frames.
It is deliberately a separate daemon (not part of hephaestusd's control websocket):
* hephaestusd's send path fragments everything as TEXT frames through one queue, which
cannot carry binary capnp and would head-of-line-block the control plane at 100Hz.
* a dedicated socket means CAN traffic and control traffic never contend.
Lifecycle: the manager launches canlived only while the `CanLiveStreaming` param is set.
hephaestusd sets/clears that param via startCanLive/stopCanLive, which the konn3kt server
calls when the first viewer connects / the last viewer disconnects. So canlived runs only
during an active debug session — no idle connections, no battery/data cost otherwise.
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved.
"""
import os
import threading
from websocket import ABNF, create_connection
import cereal.messaging as messaging
from openpilot.common.api import Api
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
# Cabana's live "Device" stream subscribes only to "can", so that's all we forward to
# match the local experience exactly. (sendcan/TX is not shown by the live device view.)
CAN_SERVICES = ["can"]
# Reconnect backoff bounds (seconds).
RECONNECT_MIN = 1.0
RECONNECT_MAX = 10.0
def _api_host() -> str:
# Same host hephaestusd talks to; force the websocket scheme.
host = os.getenv("HEPHAESTUS_HOST") or os.getenv("KONN3KT_API_HOST") or "wss://api-iqlabs.konn3kt.com"
host = host.rstrip("/")
if host.startswith("https://"):
host = "wss://" + host[len("https://"):]
elif host.startswith("http://"):
host = "ws://" + host[len("http://"):]
return host
def _stream_once(dongle_id: str, ws_uri: str, token: str, exit_event: threading.Event) -> None:
"""Open one websocket and pump CAN until it drops or we're asked to exit."""
ws = create_connection(ws_uri, cookie="jwt=" + token, enable_multithread=True, timeout=30.0)
cloudlog.info("canlived: connected to %s", ws_uri)
try:
# Blocking receive with a short timeout so we periodically re-check exit_event and
# the socket stays responsive to shutdown even when the bus is quiet.
socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in CAN_SERVICES]
while not exit_event.is_set():
got_any = False
for sock in socks:
while True:
raw = sock.receive(non_blocking=True)
if raw is None:
break
got_any = True
# Forward the exact capnp Event bytes as a single binary frame. canlived owns
# this socket, so there is no fragmentation/interleaving to worry about.
ws.send_frame(ABNF.create_frame(raw, ABNF.OPCODE_BINARY, 1))
if not got_any:
# Nothing pending across any sub — yield briefly instead of busy-spinning.
exit_event.wait(0.005)
finally:
try:
ws.close()
except Exception:
pass
def main(exit_event: threading.Event | None = None) -> None:
if exit_event is None:
exit_event = threading.Event()
params = Params()
dongle_id = params.get("DongleId", encoding="utf-8")
if not dongle_id:
cloudlog.error("canlived: no DongleId, cannot stream")
return
api = Api(dongle_id)
host = _api_host()
ws_uri = f"{host}/ws/can/{dongle_id}"
backoff = RECONNECT_MIN
while not exit_event.is_set():
try:
token = api.get_token(expiry_hours=1)
_stream_once(dongle_id, ws_uri, token, exit_event)
backoff = RECONNECT_MIN # clean disconnect, reset backoff
except Exception as e:
cloudlog.exception("canlived: stream error: %s", e)
exit_event.wait(backoff)
backoff = min(backoff * 2, RECONNECT_MAX)
if __name__ == "__main__":
main()
+91
View File
@@ -0,0 +1,91 @@
from openpilot.common.params import Params
from openpilot.iqpilot.common.auto_units import CONFIRMATIONS, AutoUnits
SEATTLE = (47.6062, -122.3321)
BERLIN = (52.5200, 13.4050)
LONDON = (51.5074, -0.1278)
class StubAutoUnits(AutoUnits):
def __init__(self, params):
super().__init__(params)
self.position = (0.0, 0.0, False)
def _position(self):
return self.position
def settle(auto_units, position, count=CONFIRMATIONS, start=0.0):
auto_units.position = position
for i in range(count):
auto_units.update(now=start + i * 100.0)
return start + count * 100.0
class TestAutoUnits:
def setup_method(self):
self.params = Params()
self.params.put_bool("IQAutoUnits", True)
self.params.remove("IQAutoUnitsRegion")
self.params.put_bool("IsMetric", False)
self.auto_units = StubAutoUnits(self.params)
def test_no_fix_does_nothing(self):
settle(self.auto_units, (0.0, 0.0, False))
assert self.params.get("IQAutoUnitsRegion") is None
assert not self.params.get_bool("IsMetric")
def test_disabled_does_nothing(self):
self.params.put_bool("IQAutoUnits", False)
settle(self.auto_units, (*BERLIN, True))
assert self.params.get("IQAutoUnitsRegion") is None
assert not self.params.get_bool("IsMetric")
def test_metric_region_switches_to_metric(self):
settle(self.auto_units, (*BERLIN, True))
assert self.params.get("IQAutoUnitsRegion") == "METRIC"
assert self.params.get_bool("IsMetric")
def test_mph_region_stays_imperial(self):
settle(self.auto_units, (*SEATTLE, True))
assert self.params.get("IQAutoUnitsRegion") == "US"
assert not self.params.get_bool("IsMetric")
def test_uk_stays_imperial(self):
self.params.put_bool("IsMetric", True)
settle(self.auto_units, (*LONDON, True))
assert self.params.get("IQAutoUnitsRegion") == "GB"
assert not self.params.get_bool("IsMetric")
def test_border_crossing_switches_units(self):
now = settle(self.auto_units, (*SEATTLE, True))
assert not self.params.get_bool("IsMetric")
settle(self.auto_units, (*BERLIN, True), start=now)
assert self.params.get("IQAutoUnitsRegion") == "METRIC"
assert self.params.get_bool("IsMetric")
def test_manual_override_is_kept_within_a_region(self):
now = settle(self.auto_units, (*SEATTLE, True))
self.params.put_bool("IsMetric", True)
settle(self.auto_units, (*SEATTLE, True), start=now)
assert self.params.get_bool("IsMetric")
def test_unconfirmed_region_is_not_applied(self):
settle(self.auto_units, (*BERLIN, True), count=CONFIRMATIONS - 1)
assert self.params.get("IQAutoUnitsRegion") is None
assert not self.params.get_bool("IsMetric")
def test_flapping_region_resets_confirmations(self):
now = 0.0
for position in (BERLIN, SEATTLE, BERLIN, SEATTLE):
now = settle(self.auto_units, (*position, True), count=1, start=now)
assert self.params.get("IQAutoUnitsRegion") is None
assert not self.params.get_bool("IsMetric")
def test_rate_limited(self):
self.auto_units.position = (*BERLIN, True)
for _ in range(CONFIRMATIONS * 4):
self.auto_units.update(now=1.0)
assert self.params.get("IQAutoUnitsRegion") is None
+165
View File
@@ -0,0 +1,165 @@
import pytest
from openpilot.iqpilot.common.geo_regions import METRIC_REGION, UNKNOWN_REGION, region_for_position, region_is_metric
US_POINTS = [
(47.6062, -122.3321, "Seattle"),
(42.3314, -83.0458, "Detroit"),
(42.8864, -78.8784, "Buffalo"),
(25.7617, -80.1918, "Miami"),
(29.7604, -95.3698, "Houston"),
(32.7157, -117.1611, "San Diego"),
(34.0522, -118.2437, "Los Angeles"),
(61.2181, -149.9003, "Anchorage"),
(58.3019, -134.4197, "Juneau"),
(64.8378, -147.7164, "Fairbanks"),
(21.3069, -157.8583, "Honolulu"),
(18.4655, -66.1057, "San Juan"),
(13.4757, 144.7489, "Guam"),
(44.9778, -93.2650, "Minneapolis"),
(40.7128, -74.0060, "New York"),
(41.8781, -87.6298, "Chicago"),
(31.7900, -106.4300, "El Paso"),
(26.2034, -98.2300, "McAllen"),
(44.8016, -68.7712, "Bangor"),
(48.7519, -122.4787, "Bellingham"),
(46.8772, -96.7898, "Fargo"),
(48.6023, -93.4093, "International Falls"),
(46.4953, -84.3453, "Sault Ste. Marie MI"),
(47.1211, -88.5694, "Houghton"),
(41.6528, -83.5379, "Toledo"),
(42.1370, -83.1930, "Trenton MI"),
(39.7392, -104.9903, "Denver"),
(33.4484, -112.0740, "Phoenix"),
(30.3322, -81.6557, "Jacksonville"),
(42.3601, -71.0589, "Boston"),
(38.9072, -77.0369, "Washington DC"),
]
GB_POINTS = [
(51.5074, -0.1278, "London"),
(54.5973, -5.9301, "Belfast"),
(55.8642, -4.2518, "Glasgow"),
(51.4816, -3.1791, "Cardiff"),
(51.4545, -2.5879, "Bristol"),
(53.4084, -2.9916, "Liverpool"),
(53.4808, -2.2426, "Manchester"),
(55.9533, -3.1883, "Edinburgh"),
(52.4862, -1.8904, "Birmingham"),
(53.8008, -1.5491, "Leeds"),
(57.4778, -4.2247, "Inverness"),
(57.1497, -2.0943, "Aberdeen"),
(56.4620, -2.9707, "Dundee"),
(58.6373, -3.0689, "John o' Groats"),
(54.1509, -4.4814, "Douglas"),
(49.1858, -2.1064, "St Helier"),
(55.0000, -7.3200, "Derry"),
(54.3438, -7.6315, "Enniskillen"),
(54.1751, -6.3402, "Newry"),
(54.4783, -8.0906, "Belleek"),
(54.5973, -7.3095, "Omagh"),
(55.2053, -6.6570, "Portrush"),
(58.2090, -6.3890, "Stornoway"),
(58.9809, -2.9605, "Kirkwall"),
(60.1546, -1.1494, "Lerwick"),
(50.7184, -3.5339, "Exeter"),
(50.3755, -4.1427, "Plymouth"),
(52.6309, 1.2974, "Norwich"),
(54.9783, -1.6178, "Newcastle"),
(51.8642, -2.2382, "Gloucester"),
(52.4140, -4.0810, "Aberystwyth"),
(51.6214, -3.9436, "Swansea"),
(50.6938, -1.3040, "Newport IoW"),
]
LR_POINTS = [
(6.3005, -10.7969, "Monrovia"),
(4.3750, -7.7169, "Harper"),
(6.9956, -9.4722, "Gbarnga"),
(6.0667, -8.1333, "Zwedru"),
(8.4219, -9.7478, "Voinjama"),
(5.8808, -10.0467, "Buchanan"),
(5.0100, -9.0400, "Greenville"),
(7.3500, -8.7200, "Ganta"),
]
METRIC_POINTS = [
(49.2827, -123.1207, "Vancouver"),
(48.4284, -123.3656, "Victoria"),
(43.6532, -79.3832, "Toronto"),
(42.3149, -83.0364, "Windsor"),
(46.5136, -84.3358, "Sault Ste. Marie ON"),
(42.9745, -82.4066, "Sarnia"),
(43.2557, -79.8711, "Hamilton"),
(42.9849, -81.2453, "London ON"),
(45.5019, -73.5674, "Montreal"),
(45.4765, -75.7013, "Gatineau"),
(46.8139, -71.2080, "Quebec City"),
(46.0878, -64.7782, "Moncton"),
(44.6488, -63.5752, "Halifax"),
(49.8951, -97.1384, "Winnipeg"),
(51.0447, -114.0719, "Calgary"),
(53.5461, -113.4938, "Edmonton"),
(52.1332, -106.6700, "Saskatoon"),
(50.6745, -120.3273, "Kamloops"),
(32.5149, -117.0382, "Tijuana"),
(31.7000, -106.4700, "Ciudad Juarez"),
(25.6866, -100.3161, "Monterrey"),
(27.5060, -99.5075, "Nuevo Laredo"),
(19.4326, -99.1332, "Mexico City"),
(53.3498, -6.2603, "Dublin"),
(51.8985, -8.4756, "Cork"),
(53.2707, -9.0568, "Galway"),
(54.9503, -7.7345, "Letterkenny"),
(54.0000, -6.4000, "Dundalk"),
(54.2489, -6.9683, "Monaghan"),
(54.2766, -8.4761, "Sligo"),
(54.6538, -8.1096, "Donegal"),
(52.5200, 13.4050, "Berlin"),
(52.2297, 21.0122, "Warsaw"),
(48.8566, 2.3522, "Paris"),
(60.1699, 24.9384, "Helsinki"),
(50.4501, 30.5234, "Kyiv"),
(-33.8688, 151.2093, "Sydney"),
(35.6762, 139.6503, "Tokyo"),
(8.4844, -13.2299, "Freetown"),
(7.8767, -11.1875, "Kenema"),
(8.2783, -10.5733, "Kailahun"),
(9.6412, -13.5784, "Conakry"),
(7.7562, -8.8179, "Nzerekore"),
(7.4125, -7.5539, "Man"),
(5.3600, -4.0083, "Abidjan"),
]
@pytest.mark.parametrize("lat, lon, name", US_POINTS)
def test_us_positions(lat, lon, name):
assert region_for_position(lat, lon) == "US", name
@pytest.mark.parametrize("lat, lon, name", GB_POINTS)
def test_gb_positions(lat, lon, name):
assert region_for_position(lat, lon) == "GB", name
@pytest.mark.parametrize("lat, lon, name", LR_POINTS)
def test_lr_positions(lat, lon, name):
assert region_for_position(lat, lon) == "LR", name
@pytest.mark.parametrize("lat, lon, name", METRIC_POINTS)
def test_metric_positions(lat, lon, name):
assert region_for_position(lat, lon) == METRIC_REGION, name
@pytest.mark.parametrize("lat, lon", [(0.0, 0.0), (0.0, 0.00001), (91.0, 10.0), (10.0, 181.0)])
def test_invalid_positions(lat, lon):
assert region_for_position(lat, lon) == UNKNOWN_REGION
def test_region_is_metric():
assert not region_is_metric("US")
assert not region_is_metric("GB")
assert not region_is_metric("LR")
assert region_is_metric(METRIC_REGION)
assert not region_is_metric(UNKNOWN_REGION)
+16 -4
View File
@@ -128,6 +128,16 @@ function launch {
fi
fi
# Best-effort: install the verified runtime before anything starts the services that
# need it. This only succeeds on a tree that has built _verified_import.so, so on a
# prebuilt release install it is a no-op -- there the runtime arrives with the IQ.OS
# flash, whose image bakes it in. What actually fixes the 20+ minute first-install hang
# is --no-block on the service starts below: hephaestusd/ble-transportd/flockd each
# ExecStartPre-wait up to 600s for /usr/libexec/iqpilot/iqpilot_bundle_runner.
if [ -x "$DIR/system/proprietary_runtime/install_verified_runtime.sh" ]; then
"$DIR/system/proprietary_runtime/install_verified_runtime.sh" || true
fi
# Install/update proprietary runtime bundles.
if [ -f "$DIR/artifacts/runtime/ensure_private_installed.sh" ]; then
bash "$DIR/artifacts/runtime/ensure_private_installed.sh" || true
@@ -161,10 +171,12 @@ function launch {
sudo mount -o remount,ro /
fi
sudo systemctl enable "${service_name}.service"
# --no-block: these units ExecStartPre-wait for the verified runtime. Blocking here
# made a missing runner stall the whole install for the unit's 600s timeout (x3 units).
if systemctl is-active --quiet "${service_name}.service"; then
sudo systemctl restart "${service_name}.service"
sudo systemctl restart --no-block "${service_name}.service"
else
sudo systemctl start "${service_name}.service"
sudo systemctl start --no-block "${service_name}.service"
fi
elif [ -f "$service_src" ]; then
if [ ! -f "$service_dst" ] || ! cmp -s "$service_src" "$service_dst"; then
@@ -173,9 +185,9 @@ function launch {
sudo systemctl daemon-reload
sudo systemctl enable "${service_name}.service"
sudo mount -o remount,ro /
sudo systemctl restart "${service_name}.service"
sudo systemctl restart --no-block "${service_name}.service"
elif ! systemctl is-active --quiet "${service_name}.service"; then
sudo systemctl start "${service_name}.service"
sudo systemctl start --no-block "${service_name}.service"
fi
fi
done
+1 -1
View File
@@ -23,7 +23,7 @@ if [ -z "$AGNOS_VERSION" ]; then
case "$DEVICE_MODEL" in
*)
export AGNOS_VERSION="IQ.OS 4.9.1"
export AGNOS_VERSION="IQ.OS 4.9.3"
;;
esac
fi
+25 -2
View File
@@ -238,13 +238,36 @@ void ignition_can_hook(CANPacket_t *msg) {
}
// Volkswagen MEB exception
// GE_Fahrstufe: 5=P, 6=R, 7=N, 8/9=D, 10=E, 13/14=T; 0/1/15 transitional/init/error.
// Both gear messages are latched independently and OR'd: cars broadcast one
// authoritative source (Gateway_73 on ALT_GEAR platforms), and a stale parked
// reading on the unused one must not veto the real one.
static bool vw_meb_getriebe_out_of_p = false;
static bool vw_meb_gateway_out_of_p = false;
// Getriebe_11->GE_Fahrstufe
if ((msg->addr == 0xADU) && (len == 8)) {
int fahrstufe = (msg->data[5] >> 2) & 0xFU;
vw_meb_getriebe_out_of_p = (fahrstufe >= 6) && (fahrstufe <= 14);
}
// Gateway_73->GE_Fahrstufe
if ((msg->addr == 0x3DCU) && (len == 8)) {
int fahrstufe = msg->data[5] & 0xFU;
vw_meb_gateway_out_of_p = (fahrstufe >= 6) && (fahrstufe <= 14);
}
if ((msg->addr == 0x3C0U) && (len == 4)) {
int counter = msg->data[1] & 0xFU;
static int prev_counter_vw_meb = -1;
if ((counter == ((prev_counter_vw_meb + 1) % 16)) && (prev_counter_vw_meb != -1)) {
// Klemmen_Status_01->ZAS_Kl_15
ignition_can = ((msg->data[2] >> 1) & 1U) != 0U;
// Klemmen_Status_01->ZAS_Kl_15, gated on gear out of P: the gateway broadcasts
// ZAS_Kl_15=1 with a live counter during network wake with the car off (unlock,
// app poll), flapping onroad while parked. No gear message is broadcast while
// parked-and-asleep, so gear-unseen means parked.
bool vw_meb_out_of_park = vw_meb_getriebe_out_of_p || vw_meb_gateway_out_of_p;
ignition_can = (((msg->data[2] >> 1) & 1U) != 0U) && vw_meb_out_of_park;
ignition_can_cnt = 0U;
}
prev_counter_vw_meb = counter;
+28 -15
View File
@@ -14,8 +14,10 @@ void set_intercept_relay(bool intercept, bool ignition_relay) {
harness.relay_driven = true;
}
// wait until we're not reading the analog voltages anymore
while (harness.sbu_adc_lock) {}
// The relay pins are separate from the SBU sense pins, so no need to wait for
// orientation sampling: it runs in thread context, sees relay_driven set, and
// discards its result. Spinning on sbu_adc_lock here would deadlock when called
// from interrupt context while thread-context sampling holds the lock.
if (harness.status == HARNESS_STATUS_NORMAL) {
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !ignition_relay);
@@ -31,20 +33,25 @@ void set_intercept_relay(bool intercept, bool ignition_relay) {
}
bool harness_check_ignition(void) {
bool ret = false;
// The SBU pins are in analog mode while orientation sampling is in flight
// (thread context), so a digital read would return 0. Return the last good
// value instead of spinning: this is called from interrupt context, where
// waiting for preempted thread-context sampling to finish would deadlock.
bool ret = harness.ignition_line;
// wait until we're not reading the analog voltages anymore
while (harness.sbu_adc_lock) {}
switch(harness.status){
case HARNESS_STATUS_NORMAL:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1);
break;
case HARNESS_STATUS_FLIPPED:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2);
break;
default:
break;
if (!harness.sbu_adc_lock) {
switch(harness.status){
case HARNESS_STATUS_NORMAL:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1);
break;
case HARNESS_STATUS_FLIPPED:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2);
break;
default:
ret = false;
break;
}
harness.ignition_line = ret;
}
return ret;
}
@@ -81,6 +88,12 @@ static uint8_t harness_detect_orientation(void) {
set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT);
set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT);
harness.sbu_adc_lock = false;
// This runs in thread context and can be preempted by an interrupt driving
// the relay mid-sample, which changes the SBU line voltages; discard
if (harness.relay_driven) {
ret = harness.status;
}
}
#endif
+8 -5
View File
@@ -4,12 +4,15 @@
#define HARNESS_STATUS_NORMAL 1U
#define HARNESS_STATUS_FLIPPED 2U
// orientation sampling runs in thread context and shares this state with
// interrupt handlers, so the fields are volatile
struct harness_t {
uint8_t status;
uint16_t sbu1_voltage_mV;
uint16_t sbu2_voltage_mV;
bool relay_driven;
bool sbu_adc_lock;
volatile uint8_t status;
volatile uint16_t sbu1_voltage_mV;
volatile uint16_t sbu2_voltage_mV;
volatile bool ignition_line;
volatile bool relay_driven;
volatile bool sbu_adc_lock;
};
extern struct harness_t harness;
+4
View File
@@ -74,6 +74,10 @@ void init_interrupts(bool check_rate_limit){
for(uint16_t i=0U; i<NUM_INTERRUPTS; i++){
interrupts[i].handler = unused_interrupt_handler;
// Default priority, lowered so the comms link can preempt everything else and
// re-arm its DMA (see IRQ_PRIORITY_COMMS). Shared state is guarded by
// ENTER_CRITICAL, which masks all interrupts regardless of priority.
NVIC_SetPriority((IRQn_Type)i, IRQ_PRIORITY_DEFAULT);
}
// Init interrupt timer for a 1s interval
@@ -1,5 +1,12 @@
#pragma once
// The SPI slave must re-arm its RX DMA at every protocol turnaround before the
// master clocks the next phase. Without preemption that re-arm waits behind any
// in-flight handler (CAN RX under bus load), the master clocks into an unarmed
// peripheral, and the transfer fails its checksum -> NACK retry storms.
#define IRQ_PRIORITY_COMMS 0U
#define IRQ_PRIORITY_DEFAULT 2U
typedef struct interrupt {
IRQn_Type irq_type;
void (*handler)(void);
+29 -2
View File
@@ -117,6 +117,22 @@ static void __attribute__ ((noinline)) enable_fpu(void) {
#define HEARTBEAT_IGNITION_CNT_OFF 2U
// called at 8Hz
static volatile bool tick_sample_pending = false;
// All runtime ADC sampling runs here, in thread context. ADC conversions
// busy-wait for tens to hundreds of microseconds, and all interrupts share one
// NVIC priority: sampling in the 8Hz tick interrupt delayed the SPI slave's
// RX DMA re-arm long enough for the host to clock into an unarmed peripheral
// (checksum failures -> NACK retry storms -> multi-second CAN blackouts).
static void tick_sample_poll(void) {
if (tick_sample_pending) {
tick_sample_pending = false;
harness_tick();
voltage_mV = current_board->read_voltage_mV();
current_mA = current_board->read_current_mA();
}
}
static void tick_handler(void) {
static uint32_t siren_countdown = 0; // siren plays while countdown > 0
static uint32_t controls_allowed_countdown = 0;
@@ -131,7 +147,7 @@ static void tick_handler(void) {
// tick drivers at 8Hz
fan_tick();
harness_tick();
tick_sample_pending = true; // ADC sampling deferred to thread context (see tick_sample_poll)
simple_watchdog_kick();
sound_tick();
@@ -176,7 +192,11 @@ static void tick_handler(void) {
const bool recent_heartbeat = heartbeat_counter == 0U;
// tick drivers at 1Hz
bool started = harness_check_ignition() || ignition_can;
// MEB/MQBevo have no harness ignition line; the SBU pin can sit asserted on a
// sleeping car (held true for days on an ID.4, pinning the device onroad against
// a silent bus). CAN ignition (0x3C0 Klemmen_Status_01) is the only valid source
// on this branch.
bool started = ignition_can;
bootkick_tick(started, recent_heartbeat);
// increase heartbeat counter and cap it at the uint32 limit
@@ -304,6 +324,10 @@ int main(void) {
current_board->set_can_mode(CAN_MODE_NORMAL);
harness_init();
// seed the ADC caches before interrupts are live
voltage_mV = current_board->read_voltage_mV();
current_mA = current_board->read_current_mA();
// panda has an FPU, let's use it!
enable_fpu();
@@ -347,6 +371,7 @@ int main(void) {
// LED should keep on blinking all the time
while (true) {
tick_sample_poll();
if (power_save_status == POWER_SAVE_STATUS_DISABLED) {
#ifdef DEBUG_FAULTS
if (fault_status == FAULT_STATUS_NONE) {
@@ -357,6 +382,7 @@ int main(void) {
delay(fade >> 4);
led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
tick_sample_poll();
}
for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) {
@@ -364,6 +390,7 @@ int main(void) {
delay(fade >> 4);
led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
tick_sample_poll();
}
#ifdef DEBUG_FAULTS
+7 -3
View File
@@ -9,10 +9,14 @@ static int get_health_pkt(void *dat) {
struct health_t * health = (struct health_t*)dat;
health->uptime_pkt = uptime_cnt;
health->voltage_pkt = current_board->read_voltage_mV();
health->current_pkt = current_board->read_current_mA();
// cached values sampled in thread context (see tick_sample_poll in main.c);
// reading the ADC here would busy-wait in interrupt context
health->voltage_pkt = voltage_mV;
health->current_pkt = current_mA;
health->ignition_line_pkt = (uint8_t)(harness_check_ignition());
// no ignition line on MEB/MQBevo (see started in main.c); pandad ORs line with
// CAN ignition, so reporting the floating SBU read would pin the device onroad
health->ignition_line_pkt = 0U;
health->ignition_can_pkt = ignition_can;
health->controls_allowed_pkt = controls_allowed;
+4
View File
@@ -13,6 +13,10 @@ extern uint8_t hw_type;
extern board *current_board;
extern uint32_t uptime_cnt;
// ADC results, sampled in thread context (see tick_sample_poll in main.c)
extern uint32_t voltage_mV;
extern uint32_t current_mA;
// heartbeat state
extern uint32_t heartbeat_counter;
extern bool heartbeat_lost;
+4
View File
@@ -5,6 +5,10 @@ uint8_t hw_type = 0;
board *current_board;
uint32_t uptime_cnt = 0;
// ADC results, sampled in thread context (see tick_sample_poll in main.c)
uint32_t voltage_mV = 0;
uint32_t current_mA = 0;
// heartbeat state
uint32_t heartbeat_counter = 0;
bool heartbeat_lost = false;
+6
View File
@@ -101,6 +101,12 @@ void llspi_init(void) {
register_set(&(SPI4->CR1), SPI_CR1_SPE, 0xFFFFU);
register_set(&(SPI4->CR2), 0, 0xFFFFU);
// preempt other handlers so the RX DMA is re-armed before the master clocks
// the next phase of a transfer
NVIC_SetPriority(DMA2_Stream2_IRQn, IRQ_PRIORITY_COMMS);
NVIC_SetPriority(DMA2_Stream3_IRQn, IRQ_PRIORITY_COMMS);
NVIC_SetPriority(SPI4_IRQn, IRQ_PRIORITY_COMMS);
NVIC_EnableIRQ(DMA2_Stream2_IRQn);
NVIC_EnableIRQ(DMA2_Stream3_IRQn);
NVIC_EnableIRQ(SPI4_IRQn);
+5 -2
View File
@@ -168,8 +168,11 @@ class Controls(IQControlsLayer):
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
(not standstill or self.CP.steerAtStandstill)
# long control may stay active through a gas override on platforms that opt in
override_longitudinal = any(e.overrideLongitudinal for e in self.sm['onroadEvents'])
long_through_override = self.CP_IQ.longActiveWithGasOverride and self.CP.openpilotLongitudinalControl
CC.longActive = CC.enabled and not getattr(CS, 'cruiseFaultLateralMode', False) and \
not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and \
(not override_longitudinal or long_through_override) and \
(self.CP.openpilotLongitudinalControl or not self.CP_IQ.pcmCruiseSpeed)
actuators = CC.actuators
@@ -184,7 +187,7 @@ class Controls(IQControlsLayer):
# accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, self.CP_IQ, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits,
long_plan.leadDistance, long_plan.hasLead))
long_plan.leadDistance, long_plan.hasLead, gas_override=override_longitudinal))
# Steering PID loop and lateral MPC
# Reset desired curvature to current to avoid violating the limits on engage
+7 -2
View File
@@ -53,7 +53,7 @@ class LongControl:
def reset(self):
self.pid.reset()
def update(self, active, CS, a_target, should_stop, accel_limits, lead_distance=0.0, has_lead=False):
def update(self, active, CS, a_target, should_stop, accel_limits, lead_distance=0.0, has_lead=False, gas_override=False):
"""Update longitudinal control. This updates the state machine and runs a PID loop"""
self.pid.neg_limit = accel_limits[0]
self.pid.pos_limit = accel_limits[1]
@@ -87,8 +87,13 @@ class LongControl:
else:
error = a_target - CS.aEgo
output_accel = self.pid.update(error, speed=CS.vEgo,
feedforward=a_target)
feedforward=a_target,
freeze_integrator=gas_override)
self.smooth.reset()
if gas_override:
# safety blocks braking while the gas is pressed, and a blocked tx drops the whole frame
output_accel = max(output_accel, 0.0)
self.last_output_accel = np.clip(output_accel, accel_limits[0], accel_limits[1])
return self.last_output_accel
+1 -1
View File
@@ -4,6 +4,6 @@
"dmonitoring_model_metadata.pkl": "31a86ab7a92dc0af088b15787a440dd3b210aa662e445a15145900e559a1b5c3",
"dmonitoring_model_tinygrad.pkl": "806c0ea75df6bf6dfeb81b832314c68e31df5865a52d0359e6eeb76d93ad2b52"
},
"signature": "e1eeb5ce45774a816c8da2394e6ee35ebf700b71dff345e0141dbab8ff592349"
"signature": "64740782bfd5b1720978aab24a2b1b37c99542f97db3c8637227c7412bb30bbf"
}
}
+4 -2
View File
@@ -29,10 +29,12 @@ def _file_sha256(path: Path) -> str:
@functools.lru_cache(maxsize=1)
def _tinygrad_digest() -> str:
h = hashlib.sha256()
# mirror the SConscript glob: no hidden files/dirs (also excludes .git, which differs per clone), no pycache
# mirror the SConscript glob: no hidden files/dirs (also excludes .git, which differs per clone), no pycache.
# 'weights' holds untracked/gitignored model weights (e.g. iqvd yolov8n .safetensors) present on dev machines
# but absent from a clean CI/device clone — hashing them makes the signature unreproducible (false STALE).
def _included(p: Path) -> bool:
rel = p.relative_to(TINYGRAD_DIR).parts
return p.is_file() and '__pycache__' not in rel and not any(part.startswith('.') for part in rel)
return p.is_file() and '__pycache__' not in rel and 'weights' not in rel and not any(part.startswith('.') for part in rel)
files = sorted(p for p in TINYGRAD_DIR.rglob('*') if _included(p))
for p in files:
h.update(str(p.relative_to(BASEDIR)).encode())
+2 -1
View File
@@ -26,7 +26,8 @@ class ExcessiveActuationCheck:
# CS.aEgo can be noisy to bumps in the road, transitioning from standstill, losing traction, etc.
# longitudinal
accel_calibrated = calibrated_pose.acceleration.x
excessive_long_actuation = sm['carControl'].longActive and (accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2)
excessive_long_actuation = sm['carControl'].longActive and ((not CS.gasPressed and accel_calibrated > ACCEL_MAX * 2) or
accel_calibrated < ACCEL_MIN * 2)
# lateral
yaw_rate = calibrated_pose.angular_velocity.yaw
+12
View File
@@ -41,6 +41,10 @@ DESCRIPTIONS = {
"DashcamEnabled": tr_noop("Record and upload driving data and video. Disabling this stops all recording! No logs, no video, no audio."),
'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
"IsMetric": tr_noop("Display speed in km/h instead of mph."),
"IQAutoUnits": tr_noop(
"Set the units from the device location. Speeds switch to km/h everywhere except the United States, " +
"the United Kingdom and Liberia, and are re-checked when you cross a border."
),
"RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in Konn3kt."),
"LongitudinalControlMode": tr_noop(
"Choose longitudinal behavior: IQ.Pilot (IQ longitudinal + end-to-end), "
@@ -95,6 +99,12 @@ class TogglesLayout(Widget):
"metric.png",
False,
),
"IQAutoUnits": (
lambda: tr("Set Units From Location"),
DESCRIPTIONS["IQAutoUnits"],
"metric.png",
False,
),
}
self._long_personality_setting = multiple_button_item(
@@ -296,6 +306,8 @@ class TogglesLayout(Widget):
def _toggle_callback(self, state: bool, param: str):
self._params.put_bool(param, state)
if param == "IQAutoUnits" and state:
self._params.remove("IQAutoUnitsRegion")
if self._toggle_defs[param][3]:
self._params.put_bool("OnroadCycleRequested", True)
@@ -18,13 +18,15 @@ class TogglesLayoutMici(NavScroller):
disengage = BigParamControl("disengage on accelerator", "DisengageOnAccelerator")
ldw = BigParamControl("lane departure warnings", "IsLdwEnabled")
is_metric = BigParamControl("use metric units", "IsMetric")
auto_units = BigParamControl("set units from location", "IQAutoUnits", toggle_callback=self._auto_units_callback)
self._scroller.add_widgets([disengage, ldw, is_metric])
self._scroller.add_widgets([disengage, ldw, is_metric, auto_units])
self._refresh_toggles = (
("DisengageOnAccelerator", disengage),
("IsLdwEnabled", ldw),
("IsMetric", is_metric),
("IQAutoUnits", auto_units),
)
if ui_state.params.get_bool("ShowDebugInfo"):
@@ -35,6 +37,10 @@ class TogglesLayoutMici(NavScroller):
super().show_event()
self._update_toggles()
def _auto_units_callback(self, state: bool):
if state:
ui_state.params.remove("IQAutoUnitsRegion")
def _update_toggles(self):
ui_state.update_params()
for key, item in self._refresh_toggles:
+14 -14
View File
@@ -4,7 +4,7 @@ import pyray as rl
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware import TICI
from openpilot.system.hardware import EGL_DMA_BUF_SUPPORTED
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage
from openpilot.system.ui.widgets import Widget
@@ -38,7 +38,7 @@ void main() {
"""
# Choose fragment shader based on platform capabilities
if TICI:
if EGL_DMA_BUF_SUPPORTED:
FRAME_FRAGMENT_SHADER = """
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : enable
@@ -121,7 +121,7 @@ class CameraView(Widget):
self._texture_needs_update = True
self.last_connection_attempt: float = 0.0
self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER)
self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not TICI else -1
self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not EGL_DMA_BUF_SUPPORTED else -1
self._engaged_loc = rl.get_shader_location(self.shader, "engaged")
self._engaged_val = rl.ffi.new("int[1]", [1])
self._enhance_driver_loc = rl.get_shader_location(self.shader, "enhance_driver")
@@ -137,8 +137,8 @@ class CameraView(Widget):
self._placeholder_color: rl.Color | None = None
# Initialize EGL for zero-copy rendering on TICI
if TICI:
# Initialize EGL for zero-copy rendering on comma 3/3X.
if EGL_DMA_BUF_SUPPORTED:
if not init_egl():
raise RuntimeError("Failed to initialize EGL")
@@ -189,7 +189,7 @@ class CameraView(Widget):
self._clear_textures()
# Clean up EGL texture
if TICI and self.egl_texture:
if EGL_DMA_BUF_SUPPORTED and self.egl_texture:
rl.unload_texture(self.egl_texture)
self.egl_texture = None
@@ -264,7 +264,7 @@ class CameraView(Widget):
dst_rect = rl.Rectangle(x_offset, y_offset, scale_x, scale_y)
# Render with appropriate method
if TICI:
if EGL_DMA_BUF_SUPPORTED:
self._render_egl(src_rect, dst_rect)
else:
self._render_textures(src_rect, dst_rect)
@@ -387,12 +387,12 @@ class CameraView(Widget):
self._initialize_textures()
def _initialize_textures(self):
self._clear_textures()
if not TICI:
self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride),
int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE))
self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2),
int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA))
self._clear_textures()
if not EGL_DMA_BUF_SUPPORTED:
self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride),
int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE))
self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2),
int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA))
def _clear_textures(self):
if self.texture_y and self.texture_y.id:
@@ -404,7 +404,7 @@ class CameraView(Widget):
self.texture_uv = None
# Clean up EGL resources
if TICI:
if EGL_DMA_BUF_SUPPORTED:
for data in self.egl_images.values():
destroy_egl_image(data)
self.egl_images = {}
@@ -9,6 +9,7 @@ from openpilot.selfdrive.ui.onroad.alert_renderer import AlertRenderer
from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer as BaseDriverStateRenderer, BTN_SIZE
from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer as BaseHudRenderer
from openpilot.selfdrive.ui.onroad.model_renderer import ModelRenderer
from openpilot.selfdrive.ui.onroad.environment_renderer import EnvironmentRenderer
from openpilot.selfdrive.ui.onroad.cameraview import CameraView
from openpilot.system.ui.lib.application import gui_app
from openpilot.common.issue_debug import log_issue_limited
@@ -55,6 +56,7 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewIQ):
self._split_nav_available = False
self.model_renderer = ModelRenderer()
self.environment_renderer = EnvironmentRenderer()
self.alert_renderer = AlertRenderer()
self._hud_renderer = IQHudRenderer()
self.driver_state_renderer = DriverStateRendererIQ()
@@ -114,6 +116,7 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewIQ):
# Draw all UI overlays
self.model_renderer.render(camera_rect)
self.environment_renderer.render(camera_rect)
AugmentedRoadViewIQ.update_fade_out_bottom_overlay(self, camera_rect)
self._hud_renderer.render(camera_rect)
@@ -280,6 +283,7 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewIQ):
])
self.model_renderer.set_transform(video_transform @ calib_transform)
self.model_renderer.set_frame_transform(video_transform, is_wide_camera)
self.environment_renderer.set_transform(video_transform @ calib_transform)
return self._cached_matrix
+9 -9
View File
@@ -4,7 +4,7 @@ import pyray as rl
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware import TICI
from openpilot.system.hardware import EGL_DMA_BUF_SUPPORTED
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage
from openpilot.system.ui.widgets import Widget
@@ -38,7 +38,7 @@ void main() {
"""
# Choose fragment shader based on platform capabilities
if TICI:
if EGL_DMA_BUF_SUPPORTED:
FRAME_FRAGMENT_SHADER = """
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : enable
@@ -82,7 +82,7 @@ class CameraView(Widget):
self._texture_needs_update = True
self.last_connection_attempt: float = 0.0
self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER)
self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not TICI else -1
self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not EGL_DMA_BUF_SUPPORTED else -1
self.frame: VisionBuf | None = None
self.texture_y: rl.Texture | None = None
@@ -94,8 +94,8 @@ class CameraView(Widget):
self._placeholder_color: rl.Color | None = None
# Initialize EGL for zero-copy rendering on TICI
if TICI:
# Initialize EGL for zero-copy rendering on comma 3/3X.
if EGL_DMA_BUF_SUPPORTED:
if not init_egl():
raise RuntimeError("Failed to initialize EGL")
@@ -146,7 +146,7 @@ class CameraView(Widget):
self._clear_textures()
# Clean up EGL texture
if TICI and self.egl_texture:
if EGL_DMA_BUF_SUPPORTED and self.egl_texture:
rl.unload_texture(self.egl_texture)
self.egl_texture = None
@@ -220,7 +220,7 @@ class CameraView(Widget):
dst_rect = rl.Rectangle(x_offset, y_offset, scale_x, scale_y)
# Render with appropriate method
if TICI:
if EGL_DMA_BUF_SUPPORTED:
self._render_egl(src_rect, dst_rect)
else:
self._render_textures(src_rect, dst_rect)
@@ -337,7 +337,7 @@ class CameraView(Widget):
def _initialize_textures(self):
self._clear_textures()
if not TICI:
if not EGL_DMA_BUF_SUPPORTED:
self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride),
int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE))
self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2),
@@ -353,7 +353,7 @@ class CameraView(Widget):
self.texture_uv = None
# Clean up EGL resources
if TICI:
if EGL_DMA_BUF_SUPPORTED:
for data in self.egl_images.values():
destroy_egl_image(data)
self.egl_images = {}
+141
View File
@@ -0,0 +1,141 @@
import numpy as np
import pyray as rl
from cereal import custom
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget
MODE_OFF = 0
MODE_OVERLAY = 1
MODE_REPLACE = 2
_ENV_LABEL = custom.IQEnvironment.Object.Label
_OBJECT_COLORS = {
_ENV_LABEL.car: (40, 210, 200),
_ENV_LABEL.truck: (40, 210, 200),
_ENV_LABEL.bus: (40, 210, 200),
_ENV_LABEL.motorcycle: (90, 220, 255),
_ENV_LABEL.bicycle: (90, 220, 255),
_ENV_LABEL.person: (255, 210, 90),
_ENV_LABEL.stopSign: (255, 60, 45),
_ENV_LABEL.trafficLight: (255, 190, 0),
}
_BOX_EDGES = (
(0, 1), (1, 3), (3, 2), (2, 0),
(4, 5), (5, 7), (7, 6), (6, 4),
(0, 4), (1, 5), (2, 6), (3, 7),
)
GRID_HALF_WIDTH = 12.0
GRID_MAX_DISTANCE = 90.0
GRID_STEP = 6.0
class EnvironmentRenderer(Widget):
def __init__(self):
Widget.__init__(self)
self._car_space_transform = np.zeros((3, 3), dtype=np.float32)
self._mode = MODE_OFF
self._counter = 0
def set_transform(self, transform: np.ndarray):
self._car_space_transform = transform.astype(np.float32)
def _project(self, pt: np.ndarray):
p = self._car_space_transform @ pt
if abs(p[2]) < 1e-6:
return None
return p[0] / p[2], p[1] / p[2]
def _in_rect(self, x: float, y: float) -> bool:
r = self._rect
return r.x - 400 <= x <= r.x + r.width + 400 and r.y - 400 <= y <= r.y + r.height + 400
def _render(self, rect: rl.Rectangle):
sm = ui_state.sm
if self._counter % 30 == 0:
self._mode = int(ui_state.params.get("EnvironmentView", return_default=True) or 0) if ui_state.active_bundle else 0
self._counter += 1
if self._mode == MODE_OFF:
return
if sm.recv_frame["liveCalibration"] < ui_state.started_frame:
return
if self._mode == MODE_REPLACE:
self._draw_backdrop(rect)
self._draw_ground_grid()
if sm.valid["modelV2"]:
self._draw_model_scene(sm["modelV2"])
if sm.alive["iqEnvironment"] and sm.valid["iqEnvironment"]:
self._draw_objects(sm["iqEnvironment"])
def _draw_backdrop(self, rect: rl.Rectangle):
rl.draw_rectangle_gradient_v(int(rect.x), int(rect.y), int(rect.width), int(rect.height),
rl.Color(14, 17, 22, 255), rl.Color(6, 8, 11, 255))
def _draw_ground_grid(self):
col = rl.Color(60, 70, 82, 90)
dist = GRID_STEP
while dist <= GRID_MAX_DISTANCE:
a = self._project(np.array([dist, -GRID_HALF_WIDTH, 0.0]))
b = self._project(np.array([dist, GRID_HALF_WIDTH, 0.0]))
if a and b and self._in_rect(*a) and self._in_rect(*b):
rl.draw_line_ex(rl.Vector2(*a), rl.Vector2(*b), 1.5, col)
dist += GRID_STEP
for off in np.arange(-GRID_HALF_WIDTH, GRID_HALF_WIDTH + 0.1, 3.0):
a = self._project(np.array([GRID_STEP, float(off), 0.0]))
b = self._project(np.array([GRID_MAX_DISTANCE, float(off), 0.0]))
if a and b and self._in_rect(*a) and self._in_rect(*b):
rl.draw_line_ex(rl.Vector2(*a), rl.Vector2(*b), 1.5, col)
def _draw_polyline(self, xs, ys, zs, color, thick):
pts = []
for x, y, z in zip(xs, ys, zs, strict=False):
if x < 0:
continue
s = self._project(np.array([x, y, z], dtype=np.float32))
if s and self._in_rect(*s):
pts.append(rl.Vector2(*s))
for i in range(len(pts) - 1):
rl.draw_line_ex(pts[i], pts[i + 1], thick, color)
def _draw_model_scene(self, model):
for i, lane in enumerate(model.laneLines):
a = int(np.clip(model.laneLineProbs[i], 0.0, 0.9) * 255)
self._draw_polyline(lane.x, lane.y, lane.z, rl.Color(235, 235, 235, a), 3.0)
for edge in model.roadEdges:
self._draw_polyline(edge.x, edge.y, edge.z, rl.Color(230, 70, 70, 180), 3.0)
pos = model.position
self._draw_polyline(pos.x, pos.y, pos.z, rl.Color(40, 210, 200, 220), 6.0)
def _draw_objects(self, env):
for obj in env.objects:
self._draw_box(obj)
def _draw_box(self, obj):
hx, hy = obj.length / 2.0, obj.width / 2.0
base = np.array([
[obj.x - hx, obj.y - hy, obj.z], [obj.x - hx, obj.y + hy, obj.z],
[obj.x + hx, obj.y - hy, obj.z], [obj.x + hx, obj.y + hy, obj.z],
[obj.x - hx, obj.y - hy, obj.z + obj.height], [obj.x - hx, obj.y + hy, obj.z + obj.height],
[obj.x + hx, obj.y - hy, obj.z + obj.height], [obj.x + hx, obj.y + hy, obj.z + obj.height],
], dtype=np.float32)
screen = []
for corner in base:
s = self._project(corner)
if s is None or not self._in_rect(*s):
return
screen.append(s)
r, g, b = _OBJECT_COLORS.get(obj.label, (40, 210, 200))
a = int(np.clip(obj.prob, 0.3, 1.0) * 210)
floor = [rl.Vector2(*screen[i]) for i in (0, 1, 3, 2)]
rl.draw_triangle(floor[0], floor[1], floor[2], rl.Color(r, g, b, a // 5))
rl.draw_triangle(floor[0], floor[2], floor[3], rl.Color(r, g, b, a // 5))
for i, j in _BOX_EDGES:
rl.draw_line_ex(rl.Vector2(*screen[i]), rl.Vector2(*screen[j]), 2.0, rl.Color(r, g, b, a))
+11
View File
@@ -224,6 +224,17 @@ class Soundd(AlertSoundFilter):
sm = messaging.SubMaster(['selfdriveState', 'soundPressure'])
threading.Thread(target=self.webrtc_audio_thread, daemon=True).start()
while True:
try:
self._stream_loop(sd, sm)
except Exception:
# Some A1s wedge the audio DSP (ALSA EINVAL / ADSP_EFAILED until reboot). Dying here
# crash-loops the process and selfdrived raises a takeover alert mid-drive over alert
# sounds - stay alive and keep retrying instead; recovers if the DSP comes back.
cloudlog.exception("soundd: audio stream unavailable, retrying")
time.sleep(10)
def _stream_loop(self, sd, sm):
with self.get_stream(sd) as stream:
rk = Ratekeeper(20)
+4 -1
View File
@@ -8,6 +8,7 @@ from cereal import messaging, car, log, custom
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.iqpilot.common.auto_units import AutoUnits
from openpilot.selfdrive.ui.lib.prime_state import PrimeState
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.hardware import HARDWARE, PC
@@ -42,12 +43,13 @@ class IQUIState:
self.update_params()
self.auto_units = AutoUnits(self.params)
self.onroad_brightness_timer: int = 0
self.custom_interactive_timeout: int = self.params.get("InteractivityTimeout", return_default=True)
self.reset_onroad_sleep_timer()
def update(self) -> None:
pass
self.auto_units.update()
def onroad_brightness_handle_alerts(self, started: bool, alert):
# while an alert is on screen the dim countdown is frozen and re-armed; otherwise it ticks down
@@ -254,6 +256,7 @@ class UIState(IQUIState):
"radarState",
"liveTracks",
"iqVehicleTracks",
"iqEnvironment",
"deviceState",
"pandaStates",
"carParams",
+2 -2
View File
@@ -596,7 +596,7 @@ void SpectraCamera::config_bps(int idx, int request_id) {
tmp.header = CAM_ICP_CMD_GENERIC_BLOB_CLK;
tmp.header |= (sizeof(cam_icp_clk_bw_request)) << 8;
tmp.clk.budget_ns = 0x1fca058;
tmp.clk.frame_cycles = 2329024; // comes from the striping lib
tmp.clk.frame_cycles = 20000000; // force max BPS clock (600 MHz)
tmp.clk.rt_flag = 0x0;
tmp.clk.uncompressed_bw = 0x38512180;
tmp.clk.compressed_bw = 0x38512180;
@@ -843,7 +843,7 @@ void SpectraCamera::config_bps_downscale(int idx, int request_id) {
tmp.header = CAM_ICP_CMD_GENERIC_BLOB_CLK;
tmp.header |= (sizeof(cam_icp_clk_bw_request)) << 8;
tmp.clk.budget_ns = 0x1fca058;
tmp.clk.frame_cycles = sensor->frame_width * sensor->frame_height; // matches striping lib pixelCount
tmp.clk.frame_cycles = 20000000; // force max BPS clock (600 MHz)
tmp.clk.rt_flag = 0x0;
tmp.clk.uncompressed_bw = 0x38512180;
tmp.clk.compressed_bw = 0x38512180;
+6
View File
@@ -14,3 +14,9 @@ if TICI:
HARDWARE = cast(HardwareBase, Tici())
else:
HARDWARE = cast(HardwareBase, Pc())
# Only comma 3/3X expose the DMA-BUF EGL extensions used by the zero-copy
# camera renderer and the direct EGL frame-pacing calls. /TICI is also present
# on comma 4, so it identifies the AGNOS hardware family rather than this GPU
# capability.
EGL_DMA_BUF_SUPPORTED = TICI and HARDWARE.get_device_type() in ("tici", "tizi")
+12
View File
@@ -1,15 +1,25 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "cereal/gen/cpp/log.capnp.h"
// no-op base hw class
class HardwareNone {
public:
struct UfsHealth {
uint8_t pre_eol_info;
uint8_t life_time_estimate_a;
uint8_t life_time_estimate_b;
std::vector<uint8_t> vendor_health_report;
};
static std::string get_name() { return ""; }
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; }
static int get_voltage() { return 0; }
@@ -21,6 +31,8 @@ public:
return {};
}
static std::optional<UfsHealth> get_ufs_health() { return std::nullopt; }
static void set_ir_power(int percentage) {}
static bool PC() { return false; }
+5 -1
View File
@@ -6,7 +6,11 @@ class FanController:
def __init__(self) -> None:
self.last_ignition = False
def update(self, cur_temp: float, ignition: bool) -> int:
def update(self, cur_temp: float, ignition: bool, max_cool: bool = False) -> int:
if max_cool:
self.last_ignition = ignition
return 100
if cur_temp < 70.0:
fan_pwr_out = 0
elif cur_temp > 85.0:
+4 -2
View File
@@ -432,8 +432,6 @@ def hardware_thread(end_event, hw_queue) -> None:
all_comp_temp = all_temp_filter.update(max(temp_sources))
msg.deviceState.maxTempC = all_comp_temp
msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"])
is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5))
if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP:
# if device is offroad and already hot without the extra onroad load,
@@ -447,6 +445,10 @@ def hardware_thread(end_event, hw_queue) -> None:
elif current_band.max_temp is not None and all_comp_temp > current_band.max_temp:
thermal_status = list(THERMAL_BANDS.keys())[band_idx + 1]
# the car is running but temperature is blocking the start, so cool as fast as we can
max_cool = (started_ts is None) and onroad_conditions["ignition"] and thermal_status >= ThermalStatus.red
msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"], max_cool)
# **** starting logic ****
startup_conditions["up_to_date"] = True
@@ -41,6 +41,13 @@ class TestFanController:
self.wind_up(controller, True)
assert controller.update(100, True) == 100
@pytest.mark.parametrize("controller_class", ALL_CONTROLLERS)
def test_max_cool(self, mocker, controller_class):
controller = patched_controller(mocker, controller_class)
self.wind_down(controller)
assert controller.update(80, True, True) == 100
assert controller.update(80, False, True) == 100
@pytest.mark.parametrize("controller_class", ALL_CONTROLLERS)
def test_windup_speed(self, mocker, controller_class):
controller = patched_controller(mocker, controller_class)
+10 -10
View File
@@ -67,28 +67,28 @@
},
{
"name": "boot",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-024e5c1f1785b2190520e57c7f88e41436fc22cae32a52d1d68dab65e1370500.img.xz",
"hash": "024e5c1f1785b2190520e57c7f88e41436fc22cae32a52d1d68dab65e1370500",
"hash_raw": "024e5c1f1785b2190520e57c7f88e41436fc22cae32a52d1d68dab65e1370500",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89.img.xz",
"hash": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"hash_raw": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"size": 18216960,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "737126dfeaa85870b4615e6310d4686bd049eac8176fc54608b88ecde590f21a"
"ondevice_hash": "fdcb135e8412a896c4a882738a7022f6b7779939d8716f24b9a566b0b3ff3757"
},
{
"name": "system",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b.img.xz",
"hash": "e6f7a840437bae56ca49b1f1709f6ba9645ed83484cd02494c05327c6910761b",
"hash_raw": "640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img.xz",
"hash": "62a08bdf79f6dc0075eba8b93b55cbac6eeee0ecd2b6e8ab05d51ef61a9a880a",
"hash_raw": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"size": 6291456000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "f7ebe73504e5c2b5f9d3b1f9db5b120badfa313dc78b4d50e6cd26463697aa2c",
"ondevice_hash": "9ed5bc147a6d4c2e0a89562f31096a7802485ee8e740defeab7674abbf6d4baa",
"alt": {
"hash": "640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b.img",
"hash": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img",
"size": 6291456000
}
}
+1 -1
View File
@@ -1 +1 @@
bEKwMhJJ7QIhYyXKRLIVyH5xr5c9sjcUf8XS4+/Q9PN6BI23V8OXOkuuX0zREs1Hz4qPPQ9+kKjNoaKydrSwDg==
LxtrHkl5DFbSWnd1DZCkinmXiKFUVUausf0oYpa97dyAp3QGpWx7wrEAUZuJbrERQtdIV72ZU2eKOaQwGMBQBg==
+10 -10
View File
@@ -56,28 +56,28 @@
},
{
"name": "boot",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-024e5c1f1785b2190520e57c7f88e41436fc22cae32a52d1d68dab65e1370500.img.xz",
"hash": "024e5c1f1785b2190520e57c7f88e41436fc22cae32a52d1d68dab65e1370500",
"hash_raw": "024e5c1f1785b2190520e57c7f88e41436fc22cae32a52d1d68dab65e1370500",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89.img.xz",
"hash": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"hash_raw": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"size": 18216960,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "737126dfeaa85870b4615e6310d4686bd049eac8176fc54608b88ecde590f21a"
"ondevice_hash": "fdcb135e8412a896c4a882738a7022f6b7779939d8716f24b9a566b0b3ff3757"
},
{
"name": "system",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b.img.xz",
"hash": "e6f7a840437bae56ca49b1f1709f6ba9645ed83484cd02494c05327c6910761b",
"hash_raw": "640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img.xz",
"hash": "62a08bdf79f6dc0075eba8b93b55cbac6eeee0ecd2b6e8ab05d51ef61a9a880a",
"hash_raw": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"size": 6291456000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "f7ebe73504e5c2b5f9d3b1f9db5b120badfa313dc78b4d50e6cd26463697aa2c",
"ondevice_hash": "9ed5bc147a6d4c2e0a89562f31096a7802485ee8e740defeab7674abbf6d4baa",
"alt": {
"hash": "640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-640f36a79dc2a3237d9dd253e55cee3ea9cb55988468482d489f38f6b893fb3b.img",
"hash": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img",
"size": 6291456000
}
}
@@ -1 +1 @@
A9eBPajhAHUyx3wzPP/pZn2VRIMS2UNtSoeuIZTCAFkbV5HhAkD8HlLSemijmRQtMR62adjV+HUwZ0eRaZpdCg==
mKCTfzNHr1d0BYPdmXQRPGpnVuLaDLGZGLbq+86ZJQaGIK1ekcKeYeycoX8BTMNFr+89Tnd77M/CvmjM8yteCg==
+47 -1
View File
@@ -1,17 +1,63 @@
#pragma once
#include <cstdlib>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <fcntl.h>
#include <fstream>
#include <map>
#include <string>
#include <algorithm> // for std::clamp
#include <sys/ioctl.h>
#include <unistd.h>
#include "common/util.h"
#include "system/hardware/base.h"
class HardwareTici : public HardwareNone {
public:
static std::optional<UfsHealth> get_ufs_health() {
constexpr unsigned long UFS_IOCTL_QUERY = 0x5388;
constexpr uint32_t UPIU_QUERY_OPCODE_READ_DESC = 0x1;
constexpr uint8_t QUERY_DESC_IDN_HEALTH = 0x9;
constexpr uint16_t QUERY_DESC_HEALTH_SIZE = 0x25;
struct UfsQuery {
uint32_t opcode;
uint8_t idn;
uint8_t reserved;
uint16_t buf_size;
std::array<uint8_t, QUERY_DESC_HEALTH_SIZE> buffer;
};
static_assert(offsetof(UfsQuery, buffer) == 8);
UfsQuery query = {};
query.opcode = UPIU_QUERY_OPCODE_READ_DESC;
query.idn = QUERY_DESC_IDN_HEALTH;
query.buf_size = QUERY_DESC_HEALTH_SIZE;
int fd = open("/dev/sda", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return std::nullopt;
}
int ret = ioctl(fd, UFS_IOCTL_QUERY, &query);
close(fd);
if (ret != 0 || query.buf_size < 5 || query.buf_size > query.buffer.size() ||
query.buffer[0] != query.buf_size || query.buffer[1] != QUERY_DESC_IDN_HEALTH) {
return std::nullopt;
}
return UfsHealth{
query.buffer[2],
query.buffer[3],
query.buffer[4],
std::vector<uint8_t>(query.buffer.begin() + 5, query.buffer.begin() + query.buf_size),
};
}
static std::string get_name() {
std::string model = util::read_file("/sys/firmware/devicetree/base/model");
return util::strip(model.substr(std::string("comma ").size()));
+7 -2
View File
@@ -85,8 +85,13 @@ def affine_irq(val, action):
@lru_cache
def get_device_type():
# lru_cache and cache can cause memory leaks when used in classes
with open("/sys/firmware/devicetree/base/model") as f:
model = f.read().strip('\x00')
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):
+6 -5
View File
@@ -78,9 +78,10 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) {
uint32_t idx = -1;
bool exit = false;
// POLLIN is capture, POLLOUT is frame
// POLLIN is capture, POLLOUT is frame. Qualcomm's reference client also
// requests the corresponding normal-data bits.
struct pollfd pfd;
pfd.events = POLLIN | POLLOUT;
pfd.events = POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
pfd.fd = e->fd;
// save the header
@@ -105,7 +106,7 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) {
}
int frame_id = -1;
if (pfd.revents & POLLIN) {
if (pfd.revents & (POLLIN | POLLRDNORM)) {
unsigned int bytesused, flags, index;
struct timeval timestamp;
dequeue_buffer(e->fd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, &index, &bytesused, &flags, &timestamp);
@@ -136,7 +137,7 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) {
queue_buffer(e->fd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, index, &e->buf_out[index]);
}
if (pfd.revents & POLLOUT) {
if (pfd.revents & (POLLOUT | POLLWRNORM)) {
unsigned int index;
dequeue_buffer(e->fd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, &index);
e->free_buf_in.push(index);
@@ -244,7 +245,7 @@ V4LEncoder::V4LEncoder(const EncoderInfo &encoder_info, int in_width, int in_hei
{ .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, .value = V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN},
{ .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, .value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC},
{ .id = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL, .value = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, .value = 0},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, .value = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, .value = 0},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, .value = 0},
{ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, .value = 0},
+8
View File
@@ -46,6 +46,14 @@ kj::Array<capnp::word> logger_build_init_data() {
init.setKernelVersion(util::read_file("/proc/version"));
init.setOsVersion(util::read_file("/VERSION"));
if (auto health = Hardware::get_ufs_health()) {
auto ufs_health = init.initUfsHealth();
ufs_health.setPreEolInfo(health->pre_eol_info);
ufs_health.setLifeTimeEstimateA(health->life_time_estimate_a);
ufs_health.setLifeTimeEstimateB(health->life_time_estimate_b);
ufs_health.setVendorHealthReport(capnp::Data::Reader(health->vendor_health_report.data(), health->vendor_health_report.size()));
}
// log params
Params params(util::getenv("PARAMS_COPY_PATH", ""));
std::map<std::string, std::string> params_map = params.readAll();
+9
View File
@@ -170,6 +170,15 @@ class TestLoggerd:
assert initData.dirty != bool(os.environ["CLEAN"])
assert initData.version == get_version()
if TICI:
assert initData._has("ufsHealth")
assert initData.ufsHealth.preEolInfo in (1, 2, 3)
assert 1 <= initData.ufsHealth.lifeTimeEstimateA <= 11
assert 1 <= initData.ufsHealth.lifeTimeEstimateB <= 11
assert len(initData.ufsHealth.vendorHealthReport) == 32
else:
assert not initData._has("ufsHealth")
if os.path.isfile("/proc/cmdline"):
with open("/proc/cmdline") as f:
assert list(initData.kernelArgs) == f.read().strip().split(" ")
+10 -1
View File
@@ -98,7 +98,9 @@ def constructiond_onroad(started: bool, params: Params, CP: car.CarParams) -> bo
return started and params.get_bool("ConstructionZoneAssist")
def iqvd_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
return started and params.get_bool("VisionVehicleTracks")
# held for 1.0d: iqvd runs a detector per frame and the added load is not
# something 1.0c needs to carry. re-enable by restoring the param check.
return False
def only_offroad(started: bool, params: Params, CP: car.CarParams) -> bool:
return not started
@@ -109,6 +111,12 @@ def livestream(started: bool, params: Params, CP: car.CarParams) -> bool:
# down cleanly when the session ends — no subprocess management inside hephaestusd.
return params.get_bool("IsLiveStreaming")
def canlive(started: bool, params: Params, CP: car.CarParams) -> bool:
# Remote live CAN debugging via konn3kt. hephaestusd sets CanLiveStreaming when a viewer
# connects (startCanLive) and clears it when the last one leaves (stopCanLive), so canlived
# runs only during an active debug session — no idle connection or battery cost otherwise.
return params.get_bool("CanLiveStreaming")
def is_tinygrad_model(started, params, CP: car.CarParams) -> bool:
"""Check if the active model runner is tinygrad."""
return bool(get_active_model_runner(params, not started) == custom.IQModelManager.Runner.tinygrad)
@@ -179,6 +187,7 @@ procs = [
# debug procs
NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar),
PythonProcess("webrtcd", "system.webrtc.webrtcd", or_(iscar, livestream)),
PythonProcess("canlived", "iqpilot.konn3kt.canlive.canlived", canlive),
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
]
+13 -4
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import numpy as np
import os
import time
from functools import cache
import threading
@@ -136,10 +137,18 @@ class Mic:
self.last_device = f"{device}: {sd.query_devices(device)['name']}" if isinstance(device, int) else str(device)
cloudlog.info(f"micd selecting input device {self.last_device}")
with self.get_stream(sd) as stream:
cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}")
while True:
self.update()
while True:
try:
with self.get_stream(sd) as stream:
cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}")
while True:
self.update()
except Exception:
# Some A1s wedge the audio DSP (ALSA EINVAL / ADSP_EFAILED until reboot). Dying here
# crash-loops the process and selfdrived raises a takeover alert mid-drive over a
# microphone - stay alive and keep retrying instead; recovers if the DSP comes back.
cloudlog.exception("micd: audio stream unavailable, retrying")
time.sleep(10)
def main():
+2 -2
View File
@@ -12,7 +12,7 @@ from openpilot.common.gps import get_gps_location_service
def set_time(new_time):
diff = datetime.datetime.now() - new_time
diff = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - new_time
if abs(diff) < datetime.timedelta(seconds=10):
cloudlog.debug(f"Time diff too small: {diff}")
return
@@ -47,7 +47,7 @@ def main() -> NoReturn:
pm.send('clocks', msg)
gps = sm[gps_location_service]
gps_time = datetime.datetime.fromtimestamp(gps.unixTimestampMillis / 1000.)
gps_time = datetime.datetime.fromtimestamp(gps.unixTimestampMillis / 1000., datetime.UTC).replace(tzinfo=None)
if not sm.updated[gps_location_service] or (time.monotonic() - sm.logMonoTime[gps_location_service] / 1e9) > 2.0:
continue
if not gps.hasFix:
+6 -6
View File
@@ -22,7 +22,7 @@ from pathlib import Path
from typing import NamedTuple
from importlib.resources import as_file, files
from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware import EGL_DMA_BUF_SUPPORTED, HARDWARE, PC
from openpilot.system.ui.lib.multilang import multilang
from openpilot.common.realtime import Ratekeeper
@@ -326,7 +326,7 @@ class GuiApplication(IQAppHooks):
rl.glfw_swap_interval(0)
except Exception:
pass
if not PC and rl.is_window_ready() and not OFFSCREEN:
if EGL_DMA_BUF_SUPPORTED and rl.is_window_ready() and not OFFSCREEN:
try:
from openpilot.system.ui.lib.egl import set_swap_interval
set_swap_interval(0)
@@ -349,7 +349,7 @@ class GuiApplication(IQAppHooks):
glfw_vsync = False
egl_vsync = False
if not PC:
if EGL_DMA_BUF_SUPPORTED:
try:
from openpilot.system.ui.lib.egl import set_swap_interval
egl_vsync = set_swap_interval(interval)
@@ -367,7 +367,7 @@ class GuiApplication(IQAppHooks):
rl.rl_draw_render_batch_active()
def _apply_display_sync_before_swap(self) -> None:
if PC or OFFSCREEN or not DISPLAY_SYNC_BEFORE_SWAP or self._display_sync_available is False:
if not EGL_DMA_BUF_SUPPORTED or OFFSCREEN or not DISPLAY_SYNC_BEFORE_SWAP or self._display_sync_available is False:
return
try:
self._flush_raylib_batch()
@@ -395,7 +395,7 @@ class GuiApplication(IQAppHooks):
# intermittent screen tearing when scrolling (the content moves, so a mid-scanout swap is
# visible). eglSwapInterval is a trivial call, so re-assert it every frame to keep FIFO vsync
# pinned instead of relying on the slow (VSYNC_REAPPLY_INTERVAL) full re-apply below.
if self._paced_by_vsync and self._vsync_interval > 0 and not PC and rl.is_window_ready():
if self._paced_by_vsync and self._vsync_interval > 0 and EGL_DMA_BUF_SUPPORTED and rl.is_window_ready():
try:
from openpilot.system.ui.lib.egl import set_swap_interval
set_swap_interval(self._vsync_interval)
@@ -850,7 +850,7 @@ class GuiApplication(IQAppHooks):
# full-res frame texture can make the Adreno/GBM driver silently reset the swap interval to 0
# *after* that call but before this swap, which reintroduced tearing. This is the swap that
# actually matters, so pin the interval here too.
if (ENABLE_VSYNC and not OFFSCREEN and not PC and self._paced_by_vsync
if (ENABLE_VSYNC and not OFFSCREEN and EGL_DMA_BUF_SUPPORTED and self._paced_by_vsync
and self._vsync_interval > 0 and rl.is_window_ready()):
try:
from openpilot.system.ui.lib.egl import set_swap_interval
BIN
View File
Binary file not shown.
@@ -16,8 +16,19 @@ if getenv("IOCTL"): import extra.qcom_gpu_driver.opencl_ioctl # noqa: F401 # p
BUFTYPE_BUF, BUFTYPE_TEX, BUFTYPE_IBO = 0, 1, 2
# precompiled aarch64 machine code for the dcache_flush kernel below (dc cvac
# loop + dsb sy). Shipping the bytes avoids a runtime clang subprocess whose
# compile was getting killed by an msgq SIGUSR2 landing on the clang child's
# recycled TID -> modeld crash. Regenerate if the kernel source changes:
# prg = to_program(...); print(prg.src[4].arg.hex())
_DCACHE_FLUSH_AARCH64 = bytes.fromhex("3f040071cb000054287c4092207a0bd500000191080500f1a1ffff549f3f03d5c0035fd6")
@functools.cache
def dcache_flush():
# fast path: load shipped machine code, no compile at all (device is aarch64)
if os.uname().machine in ("aarch64", "arm64"):
try: return Device["CPU"].runtime("dcache_flush", _DCACHE_FLUSH_AARCH64)
except Exception: pass
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from tinygrad.codegen import to_program
buf, n = UOp.param(0, dtypes.uint8.ptr()), UOp.param(1, dtypes.uint8.ptr())
@@ -1,9 +1,13 @@
import ctypes, subprocess
import ctypes, subprocess, signal
from tinygrad.device import Compiler
from tinygrad.helpers import getenv, capstone_flatdump, DEBUG, unwrap
from tinygrad.runtime.support.elf import jit_loader
from tinygrad.runtime.autogen import llvm
def _block_sigusr2():
try: signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGUSR2})
except (ValueError, OSError): pass
class ClangCompiler(Compiler):
def __init__(self, arch:list[str], cachekey="compile_clang_jit"):
assert len(arch) >= 2, f"invalid arch string: {','.join(arch)!r}, expected '<arch>,<cpu>,[<feats>]' (eg. 'x86_64,znver2')"
@@ -21,7 +25,8 @@ class ClangCompiler(Compiler):
"""Compile C source to ELF object file (before linking)."""
# -fno-math-errno is required for __builtin_sqrt to become an instruction instead of a function call
return subprocess.check_output([getenv("CC", 'clang'), '-c', '-x', 'c', '-O2', '-fPIC', '-ffreestanding', '-fno-math-errno', '-nostdlib',
'-fno-ident', f'--target={self.arch}-none-unknown-elf', *self.args, '-', '-o', '-'], input=src.encode('utf-8'))
'-fno-ident', f'--target={self.arch}-none-unknown-elf', *self.args, '-', '-o', '-'], input=src.encode('utf-8'),
preexec_fn=_block_sigusr2)
def compile(self, src:str) -> bytes: return jit_loader(self.compile_to_obj(src))

Some files were not shown because too many files have changed in this diff Show More