mirror of
https://gitlvb.teallvbs.xyz/IQ.Lvbs/IQ.Pilot.git
synced 2026-08-22 01:53:43 +08:00
IQ.Pilot Release Commit @ e46d557
This commit is contained in:
@@ -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/
|
||||
|
||||
+16
-2
@@ -10,6 +10,13 @@ import SCons.Errors
|
||||
|
||||
SCons.Warnings.warningAsException(True)
|
||||
|
||||
# scons only auto-loads a site dir named site_scons at the repo root; ours lives under tools/,
|
||||
# so replicate what _load_site_scons_dir does (sys.path for site_tools imports + run site_init)
|
||||
SITE_DIR = Dir('#tools/scons').abspath
|
||||
if SITE_DIR not in sys.path:
|
||||
sys.path.insert(0, SITE_DIR)
|
||||
import site_init # noqa: F401
|
||||
|
||||
# capnp's kj library warns when $PWD is stale (doesn't match the real cwd); keep them in sync
|
||||
os.environ.pop('PWD', None)
|
||||
|
||||
@@ -49,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'],
|
||||
@@ -100,7 +114,7 @@ env = Environment(
|
||||
COMPILATIONDB_USE_ABSPATH=True,
|
||||
REDNOSE_ROOT="#",
|
||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||
toolpath=["#tools/scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||
)
|
||||
|
||||
# Arch-specific flags and paths
|
||||
@@ -187,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')
|
||||
|
||||
@@ -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 May–July 2026 (`4b68073c4`, `b26218e85`,
|
||||
`2cd89a198`) and is nearly current — most of its files are **byte-identical** to upstream HEAD.
|
||||
|
||||
A critical structural fact: upstream moved the whole tree under a nested `openpilot/` package
|
||||
(`#38219`/`#38220`/`#38223`, 2026-06-21) and moved the HAL to `common/hardware/` (`#38202`).
|
||||
iqpilot has **not** adopted either. Every ported file needs a de-prefix pass:
|
||||
|
||||
- `#include "openpilot/cereal/..."` → `"cereal/..."`
|
||||
- `#include "common/hardware/hw.h"` → `"system/hardware/hw.h"`
|
||||
- `#include "json11/json11.hpp"` → `"third_party/json11/json11.hpp"`
|
||||
- `repo_root() / "openpilot" / "tools" / ...` → `repo_root() / "tools" / ...`
|
||||
- SCons nodes `#openpilot/common`, `#openpilot/cereal` → `#common`, `#cereal`
|
||||
- `opendbc_repo/opendbc/dbc` → `iqdbc/dbc`; `#opendbc_repo` → `#iqdbc_repo`
|
||||
|
||||
---
|
||||
|
||||
## 2. konn3kt invariants — the contract the port must not break
|
||||
|
||||
These are the only genuinely IQ-owned surfaces. Everything else in both trees is upstream code we
|
||||
are behind on. **Anything not on this list should be taken from upstream verbatim** (modulo §1).
|
||||
|
||||
### Cabana
|
||||
|
||||
| File | What is IQ-owned | Disposition |
|
||||
|---|---|---|
|
||||
| `utils/api.{cc,h}` | `CommaApi::BASE_URL = API_HOST ?: https://api-iqlabs.konn3kt.com`; RSA-signed device JWT; Qt `HttpRequest` | **Delete** — fold into `tools/replay/api.{cc,h}` (§3.A) |
|
||||
| `streams/routes.{cc,h}` | konn3kt device list + route listing | Rewrite on upstream HEAD shape, konn3kt endpoints |
|
||||
| `konn3kt_canproxy.py` | `canlived → konn3kt relay → local ZMQ → Cabana` proxy | Keep; must stay wired to the Device/ZMQ stream (§3.C) |
|
||||
| `SConscript` | iqdbc paths, non-nested paths, `PrettyAction`, extra libs | Rebase onto upstream, re-apply IQ deltas |
|
||||
| `mainwin.cc` | "Load DBC from commaai/**iqdbc**" menu | Re-apply rename only |
|
||||
| `dbc/generate_dbc_json.py` | `from iqdbc.car import ...` | Re-apply rename only |
|
||||
| `assets/assets.cc` (tracked) | bootstrap-icons LFS bypass | See §3.K |
|
||||
| `dbc/car_fingerprint_to_dbc.json` (+`.log`) | tracked build product | Keep tracked |
|
||||
| `.gitignore`, `README.md` | binary name, iqdbc, konn3kt route examples | Re-apply |
|
||||
|
||||
### jotpluggler
|
||||
|
||||
All IQ deltas are already carrying `// IQ.Pilot patch:` comments — preserve that convention.
|
||||
|
||||
| File | IQ patch |
|
||||
|---|---|
|
||||
| `SConscript` | `.venv` site-packages injection; `iqdbc.dbc.generator.generator.create_all()` instead of upstream `get_generated_dbcs()`; no `bootstrap_icons` module; explicit `avformat/avcodec/avutil/x264/yuv/z/bz2/zstd/curl/ssl/crypto` + `OpenCL`/`va`/`va-drm`/`drm`; `PrettyAction`; guards `imgui.MESA_DIR` existence |
|
||||
| `icons.cc` | vendored `tools/jotpluggler/assets/bootstrap-icons.ttf` (third_party/bootstrap is LFS, TTF not checked in) |
|
||||
| `common.cc` | iqpilot `common/util.h` has no `check_system`; inline `std::system` + stderr |
|
||||
| `runtime.cc` | no `common/yuv.h`; vendored libyuv lacks `NV12ToABGR` → `NV12ToARGB` + `ARGBToABGR` |
|
||||
| `sketch_layout.cc` | `PyDownloader::getRouteFiles` → `CommaApi2::httpGet(BASE_URL + "/v1/route/<name>/files")`; `androidLog` (cereal not renamed); iqdbc paths |
|
||||
| `app.{cc,h}`, `layout.cc`, `layout_io.cc`, `map.cc`, `custom_series.cc` | path/include de-prefixing; `LogOrigin::Android`; iqdbc |
|
||||
| `dbc.h` | empty-multiplex guard (`mux.empty() ? 0 : std::stoi(mux)`) — a genuine bugfix, **upstream this** |
|
||||
| `generate_event_extractors.py` | `getBusTimeDEPRECATED()` (iqpilot cereal keeps a flat field, not upstream's `deprecated :group`) |
|
||||
| `pluggle.py` | iqpilot-only launcher (upstream has none) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Conflict matrix — upstream change vs konn3kt
|
||||
|
||||
### A. File downloading / API moved from C++ to Python (`#37497`, `#38430`) — **highest impact**
|
||||
|
||||
Upstream cabana HEAD calls `PyDownloader::getDevices()`, `getDeviceRoutes()`, `getRouteFiles()`
|
||||
(`tools/replay/py_downloader.h`), which shell into `openpilot/tools/lib/`. iqpilot's replay has
|
||||
**no** `py_downloader`; it keeps a C++/libcurl path in `tools/replay/api.{cc,h}`
|
||||
(`CommaApi2::BASE_URL`, `create_token(use_jwt)`, `httpGet`). jotpluggler already bridges this gap
|
||||
in `sketch_layout.cc` — **reuse that pattern**, don't invent a second one.
|
||||
|
||||
Resolution: extend `tools/replay/api.{cc,h}` with
|
||||
|
||||
```
|
||||
std::string getDevices(); // GET /v1/me/devices/
|
||||
std::string getDeviceRoutes(dongle_id, start_ms, end_ms, preserved);
|
||||
// GET /v1/devices/<id>/routes_segments?start=&end=
|
||||
// GET /v1/devices/<id>/routes/preserved
|
||||
std::string getRouteFiles(route); // GET /v1/route/<name>/files (already reachable via httpGet)
|
||||
```
|
||||
|
||||
returning the same JSON shapes upstream's `PyDownloader` returns, then port upstream's `routes.cc`
|
||||
verbatim with `PyDownloader::` → `CommaApi2::`. Upstream's HEAD `routes.cc` is already Qt-free
|
||||
(json11 + `strptime`/`strftime` for the preserved-route ISO timestamps) — that code ports as-is.
|
||||
|
||||
Auth semantics to preserve: `create_token(!Hardware::PC())` — device RSA-JWT on device,
|
||||
`~/.comma/auth.json` `access_token` on a PC. Upstream's Python path only ever does the latter.
|
||||
|
||||
Consequence: `tools/cabana/utils/api.{cc,h}` (Qt `QNetworkAccessManager` + duplicate JWT code)
|
||||
**gets deleted**, which drops `Qt5Network` from the cabana link and converges with §3.B.
|
||||
|
||||
### B. de-Qt: `#37519`, `#37521`, `#37522`, `#37523`, `#38357`, `#38359`, `#38360`
|
||||
|
||||
The bulk of the 53-commit gap. `QString`→`std::string` through the DBC core, new Qt-free
|
||||
`core/{can_data,color,message_id,settings}.h`, Qt/core split in `dbc/dbcqt.{cc,h}`, QtXml dropped
|
||||
(bootstrap SVG symbol extraction hand-rolled in `utils/util.cc`), QtConcurrent dropped, QtSerialBus
|
||||
dropped (SocketCAN now raw `linux/can` sockets, `#37553` excludes it on macOS).
|
||||
|
||||
**Zero konn3kt content.** Pure upstream adoption. It is also why almost every cabana file shows a
|
||||
large diff — those are not IQ changes, they are upstream changes we lack.
|
||||
|
||||
Net effect on `SConscript`: `qt_modules` goes from
|
||||
`["Widgets","Gui","Core","Network","Concurrent","DBus","Xml"]` → `["Widgets","Gui","Core"]`,
|
||||
and `Qt5SerialBus` / `QtSerialBus` framework drop out.
|
||||
|
||||
### C. `--zmq` bridge path (`#38484`) — **breaks konn3kt_canproxy if ported blind**
|
||||
|
||||
Upstream HEAD `DeviceStream::start()` forks `cereal/messaging/bridge <addr> /"can/"` and
|
||||
`streamThread()` then always subscribes on `127.0.0.1`. `bridge <ip> <whitelist>` is
|
||||
`zmq_to_msgq` (see `cereal/messaging/bridge.cc:60`) — it ZMQ-subscribes from `<ip>` and republishes
|
||||
to local msgq.
|
||||
|
||||
iqpilot's documented remote-CAN workflow is the opposite direction:
|
||||
`konn3kt_canproxy.py` sets `ZMQ=1` and **publishes** capnp `Event` frames on a local ZMQ `can`
|
||||
socket; the user then picks *Live → Device → ZMQ → 127.0.0.1*. Under upstream HEAD semantics that
|
||||
forks a bridge which subscribes to the same loopback endpoint the proxy publishes on and
|
||||
republishes into msgq, while cabana reads ZMQ — the proxy path stops being the thing cabana reads.
|
||||
|
||||
Resolution: keep the pre-`#38484` direct-attach semantics as an explicit IQ escape hatch. Port
|
||||
upstream's bridge-forking code, but subscribe to `zmq_address` directly (no bridge fork) when the
|
||||
stream is started in "attach to an existing ZMQ publisher" mode — either by treating loopback
|
||||
addresses as attach-mode, or by adding a third radio button next to MSGQ/ZMQ. Mark it
|
||||
`// IQ.Pilot patch:` and state why (konn3kt_canproxy). **This must be re-verified end-to-end on a
|
||||
real device before the sync is called done** (§5.3).
|
||||
|
||||
### D. libyuv removed, `common/yuv.h` added (`#38306`)
|
||||
|
||||
jotpluggler already patches around this. Cabana's `cameraview.cc` does NV12 conversion too.
|
||||
Decision: port `common/yuv.h` into iqpilot once, then **delete** the `runtime.cc` IQ patch and take
|
||||
upstream's `cameraview.cc` verbatim. One-time cost, removes a recurring patch site.
|
||||
|
||||
### E. Vendored native deps via `comma-deps-*` wheels (`#37327`, `#37994`, `#37681`, `#38308`)
|
||||
|
||||
Upstream `SConstruct` now exports `ffmpeg_libs` and pulls capnproto/ffmpeg/zstd/zeromq/json11/
|
||||
libusb/imgui/bootstrap-icons from pip wheels. iqpilot pins `imgui`/`libusb` from
|
||||
`git.konn3kt.com/IQ.Lvbs/dependencies`, vendors json11 in `third_party/json11`, and has **no**
|
||||
`bootstrap_icons` module and **no** `ffmpeg_libs` export.
|
||||
|
||||
Resolution: add an `ffmpeg_libs` list + `Export` to iqpilot's `SConstruct` mirroring upstream's
|
||||
(`avformat avcodec swresample avutil` [+ `x264 z` [+ `va va-drm drm`]]) so both tool SConscripts can
|
||||
take upstream's form nearly verbatim instead of hand-listing libs. Keep `third_party/json11`.
|
||||
Do **not** chase the wheels.
|
||||
|
||||
### F. `androidLog` → `operatingSystemLog` (`#38209`)
|
||||
|
||||
iqpilot cereal still has `androidLog`. Keep the existing `sketch_layout.cc` / `app.h` IQ patch.
|
||||
Renaming cereal is out of scope (it is a schema change with device-side blast radius).
|
||||
|
||||
### G. Tests: catch2 dropped (`#38408`), unittest conversion (`#38387`, `#38384`)
|
||||
|
||||
Upstream deleted `tools/cabana/tests/test_runner.cc` and split a deliberately Qt-free
|
||||
`tests/test_dbc_core` target (objects built from the **base** env, linking no Qt — it exists
|
||||
specifically to stop Qt creeping back into the DBC core). Adopt that shape; drop `test_runner.cc`.
|
||||
jotpluggler gains `test_jotpluggler.py`.
|
||||
|
||||
### H. `cabana` launcher script + `_cabana` binary (`#37814`, `c02cf706a`, `05cf8023a`)
|
||||
|
||||
Upstream builds `Program('_cabana')` and ships `tools/cabana/cabana` as a bash launcher that
|
||||
installs Qt if missing and runs `scons -u openpilot/tools/cabana/_cabana openpilot/cereal/messaging/bridge`.
|
||||
iqpilot builds `Program('cabana')` — so `tools/cabana/cabana` is currently a **committed binary**.
|
||||
Adopting upstream's split means: `.gitignore` `cabana` → `_cabana`, and the launcher's scons
|
||||
targets de-prefixed to `tools/cabana/_cabana cereal/messaging/bridge`.
|
||||
|
||||
### I. `assets.cc` / bootstrap icons (`#37994`, `71290f380`, `15267e408`)
|
||||
|
||||
Upstream generates `assets/assets.cc` at build time (`rcc` over an `assets.generated.qrc` that
|
||||
interpolates the packaged bootstrap-icons SVG path) and gitignores it. iqpilot tracks the generated
|
||||
14.9k-line `assets.cc` because the icons package isn't available.
|
||||
|
||||
Recommended: vendor the bootstrap-icons **SVG** next to the already-vendored TTF
|
||||
(`tools/jotpluggler/assets/bootstrap-icons.ttf`), add a tiny local shim module exposing
|
||||
`SVG_PATH`/`TTF_PATH`, adopt upstream's generation, and untrack `assets.cc`. This also lets
|
||||
jotpluggler's `icons.cc` IQ patch collapse back to upstream's `BOOTSTRAP_ICONS_TTF` define.
|
||||
Acceptable fallback: keep tracking `assets.cc` and keep both patches.
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
**Phases 0–2 are done and landed.** Both tools are at upstream `e7b1ee3a5` (2026-08-05) with
|
||||
konn3kt preserved. Departures from the plan as written, and what remains:
|
||||
|
||||
- §3.I resolved better than planned: `third_party/bootstrap/bootstrap-icons.svg` turned out to be
|
||||
**tracked already**, so upstream's build-time embedding was adopted as-is — no vendored SVG, and
|
||||
the 14.9k-line generated `assets.cc` is gone (it was never tracked; it was a gitignored build
|
||||
product).
|
||||
- §3.D done both ways: `common/yuv.{cc,h}` ported, and `tools/replay/framereader.cc` moved off
|
||||
libyuv too, so the jotpluggler `runtime.cc` patch disappeared entirely.
|
||||
- Unplanned dependency found during the port: upstream's `#38430` also added download/decompress/
|
||||
parse instrumentation to `LogReader`, which jotpluggler's load-stats panel reads. Ported into
|
||||
iqpilot's `LogReader` around its own FileReader/decompress steps (so decompress is timed
|
||||
separately, unlike upstream where the Python downloader does it inline).
|
||||
- §3.C landed as a **three-mode** `DeviceStream` (`Msgq` / `Zmq` / `Bridge`) rather than an
|
||||
escape hatch. Upstream's ZMQ path forks a bridge that republishes to **msgq** while still
|
||||
setting `ZMQ=1` and subscribing over **ZMQ** — that combination reads nothing. Keeping the modes
|
||||
explicit preserves upstream's bridge convenience *and* the direct attach `konn3kt_canproxy.py`
|
||||
needs, and fixes that inconsistency. `--bridge <ip>` is a new flag.
|
||||
- Also de-comma'd beyond the plan: jotpluggler's "Useradmin" / "comma connect" buttons became a
|
||||
single **konn3kt** link (`KONN3KT_APP_HOST`, default `https://konn3kt.com`, same
|
||||
`<dongle_id>/<log_id>` path shape `tools/lib/logreader.py` already parses).
|
||||
|
||||
### Phase 3 verification actually performed (macOS arm64, live konn3kt)
|
||||
|
||||
- **konn3kt API, live, real credentials** — every endpoint the port builds returns 200 with real
|
||||
data: `/v1/me/devices/` (2 devices), `/v1/devices/<id>/routes_segments?start=&end=` (1776
|
||||
routes), `/v1/devices/<id>/routes/preserved` (16), `/v1/route/<name>/files` (48 logs / 48 qlogs
|
||||
/ 48 qcameras).
|
||||
- **Real route in the real GUI** — `_cabana --qcam 0f53129ed44f6920|000001a5--07db1da02e` loaded
|
||||
**48 valid segments** and came up as a foreground Cocoa app (not offscreen).
|
||||
- **Live CAN over the ZMQ attach** — a local publisher mimicking `konn3kt_canproxy.py` fed
|
||||
`_cabana --zmq 127.0.0.1`; cabana ingested **2179 CAN frames** and wrote them to
|
||||
`~/cabana_live_stream/.../rlog`. This is the exact path `#38484` would have broken.
|
||||
- **Fixed while verifying:** `utils::icon()` painted a null QPixmap whenever an empty icon id was
|
||||
requested (`ToolButton("")` in `chartswidget.cc`), logging two `QPainter` warnings on every
|
||||
dark-theme macOS start. Pre-existing upstream bug, not a port regression; guarded here and worth
|
||||
upstreaming alongside the `dbc.h` mux fix.
|
||||
|
||||
**Still outstanding:**
|
||||
- **Phase 3.3 (device half):** the konn3kt relay accepts the websocket and authorizes the stream,
|
||||
but `0f53129ed44f6920` emits no frames because `canlived` only runs while the `CanLiveStreaming`
|
||||
param is set. Needs a device with that enabled to close the loop.
|
||||
- **Phase 3.5:** cabana has not been built for larch64.
|
||||
- Charts/video widgets were not visually inspected — `screencapture` is blocked by macOS
|
||||
screen-recording permission for the terminal, so GUI rendering is evidenced by process state and
|
||||
the ingested-frame log rather than by a screenshot.
|
||||
|
||||
Tests added: `tools/replay/tests/test_api.cc` (konn3kt endpoint paths + error envelope),
|
||||
`tools/cabana/test_cabana_konn3kt.py`, `tools/jotpluggler/test_jotpluggler.py`, and
|
||||
`tools/test_tools_local_route.py` (synthesizes a local route and drives both tools headlessly).
|
||||
|
||||
## 4. Execution plan
|
||||
|
||||
Land each phase as its own commit. Build after every sub-step — do not batch §4.2.
|
||||
|
||||
### Phase 0 — infrastructure (no tool code)
|
||||
|
||||
0.1 `SConstruct`: add and `Export` `ffmpeg_libs`.
|
||||
0.2 Port `common/yuv.h`; drop the `runtime.cc` libyuv patch.
|
||||
0.3 Vendor bootstrap-icons SVG + local `bootstrap_icons` shim (`SVG_PATH`, `TTF_PATH`).
|
||||
0.4 `tools/replay/api.{cc,h}`: add `getDevices()`, `getDeviceRoutes()`, `getRouteFiles()` against
|
||||
the konn3kt endpoints, matching upstream `PyDownloader` JSON shapes.
|
||||
|
||||
**Gate:** `scons -u -j8 tools/replay` clean; a scratch binary lists real devices from
|
||||
`api-iqlabs.konn3kt.com` with both a device JWT and a PC `auth.json` token.
|
||||
|
||||
### Phase 1 — jotpluggler (6 commits; do first, it validates the whole pattern cheaply)
|
||||
|
||||
1.1 `3f49e2d33` thumbnail source — add `thumbnail.{cc,h}`; `common.h` `kSpecialItemSpecs` 5→6;
|
||||
`common.cc` `PaneKind::Thumbnail`; `layout_io.cc` `"thumbnail"`; `app.{cc,h}` `ThumbnailView`
|
||||
+ `ThumbnailFrame`; `session.cc` `setThumbnails`. Re-apply the json11/path/`Android` patches.
|
||||
1.2 `576de9c7e` build speedup — take upstream `generate_event_extractors.py` **wholesale**
|
||||
(iqpilot's copy has diverged: `event_base_slots`, `static_enums`, single-use scalar getters)
|
||||
and re-apply only the `getBusTimeDEPRECATED()` patch.
|
||||
1.3 `e7b1ee3a5` segment-range parsing — empty-begin guard in `sketch_layout.cc`.
|
||||
1.4 `6b47a5b6b` decompress-in-downloader — iqpilot has no `PyDownloader`; confirm the replay-side
|
||||
decompression path is unaffected (expected no-op).
|
||||
1.5 `fef29ad22`/`911f07ee8` — add `test_jotpluggler.py`.
|
||||
|
||||
**Gate:** `scons -u -j8 tools/jotpluggler`; `./tools/jotpluggler/pluggle.py <konn3kt route>` opens,
|
||||
thumbnails render, CAN series decode against iqdbc, a saved layout round-trips.
|
||||
|
||||
### Phase 2 — cabana (53 commits). Bottom-up, one layer per build.
|
||||
|
||||
2.1 **DBC core** — `dbc/{dbc,dbcfile,dbcmanager}.{cc,h}`, new `core/{can_data,color,message_id,settings}.h`,
|
||||
new `dbc/dbcqt.{cc,h}`. Re-apply iqdbc rename in `dbc/generate_dbc_json.py`.
|
||||
2.2 **Streams** — `abstractstream`, `livestream`, `replaystream`, `pandastream`, `socketcanstream`
|
||||
(raw `linux/can`, macOS-excluded), `devicestream`. Re-apply the §3.C canproxy escape hatch.
|
||||
2.3 **Routes** — port upstream HEAD `streams/routes.{cc,h}`, swap `PyDownloader::` → `CommaApi2::`.
|
||||
**Delete `utils/api.{cc,h}`.**
|
||||
2.4 **Widgets** — `binaryview`, `historylog`, `videowidget`, `signalview`, `detailwidget`,
|
||||
`messageswidget`, `mainwin` (re-apply the iqdbc menu label), `streamselector`, `commands`,
|
||||
`settings`, `cameraview`, `chart/*`, `tools/*`, `utils/{util,export,elidedlabel}`.
|
||||
2.5 **SConscript** — rebase on upstream; re-apply: non-nested paths, `iqdbc/dbc` +
|
||||
`#iqdbc_repo`, `PrettyAction` wrappers, `third_party/json11`, `ffmpeg_libs` from Phase 0.1.
|
||||
Qt modules collapse to `Widgets/Gui/Core` (+Charts).
|
||||
2.6 **Tests** — adopt the Qt-free `tests/test_dbc_core` target; drop `tests/test_runner.cc`.
|
||||
2.7 **Launcher** — adopt `cabana` bash script + `Program('_cabana')`; de-prefix its scons targets;
|
||||
fix `.gitignore` (`cabana` → `_cabana`, `*.generated.qrc`, `bootstrap_icons.cc`).
|
||||
|
||||
**Gate (each sub-step):** `scons -u -j8 tools/cabana` clean, no new warnings.
|
||||
|
||||
### Phase 3 — konn3kt verification (the part that actually proves the sync)
|
||||
|
||||
3.1 Remote-routes dialog against `api-iqlabs.konn3kt.com`: device list populates; all five periods
|
||||
(7d / 14d / 30d / 6mo / **Preserved**) return routes with correct start times and durations.
|
||||
Preserved uses ISO timestamps, the rest use `*_utc_millis` — both paths must be exercised.
|
||||
3.2 Open a konn3kt route end-to-end: route-file listing, log download + decompression, qcam/fcam
|
||||
video, iqdbc DBC load, chart, CSV export.
|
||||
3.3 **Remote live CAN:** `canlived` on device → konn3kt relay → `konn3kt_canproxy.py` on laptop →
|
||||
Cabana *Live → Device → ZMQ → 127.0.0.1*. Frames must decode. Per prior work this is the
|
||||
untested leg of the canlive feature — run it on `gutek-a1`.
|
||||
3.4 Local live CAN: panda stream, MSGQ device stream, SocketCAN (Linux only).
|
||||
3.5 larch64 device build, if cabana is still expected to build on-device.
|
||||
|
||||
### Phase 4 — plotjuggler (secondary, 14 commits)
|
||||
|
||||
Only `juggle.py`, `layouts/`, `README.md` diverge, and the only IQ delta is the `iqdbc.car.fingerprints`
|
||||
import plus non-nested paths. Cheap; fold in after Phase 3 or skip.
|
||||
|
||||
---
|
||||
|
||||
## 5. Risks and rules
|
||||
|
||||
- **Don't batch Phase 2.** ~40 files change; a single "port everything then build" pass produces an
|
||||
unbisectable wall of link errors. One layer, one build.
|
||||
- **`git add` only ported files.** The tree currently contains built artifacts (`*.o`, `*.a`,
|
||||
`moc_*.cc`, `tools/cabana/cabana`, `tools/jotpluggler/jotpluggler`,
|
||||
`generated_event_extractors.h`, `generated_dbcs/`). Never `git add -A` here.
|
||||
- `dbc/car_fingerprint_to_dbc.json` is a tracked build product regenerated by scons — expect churn
|
||||
on every build; keep it tracked so cabana doesn't need iqdbc importable at runtime.
|
||||
- Dropping `Qt5Network/Concurrent/DBus/Xml/SerialBus` changes the larch64 link set; verify the
|
||||
device build before assuming the SConscript is done.
|
||||
- §3.C is the one place where a faithful upstream port actively regresses a konn3kt feature. If
|
||||
Phase 3.3 can't be run on hardware in this pass, **do not** land 2.2 with upstream's bridge fork
|
||||
as the only ZMQ path.
|
||||
- Upstream candidate: the `dbc.h` empty-multiplex-indicator guard is a real upstream bug fix worth
|
||||
sending back.
|
||||
|
||||
## 6. Explicit non-goals
|
||||
|
||||
- Adopting upstream's nested `openpilot/` tree layout.
|
||||
- Migrating iqpilot's replay to `PyDownloader` / `tools/lib` Python downloading.
|
||||
- Renaming cereal `androidLog` → `operatingSystemLog`.
|
||||
- Converging on the `comma-deps-*` pip wheels.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A — cabana commits to port (`bcdeec313..e7b1ee3a5`, 53)
|
||||
|
||||
```
|
||||
4cfdcea1d 2026-07-28 cabana: fix macOS build and --zmq bridge path (#38484) <- §3.C
|
||||
6b47a5b6b 2026-07-23 tools: decompress in the python downloader (#38430) <- §3.A
|
||||
75d590fb9 2026-07-21 replace catch2 tests for 20% faster builds (#38408) <- §3.G
|
||||
e124d6df9 2026-07-19 more dead code gc
|
||||
a04c045cd 2026-07-17 cabana: de-Qt, part 3 (#38360) <- §3.B
|
||||
5d23a78c7 2026-07-16 cabana: de-Qt, part 2 (#38359) <- §3.B
|
||||
06a73f538 2026-07-16 cabana: de-Qt, part 1 (#38357) <- §3.B
|
||||
39117a587 2026-07-08 remove submodule symlinks (#38312)
|
||||
f283f6703 2026-07-08 Revert "try removing submodule symlinks (#38310)"
|
||||
b827c0f55 2026-07-08 try removing submodule symlinks (#38310)
|
||||
1e49eac4d 2026-07-08 rm libyuv (#38306) <- §3.D
|
||||
9877f6ac0 2026-07-08 ffmpeg: use shared libraries (#38308) <- §3.E
|
||||
05cf8023a 2026-07-07 fix cabana <- §3.H
|
||||
c02cf706a 2026-07-07 fix cabana launch script <- §3.H
|
||||
5edc0bd89 2026-06-21 mv root dirs into nested openpilot (#38219) <- §1 (de-prefix)
|
||||
20e0f21b5 2026-06-21 prefix paths with openpilot (#38223) <- §1 (de-prefix)
|
||||
37eda06c9 2026-06-21 move cereal/ into nested openpilot (#38220) <- §1 (de-prefix)
|
||||
bfd8d4868 2026-06-06 cabana: fix "seperated" typo in findsignal placeholders (#38142)
|
||||
5408c86b7 2026-05-28 Cabana: Fixed internal typos and method casing (#38099)
|
||||
15267e408 2026-05-11 cabana: gitignore generated file <- §3.I
|
||||
bea893820 2026-05-10 use packaged bootstrap icons (#37994) <- §3.I
|
||||
bd1c7f39e 2026-05-07 scons build cleanups (#37981)
|
||||
0584a5f5e 2026-04-12 add bridge target to cabana run script (#37814) <- §3.H
|
||||
31e4fe55a 2026-03-22 tools: setup ffmpeg hwaccel (#37718)
|
||||
a8b5c7450 2026-03-21 prep for imgui tools (#37712)
|
||||
240e0036d 2026-03-20 macOS: fix build (#37686)
|
||||
a68ea44af 2026-03-14 cabana: use vendored libusb from commaai/dependencies (#37681) <- §3.E
|
||||
5e7f5dd84 2026-03-14 replay/cabana: remove unused openssl dependency (#37680)
|
||||
ee9da82aa 2026-03-13 cleanup build paths (#37667)
|
||||
71290f380 2026-03-08 cabana: gitignore assets.cc <- §3.I
|
||||
e42ee228c 2026-03-08 gitignore cleanups (#37615)
|
||||
5e1a576f3 2026-03-07 cabana: exclude SocketCAN on macOS (#37553) <- §3.B
|
||||
0c452dbaf 2026-03-03 cabana: fix right pane width limitation (#37527)
|
||||
06b2c68e0 2026-03-01 macOS: fix cabana builds (#37518)
|
||||
3478ac133 2026-03-01 cabana: remove QtSerialBus (#37523) <- §3.B
|
||||
ce04d25f7 2026-03-01 cabana: remove QtConcurrent (#37522) <- §3.B
|
||||
0c7abf385 2026-03-01 cabana: remove QtXml (#37521) <- §3.B
|
||||
0b9ab8bb9 2026-03-01 cabana: replace Qt types with stdlib (#37519) <- §3.B (largest)
|
||||
885658512 2026-02-28 new demo route (#37457)
|
||||
e7cc70f3f 2026-02-28 consolidate file downloading from C++ to Python (#37497) <- §3.A
|
||||
276713ddf 2026-02-27 add back bz2 support with vendored bzip2 (#37459)
|
||||
238fca233 2026-02-25 tools: fix darwin compile errors (#37399)
|
||||
8810948ec 2026-02-24 CI: ensure no brew (#37387)
|
||||
76d084d87 2026-02-23 switch to system compilers (GCC on Linux, Apple Clang on macOS) (#37355)
|
||||
f4a36f7f7 2026-02-22 rm cpp bz2 (#37332)
|
||||
4bffe422e 2026-02-22 vendor capnproto and ffmpeg via dependencies repo (#37327) <- §3.E
|
||||
c98ba4ff4 2026-02-20 Qt is optional (#37295)
|
||||
037e6e749 2026-02-17 cabana: fix crash when zmq address is used (#37222)
|
||||
af1583cdf 2026-02-12 Reapply tgwarp w NV12 fix (#37168)
|
||||
45099e7fc 2026-02-10 Revert tgwarp again (#37161)
|
||||
667f3bb32 2026-02-07 Revert "revert tg calib and opencl cleanup (#37113)" (#37115)
|
||||
51312afd3 2026-02-07 revert tg calib and opencl cleanup (#37113)
|
||||
d5cbb89d8 2026-02-06 Remove all the OpenCL (#37105)
|
||||
```
|
||||
|
||||
## Appendix B — jotpluggler commits to port (`61608db78..e7b1ee3a5`, 6)
|
||||
|
||||
```
|
||||
e7b1ee3a5 2026-08-05 jp: fix segment range parsing
|
||||
6b47a5b6b 2026-07-23 tools: decompress in the python downloader (#38430)
|
||||
576de9c7e 2026-07-21 speed up jotpluggler build (#38406)
|
||||
911f07ee8 2026-07-21 convert tests to unittest (#38387)
|
||||
fef29ad22 2026-07-19 start porting tests to unittest style (#38384)
|
||||
3f49e2d33 2026-07-18 jp: add thumbnail source (#38363)
|
||||
```
|
||||
|
||||
## Appendix C — how the baselines were established
|
||||
|
||||
Blob-hash matching, not commit archaeology: for each iqpilot file, walk upstream history
|
||||
newest-first and report the newest commit whose blob for that path is byte-identical. Files with no
|
||||
match are IQ-modified or IQ-new; the minimum over the untouched files bounds the fork point.
|
||||
|
||||
```
|
||||
cd /tmp/openpilot-upstream
|
||||
git log --format='%H %ad %s' --date=short -30 -- openpilot/tools/cabana tools/cabana > /tmp/cab_commits.txt
|
||||
for f in $(cd $LOCAL/tools/cabana && find . -type f \( -name '*.cc' -o -name '*.h' \) -not -name 'moc_*' | sed 's|^\./||' | sort); do
|
||||
h=$(git hash-object "$LOCAL/tools/cabana/$f") || continue
|
||||
while read -r c d rest; do
|
||||
b=$(git rev-parse "$c:openpilot/tools/cabana/$f" 2>/dev/null || git rev-parse "$c:tools/cabana/$f" 2>/dev/null)
|
||||
[ "$b" = "$h" ] && { echo "$f -> ${c:0:9} $d $rest"; break; }
|
||||
done < /tmp/cab_commits.txt
|
||||
done
|
||||
```
|
||||
|
||||
Cross-checked against the last upstream-authored commits in iqpilot's own history
|
||||
(`bcdeec313 Reduce pub-sub memory usage by 10x (#36884)` for cabana) and against feature markers
|
||||
(`virtual std::string routeName` first appears in `0b9ab8bb9`, 2026-03-01 — absent from iqpilot,
|
||||
confirming the fork predates it).
|
||||
@@ -16,27 +16,27 @@
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "2d4cfb58875fee44a7baea50946fb8b31feb5ceb0daf61598b032634f336363e",
|
||||
"sha256": "9368086c4cdd6cf46b6a6d0a1e1e83fdc7e7dff8d3bd67a42b3bffa91b45dc26",
|
||||
"size": 135536
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "903174fad1ee61fe959be5c9401b8d10e2edd05eadfa02e19e4cd599aa1e464d",
|
||||
"sha256": "291789f912304c72cd1e0da955bd58fa903cd99e7d6504e6c3c51d39befaf6e8",
|
||||
"size": 67664
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "fd38412866cefa4be7777b7573b46e57665756f0d4d7decdfb189a8dafae8067",
|
||||
"sha256": "c47ec079040efee31673f55752a6215b3a8908c63c8373625ce5cb4cb1177aff",
|
||||
"size": 204624
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "c95f786dbff8484c2ad63ed04fa37bcf0bdaf16f0b9f80d37e5d1e8e5b224554",
|
||||
"sha256": "a0d5710938b32e56f2fc55c86ff8947787a3ebeb43850a94a90202c3773ff74e",
|
||||
"size": 69752
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "88b9a944da9cfbf6fe043ee685d4cd04e900b34996e69ab2eb7cedd807798854",
|
||||
"sha256": "ff345aa71ac599bdd16747368ceb92fae7b5b35657f81466fc0660b76d44aa22",
|
||||
"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": "83336a6b02ffdb451297e3d4f6b586fa764f87195dfe6094805a4a18fcd9d279",
|
||||
"sha256": "2aa26e4bc71870f639fca6e8e273deb59f50cf6a6d963ba77ce0949c65a52d63",
|
||||
"size": 202168
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "563e6dd93009fd5e99de5d6abebbed20a7020683a8a0266e3c1a1f9d6b5f2889",
|
||||
"sha256": "96b47ec40dd7e3430cebf3979b4fdfb0096e80029b40da6445cdf44dbcdb8917",
|
||||
"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": "dc0184fd4971a040ca4ca332b7bcf6b2ee6b8040804c90d3efc3f2806359106c",
|
||||
"sha256": "f12e44b7a986616d6b3c4e127e4eeae793e346f31d5b821ad9c44249e4731ebd",
|
||||
"size": 2100238
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "8036f724630003afda855cf293f098cc3bb50801a5ca7626c3b57d4556605f5a",
|
||||
"sha256": "4ac192d102bde18d9b3215ff637e1c316a08eb0eb00c595c07eaa07284d1f28c",
|
||||
"size": 268064
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "a97b390dd697dbba69ac83663c49f65c6408bbcb03be05c889bdee622d9e5efa",
|
||||
"sha256": "6f2dccd9f85db9d4948ddb31f62b6059d9359f7f16df6d55455b05c6ae4f37b7",
|
||||
"size": 340584
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "a3fea60218cd759a37d4ca423b6b55d02fd5e36cb762e8333604d7fb322b4923",
|
||||
"sha256": "abb3eb9033205d1c2903b619dbbab7e3de10b859493239c35533b75d00bc3305",
|
||||
"size": 68048
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "548e4910fd18dfc8058c17bfabfbb5f69db15481169efd4fe68841f20c8a0692",
|
||||
"sha256": "a631270d167cbd9a67db6111de57e5d6ddcdd554706fd9fd919d8f116733bf66",
|
||||
"size": 335728
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "3ed0ce5cfeec2f8cc3b6a0a85b6137d3bf7219904e5e76f6490f027e9d6346a0",
|
||||
"sha256": "c5c4f480275ddc00e2d7d44aee76808f2b4184b6a6354ef2e757d8b3a7ae8e44",
|
||||
"size": 271680
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "a8667a7a12c8a096e68b8716b0f309fad8d98e5cf056121a8f84371908421402",
|
||||
"sha256": "a9d9fdfcd6f154e093ed1cc678bf9a1e12c7a3e475a358c19941af7777c40807",
|
||||
"size": 134408
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "af175ad8b662828c25f2e53dd4af1e323318f4b7df51276914327ae110a8283d",
|
||||
"size": 3514480
|
||||
"sha256": "d3468f09df47814c161937306c776df5fe9a6a6d819859a596f7e33be6d253e8",
|
||||
"size": 3581928
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "0be3eb36bd0fcc798ad602ff7be133366d82c3b112df9663a94347e431dd6486",
|
||||
"sha256": "c7cf16d3b2297deafd87c53c8b61031d2af84ff04848bab34d9dde37ba939ed5",
|
||||
"size": 136448
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "fa70ebb1569dde9f474040476c495d170874217cac0611e8057a92c9bfee0c92",
|
||||
"sha256": "113890a2fa588f0c3d6d0c6723aef63adb58fc3ad5355174effe5ac2d3d6d364",
|
||||
"size": 68136
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "674acf92f744733857437a9993e6e7bb41190e924ec894a45adb4acf396b560f",
|
||||
"sha256": "e97f1650766b15b231657afd6530c019d09352dfa0d96deb2643893acf56efa4",
|
||||
"size": 67840
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "0b4e93091aaf56a2ae1b0a22dab3e7c07e2172df80308f97edb793e412310a3d",
|
||||
"sha256": "7be8a68c56f6ff37ef887b87275c78e517065995243e88d1ab6a10f3b028abe5",
|
||||
"size": 135648
|
||||
},
|
||||
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": {
|
||||
"mode": 493,
|
||||
"sha256": "0b6ddf3b89bb78d1d2c68ef0c3f93796867c150606269f7666bdca093ecc856f",
|
||||
"sha256": "2a1f6cbb8794a29a55decf6d04915e752360eac7701ed55c826355bb348e39a6",
|
||||
"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": "9104feff3435925c9235b8424f08dc52eee877ae546093ecea83687b3fd6478a",
|
||||
"sha256": "613141bdba6cabaf47fa07b438fe964e90796b7e0309c8f0fbfa60b6bf54e6ad",
|
||||
"size": 204184
|
||||
},
|
||||
"runtime": {
|
||||
@@ -171,25 +171,25 @@
|
||||
}
|
||||
},
|
||||
"signatures": {
|
||||
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "P79t7+Txevk0MV7hDKGcDPlx2RH9CACuI89MdIuouzwjTIPyi/XZxb2z5qukLp/SENm8tIO/uvDqNMGg1zsNDQ==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "9iVq7Mj2pYNsFG/663bMRD1XH2IEk6+Xbpd41jT83rhtYCi83aHzXcXyDcqNmyYhQpPeu4It9sp1eSj42w5DBg==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "f3SUgO2s1N9juez3vvXlhi4KV9gibraZs0XKdtlTCHS9TVpUPAforrIv+fiiv+CnXuNQaFxOsVE1nsVxYUFVCg==",
|
||||
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "+Em9DTW7a6uzqgeZlLCFFjIjN3su8P7SImlAiLLc9Bc8ylsmvaEqfqLewnblANN0E3fPefIWPy2SXtE/sh8DDw==",
|
||||
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "adDfmCgrUH/7F/tctcIfy7PMLkdnEj0Y8dJO6q7/Ds34WpyNZy0JousS007TGsryrIAjZsfujMieVei7DCvbDA==",
|
||||
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "khtz+JnG262yIsCgaqzeSxqlkbyXbzVbZrVxuXV6SQhhl+Ob/LL1ZgSs6Wpca48STo2NMYjglzeaM5jwj847Bg==",
|
||||
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "ZFG+EQgMvwqq8T6VNUqqgQJmIgAC1AIYNF/QWYU7MxYWWWfnx9s+G5Mz2EwLMUIdW16pGXsqkn02u4kHNxpcCg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "KW+OPawOOOKdeIl4l7gc1hjV8sdd0QXk8tIBl5UXwXgQSdbGwE+H8mCc2EKEhDb5wR1Zl/Cb6pC7D8ksNZcFCQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "Q+2toj33o3kivPIA152qAIRGdUMFI9jKJ97W9bf2cNXCWAs04p8XvHIxjD1JChJMfvcZLHIk9HlumRSalcT1Dg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "s7qLI/aus4eexP6XEqrreU/stI781n0T0F1yeerxo0BoaUc5oT0HTQkrZTgSagFfGnPT228MKEhB7xW2JOuAAg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "ESRJsvz0NplwLhzY/oGMGP4ZD3S70fd07QSl/xLdFcX36Scelic5pJO9ClBSeoMd1uA1iWmSMARK4vugJHTBBQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "DPbD2qSAUTkR0JmZT+9bPYDZlSeAmZaKnRJiC9Av+uLpX/lCnbIeLMEgRFrOdho/gf4CzmDcbfboULYwgd06AQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "ut5z43F74P4rq51YQBaPU35hrOsTphthrkAfYv7ezFUwSfZei0jUBvJAujU9n6fbg58NiUYrIgwe8/AnntpaCw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "IUzori/U8oSQPTO9KSMvR0/B3zBmpwmSUNdnb2Idop3gGaGBrebNaRv4Ilbjp6+xVNArvEsOybVxPPSapVsjDg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "7aEw/FLCYQuH+VqyeV88nnfHG9xFrfP0XK9rNltmT6YXAWKN6PtnpXVqPTmrBuuOlkl+pCIKLIvKVm52dbAOAg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "EDpN12qdopPVzYu4aHbrNwYjN3vfRrEJhln5XNrVnh/RsZNkM0HKTJJdcVauJz//lEwIHTn1KLvOgiKzn5rlBA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "0ud+tsJGU2wew33iJmC6bJ+Pg9f4Fl0znsbXhO4lUXmuqCWTfaCWYbAASggUVF0MFYccJ9aZsARON2S/24igAw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "+3ahkAwXUIluEv86JNK0HpnsrprT8iPdKDRBWdCWc4ux+k8YN7bCDQSl+B1gkGRxB/2hCQq0ncF3EKb3+yTCCQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "ch5zd8KmXfi/vyI0see68Qdcr7PUBIyJ2iJ2KMGGfIXufa57DBDuyzcwfkyeA6btC8mt+mokIeRap/gUhyraAg==",
|
||||
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "iLrfT53m4UBggK+ql3XR2FYQrPtuIxnfGe7jSLUd3skttpG6FNL9g+dEzbU4IPoc9wgE6kvxorjz7KejFq7WBw=="
|
||||
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "nSuJmlS6Uu7Z2hCUfo8ia0v3TtLy0k7vXuUtkRteuXr283FbjSL/SAnGZtotLam9QqWcKUZiX5AY7iKRxdJRDA==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "b4kzh2Ih+crCs71fFdIku1vWUarJheL2EFkX9K85ppio/t27Vml4wjdRXt6G9ImEIwXb0FwZQ5TO290oTS81DQ==",
|
||||
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "Y4Z6VxELzmD2+MZKv/Uk7mE03ZFxxjXFiV21zHXbGTTQ8x+3OWSs+c43kfEFO7+Y0GRLAV/pFVdS4TJotL1bCA==",
|
||||
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "uKdDonv7l3RV5Rk0p18w2B7OFo56b61Gdzj4Q2HCa4MkjzVv81Pe7kf+2AbRXJ+tV74P1Xg/S4uFYH9B03D7Cw==",
|
||||
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "+HMqTUeIHFUo9Fr7/yzkI9tQ+/uCJI1dH06oRDg1xgU8YjuiI8XUZCpgsUR4nMn7O497qiCdL5TfiYJw/0j/AQ==",
|
||||
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "EOxdzTbj5/I7gOu48kM3Z0G54cJ1pII4tikrDqYhVSFRiJopUPtjb2IAG/Lg0D6kwZaIzbkEVpRKq707MF2BAw==",
|
||||
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "fZ8UhZhwmPn4IMxudbJkmrCsBj/A+EOH4cdzT8Vb75kI7SD9V5rR7gRCPFuVEL1nQ6lnlF2QW4Osmik2WOqeDA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "bptAbc1grlaQ8cQoR1RZ1p5GKEvnHUGOpyRoCmkK2xW2HMRD9YmY+t7HrARFfr14Bkp0dgdVKswzZWa6aUR3Cg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "4Q9ZzJhFaTqnaj66o+CHE/vtjY9qWOHO7UFYZrxs3uQdUaeIO5JUkXHtr4Fl7mDrZy0zaT8hL+R6g7zj8VLCCg==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "bzGWFkSPQg9xj1Rl8jBkGx3coMF6srLe509MLng4gQMqo8hHHWZTFI3235UsSJCkbnPjOolUWzVLtbDp5bMmDQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "pIB/KThS9GZ+HYf+SSd14rxawltaKKAZ1cFyYRPWpAnpEZ4q/Vz7G2o6bdEZ2r8bJsfBMMkzyPLRJQwpX0jODQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "FEbOGV+d8BvV4yurjBNz74nK0CT+yQSReXPRliMt9uuMNLKVuRMlUibmgD63sx7tv0hz7Z49VwYYZVlwEPNrDw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "x17n17jSDgrTCCphHNWZQ4ibHMGITtrhaDK1wBmWFqj6uzr3cWWXV6THxNJy4qbj9/yHVLICGNuW7wIOb0tzDQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "ldjMNYlSmOa3XjWrcqlDMrNybvFZ3T9aVVZevwt9TYxVgUrz/5qMui7KIAPdEins39c4fu0jayqM/1V0kjsUCA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "Cvq80OZLZ0rIywx1RjkV5cd6jKnpL0Rq9evyOxGqYwc0xGCGL1t2YqsoiyyODMeMphEdvdK+w+OMBge6OGk1DQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "17P0y74P0Ug92QlhioE6XJJ6p3tz8atS5Wp2SihaQZfnERG8CCjQ6RjVBSwINdvJuU05sGexOlb29Cjebc92DQ==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "wcXjEUKfLHopsy91qnShxa8/1D6wChOsFZcOkPnA0b61vPY7ceKlVMXcv+ECr35BiMsW4gHQG9MSMoyXo1ZJAw==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "8nTnxWCYCQ2mtzd8JL8kcRLsU6JrKVrfMNfI84ovoejxH7kAXCM6d3f1u6TFNPqrQjlIFlc1AyUoL7UoFsQJCA==",
|
||||
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "fs6ZLA1aFyUMWRpeSduc0nnv4cJjb3OIdxmUGaCr58D5GAGBSOOUQszzMWDwrEOCQ0JOsW27TM3VmliIhP1cBA==",
|
||||
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "YleZT+ZmBpdryTlsH4k+yoLYhyIJ1DMPYnEC8L2ixxJejAjcUBmn2GjXFSS+7JzK6g8aokV6NswVSqbuIVOJDQ=="
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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=="
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+28
-1
@@ -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
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -6,6 +6,7 @@ common_libs = [
|
||||
'util.cc',
|
||||
'ratekeeper.cc',
|
||||
'clutil.cc',
|
||||
'yuv.cc',
|
||||
]
|
||||
|
||||
_common = env.Library('common', common_libs, LIBS="json11")
|
||||
|
||||
+13
-1
@@ -3,6 +3,7 @@ import os
|
||||
import requests
|
||||
import unicodedata
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from functools import lru_cache
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.system.version import get_version
|
||||
|
||||
@@ -11,6 +12,16 @@ KEYS = {"id_rsa": "RS256",
|
||||
"id_ecdsa": "ES256"}
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def load_signing_key(private_key: str):
|
||||
# PyJWT re-parses a PEM string on every encode; an RSA parse is ~40ms, so cache the key object
|
||||
try:
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
return load_pem_private_key(private_key.encode(), password=None)
|
||||
except Exception:
|
||||
return private_key
|
||||
|
||||
|
||||
class BaseApi:
|
||||
def __init__(self, dongle_id, api_host, user_agent="openpilot-"):
|
||||
self.dongle_id = dongle_id
|
||||
@@ -38,7 +49,8 @@ class BaseApi:
|
||||
}
|
||||
if payload_extra is not None:
|
||||
payload.update(payload_extra)
|
||||
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
|
||||
key = load_signing_key(self.private_key) if self.private_key else self.private_key
|
||||
token = jwt.encode(payload, key, algorithm=self.jwt_algorithm)
|
||||
if isinstance(token, bytes):
|
||||
token = token.decode('utf8')
|
||||
return token
|
||||
|
||||
@@ -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"}},
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -208,6 +208,7 @@ struct CarState {
|
||||
blockPcmEnable @60 :Bool; # whether to allow PCM to enable this frame
|
||||
lateralAvailable @61 :Bool; # lateral control is available even if cruise is faulted
|
||||
cruiseFaultLateralMode @62 :Bool; # cruise is faulted but lateral control is still active
|
||||
carNotReady @95 :Bool; # car is transiently refusing engagement, not a fault
|
||||
radarDisableFailed @66 :Bool;
|
||||
# Physical vehicle odometer in kilometers. Zero means unavailable on this platform.
|
||||
odometer @67 :Float64;
|
||||
|
||||
@@ -53,6 +53,7 @@ class IQCarParams:
|
||||
enableGasInterceptor: bool = auto_field()
|
||||
longitudinalStoppingSpeedOverride: float = auto_field()
|
||||
stoppingDecelRateOverride: float = auto_field()
|
||||
longActiveWithGasOverride: bool = auto_field()
|
||||
|
||||
iqLateralNet: 'IQCarParams.LateralNet' = field(default_factory=lambda: IQCarParams.LateralNet())
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ class CarState(CarStateBase, IQCarState):
|
||||
|
||||
self.hands_on_level = 0
|
||||
self.acc_state_last = 0
|
||||
self.das_accCancel = False
|
||||
self.das_cancel_last = True
|
||||
self.das_control = None
|
||||
self.das_body_controls_dat = b""
|
||||
self._odometer_store = vehicle_state.VehicleOdometerStore(CP, Params())
|
||||
@@ -93,15 +95,24 @@ class CarState(CarStateBase, IQCarState):
|
||||
cruise_state = self.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp_party.vl["DI_state"]["DI_cruiseState"]), None)
|
||||
speed_units = self.can_define.dv["DI_state"]["DI_speedUnits"].get(int(cp_party.vl["DI_state"]["DI_speedUnits"]), None)
|
||||
acc_state = cp_ap_party.vl["DAS_control"]["DAS_accState"]
|
||||
# Respect all stock DAS cancel states, not just ACC_CANCEL_GENERIC_SILENT(13).
|
||||
# ELDA/ELK triggers ACC_CANCEL_GENERIC(0) which must also be forwarded.
|
||||
self.das_accCancel = acc_state in (0, 1, 2, 12, 13, 14, 15)
|
||||
|
||||
summon_state = self.can_define.dv["DI_state"]["DI_autoparkState"].get(int(cp_party.vl["DI_state"]["DI_autoparkState"]), None)
|
||||
cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
|
||||
self.cruise_override = cruise_state in ("OVERRIDE")
|
||||
self.update_summon_state(summon_state, cruise_enabled)
|
||||
|
||||
# Respect all stock DAS cancel states, not just ACC_CANCEL_GENERIC_SILENT(13).
|
||||
# ELDA/ELK triggers ACC_CANCEL_GENERIC(0) which must also be forwarded.
|
||||
# The stock AP is isolated from the party bus while the relay is closed, so its accState
|
||||
# free-runs between ACC_ON and ACC_CANCEL_GENERIC. Only a rising edge while ACC is engaged
|
||||
# is a real cancel; level-forwarding it pins DI_cruiseState to UNAVAILABLE and blocks engaging.
|
||||
das_cancel = acc_state in (0, 1, 2, 12, 13, 14, 15)
|
||||
if not cruise_enabled:
|
||||
self.das_accCancel = False
|
||||
elif das_cancel and not self.das_cancel_last:
|
||||
self.das_accCancel = True
|
||||
self.das_cancel_last = das_cancel
|
||||
|
||||
# Match panda safety cruise engaged logic
|
||||
ret.cruiseState.enabled = cruise_enabled and not self.summon
|
||||
if speed_units == "KPH":
|
||||
|
||||
@@ -44,6 +44,7 @@ FW_VERSIONS = {
|
||||
b'TeMYG4_Legacy3Y_0.0.0 (6),Y4003.04.0',
|
||||
b'TeMYG4_Main_0.0.0 (77),Y4003.05.4',
|
||||
b'TeMYG4_Main_0.0.0 (78),Y4003.06.0',
|
||||
b'TeMYG4_Main_0.0.0 (87),Y4003.09.3',
|
||||
],
|
||||
},
|
||||
CAR.TESLA_MODEL_X: {
|
||||
|
||||
@@ -2,7 +2,7 @@ from iqdbc.car import Bus, get_safety_config, structs
|
||||
from iqdbc.car.interfaces import CarInterfaceBase
|
||||
from iqdbc.car.tesla.carcontroller import CarController
|
||||
from iqdbc.car.tesla.carstate import CarState
|
||||
from iqdbc.car.tesla.values import TeslaSafetyFlags, TeslaFlags, CANBUS, CAR, DBC, LEGACY_DAS_STEERING_FW, Ecu
|
||||
from iqdbc.car.tesla.values import TeslaSafetyFlags, TeslaFlags, CANBUS, CAR, DBC, Ecu, is_legacy_das_steering
|
||||
from iqdbc.car.tesla.radar_interface import RadarInterface, RADAR_START_ADDR
|
||||
|
||||
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ
|
||||
@@ -41,7 +41,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.LONG_CONTROL.value
|
||||
|
||||
legacy_das = any(fw.ecu == Ecu.eps and fw.fwVersion in LEGACY_DAS_STEERING_FW.get(candidate, []) for fw in car_fw)
|
||||
legacy_das = any(fw.ecu == Ecu.eps and is_legacy_das_steering(candidate, fw.fwVersion) for fw in car_fw)
|
||||
if legacy_das:
|
||||
ret.flags |= TeslaFlags.LEGACY_DAS_STEERING.value
|
||||
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.LEGACY_DAS_STEERING.value
|
||||
|
||||
@@ -1,11 +1,110 @@
|
||||
from collections import defaultdict
|
||||
|
||||
import pytest
|
||||
|
||||
from iqdbc.car import gen_empty_fingerprint, structs
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.fw_versions import match_fw_to_car
|
||||
from iqdbc.car.tesla.fingerprints import FW_VERSIONS
|
||||
from iqdbc.car.tesla.interface import CarInterface
|
||||
from iqdbc.car.tesla.teslacan import TeslaCAN
|
||||
from iqdbc.car.tesla.radar_interface import RADAR_START_ADDR
|
||||
from iqdbc.car.tesla.carcontroller import CarController
|
||||
from iqdbc.car.tesla.values import CAR
|
||||
from iqdbc.car.tesla.values import (CAR, FW_PATTERN, LEGACY_DAS_STEERING_FW, TeslaFlags, TeslaSafetyFlags,
|
||||
get_platform_codes, is_legacy_das_steering)
|
||||
from iqdbc.can import CANPacker, CANParser
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
EPS_ADDR = 0x730
|
||||
|
||||
|
||||
def fw_match(fw: bytes):
|
||||
car_fw = [CarParams.CarFw(ecu=Ecu.eps, fwVersion=fw, address=EPS_ADDR, subAddress=0, brand='tesla')]
|
||||
exact, matches = match_fw_to_car(car_fw, '0' * 17, log=False)
|
||||
return exact, matches
|
||||
|
||||
|
||||
class TestTeslaFwPattern:
|
||||
def test_all_known_fw_parses(self):
|
||||
for car, ecus in FW_VERSIONS.items():
|
||||
for fws in ecus.values():
|
||||
for fw in fws:
|
||||
assert FW_PATTERN.match(fw) is not None, f'{car}: unparsed FW version: {fw}'
|
||||
|
||||
def test_model_code_identifies_one_platform(self):
|
||||
# a new platform reusing an existing model code would silently misfingerprint
|
||||
platforms = defaultdict(set)
|
||||
for car, ecus in FW_VERSIONS.items():
|
||||
for fws in ecus.values():
|
||||
for model, _, _ in get_platform_codes(fws):
|
||||
platforms[model].add(car)
|
||||
|
||||
for model, cars in platforms.items():
|
||||
assert len(cars) == 1, f'model code {model} maps to multiple platforms: {cars}'
|
||||
|
||||
def test_exact_match_still_wins(self):
|
||||
for car, ecus in FW_VERSIONS.items():
|
||||
for fws in ecus.values():
|
||||
for fw in fws:
|
||||
exact, matches = fw_match(fw)
|
||||
assert exact, f'{fw} fell back to fuzzy matching'
|
||||
assert matches == {car}, f'{fw} matched {matches}, expected {car}'
|
||||
|
||||
@pytest.mark.parametrize("fw, expected", [
|
||||
# a firmware bump within a known series, the case that used to fingerprint as MOCK
|
||||
(b'TeMYG4_Main_0.0.0 (99),Y4003.14.0', CAR.TESLA_MODEL_Y),
|
||||
(b'TeMYG4_Main_0.0.0 (99),E4H015.09.0', CAR.TESLA_MODEL_3),
|
||||
(b'TeM3_SP_XP002p2_0.0.0 (40),XPR003.12.0', CAR.TESLA_MODEL_X),
|
||||
# Tesla bumps the series within a platform (E4014 -> E4015, Y4002 -> Y4003)
|
||||
(b'TeMYG4_Main_0.0.0 (12),Y4004.01.0', CAR.TESLA_MODEL_Y),
|
||||
# an unknown model code is a car we don't support
|
||||
(b'TeCT_Main_0.0.0 (1),CT001.01.0', None),
|
||||
(b'garbage', None),
|
||||
])
|
||||
def test_unknown_fw_fuzzy_match(self, fw, expected):
|
||||
exact, matches = fw_match(fw)
|
||||
if expected is None:
|
||||
assert matches == set(), f'{fw} unexpectedly matched {matches}'
|
||||
else:
|
||||
assert not exact
|
||||
assert matches == {expected}
|
||||
|
||||
|
||||
class TestTeslaLegacyDasSteering:
|
||||
def test_reproduces_known_table(self):
|
||||
for car, ecus in FW_VERSIONS.items():
|
||||
for fws in ecus.values():
|
||||
for fw in fws:
|
||||
expected = fw in LEGACY_DAS_STEERING_FW.get(car, [])
|
||||
assert is_legacy_das_steering(car, fw) == expected, f'{car}: wrong legacy DAS verdict for {fw}'
|
||||
|
||||
@pytest.mark.parametrize("car, fw, expected", [
|
||||
# below a family's known modern cutoff (Y4/003 splits at 003.04.0)
|
||||
(CAR.TESLA_MODEL_Y, b'TeMYG4_Legacy3Y_0.0.0 (5),Y4003.03.9', True),
|
||||
(CAR.TESLA_MODEL_Y, b'TeMYG4_Main_0.0.0 (99),Y4003.14.0', False),
|
||||
# E4/015 splits at 015.04.5
|
||||
(CAR.TESLA_MODEL_3, b'TeMYG4_Main_0.0.0 (68),E4H015.03.9', True),
|
||||
(CAR.TESLA_MODEL_3, b'TeMYG4_Main_0.0.0 (99),E4H015.09.0', False),
|
||||
# families with no known modern FW: interpolate legacy, extrapolate modern
|
||||
(CAR.TESLA_MODEL_3, b'TeM3_E014p10_0.0.0 (16),E014.18.00', True),
|
||||
(CAR.TESLA_MODEL_3, b'TeM3_E014p10_0.0.0 (30),E014.22.0', False),
|
||||
# numeric, not lexical, version compare (XPR003.10.0 > XPR003.6.0)
|
||||
(CAR.TESLA_MODEL_X, b'TeM3_SP_XP002p2_0.0.0 (30),XPR003.9.0', True),
|
||||
# an unknown series has no history to compare against
|
||||
(CAR.TESLA_MODEL_Y, b'TeMYG4_Main_0.0.0 (12),Y4004.01.0', False),
|
||||
(CAR.TESLA_MODEL_Y, b'garbage', False),
|
||||
])
|
||||
def test_unknown_fw(self, car, fw, expected):
|
||||
assert is_legacy_das_steering(car, fw) == expected
|
||||
|
||||
def test_flag_set_from_fuzzy_match(self):
|
||||
for fw, legacy in ((b'TeMYG4_Legacy3Y_0.0.0 (5),Y4003.03.9', True),
|
||||
(b'TeMYG4_Main_0.0.0 (99),Y4003.14.0', False)):
|
||||
car_fw = [CarParams.CarFw(ecu=Ecu.eps, fwVersion=fw, address=EPS_ADDR, subAddress=0, brand='tesla')]
|
||||
CP = CarInterface.get_params(CAR.TESLA_MODEL_Y, gen_empty_fingerprint(), car_fw, False, False, False)
|
||||
assert bool(CP.flags & TeslaFlags.LEGACY_DAS_STEERING) == legacy
|
||||
assert bool(CP.safetyConfigs[0].safetyParam & TeslaSafetyFlags.LEGACY_DAS_STEERING) == legacy
|
||||
|
||||
|
||||
class TestTeslaFingerprint:
|
||||
def test_radar_detection(self):
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, IntFlag
|
||||
from functools import cache
|
||||
from iqdbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms
|
||||
from iqdbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL
|
||||
from iqdbc.car.structs import CarParams, CarState
|
||||
from iqdbc.car.docs_definitions import CarDocs, CarFootnote, CarHarness, CarParts, Column, SupportType
|
||||
from iqdbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
from iqdbc.car.fw_query_definitions import FwQueryConfig, LiveFwVersions, OfflineFwVersions, Request, StdQueries
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
@@ -69,16 +72,6 @@ class CAR(Platforms):
|
||||
)
|
||||
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.SUPPLIER_SOFTWARE_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.SUPPLIER_SOFTWARE_VERSION_RESPONSE],
|
||||
bus=0,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Cars with this EPS FW have a 2-bit DAS_steeringControlType and use TeslaFlags.LEGACY_DAS_STEERING
|
||||
LEGACY_DAS_STEERING_FW = {
|
||||
CAR.TESLA_MODEL_3: [
|
||||
@@ -116,6 +109,99 @@ LEGACY_DAS_STEERING_FW = {
|
||||
],
|
||||
}
|
||||
|
||||
# e.g. TeMYG4_Main_0.0.0 (87),Y4003.09.3
|
||||
# 11111_22222_______33____45__666666
|
||||
# 1 = EPS firmware program, 2 = build lineage, 3 = build number, 4 = model code,
|
||||
# 5 = trim/hardware variant, 6 = series and software version
|
||||
#
|
||||
# Only the model code identifies the vehicle: 1 and 2 are shared across models (Model 3 and
|
||||
# Model Y both ship TeM3_ and TeMYG4_ firmware) and 3 is only monotone within one lineage.
|
||||
FW_PATTERN = re.compile(rb'^Te[A-Z0-9]+_[A-Za-z0-9_]+_0\.0\.0 \(\d+\),' +
|
||||
rb'(?P<model>E4|E|Y4|Y|XP)[A-Z]{0,2}(?P<series>\d{3})\.(?P<version>\d+(?:\.\d+)*)$')
|
||||
|
||||
|
||||
def get_platform_codes(fw_versions: list[bytes] | set[bytes]) -> set[tuple[bytes, bytes, tuple[int, ...]]]:
|
||||
codes = set()
|
||||
for fw in fw_versions:
|
||||
match = FW_PATTERN.match(fw)
|
||||
if match is not None:
|
||||
codes.add((match.group('model'), match.group('series'),
|
||||
tuple(int(v) for v in match.group('version').split(b'.'))))
|
||||
|
||||
return codes
|
||||
|
||||
|
||||
@cache
|
||||
def _das_steering_cutoffs() -> dict[tuple[str, bytes, bytes], tuple[tuple[int, ...] | None, tuple[int, ...] | None]]:
|
||||
"""Per (platform, model code, series) family, the oldest known modern version and the newest
|
||||
known legacy version. Tesla only ever moves a family forward, so these bound the split."""
|
||||
# imported here because fingerprints.py imports this module
|
||||
from iqdbc.car.tesla.fingerprints import FW_VERSIONS
|
||||
|
||||
legacy: defaultdict[tuple, set] = defaultdict(set)
|
||||
modern: defaultdict[tuple, set] = defaultdict(set)
|
||||
for platform, ecus in FW_VERSIONS.items():
|
||||
known_legacy = LEGACY_DAS_STEERING_FW.get(platform, [])
|
||||
for fws in ecus.values():
|
||||
for fw in fws:
|
||||
for model, series, version in get_platform_codes([fw]):
|
||||
(legacy if fw in known_legacy else modern)[(platform, model, series)].add(version)
|
||||
|
||||
return {k: (min(modern[k]) if k in modern else None, max(legacy[k]) if k in legacy else None)
|
||||
for k in set(legacy) | set(modern)}
|
||||
|
||||
|
||||
def is_legacy_das_steering(candidate: str, fw: bytes) -> bool:
|
||||
"""Whether an EPS FW uses the 2-bit DAS_steeringControlType. Unknown firmware newer than
|
||||
anything in a family is treated as modern: cars only move forward, and someone left behind
|
||||
on legacy software can force the platform with CarPlatformBundle."""
|
||||
if fw in LEGACY_DAS_STEERING_FW.get(candidate, []):
|
||||
return True
|
||||
|
||||
codes = get_platform_codes([fw])
|
||||
if not len(codes):
|
||||
return False
|
||||
|
||||
model, series, version = next(iter(codes))
|
||||
first_modern, last_legacy = _das_steering_cutoffs().get((candidate, model, series), (None, None))
|
||||
if first_modern is not None:
|
||||
return version < first_modern
|
||||
|
||||
return last_legacy is not None and version <= last_legacy
|
||||
|
||||
|
||||
def match_fw_to_car_fuzzy(live_fw_versions: LiveFwVersions, vin: str, offline_fw_versions: OfflineFwVersions) -> set[str]:
|
||||
# Tesla fingerprints on the EPS alone and Ecu.eps is in FUZZY_EXCLUDE_ECUS, so the generic fuzzy
|
||||
# matcher can never match a Tesla. Match on the model code, which survives the EPS version bumps
|
||||
# that ship with Tesla software updates. The series is deliberately not required to be known:
|
||||
# Tesla bumps it within a platform (E4014 -> E4015, Y4002 -> Y4003).
|
||||
offline_codes: defaultdict[bytes, set[str]] = defaultdict(set)
|
||||
for candidate, ecus in offline_fw_versions.items():
|
||||
for fws in ecus.values():
|
||||
for model, _, _ in get_platform_codes(fws):
|
||||
offline_codes[model].add(candidate)
|
||||
|
||||
candidates: set[str] = set()
|
||||
for ecu, addr, sub_addr in {e for ecus in offline_fw_versions.values() for e in ecus}:
|
||||
if ecu != Ecu.eps:
|
||||
continue
|
||||
for model, _, _ in get_platform_codes(live_fw_versions.get((addr, sub_addr), set())):
|
||||
candidates |= offline_codes[model]
|
||||
|
||||
return candidates if len(candidates) == 1 else set()
|
||||
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.SUPPLIER_SOFTWARE_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.SUPPLIER_SOFTWARE_VERSION_RESPONSE],
|
||||
bus=0,
|
||||
)
|
||||
],
|
||||
match_fw_to_car_fuzzy=match_fw_to_car_fuzzy,
|
||||
)
|
||||
|
||||
|
||||
class CANBUS:
|
||||
party = 0
|
||||
|
||||
@@ -381,6 +381,9 @@ class CarState(CarStateBase):
|
||||
|
||||
ret.cruiseState.available = pt_cp.vl["Motor_51"]["TSK_Status"] in (2, 3, 4, 5)
|
||||
ret.cruiseState.enabled = pt_cp.vl["Motor_51"]["TSK_Status"] in (3, 4, 5)
|
||||
# TSK winds its braking down through brake_only after a driver brake. Requesting drive-off in this
|
||||
# state can fault TSK, and stock refuses to engage here as well, so block entry until it clears.
|
||||
ret.carNotReady = pt_cp.vl["Motor_51"]["TSK_Status"] == 5 # brake_only
|
||||
acc_values = ext_cp.vl.get("MEB_ACC_01", ext_cp.vl.get("ACC_19", {}))
|
||||
ret.cruiseState.nonAdaptive = bool(acc_values.get("ACC_Limiter_Mode", 0)) if self.CP.pcmCruise else bool(pt_cp.vl["Motor_51"]["TSK_Limiter_ausgewaehlt"])
|
||||
|
||||
@@ -585,6 +588,7 @@ class CarState(CarStateBase):
|
||||
ret.cruiseFaultLateralMode = allow_lat_only and cruise_faulted and cruise_main_available
|
||||
ret.lateralAvailable = ret.cruiseState.available or ret.cruiseFaultLateralMode
|
||||
ret.blockPcmEnable = ret.cruiseFaultLateralMode
|
||||
ret.carNotReady = bool(pt_cp.vl["Bremse_8"]["BR8_Sta_VerzReg"])
|
||||
|
||||
# Update ACC setpoint. When the setpoint reads as 255, the driver has not
|
||||
# yet established an ACC setpoint, so treat it as zero.
|
||||
|
||||
@@ -4,7 +4,9 @@ from pathlib import Path
|
||||
env = Environment(ENV=os.environ)
|
||||
|
||||
# short colored build output, if the top-level pretty tool is present (main-repo build)
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
_pretty = Dir('#tools/scons/site_tools').File('pretty.py')
|
||||
if not _pretty.exists():
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
if _pretty.exists():
|
||||
env.Tool('pretty', toolpath=[_pretty.dir.abspath])
|
||||
if not hasattr(env, 'PrettyAction'):
|
||||
|
||||
@@ -26,7 +26,9 @@ env = Environment(
|
||||
)
|
||||
|
||||
# short colored build output, if the top-level pretty tool is present (main-repo build)
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
_pretty = Dir('#tools/scons/site_tools').File('pretty.py')
|
||||
if not _pretty.exists():
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
if _pretty.exists():
|
||||
env.Tool('pretty', toolpath=[_pretty.dir.abspath])
|
||||
|
||||
|
||||
@@ -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'}")
|
||||
@@ -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,27 +4,22 @@ EARTH_RADIUS = 6378137
|
||||
# Mapbox API limits
|
||||
FREE_MAPBOX_REQUESTS = 100_000
|
||||
|
||||
# Speed limit offset maps for different unit systems
|
||||
# Each entry is (min_speed_ms, max_speed_ms, param_name)
|
||||
# Speed limit offset zones for different unit systems
|
||||
# Each entry is (min_speed_ms, max_speed_ms, param_name); the param value is a
|
||||
# percent offset applied to the resolved limit (e.g. 10 -> +10%), lower bound inclusive
|
||||
|
||||
OFFSET_PERCENT_MAX = 50.0
|
||||
|
||||
OFFSET_MAP_IMPERIAL = [
|
||||
(0, 11.2, "speed_limit_offset1"), # 0-24 mph
|
||||
(11.2, 15.2, "speed_limit_offset2"), # 25-34 mph
|
||||
(15.2, 19.6, "speed_limit_offset3"), # 35-44 mph
|
||||
(19.6, 24.1, "speed_limit_offset4"), # 45-54 mph
|
||||
(24.1, 28.6, "speed_limit_offset5"), # 55-64 mph
|
||||
(28.6, 33.1, "speed_limit_offset6"), # 65-74 mph
|
||||
(33.1, 44.2, "speed_limit_offset7"), # 75-99 mph
|
||||
(0, 8.94, "speed_limit_offset1"), # 0-20 mph
|
||||
(8.94, 17.88, "speed_limit_offset2"), # 20-40 mph
|
||||
(17.88, float("inf"), "speed_limit_offset3"), # 40+ mph
|
||||
]
|
||||
|
||||
OFFSET_MAP_METRIC = [
|
||||
(0, 8.1, "speed_limit_offset1"), # 0-29 km/h
|
||||
(8.1, 13.6, "speed_limit_offset2"), # 30-49 km/h
|
||||
(13.6, 16.4, "speed_limit_offset3"), # 50-59 km/h
|
||||
(16.4, 21.9, "speed_limit_offset4"), # 60-79 km/h
|
||||
(21.9, 27.5, "speed_limit_offset5"), # 80-99 km/h
|
||||
(27.5, 33.1, "speed_limit_offset6"), # 100-119 km/h
|
||||
(33.1, 38.9, "speed_limit_offset7"), # 120-140 km/h
|
||||
(0, 8.33, "speed_limit_offset1"), # 0-30 km/h
|
||||
(8.33, 16.67, "speed_limit_offset2"), # 30-60 km/h
|
||||
(16.67, float("inf"), "speed_limit_offset3"), # 60+ km/h
|
||||
]
|
||||
|
||||
# Speed limit filler constants
|
||||
|
||||
Executable
+108
@@ -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()
|
||||
@@ -17,7 +17,7 @@ from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.iqpilot.common.k3_slc_log import k3_slc_log
|
||||
from openpilot.iqpilot.common.slc_utilities import calculate_bearing_offset, is_url_pingable
|
||||
from openpilot.iqpilot.common.slc_variables import FREE_MAPBOX_REQUESTS, OFFSET_MAP_IMPERIAL, OFFSET_MAP_METRIC
|
||||
from openpilot.iqpilot.common.slc_variables import FREE_MAPBOX_REQUESTS, OFFSET_MAP_IMPERIAL, OFFSET_MAP_METRIC, OFFSET_PERCENT_MAX
|
||||
|
||||
try:
|
||||
import requests
|
||||
@@ -354,6 +354,8 @@ class SpeedLimitController:
|
||||
pass
|
||||
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
self._offset_cache = {}
|
||||
self._offset_cache_t = 0.0
|
||||
self._last_mapbox_log_t = 0.0
|
||||
self._last_mapbox_diag_t = 0.0
|
||||
self._last_mapbox_diag_message = None
|
||||
@@ -416,17 +418,33 @@ class SpeedLimitController:
|
||||
return self._assist.output_a_target
|
||||
|
||||
def get_offset(self, is_metric):
|
||||
target = self._assist.target
|
||||
# offsets only apply to real limit sources: fallback set-speed publishes "None",
|
||||
# construction clamps must never be inflated
|
||||
if target <= 0 or self._assist.source in ("None", "Construction"):
|
||||
return 0.0
|
||||
offset_map = OFFSET_MAP_METRIC if is_metric else OFFSET_MAP_IMPERIAL
|
||||
for low, high, offset_param in offset_map:
|
||||
if low < self._assist.target < high:
|
||||
offset_value = self.params.get(offset_param)
|
||||
if offset_value is not None:
|
||||
if isinstance(offset_value, bytes):
|
||||
return float(offset_value.decode("utf-8"))
|
||||
return float(offset_value)
|
||||
return 0.0
|
||||
if low <= target < high:
|
||||
percent = float(np.clip(self._get_offset_percent(offset_param), -OFFSET_PERCENT_MAX, OFFSET_PERCENT_MAX))
|
||||
return target * percent / 100.0
|
||||
return 0.0
|
||||
|
||||
def _get_offset_percent(self, offset_param):
|
||||
now_mono = time.monotonic()
|
||||
if now_mono - self._offset_cache_t >= 5.0:
|
||||
self._offset_cache.clear()
|
||||
self._offset_cache_t = now_mono
|
||||
if offset_param not in self._offset_cache:
|
||||
offset_value = self.params.get(offset_param)
|
||||
try:
|
||||
if isinstance(offset_value, bytes):
|
||||
offset_value = offset_value.decode("utf-8")
|
||||
self._offset_cache[offset_param] = float(offset_value) if offset_value is not None else 0.0
|
||||
except (ValueError, TypeError):
|
||||
self._offset_cache[offset_param] = 0.0
|
||||
return self._offset_cache[offset_param]
|
||||
|
||||
@staticmethod
|
||||
def _is_alive(sm, key):
|
||||
if hasattr(sm, "alive"):
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.iqpilot.common.slc_variables import OFFSET_MAP_IMPERIAL
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.slc_vcruise import SLCVCruise, CRUISING_SPEED
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.speed_limit_controller import SpeedLimitController, POLICY_MAP_DATA_PRIORITY, POLICY_COMBINED
|
||||
|
||||
@@ -92,7 +94,7 @@ def test_speed_limit_controller_resolves_source_by_priority():
|
||||
slc_params = _base_slc_params_controller()
|
||||
slc_params["slc_policy"] = POLICY_MAP_DATA_PRIORITY
|
||||
|
||||
controller.update_limits(25.0, None, True, 30.0, 27.0, sm, slc_params)
|
||||
controller.update_limits(25.0, datetime.now(), True, 30.0, 27.0, sm, slc_params)
|
||||
assert controller.active_source == "Map Data"
|
||||
assert controller.active_target == 18.0
|
||||
|
||||
@@ -112,7 +114,7 @@ def test_speed_limit_controller_combined_mode_prefers_smallest_limit():
|
||||
slc_params = _base_slc_params_controller()
|
||||
slc_params["slc_policy"] = POLICY_COMBINED
|
||||
|
||||
controller.update_limits(28.0, None, True, 31.0, 27.0, sm, slc_params)
|
||||
controller.update_limits(28.0, datetime.now(), True, 31.0, 27.0, sm, slc_params)
|
||||
assert controller.active_source == "Map Data"
|
||||
assert controller.active_target == 16.0
|
||||
|
||||
@@ -128,6 +130,7 @@ def test_slc_vcruise_applies_target_without_increasing_cruise():
|
||||
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": True,
|
||||
"speed_limit_mode": 3,
|
||||
"show_speed_limits": False,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
@@ -163,6 +166,7 @@ def test_slc_vcruise_show_only_does_not_modify_cruise():
|
||||
slc.slc.active_source = "Map Data"
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": False,
|
||||
"speed_limit_mode": 1,
|
||||
"show_speed_limits": True,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
@@ -198,6 +202,7 @@ def test_slc_vcruise_auto_raises_for_higher_limit_when_confirmation_disabled():
|
||||
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": True,
|
||||
"speed_limit_mode": 3,
|
||||
"show_speed_limits": False,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
@@ -232,6 +237,7 @@ def test_slc_vcruise_does_not_auto_raise_when_higher_confirmation_enabled():
|
||||
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": True,
|
||||
"speed_limit_mode": 3,
|
||||
"show_speed_limits": False,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
@@ -392,6 +398,50 @@ def test_construction_zone_never_raises_cruise_even_with_auto_raise():
|
||||
assert abs(out - 60.0 * CV.MPH_TO_MS) < 1e-6
|
||||
|
||||
|
||||
def _offset_controller(pct1=10.0, pct2=5.0, pct3=8.0):
|
||||
params = FakeParams()
|
||||
params.put("speed_limit_offset1", pct1)
|
||||
params.put("speed_limit_offset2", pct2)
|
||||
params.put("speed_limit_offset3", pct3)
|
||||
controller = SpeedLimitController(params)
|
||||
controller._assist.source = "Map Data"
|
||||
return controller
|
||||
|
||||
|
||||
def test_get_offset_percent_per_zone():
|
||||
controller = _offset_controller()
|
||||
|
||||
controller._assist.target = 6.7 # ~15 mph -> zone 1
|
||||
assert abs(controller.get_offset(False) - 6.7 * 0.10) < 1e-9
|
||||
|
||||
controller._assist.target = 13.4 # ~30 mph -> zone 2
|
||||
assert abs(controller.get_offset(False) - 13.4 * 0.05) < 1e-9
|
||||
|
||||
controller._assist.target = 31.3 # ~70 mph -> zone 3 (open-ended)
|
||||
assert abs(controller.get_offset(False) - 31.3 * 0.08) < 1e-9
|
||||
|
||||
|
||||
def test_get_offset_zone_lower_bound_inclusive():
|
||||
controller = _offset_controller()
|
||||
boundary = OFFSET_MAP_IMPERIAL[1][0]
|
||||
controller._assist.target = boundary
|
||||
assert abs(controller.get_offset(False) - boundary * 0.05) < 1e-9
|
||||
|
||||
|
||||
def test_get_offset_zero_without_real_limit_source():
|
||||
for source in ("None", "Construction"):
|
||||
controller = _offset_controller()
|
||||
controller._assist.source = source
|
||||
controller._assist.target = 30.0
|
||||
assert controller.get_offset(False) == 0.0
|
||||
|
||||
|
||||
def test_get_offset_percent_clamped():
|
||||
controller = _offset_controller(pct3=500.0)
|
||||
controller._assist.target = 30.0
|
||||
assert abs(controller.get_offset(False) - 30.0 * 0.50) < 1e-9
|
||||
|
||||
|
||||
def test_construction_zone_fires_event_once_per_zone_entry():
|
||||
from cereal import custom
|
||||
event = custom.IQOnroadEvent.EventName.constructionZoneDetected
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -47,6 +47,8 @@ class IQHudRenderer(HudRenderer):
|
||||
has_limit = self.speed_limit_renderer.speed_limit_valid or self.speed_limit_renderer.speed_limit_last_valid
|
||||
self.limit_available = has_limit
|
||||
self.limit_speed_text = str(round(self.speed_limit_renderer.speed_limit_last)) if has_limit else "---"
|
||||
offset = round(self.speed_limit_renderer.speed_limit_offset)
|
||||
self.limit_offset_text = f"{offset:+d}" if has_limit and offset != 0 else ""
|
||||
self.turn_signal_controller.update()
|
||||
self.speed_renderer.update()
|
||||
self.soft_warning_renderer.update()
|
||||
|
||||
+16
-4
@@ -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
@@ -23,7 +23,7 @@ if [ -z "$AGNOS_VERSION" ]; then
|
||||
|
||||
case "$DEVICE_MODEL" in
|
||||
*)
|
||||
export AGNOS_VERSION="IQ.OS 4.9"
|
||||
export AGNOS_VERSION="IQ.OS 4.9.3"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
+3
-1
@@ -93,7 +93,9 @@ def build_project(project_name, project, main, extra_flags):
|
||||
)
|
||||
|
||||
# short colored build output shared with the top-level build (if present)
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
_pretty = Dir('#tools/scons/site_tools').File('pretty.py')
|
||||
if not _pretty.exists():
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
if _pretty.exists():
|
||||
env.Tool('pretty', toolpath=[_pretty.dir.abspath])
|
||||
if not hasattr(env, 'PrettyAction'):
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -27,7 +27,9 @@ if system == "Darwin":
|
||||
env.PrependENVPath('PATH', '/opt/homebrew/bin')
|
||||
|
||||
# short colored build output, if the top-level pretty tool is present (main-repo build)
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
_pretty = Dir('#tools/scons/site_tools').File('pretty.py')
|
||||
if not _pretty.exists():
|
||||
_pretty = Dir('#site_scons/site_tools').File('pretty.py')
|
||||
if _pretty.exists():
|
||||
env.Tool('pretty', toolpath=[_pretty.dir.abspath])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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))
|
||||
@@ -32,6 +32,7 @@ class FontSizes:
|
||||
max_speed: int = 28
|
||||
set_speed: int = 74
|
||||
limit_speed: int = 64
|
||||
limit_offset: int = 30
|
||||
limit_unit: int = 22
|
||||
limit_label: int = 24
|
||||
|
||||
@@ -70,6 +71,7 @@ class HudRenderer(Widget):
|
||||
self.speed: float = 0.0
|
||||
self.v_ego_cluster_seen: bool = False
|
||||
self.limit_speed_text: str = "---"
|
||||
self.limit_offset_text: str = ""
|
||||
self.limit_available: bool = False
|
||||
|
||||
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
@@ -168,14 +170,28 @@ class HudRenderer(Widget):
|
||||
else:
|
||||
limit_value_size = 48
|
||||
limit_value_width = measure_text_cached(self._font_bold, limit_value_text, limit_value_size).x
|
||||
limit_offset_text = self.limit_offset_text if self.limit_available else ""
|
||||
limit_offset_width = 0.0
|
||||
if limit_offset_text:
|
||||
limit_offset_width = measure_text_cached(self._font_semi_bold, limit_offset_text, FONT_SIZES.limit_offset).x + 8
|
||||
limit_value_x = x + (set_speed_width - limit_value_width - limit_offset_width) / 2
|
||||
rl.draw_text_ex(
|
||||
self._font_bold,
|
||||
limit_value_text,
|
||||
rl.Vector2(x + (set_speed_width - limit_value_width) / 2, y + 14),
|
||||
rl.Vector2(limit_value_x, y + 14),
|
||||
limit_value_size,
|
||||
0,
|
||||
limit_value_color,
|
||||
)
|
||||
if limit_offset_text:
|
||||
rl.draw_text_ex(
|
||||
self._font_semi_bold,
|
||||
limit_offset_text,
|
||||
rl.Vector2(limit_value_x + limit_value_width + 8, y + 22),
|
||||
FONT_SIZES.limit_offset,
|
||||
0,
|
||||
COLORS.WHITE_TRANSLUCENT,
|
||||
)
|
||||
|
||||
if self.limit_available:
|
||||
limit_unit_text = tr("LIMIT")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -67,28 +67,28 @@
|
||||
},
|
||||
{
|
||||
"name": "boot",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25.img.xz",
|
||||
"hash": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
|
||||
"hash_raw": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
|
||||
"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": "2c969938fdd59528e6244820b77ec6b2cef9ab49cc9fedd490b0b3d195c189e4"
|
||||
"ondevice_hash": "fdcb135e8412a896c4a882738a7022f6b7779939d8716f24b9a566b0b3ff3757"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz",
|
||||
"hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9",
|
||||
"hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"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": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff",
|
||||
"ondevice_hash": "9ed5bc147a6d4c2e0a89562f31096a7802485ee8e740defeab7674abbf6d4baa",
|
||||
"alt": {
|
||||
"hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img",
|
||||
"hash": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img",
|
||||
"size": 6291456000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
+LyP/p4uBBxbTwRwPrwR/2qPaHkAXmlFkg0GdxOBuujPY89GxP4t5SNKxirqiw9LmMcfA0JfVwMeR3QD9rQ6Dg==
|
||||
LxtrHkl5DFbSWnd1DZCkinmXiKFUVUausf0oYpa97dyAp3QGpWx7wrEAUZuJbrERQtdIV72ZU2eKOaQwGMBQBg==
|
||||
|
||||
@@ -56,28 +56,28 @@
|
||||
},
|
||||
{
|
||||
"name": "boot",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25.img.xz",
|
||||
"hash": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
|
||||
"hash_raw": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
|
||||
"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": "2c969938fdd59528e6244820b77ec6b2cef9ab49cc9fedd490b0b3d195c189e4"
|
||||
"ondevice_hash": "fdcb135e8412a896c4a882738a7022f6b7779939d8716f24b9a566b0b3ff3757"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz",
|
||||
"hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9",
|
||||
"hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"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": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff",
|
||||
"ondevice_hash": "9ed5bc147a6d4c2e0a89562f31096a7802485ee8e740defeab7674abbf6d4baa",
|
||||
"alt": {
|
||||
"hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img",
|
||||
"hash": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img",
|
||||
"size": 6291456000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
RCYk34Ifjx7S9PIhPm6p49vFMhamPwvw8Lfbb6a6HO8sUOi3J2PcY4VDUcqQZWU6+Am++HP3nJSea8OmAsckAg==
|
||||
mKCTfzNHr1d0BYPdmXQRPGpnVuLaDLGZGLbq+86ZJQaGIK1ekcKeYeycoX8BTMNFr+89Tnd77M/CvmjM8yteCg==
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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, ×tamp);
|
||||
@@ -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},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(" ")
|
||||
|
||||
@@ -17,6 +17,10 @@ from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
MAX_CRASH_BACKOFF = 300.0
|
||||
CRASH_RESET_TIME = 60.0
|
||||
CRASH_LOOP_THRESHOLD = 6
|
||||
|
||||
try:
|
||||
from openpilot.system.proprietary_runtime.runtime_paths import preferred_runner_path
|
||||
except ModuleNotFoundError:
|
||||
@@ -82,6 +86,10 @@ class ManagerProcess(ABC):
|
||||
name = ""
|
||||
shutting_down = False
|
||||
restart_if_crash = False
|
||||
crash_count = 0
|
||||
last_restart_time = 0.0
|
||||
last_alive_time = 0.0
|
||||
crash_loop_logged = False
|
||||
|
||||
@abstractmethod
|
||||
def prepare(self) -> None:
|
||||
@@ -318,13 +326,33 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None
|
||||
not_run = []
|
||||
|
||||
running = []
|
||||
now = time.monotonic()
|
||||
for p in procs:
|
||||
if p.enabled and p.name not in not_run and p.should_run(started, params, CP):
|
||||
if p.restart_if_crash and p.proc is not None and not p.proc.is_alive():
|
||||
cloudlog.error(f'Restarting {p.name} (exitcode {p.proc.exitcode})')
|
||||
p.restart()
|
||||
if p.restart_if_crash and p.proc is not None and p.proc.is_alive():
|
||||
p.last_alive_time = now
|
||||
elif p.restart_if_crash and p.proc is not None:
|
||||
# uptime, not time-since-restart: the latter also counts the backoff wait,
|
||||
# which would reset the counter as soon as backoff exceeds CRASH_RESET_TIME
|
||||
if p.last_alive_time - p.last_restart_time > CRASH_RESET_TIME:
|
||||
p.crash_count = 0
|
||||
p.crash_loop_logged = False
|
||||
|
||||
backoff = 0.0 if not p.crash_count else min(MAX_CRASH_BACKOFF, 2.0 ** (p.crash_count - 1))
|
||||
if now - p.last_restart_time >= backoff:
|
||||
p.crash_count += 1
|
||||
p.last_restart_time = now
|
||||
cloudlog.error(f'Restarting {p.name} (exitcode {p.proc.exitcode}) [crash {p.crash_count}]')
|
||||
if p.crash_count >= CRASH_LOOP_THRESHOLD and not p.crash_loop_logged:
|
||||
# never stop retrying: giving up on hardwared or ui is worse than restarting slowly
|
||||
cloudlog.error(f'{p.name} is in a crash loop, backing off to {MAX_CRASH_BACKOFF}s between restarts')
|
||||
p.crash_loop_logged = True
|
||||
p.restart()
|
||||
running.append(p)
|
||||
else:
|
||||
p.crash_count = 0
|
||||
p.crash_loop_logged = False
|
||||
p.last_alive_time = 0.0
|
||||
p.stop(block=False)
|
||||
|
||||
for p in running:
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -961,6 +961,10 @@ class WifiManager:
|
||||
settings['connection'] = {}
|
||||
|
||||
changes = False
|
||||
# NetworkManager updates an active modem connection in place. Quectel
|
||||
# modems keep the existing PDP bearer in that case, so an APN change
|
||||
# does not take effect until LTE is disconnected and reactivated.
|
||||
apn_settings_changed = False
|
||||
auto_config = apn == ""
|
||||
initial_eps_apn = apn if not auto_config else ""
|
||||
|
||||
@@ -968,11 +972,13 @@ class WifiManager:
|
||||
cloudlog.warning(f'Changing gsm.auto-config to {auto_config}')
|
||||
settings['gsm']['auto-config'] = ('b', auto_config)
|
||||
changes = True
|
||||
apn_settings_changed = True
|
||||
|
||||
if settings['gsm'].get('apn', ('s', ''))[1] != apn:
|
||||
cloudlog.warning(f'Changing gsm.apn to {apn}')
|
||||
settings['gsm']['apn'] = ('s', apn)
|
||||
changes = True
|
||||
apn_settings_changed = True
|
||||
|
||||
if settings['gsm'].get('home-only', ('b', False))[1] == roaming:
|
||||
cloudlog.warning(f'Changing gsm.home-only to {not roaming}')
|
||||
@@ -983,11 +989,13 @@ class WifiManager:
|
||||
cloudlog.warning(f'Changing gsm.initial-eps-bearer-configure to {bool(initial_eps_apn)}')
|
||||
settings['gsm']['initial-eps-bearer-configure'] = ('b', bool(initial_eps_apn))
|
||||
changes = True
|
||||
apn_settings_changed = True
|
||||
|
||||
if settings['gsm'].get('initial-eps-bearer-apn', ('s', ''))[1] != initial_eps_apn:
|
||||
cloudlog.warning(f'Changing gsm.initial-eps-bearer-apn to {initial_eps_apn}')
|
||||
settings['gsm']['initial-eps-bearer-apn'] = ('s', initial_eps_apn)
|
||||
changes = True
|
||||
apn_settings_changed = True
|
||||
|
||||
# Unknown means NetworkManager decides
|
||||
metered_int = int(MeteredType.UNKNOWN if metered else MeteredType.NO)
|
||||
@@ -1005,7 +1013,10 @@ class WifiManager:
|
||||
cloudlog.warning(f"Failed to update GSM settings: {reply}")
|
||||
return
|
||||
|
||||
self._activate_modem_connection(lte_connection_path)
|
||||
if apn_settings_changed:
|
||||
self._restart_modem_connection(lte_connection_path)
|
||||
else:
|
||||
self._activate_modem_connection(lte_connection_path)
|
||||
except Exception as e:
|
||||
cloudlog.exception(f"Error updating GSM settings: {e}")
|
||||
|
||||
@@ -1034,6 +1045,40 @@ class WifiManager:
|
||||
except Exception as e:
|
||||
cloudlog.exception(f"Error activating modem connection: {e}")
|
||||
|
||||
def _restart_modem_connection(self, connection_path: str):
|
||||
"""Reconnect LTE so a changed APN is used for a new PDP bearer."""
|
||||
try:
|
||||
for active_conn in self._get_active_connections():
|
||||
conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
|
||||
active_conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1]
|
||||
if active_conn_path == connection_path:
|
||||
cloudlog.warning("Restarting LTE connection to apply APN settings")
|
||||
reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (active_conn,)))
|
||||
if reply.header.message_type == MessageType.error:
|
||||
cloudlog.warning(f"Failed to deactivate LTE connection: {reply}")
|
||||
return
|
||||
|
||||
for _ in range(20):
|
||||
if not self._is_connection_active(connection_path):
|
||||
break
|
||||
time.sleep(0.25)
|
||||
else:
|
||||
cloudlog.warning("LTE connection did not deactivate after APN change")
|
||||
return
|
||||
break
|
||||
|
||||
self._activate_modem_connection(connection_path)
|
||||
except Exception as e:
|
||||
cloudlog.exception(f"Error restarting modem connection: {e}")
|
||||
|
||||
def _is_connection_active(self, connection_path: str) -> bool:
|
||||
for active_conn in self._get_active_connections():
|
||||
conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
|
||||
active_conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1]
|
||||
if active_conn_path == connection_path:
|
||||
return True
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
if not self._exit:
|
||||
self._exit = True
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user