diff --git a/.gitignore b/.gitignore
index c6b86f05..d733b470 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,6 +68,7 @@ compare_runtime*.html
iqpilot/selfdrive/pandad/pandad
iqpilot/selfdrive/pandad/tests/test_panda_safety
+iqpilot/selfdrive/pandad/tests/test_ignition_hold
iqpilot/cereal/services.h
iqpilot/cereal/gen
iqpilot/cereal/messaging/bridge
@@ -180,3 +181,5 @@ iqpilot/tools/iqpilot/big_hud_preview.py
artifacts/monrovia_runtime_packages/
artifacts/iqpilot_*_private/consumer.challenge
/docs/
+/dist/
+nuitka-crash-report*
diff --git a/LICENSE b/LICENSE
index 688ca902..6a859020 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-IQ.Lvbs License v1.8.2
+IQ.Lvbs License v1.8.3
Copyright (c) 2026 IQ.Lvbs LLC, a part of Project Teal Lvbs Inc. All Rights Reserved.
@@ -42,7 +42,8 @@ You may NOT:
5. Sublicense, sell, or transfer rights to the Software.
6. Use the Software and/or its source code to compete with or create a
- substantially similar product.
+ substantially similar product that is not a personal non-commercial fork of IQ.Pilot that complies with
+ all other subsections of this license.
7. Use the Software in closed source software not licensed by IQ.Lvbs LLC.
@@ -54,9 +55,8 @@ OWNERSHIP
All rights, title, and interest in the Software remain exclusively with the
Copyright Holder. Any modifications, improvements, or derivative works you
-create based on the Software are owned by the Copyright Holder. By
-contributing modifications, you irrevocably assign all rights to the
-Copyright Holder.
+create based on the Software are owned by you (licensing of that work is in no manner the liability of IQ Lvbs or its associates).
+Any derivative in which IQ.Pilot source code is used and modified shall remain the property of IQ Lvbs with the exception of the modifications you have added.
PROPRIETARY COMPONENTS
diff --git a/artifacts/iqpilot_alc_private/.source_sha b/artifacts/iqpilot_alc_private/.source_sha
deleted file mode 100644
index d3f47f5f..00000000
--- a/artifacts/iqpilot_alc_private/.source_sha
+++ /dev/null
@@ -1 +0,0 @@
-76a146832ea70711033c567d35af66df13221c2e
diff --git a/artifacts/iqpilot_alc_private/manifest.json b/artifacts/iqpilot_alc_private/manifest.json
deleted file mode 100644
index d97a74b5..00000000
--- a/artifacts/iqpilot_alc_private/manifest.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "python/_iqclosure/_vendored.json": {
- "mode": 420,
- "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
- "size": 3
- },
- "python/iqpilot_private/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/iqpilot_private/konn3kt/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/iqpilot_private/konn3kt/iqlvbs/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "16ce15884bfd765c17c05da0e2474aa1e7ddd8fcf68925b2169e19042235b4b6",
- "size": 469760
- },
- "python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d5afc804c71ebd0c690035cbec639f5f07112b3a7080615e6430b27a1739984f",
- "size": 69608
- },
- "runtime": {
- "entries": {
- "iqpilot_private.konn3kt.iqlvbs.alc": {
- "path": "python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so",
- "type": "python-module"
- },
- "iqpilot_private.konn3kt.iqlvbs.vehicle_state": {
- "path": "python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so",
- "type": "python-module"
- }
- }
- },
- "signatures": {
- "python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": "3OWEWh1t7VakRznr0JljvzvODcR9GG1lU9Fx0Bu+J+m7u+U8JdOeoBSjy0DQSKPXpZ4jgi4ieVGXihJME5AsCg==",
- "python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": "XzMSXupOCeoHX/pCV7X1kuDRw02qrCxJuuwEmSC50h9jy4ySWHk9vflvwRW3u+A6hZ9bljsK6wy0oFXZNoXcAg=="
- }
-}
diff --git a/artifacts/iqpilot_alc_private/manifest.json.sig b/artifacts/iqpilot_alc_private/manifest.json.sig
deleted file mode 100644
index 33a07343..00000000
--- a/artifacts/iqpilot_alc_private/manifest.json.sig
+++ /dev/null
@@ -1 +0,0 @@
-zfDjghpCSIizLkZR7YlPtvZQirhuMbIYJahB/pGnNaffGIWuXGM7NPoTol0XxKdoMYvCDCu7Kj5YcAdfYoFoBQ==
diff --git a/artifacts/iqpilot_alc_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_alc_private/python/_iqclosure/_vendored.json
deleted file mode 100644
index fe51488c..00000000
--- a/artifacts/iqpilot_alc_private/python/_iqclosure/_vendored.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index 6e22c91b..00000000
Binary files a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index a635d859..00000000
Binary files a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_commander_private/manifest.json b/artifacts/iqpilot_commander_private/manifest.json
index ed3f58d9..58e5b6c1 100644
--- a/artifacts/iqpilot_commander_private/manifest.json
+++ b/artifacts/iqpilot_commander_private/manifest.json
@@ -1,8 +1,94 @@
{
+ "bundle_version": 1792551365,
"python/_iqclosure/_vendored.json": {
"mode": 420,
- "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
- "size": 3
+ "sha256": "ca67a05945042c0f65f1b48482d9f8effa71448326f7d6ef799ec91ead96a8c9",
+ "size": 454
+ },
+ "python/_iqclosure/iqpilot/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/common/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/common/gpio.py": {
+ "mode": 420,
+ "sha256": "84edc2cba235b774293cbb1c52c6da74856425ff8ad99198997e23a05e784bb7",
+ "size": 2166
+ },
+ "python/_iqclosure/iqpilot/common/i2c.py": {
+ "mode": 420,
+ "sha256": "4af8f9da7504a548d15f18b91d71c1379cb7c05c84be031448764cc49920ce68",
+ "size": 2449
+ },
+ "python/_iqclosure/iqpilot/common/time_helpers.py": {
+ "mode": 420,
+ "sha256": "c14cda1f84149d5ffa63a2adec5ca24126f77c1412a78412e5e3e68a6fd12bcf",
+ "size": 474
+ },
+ "python/_iqclosure/iqpilot/common/utils.py": {
+ "mode": 420,
+ "sha256": "c39b89a1ab34511dfa5f9efac783fde0e6d616963a416cf472bd1f604b57f84d",
+ "size": 5892
+ },
+ "python/_iqclosure/iqpilot/system/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/system/hardware/__init__.py": {
+ "mode": 420,
+ "sha256": "889391bffca493e74bfc108cae2976a53b644caca13ab600061fd29e9622c627",
+ "size": 695
+ },
+ "python/_iqclosure/iqpilot/system/hardware/base.py": {
+ "mode": 420,
+ "sha256": "a56293f7c23e503ff62d0d3ed39f1831b5544a65b37378165934361a5ddcd304",
+ "size": 4966
+ },
+ "python/_iqclosure/iqpilot/system/hardware/pc/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/system/hardware/pc/hardware.py": {
+ "mode": 420,
+ "sha256": "2520895597c375ae570bd04d5f189e874c89d642f3f07bdbeb20388cb6691433",
+ "size": 258
+ },
+ "python/_iqclosure/iqpilot/system/hardware/tici/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
+ "mode": 420,
+ "sha256": "38e521356c56ac85f35543b9457d36639e55ed2ac916f30ea3b86948e806541f",
+ "size": 8016
+ },
+ "python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
+ "mode": 420,
+ "sha256": "da5ecb280f7af14169903260dc941e3bfac033c93b184cb0abea0f1494c34582",
+ "size": 25731
+ },
+ "python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
+ "mode": 420,
+ "sha256": "d22858982705531732d5a20a45a73795496e5bbd9e23028c24609a7e58ac7aed",
+ "size": 890
+ },
+ "python/_iqclosure/iqpilot/system/hardware/tici/lpa.py": {
+ "mode": 420,
+ "sha256": "2bffb84aef35892c4788e80b6d937bf2eebd15e226b72a82c4f39217667c1ca0",
+ "size": 53692
+ },
+ "python/_iqclosure/iqpilot/system/hardware/tici/pins.py": {
+ "mode": 420,
+ "sha256": "b57ba4262e6a6a79f2db15c27356acf3e54f579a7edd13b483b2e16d140aca6d",
+ "size": 678
},
"python/iqpilot_private/__init__.py": {
"mode": 420,
@@ -20,9 +106,9 @@
"size": 0
},
"python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "9b48803b46906943d7cb10b7e4b4f38c880f06a5c281f254a89e2254965b025f",
- "size": 68080
+ "mode": 416,
+ "sha256": "4d988d95e9de50403be4ad3f733fb4a49ee153f604736736f2fa20a44e419748",
+ "size": 1065272
},
"runtime": {
"entries": {
@@ -33,6 +119,6 @@
}
},
"signatures": {
- "python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so": "RwstZkEGexV6fia/R9BRk+6CQ9Q6SGJopSJf04UpMpaw5fRtoPjLOrDFOt9kU8GPmCmW40gaXhJK+rimnBYnBw=="
+ "python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so": "LZv8eAULL242KYGIwYc2uAfm3U2idwa3HW09LPfpVkPUK7o+sNd7RHNjUINR+tq/7QyQao9Ev8uUZCbRNxWUDA=="
}
}
diff --git a/artifacts/iqpilot_commander_private/manifest.json.sig b/artifacts/iqpilot_commander_private/manifest.json.sig
index 1384a15a..e6eba72d 100644
--- a/artifacts/iqpilot_commander_private/manifest.json.sig
+++ b/artifacts/iqpilot_commander_private/manifest.json.sig
@@ -1 +1 @@
-TV78NXdEYQiabAzLIqS2lsZbp5bO0y4EPGwLRUMyKeEH2BStZiFfpNPvlqSn5HGI+RjmTq+p5rRUDMNofbZGBw==
++0m3OFfYq3omyTvGD2hNN6MrCbY6lxnLEwQal9thza+Baxp6D5Wi38Def/7buznDfmIgJ5QenNBfP42H1NgnAg==
diff --git a/artifacts/iqpilot_commander_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_commander_private/python/_iqclosure/_vendored.json
index fe51488c..1a1b6690 100644
--- a/artifacts/iqpilot_commander_private/python/_iqclosure/_vendored.json
+++ b/artifacts/iqpilot_commander_private/python/_iqclosure/_vendored.json
@@ -1 +1,15 @@
-[]
+[
+ "iqpilot.common.gpio",
+ "iqpilot.common.i2c",
+ "iqpilot.common.time_helpers",
+ "iqpilot.common.utils",
+ "iqpilot.system.hardware",
+ "iqpilot.system.hardware.base",
+ "iqpilot.system.hardware.pc.hardware",
+ "iqpilot.system.hardware.tici",
+ "iqpilot.system.hardware.tici.amplifier",
+ "iqpilot.system.hardware.tici.hardware",
+ "iqpilot.system.hardware.tici.iwlist",
+ "iqpilot.system.hardware.tici.lpa",
+ "iqpilot.system.hardware.tici.pins"
+]
diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/__init__.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/__init__.py
similarity index 100%
rename from artifacts/iqpilot_alc_private/python/iqpilot_private/__init__.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/__init__.py
diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/__init__.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/__init__.py
similarity index 100%
rename from artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/__init__.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/__init__.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/gpio.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/gpio.py
similarity index 89%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/gpio.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/gpio.py
index 8f025a2d..5f4295e7 100644
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/gpio.py
+++ b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/gpio.py
@@ -27,16 +27,6 @@ def gpio_read(pin: int) -> bool | None:
return val
-def gpio_export(pin: int) -> None:
- if os.path.isdir(f"/sys/class/gpio/gpio{pin}"):
- return
-
- try:
- with open("/sys/class/gpio/export", 'w') as f:
- f.write(str(pin))
- except Exception:
- print(f"Failed to export gpio {pin}")
-
@cache
def get_irq_action(irq: int) -> list[str]:
try:
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/i2c.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/i2c.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/i2c.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/i2c.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/time_helpers.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/time_helpers.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/time_helpers.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/time_helpers.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/utils.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/utils.py
similarity index 60%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/utils.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/utils.py
index e0aa1605..005d75b8 100644
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/utils.py
+++ b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/common/utils.py
@@ -5,7 +5,6 @@ import contextlib
import subprocess
import time
import functools
-from subprocess import Popen, PIPE, TimeoutExpired
import zstandard as zstd
LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change
@@ -32,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
@@ -153,105 +172,6 @@ def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> st
return default
-@contextlib.contextmanager
-def managed_proc(cmd: list[str], env: dict[str, str]):
- proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE)
- try:
- yield proc
- finally:
- if proc.poll() is None:
- proc.terminate()
- try:
- proc.wait(timeout=5)
- except TimeoutExpired:
- proc.kill()
-
-
-def tabulate(tabular_data, headers=(), tablefmt="simple", floatfmt="g", stralign="left", numalign=None):
- rows = [list(row) for row in tabular_data]
-
- def fmt(val):
- if isinstance(val, str):
- return val
- if isinstance(val, (bool, int)):
- return str(val)
- try:
- return format(val, floatfmt)
- except (TypeError, ValueError):
- return str(val)
-
- formatted = [[fmt(c) for c in row] for row in rows]
- hdrs = [str(h) for h in headers] if headers else None
-
- ncols = max((len(r) for r in formatted), default=0)
- if hdrs:
- ncols = max(ncols, len(hdrs))
- if ncols == 0:
- return ""
-
- for r in formatted:
- r.extend([""] * (ncols - len(r)))
- if hdrs:
- hdrs.extend([""] * (ncols - len(hdrs)))
-
- widths = [0] * ncols
- if hdrs:
- for i in range(ncols):
- widths[i] = len(hdrs[i])
- for row in formatted:
- for i in range(ncols):
- widths[i] = max(widths[i], max(len(ln) for ln in row[i].split('\n')))
-
- def _align(s, w):
- if stralign == "center":
- return s.center(w)
- return s.ljust(w)
-
- if tablefmt == "html":
- parts = ["
"]
- if hdrs:
- parts.append("")
- parts.append("" + "".join(f"| {h} | " for h in hdrs) + "
")
- parts.append("")
- parts.append("")
- for row in formatted:
- parts.append("" + "".join(f"| {c} | " for c in row) + "
")
- parts.append("")
- parts.append("
")
- return "\n".join(parts)
-
- if tablefmt == "simple_grid":
- def _sep(left, mid, right):
- return left + mid.join("─" * (w + 2) for w in widths) + right
-
- top, mid_sep, bot = _sep("┌", "┬", "┐"), _sep("├", "┼", "┤"), _sep("└", "┴", "┘")
-
- def _fmt_row(cells):
- split = [c.split('\n') for c in cells]
- nlines = max(len(s) for s in split)
- for s in split:
- s.extend([""] * (nlines - len(s)))
- return ["│" + "│".join(f" {_align(split[i][li], widths[i])} " for i in range(ncols)) + "│" for li in range(nlines)]
-
- lines = [top]
- if hdrs:
- lines.extend(_fmt_row(hdrs))
- lines.append(mid_sep)
- for ri, row in enumerate(formatted):
- lines.extend(_fmt_row(row))
- lines.append(mid_sep if ri < len(formatted) - 1 else bot)
- return "\n".join(lines)
-
- gap = " "
- lines = []
- if hdrs:
- lines.append(gap.join(h.ljust(w) for h, w in zip(hdrs, widths, strict=True)))
- lines.append(gap.join("-" * w for w in widths))
- for row in formatted:
- lines.append(gap.join(_align(row[i], widths[i]) for i in range(ncols)))
- return "\n".join(lines)
-
-
def retry(attempts=3, delay=1.0, ignore_failure=False):
def decorator(func):
@functools.wraps(func)
diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/__init__.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/__init__.py
similarity index 100%
rename from artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/__init__.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/__init__.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/__init__.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/__init__.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/__init__.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/__init__.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/base.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/base.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/base.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/base.py
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/konn3kt/__init__.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/pc/__init__.py
similarity index 100%
rename from artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/konn3kt/__init__.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/pc/__init__.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/pc/hardware.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/pc/hardware.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/pc/hardware.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/pc/hardware.py
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/__init__.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/__init__.py
similarity index 100%
rename from artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/__init__.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/__init__.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
similarity index 92%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
index 48a34ea4..c38e33b9 100644
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
+++ b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
@@ -125,16 +125,32 @@ class Amplifier:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: list[AmpConfig]) -> bool:
+ import threading
+
tries = 15
backoff = 0.
for i in range(tries):
- try:
- self._set_configs(configs)
- return True
- except OSError:
+ result = [None]
+ error = [None]
+ def _worker():
+ try:
+ self._set_configs(configs)
+ result[0] = True
+ except OSError as e:
+ error[0] = e
+ t = threading.Thread(target=_worker, daemon=True)
+ t.start()
+ t.join(timeout=2)
+ if t.is_alive():
backoff += 0.1
time.sleep(backoff)
- print(f"Failed to set amp config, {tries - i - 1} retries left")
+ print(f"Failed to set amp config (timeout), {tries - i - 1} retries left")
+ continue
+ if result[0]:
+ return True
+ backoff += 0.1
+ time.sleep(backoff)
+ print(f"Failed to set amp config, {tries - i - 1} retries left")
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
similarity index 98%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
index b9fa7fbf..2506fb9e 100644
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
+++ b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
@@ -469,8 +469,10 @@ class Tici(HardwareBase):
if self.amplifier is not None:
self.amplifier.initialize_configuration(self.get_device_type())
- # Allow hardwared to write engagement status to kmsg
- os.system("sudo chmod a+w /dev/kmsg")
+ try:
+ subprocess.run(["sudo", "-n", "chmod", "a+w", "/dev/kmsg"], timeout=2, capture_output=True, check=False)
+ except Exception:
+ pass
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
@@ -514,10 +516,10 @@ class Tici(HardwareBase):
affine_irq(3, "xhci-hcd:usb1")
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
- subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
- subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessException as e:
- print(str(e))
+ subprocess.run(["sudo", "-n", "chrt", "-f", "-p", "1", pid], timeout=2, capture_output=True, check=False)
+ subprocess.run(["sudo", "-n", "taskset", "-pc", "3", pid], timeout=2, capture_output=True, check=False)
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+ pass
def configure_modem(self):
from iqpilot.common.params import Params
@@ -557,7 +559,7 @@ class Tici(HardwareBase):
if self.get_device_type() in ("tici", "tizi"):
if initial_eps_apn:
subprocess.call(["mmcli", "-m", "any", f'--3gpp-set-initial-eps-bearer-settings=apn={initial_eps_apn}'])
- else:
+ elif is_comma_profile:
subprocess.call(["mmcli", "-m", "any", '--3gpp-set-initial-eps-bearer-settings=apn='])
cmds += [
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/lpa.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/lpa.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/lpa.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/lpa.py
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/pins.py b/artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/pins.py
similarity index 100%
rename from artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/pins.py
rename to artifacts/iqpilot_commander_private/python/_iqclosure/iqpilot/system/hardware/tici/pins.py
diff --git a/artifacts/iqpilot_commander_private/python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_commander_private/python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 6d872d06..72acfd9c
Binary files a/artifacts/iqpilot_commander_private/python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_commander_private/python/iqpilot_private/konn3kt/iqlvbs/iqlvbs_commander.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/manifest.json b/artifacts/iqpilot_context_private/manifest.json
index 27118a3e..632e98a7 100644
--- a/artifacts/iqpilot_context_private/manifest.json
+++ b/artifacts/iqpilot_context_private/manifest.json
@@ -1,8 +1,9 @@
{
+ "bundle_version": 1792550965,
"python/_iqclosure/_vendored.json": {
"mode": 420,
- "sha256": "74f679d0ffd3a8e3225496dec9ac060c5505503c94fa04acabd0de4539c57871",
- "size": 1072
+ "sha256": "ae4c187fb594e87be0b64ff050e3ab39cff5dace25e8f5766e3f11be6736992f",
+ "size": 1137
},
"python/_iqclosure/iqpilot/__init__.py": {
"mode": 420,
@@ -16,13 +17,18 @@
},
"python/_iqclosure/iqpilot/common/api/__init__.py": {
"mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
+ "sha256": "1dd848ec6e867836a321a93f9ac218415c19528aa356a3e80969503285316b54",
+ "size": 900
},
"python/_iqclosure/iqpilot/common/api/base.py": {
"mode": 420,
- "sha256": "86b834e02e24a005c903f1ca059b1a572e37c17a87271726c90918cd04b6133b",
- "size": 3451
+ "sha256": "5b312ec45e673d9337a89ea82a157152d1f64e5e48518807a592247787087609",
+ "size": 4921
+ },
+ "python/_iqclosure/iqpilot/common/api/konn3kt.py": {
+ "mode": 420,
+ "sha256": "2282e19909079bfd77a089f480df5a229191c35f8523f005d20e2ea468a6b0bc",
+ "size": 313
},
"python/_iqclosure/iqpilot/common/basedir.py": {
"mode": 420,
@@ -61,18 +67,8 @@
},
"python/_iqclosure/iqpilot/common/utils.py": {
"mode": 420,
- "sha256": "103fa6c24f8b963576c482a34d7ecb020168fd3e85156493ae4432ee3b7b2c06",
- "size": 5159
- },
- "python/_iqclosure/iqpilot/konn3kt/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/konn3kt/cloud_client.py": {
- "mode": 420,
- "sha256": "4d58e62f8d628a8570881f2b87c82c801b46928fbb84f8dab8a81a9b55ba4e15",
- "size": 493
+ "sha256": "c39b89a1ab34511dfa5f9efac783fde0e6d616963a416cf472bd1f604b57f84d",
+ "size": 5892
},
"python/_iqclosure/iqpilot/selfdrive/__init__.py": {
"mode": 420,
@@ -116,8 +112,13 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py": {
"mode": 420,
- "sha256": "2fad977c3816ff824c126a92605f866f3bc0aa989ed8397e818bed481a41192f",
- "size": 14051
+ "sha256": "9628f88e71f7fae2c3d62dd17618367b3c7d12749cfe2a4737410288888cd3bd",
+ "size": 15008
+ },
+ "python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py": {
+ "mode": 420,
+ "sha256": "54ec8e50513edae74ddb1c28015868183cb0c103de48386292d0b7fd83bcbb44",
+ "size": 391
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/speed_limit_input.py": {
"mode": 420,
@@ -166,13 +167,13 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
"mode": 420,
- "sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
- "size": 7585
+ "sha256": "38e521356c56ac85f35543b9457d36639e55ed2ac916f30ea3b86948e806541f",
+ "size": 8016
},
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
"mode": 420,
- "sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db",
- "size": 25601
+ "sha256": "da5ecb280f7af14169903260dc941e3bfac033c93b184cb0abea0f1494c34582",
+ "size": 25731
},
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
"mode": 420,
@@ -204,43 +205,52 @@
"sha256": "b6d70f9628b596bef9eb6509e829d9cf94b4635c6e46bb8febde4a067203a222",
"size": 113
},
+ "python/iqpilot_private/context/aux_inputs.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "a6d54e9ef8773e31e28411012a447ef4880ed7f8d43751bb16666adceee5e0e0",
+ "size": 1065336
+ },
"python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "506f732da9133ba5fdf8ffcb5d7557b28de44b8b789a4994fb1bd9a7c2240641",
- "size": 138936
+ "mode": 416,
+ "sha256": "47e0652a7f87850cfc9cdc28ed88b18cd36adb4da00d5e5b4c2181df7ae09089",
+ "size": 1130696
},
"python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "33358560c99c961e172a1643fb4a0ac6048f90d85551c0dea595fc1793372199",
- "size": 202256
+ "mode": 416,
+ "sha256": "a5a4c36f9aa116467c10e4fe9f07b4fde58d98b97e0950a799b9ce1c83190a08",
+ "size": 1130920
},
"python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "e23916b1c8ce0dd6fe28e07d381e8545c14ab9851cc3c501cc8f08e2002df6ee",
- "size": 135368
+ "mode": 416,
+ "sha256": "c518443db5595c542ec1dec089be97ee79d3a1e7e0f661dbf8d069cf243da402",
+ "size": 1130872
},
"python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "cf6a559498981b3bebb4e50ef276e8c6f0816db68534c43b3372f2d88fec9f7d",
- "size": 473440
+ "mode": 416,
+ "sha256": "c7bfa17d75afa08c0f85256b461c1dd0318fadf7ca422b704673613a8ba54949",
+ "size": 1524504
},
"python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "674bb7de81a76911f536cd684b384f384b4271bdedf0591ff18c574c7378d6c7",
- "size": 137560
+ "mode": 416,
+ "sha256": "b4bd7aa63d69c0db66731eff1fb668551bb5eea17f9d0a8baba01fbb1ef05ae8",
+ "size": 1130728
},
"python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "b6b8c578e98f584ee221d35e08233f4a96afee3d3d765bc8ce5229040626f9c9",
- "size": 135872
+ "mode": 416,
+ "sha256": "9737473c0727f844593bd539cc106aa9358e6b62288e4ceb7a44c78425fec08b",
+ "size": 1065272
},
"python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "a08e7b38bdd666bf2aa38e7528574f2bb3650cd8439edc9689b1e36b08fcf5c2",
- "size": 205456
+ "mode": 416,
+ "sha256": "87c0e5b83e1f53b1984aa9debe7e6f5f8a6432b6de6c4f5d82cef01e31eb5ed8",
+ "size": 1130776
},
"runtime": {
"entries": {
+ "iqpilot_private.context.aux_inputs": {
+ "path": "python/iqpilot_private/context/aux_inputs.cpython-312-aarch64-linux-gnu.so",
+ "type": "python-module"
+ },
"iqpilot_private.context.cot_decode": {
"path": "python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so",
"type": "python-module"
@@ -272,12 +282,13 @@
}
},
"signatures": {
- "python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so": "3XxFsaC/nx8fXtDtpWs+7oPABYmwlIEj1Ex30U8kTDzguB6P5RJVmkFbstAiWOnVCYfLT47sMlvHeU3VTHehDA==",
- "python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so": "9SRCLPJAzPMXMJRyxmfzIR3B2uTfpRm4uzUi1UBnSufzR+ppaRxfCtLestlXWDf2nkQiiaHjF3JdPv3VhjjmBg==",
- "python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so": "KdG3SA295ptxX5WcOmdjeHUkE+f+DTRkPGcJFEXgLMPPMWSgo8aVYILT9z46JTUHd+va82ijRIAJZtJHE+wqAg==",
- "python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so": "yNV0YdFkYGTRyiBFwejPolZHv5ueyQ3XwBjKAoDEB5c80ps6dEDDoXzJBi+Pe1PS/hhVZq3rb5NqVzuJEBlPCA==",
- "python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so": "OD6pPW3nNwopVqsY0UXVyGrAREG7KNCEO59c5j6uIPuKHaSCu2z59tNG9WZ/mgfJCyhLCAN6K9BULmX2kwRTDA==",
- "python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so": "B4Pc4T9sPIuF9g35pB43YbaEzwX9h4IhPrvLzwf1HUmkPMT7knz2B1lgU0z8atz96F5OUpJSE2mG5usq5BzjAA==",
- "python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so": "KYtRL67X5HZ2vVmQfqW8XhnmDVK1vxpo2geVcZ7NXGZ3FFn5vcIweK2ZFVZoIgPfGvK5QMSONnLXPUNSfc9iCw=="
+ "python/iqpilot_private/context/aux_inputs.cpython-312-aarch64-linux-gnu.so": "1CTTb0TqkzHb+66U+mwhtiB0xa5ETlJmr7Hzdl7HJjEAhZVp8LImLWJm785MsaSCkC1mr2wI81FTTCM+6KOtBg==",
+ "python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so": "X1Tb8xcrygS+L0a/f0os516Q8mTPIQuCkVUNkChn77lDLc+B3UaHYiIeI7iyomzW3958NNlXo9lq+lRYw4TZBQ==",
+ "python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so": "afh0sJAKVrv2p8wGD2USTgvpme2/DgFV+mb2YAchPNGtBDL+S+y5KaxKsKXUVHVN7wU36AKCj/6T777jIVz2Dg==",
+ "python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so": "ewMXJE1p5pz0uADnJROdarY8Cw85Uis5rAJvpotH+rVOoVoTaw/nO4MRbCeDj6Ko042QiCo7p/91cO5VT/OjDw==",
+ "python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so": "4DeG7yQsLSC1kkOTusrCebb3yxQyTXZ4BbL4YDyIk9qKtGBPmPYj0x2J6b9HpAwchheqq9uNuko2nUkW3sMvCA==",
+ "python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so": "1an09hj/ok9Fk4vted3GXczjV8v+qfUsjexRpKXnFeKdQNltv6oWosjjQvtEwzkikc43mtgIKAYoAxLBiS9lDw==",
+ "python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so": "zyrasLvljreehe00C+GuDffQOE2OL3RDFy3dyCofB2WfB2EMcUyIWOFk+Ybtn+jJ9DQTHH063bDkURT5yviqDw==",
+ "python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so": "hgeXamMIvzc3BLu9PtmlvI2jqi+x+g37TWvbrmfd78/z8Nzw68KoVIju3rKZcHU0BaXgnhc5sxlQV0wswk+3Cg=="
}
}
diff --git a/artifacts/iqpilot_context_private/manifest.json.sig b/artifacts/iqpilot_context_private/manifest.json.sig
index b6d35d04..11e697eb 100644
--- a/artifacts/iqpilot_context_private/manifest.json.sig
+++ b/artifacts/iqpilot_context_private/manifest.json.sig
@@ -1 +1 @@
-NcjiBJcKitajD4QhhgJZWkc7JRypOIeWKlHvcc0gRMfLAHU7K3ogMd/ZYHUwWPaTZw0JS5zkUIPuZ7hN+chqDA==
+ODnEFb70mSlY+uG5Kl/aS6lYQ1fmNusxlf3O/ed2G/lPuA21fbzKmU+9XT9a83g9oAp5sLZuBV2zECPLewWiDg==
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_context_private/python/_iqclosure/_vendored.json
index fe911233..fc4ccee7 100644
--- a/artifacts/iqpilot_context_private/python/_iqclosure/_vendored.json
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/_vendored.json
@@ -1,5 +1,7 @@
[
+ "iqpilot.common.api",
"iqpilot.common.api.base",
+ "iqpilot.common.api.konn3kt",
"iqpilot.common.basedir",
"iqpilot.common.gpio",
"iqpilot.common.i2c",
@@ -8,13 +10,13 @@
"iqpilot.common.swaglog",
"iqpilot.common.time_helpers",
"iqpilot.common.utils",
- "iqpilot.konn3kt.cloud_client",
"iqpilot.selfdrive.iqmodeld.config",
"iqpilot.selfdrive.iqmodeld.drive_profile",
"iqpilot.selfdrive.iqmodeld.emac_model_meta",
"iqpilot.selfdrive.iqmodeld.model_map_tile",
"iqpilot.selfdrive.iqmodeld.models.fetcher",
"iqpilot.selfdrive.iqmodeld.models.helpers",
+ "iqpilot.selfdrive.iqmodeld.models.iqc",
"iqpilot.selfdrive.iqmodeld.speed_limit_input",
"iqpilot.selfdrive.iqmodeld.temporal_state",
"iqpilot.system.hardware",
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/__init__.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/__init__.py
index e69de29b..b7e90dac 100644
--- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/__init__.py
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/__init__.py
@@ -0,0 +1,26 @@
+import iqpilot.common.api.konn3kt
+
+
+class Api:
+ def __init__(self, dongle_id):
+ self.service = iqpilot.common.api.konn3kt.Konn3ktApi(dongle_id)
+
+ def request(self, method, endpoint, **params):
+ return self.service.request(method, endpoint, **params)
+
+ def get(self, *args, **kwargs):
+ return self.service.get(*args, **kwargs)
+
+ def post(self, *args, **kwargs):
+ return self.service.post(*args, **kwargs)
+
+ def get_token(self, payload_extra=None, expiry_hours=1):
+ return self.service.get_token(payload_extra, expiry_hours)
+
+
+def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
+
+
+def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).get_key_pair()
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/base.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/base.py
index 371a8ab4..0aef9c35 100644
--- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/base.py
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/base.py
@@ -7,10 +7,44 @@ from functools import lru_cache
from iqpilot.system.hardware.hw import Paths
from iqpilot.system.version import get_version
-# name: jwt signature algorithm
KEYS = {"id_rsa": "RS256",
"id_ecdsa": "ES256"}
+_CUSTODIAN_KEY = object()
+_IQKEEPER_SOCKET = "/run/iqkeeper/iqkeeper.sock"
+
+
+def _custodian_available() -> bool:
+ import stat
+ try:
+ return stat.S_ISSOCK(os.stat(_IQKEEPER_SOCKET).st_mode)
+ except OSError:
+ return False
+
+
+def _custodian_sign_jwt(dongle_id: str, payload: dict, alg: str) -> str:
+ import base64
+ import json
+ import socket
+ nonce = base64.b64encode(os.urandom(16)).decode()
+ packet = json.dumps({
+ "v": 1,
+ "op": "sign",
+ "dongle_id": dongle_id,
+ "payload": payload,
+ "alg": alg,
+ "nonce": nonce,
+ }).encode()
+ with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as sock:
+ sock.settimeout(25.0)
+ sock.connect(_IQKEEPER_SOCKET)
+ sock.send(packet)
+ reply_data = sock.recv(8192)
+ reply = json.loads(reply_data) if reply_data else {}
+ if reply.get("nonce") != nonce or not reply.get("ok"):
+ raise PermissionError(f"custodian sign failed: {reply.get('error', 'unknown')}")
+ return reply["token"]
+
@lru_cache(maxsize=4)
def load_signing_key(private_key: str):
@@ -60,6 +94,9 @@ class BaseApi:
}
if payload_extra is not None:
payload.update(payload_extra)
+ if self.private_key is _CUSTODIAN_KEY:
+ serializable = {k: v.isoformat() if isinstance(v, datetime) else v for k, v in payload.items()}
+ return _custodian_sign_jwt(self.dongle_id, serializable, 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):
@@ -73,9 +110,25 @@ class BaseApi:
return self.request(method, endpoint, timeout, access_token, session=session, json=json, **params)
@staticmethod
- def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
+ def get_key_pair() -> tuple[str, str | object, str] | tuple[None, None, None]:
for key in KEYS:
- if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
- with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
- return KEYS[key], private.read(), public.read()
+ priv_path = Paths.persist_root() + f'/comma/{key}'
+ pub_path = priv_path + '.pub'
+ if not os.path.isfile(priv_path) or not os.path.isfile(pub_path):
+ continue
+ try:
+ with open(pub_path) as public:
+ pub = public.read()
+ except OSError:
+ continue
+ try:
+ with open(priv_path) as private:
+ return KEYS[key], private.read(), pub
+ except PermissionError:
+ import time
+ for _attempt in range(10):
+ if _custodian_available():
+ return KEYS[key], _CUSTODIAN_KEY, pub
+ time.sleep(0.5)
+ continue
return None, None, None
diff --git a/iqpilot/common/api/comma_connect.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
similarity index 75%
rename from iqpilot/common/api/comma_connect.py
rename to artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
index bc16d40e..cae7ea46 100644
--- a/iqpilot/common/api/comma_connect.py
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
@@ -1,3 +1,4 @@
+# Copyright (c) 2026 IQ.Lvbs. All rights reserved.
import os
from iqpilot.common.api.base import BaseApi
@@ -5,7 +6,7 @@ from iqpilot.common.api.base import BaseApi
API_HOST = os.getenv('API_HOST', 'https://api-iqlabs.konn3kt.com')
-class CommaConnectApi(BaseApi):
+class Konn3ktApi(BaseApi):
def __init__(self, dongle_id):
super().__init__(dongle_id, API_HOST)
self.user_agent = "openpilot-"
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/utils.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/utils.py
index a80270bb..005d75b8 100644
--- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/utils.py
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/common/utils.py
@@ -31,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py
deleted file mode 100644
index 2897609e..00000000
--- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
-Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
-"""
-
-
-import os
-from iqpilot.common.api.base import BaseApi
-API_HOST = os.getenv('KONN3KT_API_HOST', 'https://api-iqlabs.konn3kt.com')
-
-class Konn3ktApi(BaseApi):
-
- def __init__(self, dongle_id):
- super().__init__(dongle_id, API_HOST)
- self.user_agent = "konn3kt-device-"
-
- def get_token(self, expiry_hours=1):
- return super()._get_token(expiry_hours=expiry_hours)
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
index 05cce298..ee2eb77f 100644
--- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
@@ -305,7 +305,40 @@ def get_runtime_bundle_upgrade(bundle, params: Params = None, available_bundles=
return _find_runtime_upgrade(bundle, params, available_bundles)
+_forced_bundle = None
+
+
+def load_default_model_session():
+ """Stage the shipped default model files and return a decoded default bundle
+ WITHOUT persisting it as the active selection, so the next boot retries the
+ user's own model instead of staying on the default forever."""
+ bundle_dict = _load_default_bundle_dict()
+ ensure_default_model_files(bundle_dict)
+ return ModelBundle(**bundle_dict)
+
+
+class forced_active_bundle:
+ """Make get_active_bundle return `bundle` for the duration of the block, used to
+ build a runner for a session-only fallback bundle without touching Params."""
+ def __init__(self, bundle):
+ self._bundle = bundle
+
+ def __enter__(self):
+ global _forced_bundle
+ self._prev = _forced_bundle
+ _forced_bundle = self._bundle
+ return self._bundle
+
+ def __exit__(self, *exc):
+ global _forced_bundle
+ _forced_bundle = self._prev
+ return False
+
+
def get_active_bundle(params: Params = None):
+ if _forced_bundle is not None:
+ return _forced_bundle
+
params = Params() if params is None else params
try:
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py
new file mode 100644
index 00000000..da84740e
--- /dev/null
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
+
+from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+
+try:
+ load_private_module(__name__, "iqpilot_private.models.iqc")
+except ProprietaryModuleMissing:
+ from iqpilot.models_private_src.iqc import * # noqa: F403
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
index 48a34ea4..c38e33b9 100644
--- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
@@ -125,16 +125,32 @@ class Amplifier:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: list[AmpConfig]) -> bool:
+ import threading
+
tries = 15
backoff = 0.
for i in range(tries):
- try:
- self._set_configs(configs)
- return True
- except OSError:
+ result = [None]
+ error = [None]
+ def _worker():
+ try:
+ self._set_configs(configs)
+ result[0] = True
+ except OSError as e:
+ error[0] = e
+ t = threading.Thread(target=_worker, daemon=True)
+ t.start()
+ t.join(timeout=2)
+ if t.is_alive():
backoff += 0.1
time.sleep(backoff)
- print(f"Failed to set amp config, {tries - i - 1} retries left")
+ print(f"Failed to set amp config (timeout), {tries - i - 1} retries left")
+ continue
+ if result[0]:
+ return True
+ backoff += 0.1
+ time.sleep(backoff)
+ print(f"Failed to set amp config, {tries - i - 1} retries left")
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
index 2f71ed63..2506fb9e 100644
--- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
+++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
@@ -469,8 +469,10 @@ class Tici(HardwareBase):
if self.amplifier is not None:
self.amplifier.initialize_configuration(self.get_device_type())
- # Allow hardwared to write engagement status to kmsg
- os.system("sudo chmod a+w /dev/kmsg")
+ try:
+ subprocess.run(["sudo", "-n", "chmod", "a+w", "/dev/kmsg"], timeout=2, capture_output=True, check=False)
+ except Exception:
+ pass
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
@@ -514,10 +516,10 @@ class Tici(HardwareBase):
affine_irq(3, "xhci-hcd:usb1")
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
- subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
- subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessException as e:
- print(str(e))
+ subprocess.run(["sudo", "-n", "chrt", "-f", "-p", "1", pid], timeout=2, capture_output=True, check=False)
+ subprocess.run(["sudo", "-n", "taskset", "-pc", "3", pid], timeout=2, capture_output=True, check=False)
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+ pass
def configure_modem(self):
from iqpilot.common.params import Params
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/aux_inputs.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/aux_inputs.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..64c68349
Binary files /dev/null and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/aux_inputs.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 31026b7b..91347616
Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index b4db0672..950aed58
Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 65f06d3a..4ed3f1b9
Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index ef676926..e64bdbdb
Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index fa8f7c2f..a0d4c386
Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 54bce54e..5111fa21
Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 9e332d5d..313ba458
Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/manifest.json b/artifacts/iqpilot_emac_private/manifest.json
index 3e863019..7a2838d0 100644
--- a/artifacts/iqpilot_emac_private/manifest.json
+++ b/artifacts/iqpilot_emac_private/manifest.json
@@ -1,8 +1,9 @@
{
+ "bundle_version": 1792551322,
"python/_iqclosure/_vendored.json": {
"mode": 420,
- "sha256": "c4e45105dedcba6f7a4abf84a6e012517ca82cd615165e162c2025ad59020fa1",
- "size": 3823
+ "sha256": "00a3c1ea8d86878488abd7ee5ae5dce065c4b3d0fcf43072999303067807bc63",
+ "size": 3668
},
"python/_iqclosure/iqpilot/__init__.py": {
"mode": 420,
@@ -16,18 +17,18 @@
},
"python/_iqclosure/iqpilot/common/api/__init__.py": {
"mode": 420,
- "sha256": "97d6e0d8cbd3e47797d734322b42d04efea4d95a2dae5a56f1baef3fd847aa0d",
- "size": 939
+ "sha256": "1dd848ec6e867836a321a93f9ac218415c19528aa356a3e80969503285316b54",
+ "size": 900
},
"python/_iqclosure/iqpilot/common/api/base.py": {
"mode": 420,
- "sha256": "86b834e02e24a005c903f1ca059b1a572e37c17a87271726c90918cd04b6133b",
- "size": 3451
+ "sha256": "5b312ec45e673d9337a89ea82a157152d1f64e5e48518807a592247787087609",
+ "size": 4921
},
- "python/_iqclosure/iqpilot/common/api/comma_connect.py": {
+ "python/_iqclosure/iqpilot/common/api/konn3kt.py": {
"mode": 420,
- "sha256": "e7c2dd0b56fb8533c2cbc614e728a9ffc5ce7360524cfa62a02f5235bc23b9a8",
- "size": 267
+ "sha256": "2282e19909079bfd77a089f480df5a229191c35f8523f005d20e2ea468a6b0bc",
+ "size": 313
},
"python/_iqclosure/iqpilot/common/atlas_alerts.py": {
"mode": 420,
@@ -81,13 +82,8 @@
},
"python/_iqclosure/iqpilot/common/realtime.py": {
"mode": 420,
- "sha256": "016be524079ef32df03b2c135eab8f83da60a46fc7f143620b836c65f33e39a3",
- "size": 4423
- },
- "python/_iqclosure/iqpilot/common/spinner.py": {
- "mode": 420,
- "sha256": "33a2eb2aa3148492414035c5dc7150b3a69467905d7a3e58c8f65cfe70d3552c",
- "size": 1371
+ "sha256": "0190cf1f239f8a2bda573524a72a4b6a74e7ed70aa4c1e5f72d1f1159d59cd60",
+ "size": 4926
},
"python/_iqclosure/iqpilot/common/steer_delay.py": {
"mode": 420,
@@ -106,23 +102,18 @@
},
"python/_iqclosure/iqpilot/common/utils.py": {
"mode": 420,
- "sha256": "103fa6c24f8b963576c482a34d7ecb020168fd3e85156493ae4432ee3b7b2c06",
- "size": 5159
+ "sha256": "c39b89a1ab34511dfa5f9efac783fde0e6d616963a416cf472bd1f604b57f84d",
+ "size": 5892
},
"python/_iqclosure/iqpilot/konn3kt/__init__.py": {
"mode": 420,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
- "python/_iqclosure/iqpilot/konn3kt/cloud_client.py": {
- "mode": 420,
- "sha256": "4d58e62f8d628a8570881f2b87c82c801b46928fbb84f8dab8a81a9b55ba4e15",
- "size": 493
- },
"python/_iqclosure/iqpilot/konn3kt/registration.py": {
"mode": 420,
- "sha256": "e9c37e8ae0a70c3a19ca1a33b290e94658b255fab153a941f50f8a29168d75c3",
- "size": 7816
+ "sha256": "5a2b06d919adce513fb8a7e4e6f26d48e502e92dbd89f9af7a0e67e9114ab3be",
+ "size": 2683
},
"python/_iqclosure/iqpilot/selfdrive/__init__.py": {
"mode": 420,
@@ -139,36 +130,16 @@
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
- "python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py": {
+ "python/_iqclosure/iqpilot/selfdrive/controls/lib/action_engine.py": {
"mode": 420,
- "sha256": "5d06908d9a5358d8b27e2a37b9f234f618966004aacb1761cf16c1a1acc74ab0",
- "size": 15285
+ "sha256": "c3e33ae73bcdab89ba32b09e18712fe91018338907e8c81af056d447c5ef04c6",
+ "size": 815
},
"python/_iqclosure/iqpilot/selfdrive/controls/lib/drive_helpers.py": {
"mode": 420,
"sha256": "94e202959eb80d841720ecd0581206fc4ac9d50ad2e2a6cf19f0de9914913988",
"size": 3159
},
- "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_change.py": {
- "mode": 420,
- "sha256": "0e4b4c54856d651d89e1c8896ebf8bb4f0a2efc0b8788a585094e06ecd6616d7",
- "size": 8461
- },
- "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py": {
- "mode": 420,
- "sha256": "d27bd9010967dd51dccc18ec005ff446ae965379b8e828ff7173331818723ec8",
- "size": 4667
- },
- "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lateral_edge_guard.py": {
- "mode": 420,
- "sha256": "6bb6bcb8e5323480bf1b992549279a7d4c17a1bd1bd260863cf7b61b887fa735",
- "size": 10227
- },
"python/_iqclosure/iqpilot/selfdrive/iqlocd/__init__.py": {
"mode": 420,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
@@ -189,6 +160,11 @@
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
+ "python/_iqclosure/iqpilot/selfdrive/iqmodeld/aux_inputs.py": {
+ "mode": 420,
+ "sha256": "e80699014729784ff72c64ac2cdf317a97e0f069ef99676581b1d179db5b4a0a",
+ "size": 493
+ },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/big_catalog.py": {
"mode": 420,
"sha256": "7ec5df43ce10b47ce925467e61100123e828b8e72bdab20bef4c8ac0ffd3cc7a",
@@ -211,8 +187,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py": {
"mode": 420,
- "sha256": "62f51d922f6883bc094590a9d95c2c0849d858bbce30f35c2eba00ec9d3d9bad",
- "size": 36325
+ "sha256": "2f94719d06907c4ac729b8aba063ad43079fde966813d55b6190af8d6c7da679",
+ "size": 37375
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/drive_profile.py": {
"mode": 420,
@@ -221,13 +197,13 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/driving_action.py": {
"mode": 420,
- "sha256": "46a46ea0675ce2f815021c63f3b1ab171c7a453c499a827f2a82b2f2e55d6f58",
- "size": 2484
+ "sha256": "83c1155d90607a2ca119ce0797474e79d741aa0399ae86a22843ad580b5c8ef7",
+ "size": 2644
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": {
"mode": 420,
- "sha256": "a9f2bc8477b58a3df7196f4b40cdddf631e28051809e9bbfdd4c78148444c474",
- "size": 10891
+ "sha256": "af83d52609ec4f2abe82f4e5f788ce4788bdc41cbf7b3de36dc63f1f598ef46c",
+ "size": 12107
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": {
"mode": 420,
@@ -256,8 +232,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py": {
"mode": 420,
- "sha256": "83b0190f19e3f037c937a03b1988931729ed43d4dac92223aba0745697611ed8",
- "size": 12835
+ "sha256": "62b13a05afbe9b5fb5d9e3d09c1491b37826ff840126c5e317a7ebf1edc28762",
+ "size": 12060
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_channel.py": {
"mode": 420,
@@ -281,8 +257,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py": {
"mode": 420,
- "sha256": "321e7223249dc7dba93effe8e834f225fb6067abf49da17fe9a6faeb71ba8e49",
- "size": 2786
+ "sha256": "170639c92d69e4ae2493ca961fb95e75d42be9cedb2442521fe3ac7c25cb977c",
+ "size": 2891
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/fetcher.py": {
"mode": 420,
@@ -291,14 +267,19 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py": {
"mode": 420,
- "sha256": "2fad977c3816ff824c126a92605f866f3bc0aa989ed8397e818bed481a41192f",
- "size": 14051
+ "sha256": "9628f88e71f7fae2c3d62dd17618367b3c7d12749cfe2a4737410288888cd3bd",
+ "size": 15008
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/inference_state.py": {
"mode": 420,
"sha256": "bdfd1c1b538e2342b17d08aa6ebe0c041c2f721025bcd7334434d1139fe6063d",
"size": 268
},
+ "python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py": {
+ "mode": 420,
+ "sha256": "54ec8e50513edae74ddb1c28015868183cb0c103de48386292d0b7fd83bcbb44",
+ "size": 391
+ },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/__init__.py": {
"mode": 420,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
@@ -306,8 +287,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py": {
"mode": 420,
- "sha256": "e0eb9d7bff1da89d52b09596f7e7109fc25c3b9b86a89674382a7dde9e0a4199",
- "size": 8555
+ "sha256": "c9645f9f377020a44d4a785de5930dcfc1b0028489461a94a3a1ddd10e7c7be3",
+ "size": 8566
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/__init__.py": {
"mode": 420,
@@ -316,13 +297,13 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py": {
"mode": 420,
- "sha256": "621968a20fec17cc69d1d68735c317251dde33168e343f0ecb692aef310861c8",
- "size": 10894
+ "sha256": "3d6cd89b8a45b246c0fea6dfc760d22cfdfc91557f95fff115bd186da7b76678",
+ "size": 10897
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py": {
"mode": 420,
- "sha256": "25304fe7ae034d1bb8c9c86a2757704944539be6a0c69c322e75adc84f66e221",
- "size": 7885
+ "sha256": "314023341e6fb0201c49975f1e3ed11ff074348e78bd42e893d2317e5c36ea14",
+ "size": 7896
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/model_types.py": {
"mode": 420,
@@ -331,8 +312,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py": {
"mode": 420,
- "sha256": "120904be1407395a0d88d28fcba1fe426a2b84e381c3c2a0e6d9665868f492fe",
- "size": 10304
+ "sha256": "9f4a0ab397ca4c10cf72318ee39072cb38258afe0a6d31950639fcad80387434",
+ "size": 10315
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runtime.py": {
"mode": 420,
@@ -341,8 +322,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py": {
"mode": 420,
- "sha256": "58ffe0afb2cd9527d2a1d5bbebf0471af8c8210534e266ab80488a7f89bb06ba",
- "size": 7684
+ "sha256": "175410433d287cfd3ac93e8fae19a5da409af61dab1d1a48731eb8aa7043c18c",
+ "size": 7702
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/split_model_constants.py": {
"mode": 420,
@@ -516,13 +497,13 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
"mode": 420,
- "sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
- "size": 7585
+ "sha256": "38e521356c56ac85f35543b9457d36639e55ed2ac916f30ea3b86948e806541f",
+ "size": 8016
},
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
"mode": 420,
- "sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db",
- "size": 25601
+ "sha256": "da5ecb280f7af14169903260dc941e3bfac033c93b184cb0abea0f1494c34582",
+ "size": 25731
},
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
"mode": 420,
@@ -575,49 +556,49 @@
"size": 119
},
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "0dcf7516014011de4b56eee175e4e299fa5fee748f6e0b72169605e098c080bc",
- "size": 400040
+ "mode": 416,
+ "sha256": "fddf1c1e66a06c6815c7b3bf3b2d35f209bc245073db60542a76a56b28c28497",
+ "size": 1327832
},
"python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "39263f739980829ebf32ba43abdf32dffa2c015944808ea64cb6ff1dc210a043",
- "size": 68152
+ "mode": 416,
+ "sha256": "8f552e5100f29394362210ced30a1e44a4b8227edb3f66fcf2b7dde2f3001e69",
+ "size": 999816
},
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "6787635f4b79687dc54fd5e3c25b72285a5a8e04e81a28b5764deac6ce2492ed",
- "size": 333704
+ "mode": 416,
+ "sha256": "17dc02560735d0adf518df30d0fceb5432a1528807e44b2924d22bc4b9f61561",
+ "size": 1393240
},
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "a3bab600ccec9e9edb5817d5a177cd6638a8a727a8d4b25c6e722d4ad038188b",
- "size": 675944
+ "mode": 416,
+ "sha256": "e99eed14d07496da9f4d085a03ae3380de93a58fadc9c8bf5247e6f07b0f57d2",
+ "size": 1655512
},
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "9476c1b5934125917bc0a0dceca95274ebf47d45a1ed66122cb34ba050f40e34",
- "size": 597936
+ "mode": 416,
+ "sha256": "64904b6215365c7db4b2df51d1706f9483b411768bfaedd888a0e3a1701e0302",
+ "size": 1655480
},
"python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "f47a9a61624b9cb5e71bfe979fbae403e04cee27db61f0ccf9b30effd1bad626",
- "size": 473440
+ "mode": 416,
+ "sha256": "80ae92f06aa2562acdb6d5c88be004bbc1bf731351640f926f42a30cbde43d39",
+ "size": 1393208
},
"python/iqpilot_private/emac/native_launcher.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "b68e259978df7824d5e992653aa1861c6fbd746e97f67c753b5f3ca5c4a93f7b",
- "size": 268000
+ "mode": 416,
+ "sha256": "41fe239760108a0a2f1c3e96fb0dec4801bcbdad5b6ed5d4b92c10120715ed22",
+ "size": 1196408
},
"python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "ab3543fc0fe22a5778cb79d63b3ff48031a36ce6e873c79dbfe54d73277d7f16",
- "size": 206032
+ "mode": 416,
+ "sha256": "dbcb1f6103caecf536d6becfad9351381b0a9eef8742ea7df4c03c817c34e000",
+ "size": 1262024
},
"python/iqpilot_private/emac/wire_codec.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "fe91bb1f156c49cecb4a4707a37affdab70b7d9125ff23e4081ca9494ba2c356",
- "size": 203152
+ "mode": 416,
+ "sha256": "22ef20fc1e70b73f135beb8abeb2831770cc39e0e2a44f3fc7bb44a5da811f93",
+ "size": 1130760
},
"runtime": {
"entries": {
@@ -628,14 +609,14 @@
}
},
"signatures": {
- "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "nSEzB01BtGt3un5WVEbn1E5IaHnj7/zLt4lXIYOfBd/yqMBpuUCZ48SUhJU5hTFKMyiuyk3HG/72iwDWMCo1BA==",
- "python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "kUADWHWJJcjYKyumfG2b+XtgDgCQTMtz371MDjnngEv4zn0BnW3AlD+WfoggzBcd6FZ5DYNB8oyjea6BkQgMDA==",
- "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "CbUzdCkb7mORLJaOMCeyLXEkeZzr3EYA1b5doki+q9AshoXNoSOkso+Uh7fApTNr1nqSy+QNfxBm8IMtNQt8Aw==",
- "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "80cJCpENHXIyhxGAHRgqBEfyN7KBQ112YQMdmPe1LOiXASSTWTsu5QMUzzV3u+X5kk+1UqRC0NZ5PjoF/sFADg==",
- "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "x4CkpFtIxtZpt3de1im0hi7jABTdJ3mB3OSN4apkeQALCdYdhFzbVasfSrtkpN6t+/CUEx3g5cLhslEUBt9lCw==",
- "python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": "ISg4GM8peP3ER4alhIe3+3ogJ39dNDq+prLO70AtEf/wqSfATPN09rt/kmIrveqgyTlGD2/v3uOvICnLotKlBQ==",
- "python/iqpilot_private/emac/native_launcher.cpython-312-aarch64-linux-gnu.so": "ttGirAmlJmDTRy2MseGmR/xSK9KkjXIe9eevQ+EJ2P2itPr1XKN8rOoFXaJdUSQaVlsXi/w/yAtLcNEBf0oABA==",
- "python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": "/v2I9Y7/OT0GhFGw8MjKWsz4hk4R/l02V5VCxD5iIYZkGBKdy9UPFcBYWjg/zpfwF+cYqwp0SmlrZOBFmykbDw==",
- "python/iqpilot_private/emac/wire_codec.cpython-312-aarch64-linux-gnu.so": "jFsJk4sFSEtSamxV6f0dBS1kWYcFQ2KEPA+0UbDqfXf4I3+KSaJpr0CQTmfGhgIWYGxAnetunoEJMKsVrNPNDA=="
+ "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "c7oPgelyaOkjpN3BDwHkt1UZCGuXa97ocZ4CkNtOyeBxW+7R26hldG7nOaif+nuNmnYNE/ya7ucuPwPkWJXYDA==",
+ "python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "6ypZpUAkpit8UhYLh0XqceQan9EduF4yQ4MA4GqmPvFUw7u/muSOaopK+lBaw1Xtg8yPbfR8DoGZu0uj3fsPDw==",
+ "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "LrhYCVk6U7yOKNcVJKEBQuOfxC6cJfeUn4IA8gpwmX+ZyaV/oUvv+nDYB37MZD72KRAarhLoTXgpAU9yG7AWCw==",
+ "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "zm5GDcrKM6wB3mr8bmmjF65QUCTjj8XtvLo1+NrP5wClZKAtVrc90Glfau8YApntZwK+uSKBeHRi2xqg4V14BQ==",
+ "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "HgA5ik+z5dVMtSJolJQ7ERbW53vlcbG9GoDKfLN+ANluWrd4q1ezQUDvJJ5JZERQEw8N8tho6du2HsPgY65mAQ==",
+ "python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": "50yzbrZZAOWidHTH6nZ+2LMvJGJ2LV60GXN2yXdKfVERrd8BGZH8j/oOrG71lW3wf/htiZmZRLgIvEKXcUJSCw==",
+ "python/iqpilot_private/emac/native_launcher.cpython-312-aarch64-linux-gnu.so": "suTF6e1oz3Lki7qhFQ5wdHa2m5ZccMmCVEy/BZ/6wqem2fxt8C3hd0OjXiM0Xyg7R+pumDpGOiL23uh2EZDcAw==",
+ "python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": "y0YKu5/ezBo04HGeD4mQVlBpRBJdHn4Y8mBt3HNo6f5mcWg9NhJ7T0u3hmvXl1JcMUnTeDDwNkVIi52VrK+5DA==",
+ "python/iqpilot_private/emac/wire_codec.cpython-312-aarch64-linux-gnu.so": "P+fIzA2ScQzFeulAfgkx5favc2mzvlTybL+e59hBEwF+5bFE3V12DFUOEBga5Rbcv9VrvfG52dMIeysNmHHHCg=="
}
}
diff --git a/artifacts/iqpilot_emac_private/manifest.json.sig b/artifacts/iqpilot_emac_private/manifest.json.sig
index cb2a261d..439b35cc 100644
--- a/artifacts/iqpilot_emac_private/manifest.json.sig
+++ b/artifacts/iqpilot_emac_private/manifest.json.sig
@@ -1 +1 @@
-gmKHJty4j+pkzT4cISfbob5EVVaY0wTii9yUBeuFC4ZF5ERST59xBav/C/5rCnBhk6ednbwTWq68l0uqgc4XDA==
+wRI3NynFc/uIF9F5+7fv4xjZh+64osm4CRaZhUM4JAnJXUaYYl5VDp+kZCDJw8cg/i/VKCRZMGNHq8+XE9eLAQ==
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json
index a4b369ca..5db6542b 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json
@@ -1,7 +1,7 @@
[
"iqpilot.common.api",
"iqpilot.common.api.base",
- "iqpilot.common.api.comma_connect",
+ "iqpilot.common.api.konn3kt",
"iqpilot.common.atlas_alerts",
"iqpilot.common.basedir",
"iqpilot.common.constants",
@@ -13,19 +13,15 @@
"iqpilot.common.issue_debug",
"iqpilot.common.logging_extra",
"iqpilot.common.realtime",
- "iqpilot.common.spinner",
"iqpilot.common.steer_delay",
"iqpilot.common.swaglog",
"iqpilot.common.time_helpers",
"iqpilot.common.utils",
- "iqpilot.konn3kt.cloud_client",
"iqpilot.konn3kt.registration",
- "iqpilot.selfdrive.controls.lib.desire_helper",
+ "iqpilot.selfdrive.controls.lib.action_engine",
"iqpilot.selfdrive.controls.lib.drive_helpers",
- "iqpilot.selfdrive.controls.lib.helpers.lane_change",
- "iqpilot.selfdrive.controls.lib.helpers.lane_turn",
- "iqpilot.selfdrive.controls.lib.helpers.lateral_edge_guard",
"iqpilot.selfdrive.iqlocd.models.constants",
+ "iqpilot.selfdrive.iqmodeld.aux_inputs",
"iqpilot.selfdrive.iqmodeld.big_catalog",
"iqpilot.selfdrive.iqmodeld.camera",
"iqpilot.selfdrive.iqmodeld.config",
@@ -47,6 +43,7 @@
"iqpilot.selfdrive.iqmodeld.models.fetcher",
"iqpilot.selfdrive.iqmodeld.models.helpers",
"iqpilot.selfdrive.iqmodeld.models.inference_state",
+ "iqpilot.selfdrive.iqmodeld.models.iqc",
"iqpilot.selfdrive.iqmodeld.models.runners.model_runner",
"iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner",
"iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.fused_runner",
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/__init__.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/__init__.py
index 7c7b26fc..b7e90dac 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/__init__.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/__init__.py
@@ -1,9 +1,9 @@
-import iqpilot.common.api.comma_connect
+import iqpilot.common.api.konn3kt
class Api:
def __init__(self, dongle_id):
- self.service = iqpilot.common.api.comma_connect.CommaConnectApi(dongle_id)
+ self.service = iqpilot.common.api.konn3kt.Konn3ktApi(dongle_id)
def request(self, method, endpoint, **params):
return self.service.request(method, endpoint, **params)
@@ -19,8 +19,8 @@ class Api:
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
- return iqpilot.common.api.comma_connect.CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
- return iqpilot.common.api.comma_connect.CommaConnectApi(None).get_key_pair()
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).get_key_pair()
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/base.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/base.py
index 371a8ab4..0aef9c35 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/base.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/base.py
@@ -7,10 +7,44 @@ from functools import lru_cache
from iqpilot.system.hardware.hw import Paths
from iqpilot.system.version import get_version
-# name: jwt signature algorithm
KEYS = {"id_rsa": "RS256",
"id_ecdsa": "ES256"}
+_CUSTODIAN_KEY = object()
+_IQKEEPER_SOCKET = "/run/iqkeeper/iqkeeper.sock"
+
+
+def _custodian_available() -> bool:
+ import stat
+ try:
+ return stat.S_ISSOCK(os.stat(_IQKEEPER_SOCKET).st_mode)
+ except OSError:
+ return False
+
+
+def _custodian_sign_jwt(dongle_id: str, payload: dict, alg: str) -> str:
+ import base64
+ import json
+ import socket
+ nonce = base64.b64encode(os.urandom(16)).decode()
+ packet = json.dumps({
+ "v": 1,
+ "op": "sign",
+ "dongle_id": dongle_id,
+ "payload": payload,
+ "alg": alg,
+ "nonce": nonce,
+ }).encode()
+ with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as sock:
+ sock.settimeout(25.0)
+ sock.connect(_IQKEEPER_SOCKET)
+ sock.send(packet)
+ reply_data = sock.recv(8192)
+ reply = json.loads(reply_data) if reply_data else {}
+ if reply.get("nonce") != nonce or not reply.get("ok"):
+ raise PermissionError(f"custodian sign failed: {reply.get('error', 'unknown')}")
+ return reply["token"]
+
@lru_cache(maxsize=4)
def load_signing_key(private_key: str):
@@ -60,6 +94,9 @@ class BaseApi:
}
if payload_extra is not None:
payload.update(payload_extra)
+ if self.private_key is _CUSTODIAN_KEY:
+ serializable = {k: v.isoformat() if isinstance(v, datetime) else v for k, v in payload.items()}
+ return _custodian_sign_jwt(self.dongle_id, serializable, 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):
@@ -73,9 +110,25 @@ class BaseApi:
return self.request(method, endpoint, timeout, access_token, session=session, json=json, **params)
@staticmethod
- def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
+ def get_key_pair() -> tuple[str, str | object, str] | tuple[None, None, None]:
for key in KEYS:
- if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
- with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
- return KEYS[key], private.read(), public.read()
+ priv_path = Paths.persist_root() + f'/comma/{key}'
+ pub_path = priv_path + '.pub'
+ if not os.path.isfile(priv_path) or not os.path.isfile(pub_path):
+ continue
+ try:
+ with open(pub_path) as public:
+ pub = public.read()
+ except OSError:
+ continue
+ try:
+ with open(priv_path) as private:
+ return KEYS[key], private.read(), pub
+ except PermissionError:
+ import time
+ for _attempt in range(10):
+ if _custodian_available():
+ return KEYS[key], _CUSTODIAN_KEY, pub
+ time.sleep(0.5)
+ continue
return None, None, None
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/comma_connect.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
similarity index 75%
rename from artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/comma_connect.py
rename to artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
index bc16d40e..cae7ea46 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/comma_connect.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
@@ -1,3 +1,4 @@
+# Copyright (c) 2026 IQ.Lvbs. All rights reserved.
import os
from iqpilot.common.api.base import BaseApi
@@ -5,7 +6,7 @@ from iqpilot.common.api.base import BaseApi
API_HOST = os.getenv('API_HOST', 'https://api-iqlabs.konn3kt.com')
-class CommaConnectApi(BaseApi):
+class Konn3ktApi(BaseApi):
def __init__(self, dongle_id):
super().__init__(dongle_id, API_HOST)
self.user_agent = "openpilot-"
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/realtime.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/realtime.py
index 72578964..ca79bbab 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/realtime.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/realtime.py
@@ -62,10 +62,23 @@ def config_background_thread() -> None:
set_core_affinity(list(range(os.cpu_count() or 1)))
+def _swap_active() -> bool:
+ try:
+ with open("/proc/swaps") as f:
+ return len(f.read().splitlines()) > 1
+ except Exception:
+ return False
+
+
def lock_memory() -> None:
- """mlockall this process so memory reclaim/compaction can't stall it. RT control
- procs only (locking ui/modeld would worsen pressure). Best-effort."""
- if sys.platform != 'linux' or PC:
+ """mlockall this process so swap can't evict its pages and stall the RT loop.
+ RT control procs only (locking ui/modeld would worsen pressure). Best-effort.
+
+ No-op without active swap: with none, anonymous pages (thread stacks, heap)
+ are already unreclaimable, so mlockall buys nothing — it only pins every 8 MB
+ thread stack and MCL_FUTURE allocation in full (~400 MB/proc here) and, being
+ non-migratable, starves CMA. The device runs swapless by design (iqos-zram)."""
+ if sys.platform != 'linux' or PC or not _swap_active():
return
try:
import ctypes
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/spinner.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/spinner.py
deleted file mode 100644
index b823d09a..00000000
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/spinner.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import os
-import subprocess
-from iqpilot.common.basedir import BASEDIR
-
-
-class Spinner:
- def __init__(self):
- try:
- self.spinner_proc = subprocess.Popen(["./spinner.py"],
- stdin=subprocess.PIPE,
- cwd=os.path.join(BASEDIR, "iqpilot", "system", "ui"),
- close_fds=True)
- except OSError:
- self.spinner_proc = None
-
- def __enter__(self):
- return self
-
- def update(self, spinner_text: str):
- if self.spinner_proc is not None:
- self.spinner_proc.stdin.write(spinner_text.encode('utf8') + b"\n")
- try:
- self.spinner_proc.stdin.flush()
- except BrokenPipeError:
- pass
-
- def update_progress(self, cur: float, total: float):
- self.update(str(round(100 * cur / total)))
-
- def close(self):
- if self.spinner_proc is not None:
- self.spinner_proc.kill()
- try:
- self.spinner_proc.communicate(timeout=2.)
- except subprocess.TimeoutExpired:
- print("WARNING: failed to kill spinner")
- self.spinner_proc = None
-
- def __del__(self):
- self.close()
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.close()
-
-
-if __name__ == "__main__":
- import time
- with Spinner() as s:
- s.update("Spinner text")
- time.sleep(5.0)
- print("gone")
- time.sleep(5.0)
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/utils.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/utils.py
index a80270bb..005d75b8 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/utils.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/utils.py
@@ -31,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py
deleted file mode 100644
index 2897609e..00000000
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
-Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
-"""
-
-
-import os
-from iqpilot.common.api.base import BaseApi
-API_HOST = os.getenv('KONN3KT_API_HOST', 'https://api-iqlabs.konn3kt.com')
-
-class Konn3ktApi(BaseApi):
-
- def __init__(self, dongle_id):
- super().__init__(dongle_id, API_HOST)
- self.user_agent = "konn3kt-device-"
-
- def get_token(self, expiry_hours=1):
- return super()._get_token(expiry_hours=expiry_hours)
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/konn3kt/registration.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/konn3kt/registration.py
index 11e4fc30..a3720bfa 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/konn3kt/registration.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/konn3kt/registration.py
@@ -1,18 +1,12 @@
#!/usr/bin/env python3
-import os
-import time
-import json
-import jwt
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
import re
import secrets
-from typing import cast
from pathlib import Path
-from datetime import datetime, timedelta, UTC
-from iqpilot.common.api import api_get, get_key_pair
from iqpilot.common.params import Params
-from iqpilot.common.spinner import Spinner
-from iqpilot.system.hardware import HARDWARE
from iqpilot.system.hardware.hw import Paths
from iqpilot.common.swaglog import cloudlog
@@ -35,6 +29,10 @@ def _read_persist_dongle_id() -> str | None:
return None
+def is_valid_dongle_id(dongle_id: str | None) -> bool:
+ return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
+
+
def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str | None:
ro = _read_persist_dongle_id()
if is_valid_dongle_id(ro):
@@ -46,8 +44,8 @@ def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = T
if v and v != UNREGISTERED_DONGLE_ID:
return v.lower() if is_valid_dongle_id(v) else v
return ro or None
-def is_valid_dongle_id(dongle_id: str | None) -> bool:
- return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
+
+
def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str:
p = Params() if params is None else params
dongle_id = get_cached_dongle_id(p, prefer_readonly=prefer_readonly)
@@ -57,164 +55,32 @@ def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool
p.put("DongleId", dongle_id)
cloudlog.warning(f"generated new DongleId={dongle_id} (no readonly dongle_id found)")
return dongle_id
+
+
+_impl = None
+
+
+def _registration_impl():
+ global _impl
+ if _impl is None:
+ from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+ try:
+ _impl = load_private_module(__name__, "iqpilot_private.konn3kt.registration.reg_core")
+ except ProprietaryModuleMissing:
+ from konn3kt_private.registration import reg_core as _impl
+ return _impl
+
+
def ensure_dev_pairing_identity(params: Params | None = None, force_reset: bool = False) -> dict[str, str]:
- p = Params() if params is None else params
-
- persist_dir = Path(Paths.persist_root()) / "comma"
- persist_dir.mkdir(parents=True, exist_ok=True)
-
- dongle_path = persist_dir / "dongle_id"
- priv_path = persist_dir / "id_rsa"
- pub_path = persist_dir / "id_rsa.pub"
-
- if force_reset:
- for fp in (dongle_path, priv_path, pub_path):
- try:
- fp.unlink(missing_ok=True)
- except Exception:
- cloudlog.exception(f"failed to remove {fp}")
- try:
- (persist_dir / "konn3kt_prime_type").unlink(missing_ok=True)
- except Exception:
- pass
- try:
- p.remove("PrimeType")
- except Exception:
- pass
- forced_dongle = os.getenv("KONN3KT_DEV_DONGLE_ID")
- dongle_id = forced_dongle.strip().lower() if forced_dongle else None
- if dongle_id and not is_valid_dongle_id(dongle_id):
- cloudlog.error("KONN3KT_DEV_DONGLE_ID must be 16 hex chars")
- dongle_id = None
- if dongle_id is None:
- existing = None
- try:
- existing = dongle_path.read_text().strip().lower() if dongle_path.is_file() else None
- except Exception:
- cloudlog.exception("failed reading existing dev dongle_id")
- dongle_id = existing if is_valid_dongle_id(existing) else secrets.token_hex(8)
- try:
- dongle_path.write_text(dongle_id)
- except Exception:
- cloudlog.exception("failed writing dev dongle_id")
- p.put("DongleId", dongle_id)
- p.put("HardwareSerial", p.get("HardwareSerial") or f"DEV-{dongle_id}")
- if force_reset or (not priv_path.is_file()) or (not pub_path.is_file()):
- try:
- from cryptography.hazmat.primitives import serialization
- from cryptography.hazmat.primitives.asymmetric import rsa
- key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
- priv_bytes = key.private_bytes(
- encoding=serialization.Encoding.PEM,
- format=serialization.PrivateFormat.TraditionalOpenSSL,
- encryption_algorithm=serialization.NoEncryption(),
- )
- pub_bytes = key.public_key().public_bytes(
- encoding=serialization.Encoding.PEM,
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
- )
- priv_path.write_bytes(priv_bytes)
- pub_path.write_bytes(pub_bytes)
- except Exception:
- cloudlog.exception("failed generating dev RSA keys")
- raise
- return {
- "dongle_id": dongle_id,
- "serial": p.get("HardwareSerial") or f"DEV-{dongle_id}",
- "persist_dir": str(persist_dir),
- }
-def _normalize_imei(value: str | None) -> str:
- return value or ""
+ return _registration_impl().ensure_dev_pairing_identity(params, force_reset)
def get_registration_identifiers(wait_timeout: float = IMEI_WAIT_TIMEOUT, show_spinner: bool = False) -> tuple[str, str, str]:
- serial = HARDWARE.get_serial()
- spinner = Spinner() if show_spinner else None
- start_time = time.monotonic()
- imei1: str | None = None
- imei2: str | None = None
-
- while time.monotonic() - start_time < wait_timeout:
- try:
- imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
- if imei1 or imei2:
- break
- except RuntimeError as e:
- if "no modems" in str(e).lower():
- cloudlog.warning("No cellular modem available, proceeding without IMEI")
- break
- cloudlog.exception("Error getting imei, trying again...")
- except Exception:
- cloudlog.exception("Error getting imei, trying again...")
- time.sleep(1)
-
- imei1 = _normalize_imei(imei1)
- imei2 = _normalize_imei(imei2)
-
- if not imei1 and not imei2:
- cloudlog.warning(f"proceeding with serial-only registration for serial={serial}")
- if spinner is not None:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
- spinner.close()
-
- return serial, imei1, imei2
+ return _registration_impl().get_registration_identifiers(wait_timeout, show_spinner)
def register(show_spinner=False) -> str | None:
- params = Params()
-
- dongle_id: str | None = get_cached_dongle_id(params, prefer_readonly=True)
- if dongle_id in ("", UNREGISTERED_DONGLE_ID):
- dongle_id = None
-
- jwt_algo, private_key, public_key = get_key_pair()
-
- if not public_key:
- dongle_id = UNREGISTERED_DONGLE_ID
- cloudlog.warning("missing public key")
- elif dongle_id is None:
- if show_spinner:
- spinner = Spinner()
- spinner.update("registering device")
-
- serial, imei1, imei2 = get_registration_identifiers(wait_timeout=IMEI_WAIT_TIMEOUT, show_spinner=False)
-
- backoff = 0
- start_time = time.monotonic()
- while True:
- try:
- register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)},
- cast(str, private_key), algorithm=jwt_algo)
- cloudlog.info("getting pilotauth")
- cloudlog.info("getting pilotauth")
- resp = api_get("v2/pilotauth/", method='POST', timeout=15,
- imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
-
- if resp.status_code in (400, 402, 403):
- cloudlog.info(f"Unable to register device, got {resp.status_code}")
- dongle_id = UNREGISTERED_DONGLE_ID
- else:
- dongleauth = json.loads(resp.text)
- dongle_id = dongleauth["dongle_id"]
- break
- except Exception:
- cloudlog.exception("failed to authenticate")
- backoff = min(backoff + 1, 15)
- time.sleep(backoff)
-
- if time.monotonic() - start_time > 60 and show_spinner:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})")
- return UNREGISTERED_DONGLE_ID
-
- if show_spinner:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
- spinner.close()
-
- if dongle_id:
- params.put("DongleId", dongle_id)
- from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
- set_offroad_alert("Offroad_UnregisteredHardware", False)
- return dongle_id
+ return _registration_impl().register(show_spinner)
if __name__ == "__main__":
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/action_engine.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/action_engine.py
new file mode 100644
index 00000000..053a5ed9
--- /dev/null
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/action_engine.py
@@ -0,0 +1,18 @@
+# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
+from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+
+try:
+ load_private_module(__name__, "iqpilot_private.navd.action_engine")
+except ProprietaryModuleMissing:
+ from iqpilot.cereal import custom, log
+
+ class ActionEngine:
+ def __init__(self):
+ self.desire = log.Desire.none
+ self.lane_change_state = log.LaneChangeState.off
+ self.lane_change_direction = log.LaneChangeDirection.none
+ self.lane_turn_direction = custom.IQTurnSignalDirection.none
+ self.lateral_edge_block = custom.IQLateralEdgeBlock.none
+
+ def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None):
+ return None
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py
deleted file mode 100644
index 0d0f09b9..00000000
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py
+++ /dev/null
@@ -1,360 +0,0 @@
-"""
-Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
-"""
-
-from __future__ import annotations
-
-import math
-
-from iqpilot.cereal import car, custom, log
-
-from iqpilot.common.constants import CV
-from iqpilot.common.params import Params
-from iqpilot.common.realtime import DT_MDL
-from iqpilot.selfdrive.controls.lib.helpers.lane_change import (
- IQLaneSwapController,
- AutoLaneChangeMode,
- NavExitLaneChangeController,
-)
-from iqpilot.selfdrive.controls.lib.helpers.lateral_edge_guard import LateralEdgeGuard
-from iqpilot.selfdrive.controls.lib.helpers.lane_turn import IQNavTurnController
-
-LaneChangeState = log.LaneChangeState
-LaneChangeDirection = log.LaneChangeDirection
-TurnDirection = custom.IQTurnSignalDirection
-LateralEdgeBlock = custom.IQLateralEdgeBlock
-NavManeuverPhase = custom.IQNavState.ManeuverPhase
-
-LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
-LANE_CHANGE_TIME_MAX = 10.0
-LANE_CHANGE_CANCEL_TORQUE_FRAMES = round(0.25 / DT_MDL)
-# only the rising edge reaches the model and it stays visible for the 5 s the history spans, so one
-# edge per 5 s keeps the turn alive through the maneuver without stacking edges that cut the corner
-TURN_DESIRE_PULSE_CYCLE_FRAMES = round(5.0 / DT_MDL)
-TURN_DESIRE_PULSE_HOLD_FRAMES = TURN_DESIRE_PULSE_CYCLE_FRAMES - 1
-TURN_RECOIL_HEADING_RAD = math.radians(55.0)
-TURN_DESIRE_MAX_SECONDS = 25.0
-TURN_DESIRE_MOVING_SPEED = 0.5
-
-_LANE_CHANGE_DESIRES = {
- (LaneChangeDirection.none, LaneChangeState.off): log.Desire.none,
- (LaneChangeDirection.none, LaneChangeState.preLaneChange): log.Desire.none,
- (LaneChangeDirection.none, LaneChangeState.laneChangeStarting): log.Desire.none,
- (LaneChangeDirection.none, LaneChangeState.laneChangeFinishing): log.Desire.none,
- (LaneChangeDirection.left, LaneChangeState.off): log.Desire.none,
- (LaneChangeDirection.left, LaneChangeState.preLaneChange): log.Desire.none,
- (LaneChangeDirection.left, LaneChangeState.laneChangeStarting): log.Desire.laneChangeLeft,
- (LaneChangeDirection.left, LaneChangeState.laneChangeFinishing): log.Desire.laneChangeLeft,
- (LaneChangeDirection.right, LaneChangeState.off): log.Desire.none,
- (LaneChangeDirection.right, LaneChangeState.preLaneChange): log.Desire.none,
- (LaneChangeDirection.right, LaneChangeState.laneChangeStarting): log.Desire.laneChangeRight,
- (LaneChangeDirection.right, LaneChangeState.laneChangeFinishing): log.Desire.laneChangeRight,
-}
-
-_TURN_DESIRES = {
- TurnDirection.none: log.Desire.none,
- TurnDirection.turnLeft: log.Desire.turnLeft,
- TurnDirection.turnRight: log.Desire.turnRight,
-}
-
-_PULSED_TURN_DESIRES = {
- log.Desire.turnLeft,
- log.Desire.turnRight,
-}
-
-
-def turn_desire(turn_direction) -> log.Desire:
- return _TURN_DESIRES[getattr(turn_direction, "raw", turn_direction)]
-
-
-def _direction_from_blinkers(carstate) -> int:
- if carstate.leftBlinker:
- return LaneChangeDirection.left
- if carstate.rightBlinker:
- return LaneChangeDirection.right
- return LaneChangeDirection.none
-
-
-def _steering_nudge_matches(carstate, direction: int) -> bool:
- if not carstate.steeringPressed:
- return False
- return (
- (direction == LaneChangeDirection.left and carstate.steeringTorque > 0) or
- (direction == LaneChangeDirection.right and carstate.steeringTorque < 0)
- )
-
-
-def _opposite_direction(direction: int) -> int:
- if direction == LaneChangeDirection.left:
- return LaneChangeDirection.right
- if direction == LaneChangeDirection.right:
- return LaneChangeDirection.left
- return LaneChangeDirection.none
-
-
-def _blinker_matches(carstate, direction: int) -> bool:
- return (
- (direction == LaneChangeDirection.left and carstate.leftBlinker and not carstate.rightBlinker) or
- (direction == LaneChangeDirection.right and carstate.rightBlinker and not carstate.leftBlinker)
- )
-
-
-def _blindspot_matches(carstate, direction: int) -> bool:
- return (
- (direction == LaneChangeDirection.left and carstate.leftBlindspot) or
- (direction == LaneChangeDirection.right and carstate.rightBlindspot)
- )
-
-
-def _read_enable_bsm() -> bool:
- try:
- with car.CarParams.from_bytes(Params().get("CarParams")) as cp:
- return bool(cp.enableBsm)
- except Exception:
- return False
-
-
-class DesireHelper:
- def __init__(self):
- self.lane_change_state = LaneChangeState.off
- self.lane_change_direction = LaneChangeDirection.none
- self.lane_change_timer = 0.0
- self.lane_change_ll_prob = 1.0
- self.lane_change_counter_torque_frames = 0
- self.prev_one_blinker = False
- self.prev_nav_exit_active = False
- self.nav_exit_rearmed = False
- self.desire = log.Desire.none
-
- self.alc = IQLaneSwapController(self)
- self.lane_turn_controller = IQNavTurnController(self)
- self.nav_exit = NavExitLaneChangeController(_read_enable_bsm())
- self.lateral_edge_guard = LateralEdgeGuard()
- self.lateral_edge_block = LateralEdgeBlock.none
- self.lane_turn_direction = TurnDirection.none
- self.nav_turn_direction = TurnDirection.none
- self.turn_desire_frames = 0
- self.turn_desire_cycle_input = log.Desire.none
- self.turn_heading_rad = 0.0
- self.turn_desire_seconds = 0.0
- self.turn_recoiled = False
-
- @staticmethod
- def get_lane_change_direction(carstate):
- return _direction_from_blinkers(carstate)
-
- @staticmethod
- def _nav_turn_desire(nav_state):
- if nav_state is None or not getattr(nav_state, "active", False):
- return TurnDirection.none
- if getattr(nav_state, "maneuverPhase", NavManeuverPhase.none) != NavManeuverPhase.turnActive:
- return TurnDirection.none
- if not getattr(nav_state, "shouldSendTurnDesire", False):
- return TurnDirection.none
- return getattr(nav_state, "turnDesireDirection", TurnDirection.none)
-
- def _clear_lane_change(self) -> None:
- self.lane_change_state = LaneChangeState.off
- self.lane_change_direction = LaneChangeDirection.none
-
- def _refresh_turn_overrides(self, carstate, nav_state) -> bool:
- speed_mps = carstate.vEgo
- self.lane_turn_controller.update_params()
- self.lane_turn_controller.update_lane_turn(
- blindspot_left=carstate.leftBlindspot,
- blindspot_right=carstate.rightBlindspot,
- left_blinker=carstate.leftBlinker,
- right_blinker=carstate.rightBlinker,
- v_ego=speed_mps,
- )
- self.lane_turn_direction = self.lane_turn_controller.get_turn_direction()
- self.nav_turn_direction = self._nav_turn_desire(nav_state)
-
- self.nav_exit.update_params()
- self.nav_exit.update(nav_state, carstate)
- return bool(self.nav_exit.active)
-
- def _reset_required(self, lateral_active: bool, nav_exit_active: bool) -> bool:
- timed_out = self.lane_change_timer > LANE_CHANGE_TIME_MAX
- feature_disabled = self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF and not nav_exit_active
- return (not lateral_active) or timed_out or feature_disabled
-
- def _begin_from_idle(self, one_blinker: bool, nav_exit_active: bool, below_speed: bool) -> None:
- if below_speed:
- return
- if one_blinker and not self.prev_one_blinker:
- self.lane_change_state = LaneChangeState.preLaneChange
- self.lane_change_direction = _direction_from_blinkers(self._last_carstate)
- self.lane_change_ll_prob = 1.0
- return
- if nav_exit_active and not self.prev_nav_exit_active:
- self.lane_change_state = LaneChangeState.preLaneChange
- self.lane_change_direction = self.nav_exit.direction
- self.lane_change_ll_prob = 1.0
-
- def _refresh_requested_direction(self, one_blinker: bool, nav_exit_active: bool) -> None:
- if one_blinker:
- self.lane_change_direction = _direction_from_blinkers(self._last_carstate)
- elif nav_exit_active:
- self.lane_change_direction = self.nav_exit.direction
-
- def _step_pre_lane_change(self, one_blinker: bool, nav_exit_active: bool, below_speed: bool) -> None:
- self._refresh_requested_direction(one_blinker, nav_exit_active)
- blindspot_detected = _blindspot_matches(self._last_carstate, self.lane_change_direction)
- self.lateral_edge_block = self.lateral_edge_guard.block_for_direction(self.lane_change_direction)
- lateral_edge_blocked = self.lateral_edge_block != LateralEdgeBlock.none
- steering_ready = _steering_nudge_matches(self._last_carstate, self.lane_change_direction)
- # an exit can need more than one lane, but only the first move may start without a driver nudge
- nav_auto_start = nav_exit_active and self.nav_exit.auto_allowed and not self.nav_exit_rearmed
-
- self.alc.update_lane_change(blindspot_detected=blindspot_detected, brake_pressed=self._last_carstate.brakePressed)
- allowed_to_launch = steering_ready or self.alc.auto_lane_change_allowed or nav_auto_start
-
- if (not (one_blinker or nav_exit_active)) or below_speed:
- self._clear_lane_change()
- elif allowed_to_launch and not blindspot_detected and not lateral_edge_blocked:
- self.lane_change_state = LaneChangeState.laneChangeStarting
-
- def _step_lane_change_starting(self, lane_change_prob: float) -> None:
- self.lane_change_ll_prob = max(self.lane_change_ll_prob - (2.0 * DT_MDL), 0.0)
- if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
- self.lane_change_state = LaneChangeState.laneChangeFinishing
-
- def _step_lane_change_finishing(self, one_blinker: bool, nav_exit_active: bool) -> None:
- self.lane_change_ll_prob = min(self.lane_change_ll_prob + DT_MDL, 1.0)
- if self.lane_change_ll_prob <= 0.99:
- return
- self.lane_change_direction = LaneChangeDirection.none
- if one_blinker:
- self.lane_change_state = LaneChangeState.preLaneChange
- elif nav_exit_active:
- self.lane_change_state = LaneChangeState.preLaneChange
- self.lane_change_direction = self.nav_exit.direction
- self.nav_exit_rearmed = True
- else:
- self.lane_change_state = LaneChangeState.off
-
- def _driver_cancels_lane_change(self) -> bool:
- if self.lane_change_state not in (LaneChangeState.laneChangeStarting, LaneChangeState.laneChangeFinishing):
- self.lane_change_counter_torque_frames = 0
- return False
- opposite = _opposite_direction(self.lane_change_direction)
- if _steering_nudge_matches(self._last_carstate, opposite):
- self.lane_change_counter_torque_frames += 1
- else:
- self.lane_change_counter_torque_frames = 0
- return (_blinker_matches(self._last_carstate, opposite) or
- self.lane_change_counter_torque_frames >= LANE_CHANGE_CANCEL_TORQUE_FRAMES)
-
- def _cancel_lane_change(self) -> None:
- self._clear_lane_change()
- self.lane_change_ll_prob = 1.0
- self.lane_change_counter_torque_frames = 0
-
- def _advance_lane_change_machine(self, one_blinker: bool, nav_exit_active: bool, below_speed: bool, lane_change_prob: float) -> None:
- if self._driver_cancels_lane_change():
- self._cancel_lane_change()
- return
- if self.lane_change_state == LaneChangeState.off:
- self._begin_from_idle(one_blinker, nav_exit_active, below_speed)
- return
- if self.lane_change_state == LaneChangeState.preLaneChange:
- self._step_pre_lane_change(one_blinker, nav_exit_active, below_speed)
- return
- if self.lane_change_state == LaneChangeState.laneChangeStarting:
- self._step_lane_change_starting(lane_change_prob)
- return
- if self.lane_change_state == LaneChangeState.laneChangeFinishing:
- self._step_lane_change_finishing(one_blinker, nav_exit_active)
-
- def _update_timer(self) -> None:
- if self.lane_change_state in (LaneChangeState.off, LaneChangeState.preLaneChange):
- self.lane_change_timer = 0.0
- else:
- self.lane_change_timer += DT_MDL
-
- def _clear_turn_desire_pulse(self) -> None:
- self.turn_desire_frames = 0
- self.turn_desire_cycle_input = log.Desire.none
- self.turn_heading_rad = 0.0
- self.turn_desire_seconds = 0.0
- self.turn_recoiled = False
-
- def _measured_yaw_rate(self) -> float:
- # carState.yawRate is left unpopulated by many ports, so the model pose is the primary source
- rate = getattr(getattr(getattr(self, "_last_modeldata", None), "orientationRate", None), "z", None)
- try:
- if rate is not None and len(rate) and math.isfinite(rate[0]):
- return float(rate[0])
- except (TypeError, IndexError):
- pass
- try:
- return float(getattr(self._last_carstate, "yawRate", 0.0) or 0.0)
- except (TypeError, ValueError):
- return 0.0
-
- def _turn_recoiled(self, desired_output: log.Desire) -> bool:
- # blinkers keep running well past the apex, so the executed heading change has to end the
- # desire: one that outlives the turn keeps steering the plan into the far side of the road
- if desired_output != self.turn_desire_cycle_input:
- self.turn_heading_rad = 0.0
- self.turn_desire_seconds = 0.0
- self.turn_recoiled = False
- self.turn_heading_rad += abs(self._measured_yaw_rate()) * DT_MDL
- # a red light at the junction must not use up the turn before the car moves
- if float(getattr(self._last_carstate, "vEgo", 0.0) or 0.0) > TURN_DESIRE_MOVING_SPEED:
- self.turn_desire_seconds += DT_MDL
- if self.turn_heading_rad >= TURN_RECOIL_HEADING_RAD or self.turn_desire_seconds >= TURN_DESIRE_MAX_SECONDS:
- self.turn_recoiled = True
- return self.turn_recoiled
-
- def _pulse_turn_desire(self, desired_output: log.Desire) -> log.Desire:
- if desired_output not in _PULSED_TURN_DESIRES:
- self._clear_turn_desire_pulse()
- return desired_output
-
- recoiled = self._turn_recoiled(desired_output)
-
- if desired_output != self.turn_desire_cycle_input:
- self.turn_desire_frames = 0
- self.turn_desire_cycle_input = desired_output
-
- cycle_frame = self.turn_desire_frames % TURN_DESIRE_PULSE_CYCLE_FRAMES
- self.turn_desire_frames += 1
- if recoiled or cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES:
- return log.Desire.none
- return desired_output
-
- def _pick_desire_output(self) -> None:
- desired_output = log.Desire.none
- if self.nav_turn_direction != TurnDirection.none:
- desired_output = turn_desire(self.nav_turn_direction)
- elif self.lane_turn_direction != TurnDirection.none:
- desired_output = turn_desire(self.lane_turn_direction)
- else:
- desired_output = _LANE_CHANGE_DESIRES[(self.lane_change_direction, self.lane_change_state)]
-
- self.desire = self._pulse_turn_desire(desired_output)
-
- def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None):
- self._last_carstate = carstate
- self._last_modeldata = modeldata
- self.lateral_edge_guard.update(modeldata, carstate.vEgo, DT_MDL)
- self.lateral_edge_block = LateralEdgeBlock.none
- one_blinker = carstate.leftBlinker != carstate.rightBlinker
- below_speed = carstate.vEgo < LANE_CHANGE_SPEED_MIN
- nav_exit_active = self._refresh_turn_overrides(carstate, nav_state)
- if not nav_exit_active:
- self.nav_exit_rearmed = False
-
- self.alc.update_params()
- if self._reset_required(lateral_active, nav_exit_active):
- self._clear_lane_change()
- else:
- self._advance_lane_change_machine(one_blinker, nav_exit_active, below_speed, lane_change_prob)
-
- self._update_timer()
- self.prev_one_blinker = one_blinker and lateral_active
- self.prev_nav_exit_active = nav_exit_active
- self.alc.update_state()
- self._pick_desire_output()
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/aux_inputs.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/aux_inputs.py
new file mode 100644
index 00000000..28b599ad
--- /dev/null
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/aux_inputs.py
@@ -0,0 +1,17 @@
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
+from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+
+try:
+ load_private_module(__name__, "iqpilot_private.context.aux_inputs")
+except ProprietaryModuleMissing:
+ class AuxModelInputs:
+ def __init__(self, input_spec: dict):
+ self.active = False
+
+ def poll(self) -> None:
+ pass
+
+ def extras(self) -> dict:
+ return {}
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py
index e8c1a9ee..104b102a 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py
@@ -23,7 +23,7 @@ from iqpilot.common.realtime import DT_MDL, config_realtime_process
from iqpilot.common.swaglog import cloudlog
from iqpilot.common.transformations.camera import DEVICE_CAMERAS
from iqpilot.common.transformations.model import get_warp_matrix
-from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper
+from iqpilot.selfdrive.controls.lib.action_engine import ActionEngine
from iqpilot.selfdrive.controls.lib.drive_helpers import (
MODEL_SMOOTHING_MAX_TOTAL_SEC,
dynamic_lat_smooth_extra_seconds,
@@ -34,7 +34,7 @@ from iqpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy
from iqpilot.system import sentry
from iqpilot.common.steer_delay import lateral_action_delay
-from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
+from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle, load_default_model_session, forced_active_bundle
from iqpilot.selfdrive.iqmodeld.models.inference_state import InferenceStateBase
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import get_model_runner
from iqpilot.selfdrive.iqmodeld.camera import CameraOffsetHelper
@@ -243,13 +243,30 @@ def _merged_plan(runtime_state: "NeuralEngineState", outputs: dict[str, np.ndarr
return base_plan + (runtime_state.PLANPLUS_CONTROL * _planplus_gain(vehicle_speed)) * outputs["planplus"][0]
+def _set_default_fallback_alert(active: bool) -> None:
+ try:
+ from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
+ set_offroad_alert("Offroad_ModelFallbackDefault", active)
+ except Exception:
+ cloudlog.exception("could not update the default-model fallback alert")
+
+
class NeuralEngineState(InferenceStateBase):
frames: dict[str, RoadProjector]
def __init__(self, gpu_context: WarpContext):
super().__init__()
- runner = get_model_runner()
- bundle = get_active_bundle()
+ try:
+ runner = get_model_runner()
+ bundle = get_active_bundle()
+ _set_default_fallback_alert(False)
+ except Exception:
+ cloudlog.exception("active model failed to load; running the default model this session so pose stays valid")
+ default_bundle = load_default_model_session()
+ with forced_active_bundle(default_bundle):
+ runner = get_model_runner()
+ bundle = get_active_bundle()
+ _set_default_fallback_alert(True)
self.model_runner = runner
self.constants = runner.constants
@@ -354,7 +371,10 @@ class NeuralEngineState(InferenceStateBase):
def get_action_from_model(self, outputs: dict[str, np.ndarray], previous_action: log.ModelDataV2.Action,
lat_action_t: float, long_action_t: float, vehicle_speed: float,
- lat_smooth_seconds: float | None = None) -> log.ModelDataV2.Action:
+ lat_smooth_seconds: float | None = None, *,
+ lat_active: bool = True, current_curvature: float = 0.0) -> log.ModelDataV2.Action:
+ if not lat_active:
+ previous_action.desiredCurvature = current_curvature
if lat_smooth_seconds is None:
lat_smooth_seconds = self.LAT_SMOOTH_SECONDS
@@ -568,7 +588,7 @@ class InferenceDaemon:
self._pub = PubMaster(pub_services)
self._sub = SubMaster([
"deviceState", "carState", "roadCameraState", "extrinsicsCalibration",
- "driverMonitoringState", "carControl", "lateralDelay", "iqNavState", "navInstruction", "radarState",
+ "driverMonitoringState", "carControl", "controlsState", "lateralDelay", "iqNavState", "navInstruction", "radarState",
] + self._feature_inputs.services)
self._message_memory = DrivePacketMemory()
self._params = Params()
@@ -580,7 +600,7 @@ class InferenceDaemon:
self._car_params = self._load_car_params(demo)
self._long_action_delay = self._car_params.longitudinalActuatorDelay + self._runtime.LONG_SMOOTH_SECONDS
self._previous_action = log.ModelDataV2.Action()
- self._desire_logic = DesireHelper()
+ self._desire_logic = ActionEngine()
self._lat_smooth_extra_sec = 0.0
self._drive_profile_state = DriveProfileState()
self._memory_resets = MemoryResetTracker()
@@ -664,7 +684,8 @@ class InferenceDaemon:
)
lat_smooth_total_sec = min(self._runtime.LAT_SMOOTH_SECONDS + self._lat_smooth_extra_sec, MODEL_SMOOTHING_MAX_TOTAL_SEC)
action = self._runtime.get_action_from_model(
- outputs, self._previous_action, lat_horizon, long_horizon, vehicle_speed, lat_smooth_total_sec
+ outputs, self._previous_action, lat_horizon, long_horizon, vehicle_speed, lat_smooth_total_sec,
+ lat_active=self._sub["carControl"].latActive, current_curvature=self._sub["controlsState"].curvature,
)
self._previous_action = action
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/driving_action.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/driving_action.py
index 67a38f68..d994f868 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/driving_action.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/driving_action.py
@@ -17,7 +17,10 @@ DESIRE_LEN = 8
def get_action_from_model(outputs: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
v_ego: float, lat_action_t: float, long_action_t: float,
- lat_smooth_seconds: float | None = None) -> log.ModelDataV2.Action:
+ lat_smooth_seconds: float | None = None, *,
+ lat_active: bool = True, current_curvature: float = 0.0) -> log.ModelDataV2.Action:
+ if not lat_active:
+ prev_action.desiredCurvature = current_curvature
if "action" in outputs:
desired_accel = float(outputs["action"][0, 1])
desired_curvature = float(outputs["action"][0, 0]) / (max(1.0, v_ego)) ** 2
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
index 1749bfe0..6ba1c352 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
@@ -139,7 +139,18 @@ def egpu_oob_pkl_path(meta: dict) -> str:
def egpu_model_oob_pkl_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths
- return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob.pkl")
+ return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob2.pkl")
+
+
+def drop_frozen_model_oob(meta: dict) -> None:
+ from iqpilot.system.hardware.hw import Paths
+ # warp-on-dock bundles saved under the old name were built before the prune fix and replay capture-time feeds
+ stale = os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob.pkl")
+ try:
+ os.remove(stale)
+ cloudlog.warning(f"removed frozen-feed warp-on-dock artifact {stale}")
+ except OSError:
+ pass
def onnx_cache_path(meta: dict) -> str:
@@ -288,6 +299,28 @@ def patch_tinygrad_fetch_fw() -> None:
helpers.fetch_fw = fetch_fw
+def patch_tinygrad_prune_views() -> None:
+ from tinygrad.engine import jit
+ from tinygrad.uop.ops import Ops
+ if getattr(jit.prune_linear, "_iq_patched", False):
+ return
+
+ # upstream prune only sees bare BUFFER args, so a kernel reading an input through a SLICE view ran once at capture and froze that input
+ def prune_linear(linear, needed):
+ kept, onetime = [], []
+ for si in linear.src:
+ si_bufs = {b for src in si.src[1:] for b in src.toposort() if b.op is Ops.BUFFER}
+ if not si_bufs.isdisjoint(needed):
+ kept.append(si)
+ needed |= si_bufs
+ else:
+ onetime.append(si)
+ return linear.replace(src=tuple(kept)), linear.replace(src=tuple(onetime))
+
+ prune_linear._iq_patched = True
+ jit.prune_linear = prune_linear
+
+
def patch_usb_signal_wait() -> None:
import time
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
index b8f4ccfc..7b75718a 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
@@ -3,7 +3,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
"""
from __future__ import annotations
-import functools
import hashlib
import json
import os
@@ -24,26 +23,29 @@ RANGE_RETRIES = 4
PARALLEL_PARTS = max(1, int(os.environ.get("IQ_DOWNLOAD_PARTS", "3")))
-def _requests_auth():
+def _git_auth():
import importlib
for mod in ("iqpilot_private.models.git_auth", "iqpilot.models_private_src.git_auth",
"iqpilot.selfdrive.iqmodeld.models.git_auth"):
try:
- return importlib.import_module(mod).get_requests_auth()
+ return importlib.import_module(mod)
except Exception:
continue
return None
-def _hf():
- import importlib
- for mod in ("iqpilot_private.models.git_auth", "iqpilot.selfdrive.iqmodeld.models.git_auth"):
- try:
- m = importlib.import_module(mod)
- return m.get_hf_headers(), m.hf_resolve_url
- except Exception:
- continue
- return None, None
+def _resolve_hf(hf_path: str) -> tuple[str | None, dict]:
+ m = _git_auth()
+ if m is None:
+ return None, {}
+ return m.resolve_hf(hf_path)
+
+
+def requests_get(url: str, **kwargs):
+ m = _git_auth()
+ if m is None:
+ raise RuntimeError("no asset broker available")
+ return m.requests_get(url, **kwargs)
class RangesNotHonoured(Exception):
@@ -189,10 +191,9 @@ def _verify_file(tmp: str, sha256: str, size: int) -> None:
def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
- headers, resolve = _hf()
- if resolve is None:
+ url, headers = _resolve_hf(hf_path)
+ if url is None:
raise RuntimeError("no HF credentials available")
- url = resolve(hf_path)
os.makedirs(os.path.dirname(dst), exist_ok=True)
tmp = dst + ".hfpart"
last_error: Exception | None = None
@@ -213,8 +214,10 @@ def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb
os.remove(leftover)
except Exception as e:
last_error = e
- for _attempt in range(STREAM_RETRIES):
+ for attempt in range(STREAM_RETRIES):
try:
+ if attempt:
+ url, headers = _resolve_hf(hf_path)
have = os.path.getsize(tmp) if os.path.isfile(tmp) else 0
if size and have > size:
os.remove(tmp)
@@ -242,26 +245,6 @@ def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb
raise RuntimeError(f"HF download failed: {last_error}")
-def _lfs_endpoint(base_url: str) -> str:
- return base_url.split("/raw/", 1)[0] + ".git/info/lfs"
-
-
-def _resolve_oid(session, base_url: str, oid: str, size: int, auth):
- import requests
- batch = session.post(f"{_lfs_endpoint(base_url)}/objects/batch",
- data=json.dumps({"operation": "download", "transfers": ["basic"],
- "objects": [{"oid": oid, "size": size}]}),
- headers={"Content-Type": "application/vnd.git-lfs+json",
- "Accept": "application/vnd.git-lfs+json"},
- auth=auth, timeout=HTTP_TIMEOUT_S)
- batch.raise_for_status()
- entry = batch.json()["objects"][0]
- if "actions" not in entry:
- raise requests.RequestException(f"LFS object unavailable: {entry.get('error', oid)}")
- action = entry["actions"]["download"]
- return action["href"], action.get("header", {})
-
-
def _part_path(dst: str, oid: str) -> str:
return os.path.join(dst + ".parts", oid)
@@ -276,19 +259,28 @@ def _part_complete(path: str, oid: str, size: int) -> bool:
return digest.hexdigest() == oid
-def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) -> None:
+def _fetch_part(session, obj: dict, path: str, progress) -> None:
size = int(obj["size"])
have = os.path.getsize(path) if os.path.isfile(path) else 0
if have > size:
os.remove(path)
have = 0
- href, headers = _resolve_oid(session, base_url, obj["oid"], size, auth)
- obj_auth = None if headers.get("Authorization") else auth
+ m = _git_auth()
+ if m is None:
+ raise RuntimeError("no asset broker available for LFS parts")
+ target = m.broker_lfs_url(obj["oid"], size)
+ headers = m.broker_headers() if target else {}
+ if not (target and headers):
+ raise RuntimeError("no device identity for the konn3kt asset broker")
+ _stream_part(session, target, headers, path, have, progress)
+
+
+def _stream_part(session, href: str, headers: dict, path: str, have: int, progress) -> None:
# LFS parts are content-addressed (oid == sha256), so a half-written part can be resumed with a
# Range request and verified afterwards instead of being thrown away on every restart.
if have:
headers = {**headers, "Range": f"bytes={have}-"}
- with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S, auth=obj_auth) as r:
+ with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S) as r:
r.raise_for_status()
if have and r.status_code != 206:
have = 0
@@ -300,7 +292,6 @@ def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) ->
def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
- auth = _requests_auth()
os.makedirs(dst + ".parts", exist_ok=True)
total = int(size) or sum(int(o["size"]) for o in objects)
done_bytes = sum(int(o["size"]) for o in objects if _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"])))
@@ -318,30 +309,26 @@ def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progres
with lock:
progress(n)
- def fetch(base_url: str, obj: dict) -> None:
+ def fetch(obj: dict) -> None:
path = _part_path(dst, obj["oid"])
if _part_complete(path, obj["oid"], int(obj["size"])):
return
- _fetch_part(requests.Session(), base_url, obj, path, auth, locked_progress)
+ _fetch_part(requests.Session(), obj, path, locked_progress)
if not _part_complete(path, obj["oid"], int(obj["size"])):
if os.path.getsize(path) >= int(obj["size"]):
os.remove(path)
raise RuntimeError(f"part {obj['oid'][:12]} incomplete or failed verification")
- for base_url in MODELS_BASE_URLS:
- for _attempt in range(STREAM_RETRIES):
- try:
- pending = [o for o in objects if not _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))]
- got[0] = total - sum(int(o["size"]) for o in pending)
- with ThreadPoolExecutor(max_workers=max(1, min(PARALLEL_PARTS, len(pending) or 1))) as pool:
- for _ in pool.map(functools.partial(fetch, base_url), pending):
- pass
- break
- except Exception as e:
- last_error = e
- else:
- continue
- break
+ for _attempt in range(STREAM_RETRIES):
+ try:
+ pending = [o for o in objects if not _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))]
+ got[0] = total - sum(int(o["size"]) for o in pending)
+ with ThreadPoolExecutor(max_workers=max(1, min(PARALLEL_PARTS, len(pending) or 1))) as pool:
+ for _ in pool.map(fetch, pending):
+ pass
+ break
+ except Exception as e:
+ last_error = e
else:
raise RuntimeError(f"model bundle download failed: {last_error}")
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py
index bf17800a..ea1dff01 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py
@@ -17,7 +17,8 @@ _OVERRIDE_KEYS = (
"combinedSplitArtifact",
"iqCombinedArtifact",
)
-_SPLIT_ROLE_PATTERN = re.compile(r"^driving_(vision|policy|off_policy|on_policy)_(.+)_tinygrad\.pkl$")
+_SPLIT_ROLE_PATTERN = re.compile(r"^driving_(vision|policy|off_policy|on_policy)_(.+)_tinygrad\.pkl(?:\.iqc)?$")
+_IQC_SUFFIX = ".iqc"
def _bundle_models(bundle) -> list:
@@ -91,8 +92,9 @@ def combined_split_artifact_candidates(bundle) -> list[Path]:
def resolve_combined_split_artifact(bundle) -> Path | None:
for candidate in combined_split_artifact_candidates(bundle):
- if candidate.is_file():
- return candidate
+ for path in (candidate, candidate.with_name(candidate.name + _IQC_SUFFIX)):
+ if path.is_file():
+ return path
return None
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
index 05cce298..ee2eb77f 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
@@ -305,7 +305,40 @@ def get_runtime_bundle_upgrade(bundle, params: Params = None, available_bundles=
return _find_runtime_upgrade(bundle, params, available_bundles)
+_forced_bundle = None
+
+
+def load_default_model_session():
+ """Stage the shipped default model files and return a decoded default bundle
+ WITHOUT persisting it as the active selection, so the next boot retries the
+ user's own model instead of staying on the default forever."""
+ bundle_dict = _load_default_bundle_dict()
+ ensure_default_model_files(bundle_dict)
+ return ModelBundle(**bundle_dict)
+
+
+class forced_active_bundle:
+ """Make get_active_bundle return `bundle` for the duration of the block, used to
+ build a runner for a session-only fallback bundle without touching Params."""
+ def __init__(self, bundle):
+ self._bundle = bundle
+
+ def __enter__(self):
+ global _forced_bundle
+ self._prev = _forced_bundle
+ _forced_bundle = self._bundle
+ return self._bundle
+
+ def __exit__(self, *exc):
+ global _forced_bundle
+ _forced_bundle = self._prev
+ return False
+
+
def get_active_bundle(params: Params = None):
+ if _forced_bundle is not None:
+ return _forced_bundle
+
params = Params() if params is None else params
try:
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py
new file mode 100644
index 00000000..da84740e
--- /dev/null
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/iqc.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
+
+from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+
+try:
+ load_private_module(__name__, "iqpilot_private.models.iqc")
+except ProprietaryModuleMissing:
+ from iqpilot.models_private_src.iqc import * # noqa: F403
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py
index 8ede5f6c..48d990f0 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py
@@ -2,7 +2,6 @@
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import os
-import pickle as _pk
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
@@ -13,6 +12,7 @@ from iqpilot.system.hardware import TICI
from iqpilot.system.hardware.hw import Paths as _hw_paths
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle as _fetch_bundle
from iqpilot.selfdrive.iqmodeld.models.combined_artifact import has_combined_split_artifact
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
# ---- runtime type surface (native OpenCL/frame handles resolve to Any off-device) ----
if TYPE_CHECKING:
@@ -73,8 +73,7 @@ def load_artifact_metadata(metadata_filename):
if os.path.getsize(path) > _META_MAX_BYTES:
cloudlog.error(f"metadata pkl {metadata_filename} is artifact-sized, refusing to unpickle it")
return tuple({} for _ in _META_FIELDS)
- with open(path, 'rb') as fh:
- blob = _pk.load(fh)
+ blob = load_pickle(path)
return tuple(blob.get(field, {}) for field in _META_FIELDS)
except Exception:
cloudlog.exception(f"unreadable metadata pkl {metadata_filename}, continuing without it")
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py
index 9acb0ad3..37f1233f 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py
@@ -4,7 +4,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
-import pickle
from typing import Any
import numpy as np
@@ -15,6 +14,7 @@ from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import NumpyDict, Sh
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
def _tinygrad_imports():
@@ -64,8 +64,7 @@ class TinygradCombinedSplitRunner(ModelRunner):
if self._artifact_path is None:
raise FileNotFoundError("No IQ combined split artifact is available for the active bundle")
- with open(self._artifact_path, "rb") as artifact:
- runtime_package: dict[Any, Any] = pickle.load(artifact)
+ runtime_package: dict[Any, Any] = load_pickle(self._artifact_path)
self._meta_by_role = runtime_package.get("meta_by_role", runtime_package.get("metadata", {}))
self._policy_roles = runtime_package.get("roles", _phase_roles(self._meta_by_role))
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py
index 33beea42..b8783dc8 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py
@@ -4,7 +4,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
import os
-import pickle
from typing import Any
import numpy as np
@@ -15,6 +14,7 @@ from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
def _tinygrad_imports():
@@ -40,8 +40,7 @@ class TinygradFusedRunner(ModelRunner):
self._model_data = next(iter(self.models.values()))
pkl_path = os.path.join(CUSTOM_MODEL_PATH, self._model_data.model.artifact.fileName)
- with open(pkl_path, 'rb') as f:
- self._fused: dict[Any, Any] = pickle.load(f)
+ self._fused: dict[Any, Any] = load_pickle(pkl_path)
self._vision_meta = self._fused['metadata']['vision']
self._on_meta = self._fused['metadata']['on_policy']
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py
index ea35728a..e457a5f8 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py
@@ -5,7 +5,6 @@ from __future__ import annotations
import hashlib
import os
-import pickle
import re
from typing import Any
@@ -17,6 +16,7 @@ from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runtime import TinygradSupercomboRuntime
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
def _captured_queue_depth(warp_jit: Any) -> int | None:
@@ -77,8 +77,7 @@ class TinygradSupercomboRunner(TinygradSupercomboRuntime, ModelRunner):
self._pkl_path = pkl_path
self._expected_sha256 = getattr(getattr(self._model_data.model.artifact, "downloadUri", None), "sha256", "") or ""
self._verify_artifact_file()
- with open(pkl_path, 'rb') as f:
- self._m: dict[Any, Any] = pickle.load(f)
+ self._m: dict[Any, Any] = load_pickle(pkl_path)
self._meta = self._m['metadata']
self._ish = self._meta['input_shapes']
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py
index 2d91c3df..7bde2165 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py
@@ -4,7 +4,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
-import pickle
from dataclasses import dataclass
import numpy as np
@@ -32,6 +31,7 @@ from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelCo
from iqpilot.selfdrive.iqmodeld.config import ModelConstants
from iqpilot.selfdrive.iqmodeld.runtime.tinygrad import qcom_tensor_from_opencl_address
from iqpilot.system.hardware import TICI
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
@dataclass(frozen=True)
@@ -45,12 +45,11 @@ def _artifact_path(filename: str) -> str:
def _load_program_blob(filename: str):
- with open(_artifact_path(filename), "rb") as artifact:
- try:
- return pickle.load(artifact)
- except FileNotFoundError as exc:
- assert "/dev/kgsl-3d0" not in str(exc), "Model was built on C3 or C3X, but is being loaded on PC"
- raise
+ try:
+ return load_pickle(_artifact_path(filename))
+ except FileNotFoundError as exc:
+ assert "/dev/kgsl-3d0" not in str(exc), "Model was built on C3 or C3X, but is being loaded on PC"
+ raise
def _compile_input_plan(captured) -> dict[str, _TensorShapePlan]:
@@ -81,7 +80,7 @@ class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTiny
raise ValueError(f"Model data for type {model_type} not available.")
asset_name = self._model_data.model.artifact.fileName
- assert asset_name.endswith("_tinygrad.pkl"), f"Invalid model file {asset_name} for TinygradRunner"
+ assert asset_name.removesuffix(".iqc").endswith("_tinygrad.pkl"), f"Invalid model file {asset_name} for TinygradRunner"
self.model_run = _load_program_blob(asset_name)
self._input_plan = _compile_input_plan(self.model_run.captured)
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
index 48a34ea4..c38e33b9 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
@@ -125,16 +125,32 @@ class Amplifier:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: list[AmpConfig]) -> bool:
+ import threading
+
tries = 15
backoff = 0.
for i in range(tries):
- try:
- self._set_configs(configs)
- return True
- except OSError:
+ result = [None]
+ error = [None]
+ def _worker():
+ try:
+ self._set_configs(configs)
+ result[0] = True
+ except OSError as e:
+ error[0] = e
+ t = threading.Thread(target=_worker, daemon=True)
+ t.start()
+ t.join(timeout=2)
+ if t.is_alive():
backoff += 0.1
time.sleep(backoff)
- print(f"Failed to set amp config, {tries - i - 1} retries left")
+ print(f"Failed to set amp config (timeout), {tries - i - 1} retries left")
+ continue
+ if result[0]:
+ return True
+ backoff += 0.1
+ time.sleep(backoff)
+ print(f"Failed to set amp config, {tries - i - 1} retries left")
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
index 2f71ed63..2506fb9e 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
+++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
@@ -469,8 +469,10 @@ class Tici(HardwareBase):
if self.amplifier is not None:
self.amplifier.initialize_configuration(self.get_device_type())
- # Allow hardwared to write engagement status to kmsg
- os.system("sudo chmod a+w /dev/kmsg")
+ try:
+ subprocess.run(["sudo", "-n", "chmod", "a+w", "/dev/kmsg"], timeout=2, capture_output=True, check=False)
+ except Exception:
+ pass
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
@@ -514,10 +516,10 @@ class Tici(HardwareBase):
affine_irq(3, "xhci-hcd:usb1")
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
- subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
- subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessException as e:
- print(str(e))
+ subprocess.run(["sudo", "-n", "chrt", "-f", "-p", "1", pid], timeout=2, capture_output=True, check=False)
+ subprocess.run(["sudo", "-n", "taskset", "-pc", "3", pid], timeout=2, capture_output=True, check=False)
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+ pass
def configure_modem(self):
from iqpilot.common.params import Params
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 2c9b40a6..e0659500
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index b61a4a7d..38e34598
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 3fcc68de..4db554d7
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 0d682d50..15de8c4c
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 6e65ece6..b3026557
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e25f03bf..eb3a7727
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/native_launcher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/native_launcher.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 2a6bc486..b3deb3ed
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/native_launcher.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/native_launcher.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 22bbfdad..dbf2ad46
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/wire_codec.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/wire_codec.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 1a186207..c1c0dcf3
Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/wire_codec.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/wire_codec.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/.source_sha b/artifacts/iqpilot_hephaestusd_private/.source_sha
deleted file mode 100644
index 123c7c9a..00000000
--- a/artifacts/iqpilot_hephaestusd_private/.source_sha
+++ /dev/null
@@ -1 +0,0 @@
-f217b0f040cfa83f51d6e935f1646946f5992b58
diff --git a/artifacts/iqpilot_hephaestusd_private/manifest.json b/artifacts/iqpilot_hephaestusd_private/manifest.json
index fb36ab39..101b3a09 100644
--- a/artifacts/iqpilot_hephaestusd_private/manifest.json
+++ b/artifacts/iqpilot_hephaestusd_private/manifest.json
@@ -1,8 +1,9 @@
{
+ "bundle_version": 1792395046,
"python/_iqclosure/_vendored.json": {
"mode": 420,
- "sha256": "8cbd4406c95aeca668d07f6714cffdfc00d7143988901bfe86c1bb1381019996",
- "size": 2050
+ "sha256": "93d71b5f76734cb6ceff3ead9218b21727bf47814a1a25d6be5a44994b9528fb",
+ "size": 1717
},
"python/_iqclosure/iqpilot/__init__.py": {
"mode": 420,
@@ -16,34 +17,24 @@
},
"python/_iqclosure/iqpilot/common/api/__init__.py": {
"mode": 420,
- "sha256": "97d6e0d8cbd3e47797d734322b42d04efea4d95a2dae5a56f1baef3fd847aa0d",
- "size": 939
+ "sha256": "1dd848ec6e867836a321a93f9ac218415c19528aa356a3e80969503285316b54",
+ "size": 900
},
"python/_iqclosure/iqpilot/common/api/base.py": {
"mode": 420,
- "sha256": "86b834e02e24a005c903f1ca059b1a572e37c17a87271726c90918cd04b6133b",
- "size": 3451
+ "sha256": "5b312ec45e673d9337a89ea82a157152d1f64e5e48518807a592247787087609",
+ "size": 4921
},
- "python/_iqclosure/iqpilot/common/api/comma_connect.py": {
+ "python/_iqclosure/iqpilot/common/api/konn3kt.py": {
"mode": 420,
- "sha256": "e7c2dd0b56fb8533c2cbc614e728a9ffc5ce7360524cfa62a02f5235bc23b9a8",
- "size": 267
- },
- "python/_iqclosure/iqpilot/common/atlas_alerts.py": {
- "mode": 420,
- "sha256": "a8b47e01f604d41d4959d4b0f5ed594c1ab90e5b0f7fb2962b5a4a71ec766598",
- "size": 9411
+ "sha256": "2282e19909079bfd77a089f480df5a229191c35f8523f005d20e2ea468a6b0bc",
+ "size": 313
},
"python/_iqclosure/iqpilot/common/basedir.py": {
"mode": 420,
"sha256": "58cfa5d6405903ee1acecc4544463bb8f47a66921b120c382143aa80d8539b28",
"size": 106
},
- "python/_iqclosure/iqpilot/common/constants.py": {
- "mode": 420,
- "sha256": "fc439d59c24b95ed349b84f1a5862df3ea50e0891dbec4ea3cf5e1e0ccff6513",
- "size": 434
- },
"python/_iqclosure/iqpilot/common/file_helpers.py": {
"mode": 420,
"sha256": "3c661d05dea697fae741b3e65a93385e4e0f5941eae8e8f4ea64da8da90e2f52",
@@ -64,11 +55,6 @@
"sha256": "4af8f9da7504a548d15f18b91d71c1379cb7c05c84be031448764cc49920ce68",
"size": 2449
},
- "python/_iqclosure/iqpilot/common/issue_debug.py": {
- "mode": 420,
- "sha256": "6fadd02538b85f92963f247a43dcc6f50cf4c65cdc4171f3c7e03646a76fcd1b",
- "size": 1019
- },
"python/_iqclosure/iqpilot/common/logging_extra.py": {
"mode": 420,
"sha256": "ce9b3b3d8498d76e1c4c0352a3e98e72759bb7f367ff8629c1329c46040595c3",
@@ -79,11 +65,6 @@
"sha256": "016be524079ef32df03b2c135eab8f83da60a46fc7f143620b836c65f33e39a3",
"size": 4423
},
- "python/_iqclosure/iqpilot/common/spinner.py": {
- "mode": 420,
- "sha256": "33a2eb2aa3148492414035c5dc7150b3a69467905d7a3e58c8f65cfe70d3552c",
- "size": 1371
- },
"python/_iqclosure/iqpilot/common/swaglog.py": {
"mode": 420,
"sha256": "0a133b4ef49f80b8890f115e81273f3f3e21351ae79f92dc02abc033e6d24027",
@@ -96,8 +77,8 @@
},
"python/_iqclosure/iqpilot/common/utils.py": {
"mode": 420,
- "sha256": "103fa6c24f8b963576c482a34d7ecb020168fd3e85156493ae4432ee3b7b2c06",
- "size": 5159
+ "sha256": "c39b89a1ab34511dfa5f9efac783fde0e6d616963a416cf472bd1f604b57f84d",
+ "size": 5892
},
"python/_iqclosure/iqpilot/common/version.py": {
"mode": 420,
@@ -116,8 +97,8 @@
},
"python/_iqclosure/iqpilot/konn3kt/registration.py": {
"mode": 420,
- "sha256": "e9c37e8ae0a70c3a19ca1a33b290e94658b255fab153a941f50f8a29168d75c3",
- "size": 7816
+ "sha256": "5a2b06d919adce513fb8a7e4e6f26d48e502e92dbd89f9af7a0e67e9114ab3be",
+ "size": 2683
},
"python/_iqclosure/iqpilot/konn3kt/service_health.py": {
"mode": 420,
@@ -141,8 +122,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/car/vehicle_catalog.json": {
"mode": 420,
- "sha256": "9226a2475a10088d7f472af2de9e9e3884522562db9432376c4a0def6142fe38",
- "size": 122493
+ "sha256": "ac6ffed7e5374df5add04646af383980d251d400b0617755bd54a1fc85d21325",
+ "size": 124301
},
"python/_iqclosure/iqpilot/selfdrive/car/vehicle_catalog.py": {
"mode": 420,
@@ -161,8 +142,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": {
"mode": 420,
- "sha256": "a9f2bc8477b58a3df7196f4b40cdddf631e28051809e9bbfdd4c78148444c474",
- "size": 10891
+ "sha256": "af83d52609ec4f2abe82f4e5f788ce4788bdc41cbf7b3de36dc63f1f598ef46c",
+ "size": 12107
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": {
"mode": 420,
@@ -171,8 +152,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py": {
"mode": 420,
- "sha256": "83b0190f19e3f037c937a03b1988931729ed43d4dac92223aba0745697611ed8",
- "size": 12835
+ "sha256": "62b13a05afbe9b5fb5d9e3d09c1491b37826ff840126c5e317a7ebf1edc28762",
+ "size": 12060
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/__init__.py": {
"mode": 420,
@@ -186,48 +167,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py": {
"mode": 420,
- "sha256": "2fad977c3816ff824c126a92605f866f3bc0aa989ed8397e818bed481a41192f",
- "size": 14051
- },
- "python/_iqclosure/iqpilot/selfdrive/locationd/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/selfdrive/locationd/calibrationd.py": {
- "mode": 420,
- "sha256": "6bceebb5a15895d2b6d20a28cbd14b9112f9422e0a0cf466314a7e8779da39dc",
- "size": 15698
- },
- "python/_iqclosure/iqpilot/selfdrive/selfdrived/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/selfdrive/selfdrived/alertmanager.py": {
- "mode": 420,
- "sha256": "c601bac30951a750d3ebf3d30f06c597bacd148210b1c405d5d3eca79bb7c4e3",
- "size": 2045
- },
- "python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py": {
- "mode": 420,
- "sha256": "e1b113565c45271ec5310dd83940dd338ff7fa56a2bf121f864dd58cc0ba1343",
- "size": 32024
- },
- "python/_iqclosure/iqpilot/selfdrive/ui/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/selfdrive/ui/feedback/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/selfdrive/ui/feedback/feedbackd.py": {
- "mode": 420,
- "sha256": "7c1e6cb2c3c2c6aae91210f6cbc5c372f33c28cc3edb292ef71439b83844615d",
- "size": 2615
+ "sha256": "9628f88e71f7fae2c3d62dd17618367b3c7d12749cfe2a4737410288888cd3bd",
+ "size": 15008
},
"python/_iqclosure/iqpilot/system/__init__.py": {
"mode": 420,
@@ -266,8 +207,8 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
"mode": 420,
- "sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
- "size": 7585
+ "sha256": "38e521356c56ac85f35543b9457d36639e55ed2ac916f30ea3b86948e806541f",
+ "size": 8016
},
"python/_iqclosure/iqpilot/system/hardware/tici/esim_manager.py": {
"mode": 420,
@@ -276,8 +217,8 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
"mode": 420,
- "sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db",
- "size": 25601
+ "sha256": "da5ecb280f7af14169903260dc941e3bfac033c93b184cb0abea0f1494c34582",
+ "size": 25731
},
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
"mode": 420,
@@ -316,18 +257,13 @@
},
"python/_iqclosure/iqpilot/system/manager/process.py": {
"mode": 420,
- "sha256": "04bf7d945e43ed67362916884caf9ac6011ddd5dacc985f2adb282eacbdf225b",
- "size": 10130
+ "sha256": "703663ef081e8786b0071df02d95b404b9236c6162d044a0766eb34ef3181875",
+ "size": 9926
},
"python/_iqclosure/iqpilot/system/manager/process_config.py": {
"mode": 420,
- "sha256": "f7a4f10168ce71e490578164c08328e1e983bd9e72df955c7e34d4eda6be7086",
- "size": 15379
- },
- "python/_iqclosure/iqpilot/system/micd.py": {
- "mode": 420,
- "sha256": "1a59cbe6b72fcca5723ab56dc09f52039817299071a8d8da37f04acea58776bc",
- "size": 5977
+ "sha256": "5d5f9354ac81904097cd7c25f6ea9ee2c5dcf74ed63a3400530dddcc6a1f1660",
+ "size": 15410
},
"python/_iqclosure/iqpilot/system/phonegps/__init__.py": {
"mode": 420,
@@ -366,8 +302,8 @@
},
"python/_iqclosure/iqpilot/system/ui/lib/wifi_manager.py": {
"mode": 420,
- "sha256": "c9fe91484344f154726b0cab62a19f58a172e4f06dfd6341ca9b179c0c4646a8",
- "size": 41618
+ "sha256": "859d3eb2f56eb456f1d6d2df18d93c5d4990a7f4a6e75be495930a19efcb5151",
+ "size": 42121
},
"python/_iqclosure/iqpilot/system/version.py": {
"mode": 420,
@@ -400,29 +336,29 @@
"size": 0
},
"python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "7afc7118cb0e8fcfcdc552a7df224cd02dedda078d79e3484cc7c2c87209582c",
- "size": 135480
+ "mode": 416,
+ "sha256": "97137b00af89cc21ec2b9c07f5887304ff0a7379619b37837384c3e6d49a96a2",
+ "size": 1130808
},
"python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "bd195bcc71dd7f15975ef2bbb28317fd7518f68c73a3d056d214b8b3446a8852",
- "size": 67680
+ "mode": 416,
+ "sha256": "0674dcaf72b8137be6c75ac220001f2ea440fea92c93c5211a01e0d513772df7",
+ "size": 1065192
},
"python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "53fe8078e5388290dc36fd765c3d923b9617ac35cb7cb94c4faf8234afa69766",
- "size": 204616
+ "mode": 416,
+ "sha256": "3e8e9d8d2ac2282cda1bb02e7ae366c373191bd49c49de47e09effa2f000a4f2",
+ "size": 1130968
},
"python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "c077e27d89e8b1704b4a179654690c2e0d2db08941bf8b55e511a3e524e88508",
- "size": 69752
+ "mode": 416,
+ "sha256": "2135582d5a3cedf8247dce06242619e90e1d519a44548891de071b7cab1ec694",
+ "size": 1065144
},
"python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "b014f9ddb6ad90a40877ff1bcf74f00f493b8b128e32025aff876cae6458ab03",
- "size": 203064
+ "mode": 416,
+ "sha256": "d36c107d46199f3efc6f10b266f2658119212e184dbcdbecff09c450a6dfc381",
+ "size": 1065368
},
"python/iqpilot_private/konn3kt/flockd/__init__.py": {
"mode": 420,
@@ -430,14 +366,14 @@
"size": 0
},
"python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "3a066b70e0f2755f97fa9886389dd0e50392a9a01f0dc6c8eda2655cdf44d010",
- "size": 268240
+ "mode": 416,
+ "sha256": "c801140079340b2e4ab00ad925ad7bc8b5184ea7f19601643ad03f65c2dd5f09",
+ "size": 1196424
},
"python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "15d8e8b6a620c41dc6fd597ff65e6545ba4da13297355a12efabca92107e1a23",
- "size": 68104
+ "mode": 416,
+ "sha256": "66129d17569ca6678ba81dd7bda5d06fd68c1a7a84c0b4e6a2a610cbaf640686",
+ "size": 1065144
},
"python/iqpilot_private/konn3kt/hephaestus/__init__.py": {
"mode": 420,
@@ -446,118 +382,133 @@
},
"python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip": {
"mode": 420,
- "sha256": "17a7e20afa7a49c43883f54a7b7f2dd0319eac0235a2d87ee1f6b70b37ef5af3",
- "size": 1379463
+ "sha256": "8562471927037cfce082e59054ffcdd39ca67519e8bad3f2583418f28f898960",
+ "size": 1379541
},
"python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "330b1a62d6ddc23a20c493945dcf1e68bb0144cfbe3b44f9d3e06c65ad438c02",
- "size": 268072
+ "mode": 416,
+ "sha256": "16a9c7dd5865cc62f67af7f7af931adc31a82a3292f4e952c153a68b3e557e47",
+ "size": 1262120
},
"python/iqpilot_private/konn3kt/hephaestus/ble_connection_parameters.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "ae55af9d22773bcb5b497fdc7da051345341c2699590d61dae0a6ba0ab6b8072",
- "size": 68104
+ "mode": 416,
+ "sha256": "8ec415342cdb92e468f477f36117ec9e9e3ed3c4e10625a133fbf1b6ffc7db7b",
+ "size": 999768
},
"python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "b16d70ff35a87b2707d00de69179d672f89a8cc8c355344fa0bd395df5a85d4f",
- "size": 472568
+ "mode": 416,
+ "sha256": "d940e5fa03e70641c613eb4d0a1392c4564d9393dee7226112564f6041ccc396",
+ "size": 1327640
},
"python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "9f42acb2fd579d548f05eabd6f892a27ccd0b5224e052dbe0a1caa8bcae1e15a",
- "size": 68080
+ "mode": 416,
+ "sha256": "8cef60cba9aa38e086c585c786b60223d7d7b858a02ad88d19a1f434d73db9d9",
+ "size": 1065272
},
"python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "9b3de54dc169aed94abed3620e5ddc58865317c1c4865777d249e0056e459cf8",
- "size": 402424
+ "mode": 416,
+ "sha256": "8e080bac97b176d2a7b271bedc3ac735c11079e8ac2a03cd2c19b4761155ccde",
+ "size": 1393080
},
"python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "987e5afd63f14b691fbc313b4ccd55d03be8f378a06829dde4cbe05d5f9d6fbc",
- "size": 337616
+ "mode": 416,
+ "sha256": "bae356c2438f4c779b72c829deda319e043ce5792c624864a72b0883d6196d4a",
+ "size": 1262056
},
"python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "c2e296298df67271d0a113fd36f5af5d9fe2dd645decaffe3b8af685c8e2266e",
- "size": 134424
+ "mode": 416,
+ "sha256": "a1bb5651a3f2bba40cca75070b721fb8c3e0108475e15227f4c2e218d201b4d0",
+ "size": 1065320
},
"python/iqpilot_private/konn3kt/hephaestus/driving_settings.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "3f3fd10b7394e9534738c3cfc2ae6c6a3dad8d0dd7953f009e46d017f66ef190",
- "size": 68152
+ "mode": 416,
+ "sha256": "e1184e9e8d5fee473e99e0edd504784d1edbbf4b5898f1eeb14794e25d2ccb7d",
+ "size": 999800
},
"python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d2f5d3004195e660b86639e4c82402b299204db9deb161558dad9f2907abf9ed",
- "size": 4311240
+ "mode": 416,
+ "sha256": "89a1ee80f07a7547ecc81e1293300969079095046b859fafd9a9871f5fe51687",
+ "size": 5065800
},
"python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "fab6aec7f68e57b725ec68f049c23af734502b2bbd31e0c582ad8147ed9fa64c",
- "size": 402296
+ "mode": 416,
+ "sha256": "690af279a513a1491ec2639d6aa6bf391a5a690456458ab1c122e90664c0848f",
+ "size": 1393128
},
"python/iqpilot_private/konn3kt/hephaestus/iqsentryvisiond.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "df46250fca25ef5e87c50c3a0caad434dd92fa893f8c9e77300d331d63cb2fc5",
- "size": 271008
+ "mode": 416,
+ "sha256": "92a8bc5e07148a44920f90ed6de201c81011f77a7d38675ad8ae60ac263f3bde",
+ "size": 1196504
},
"python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "0430935f4dea6b17d1eea372e07203b74b2842cef69bcc149db5921b99ce5499",
- "size": 338912
+ "mode": 416,
+ "sha256": "f68dac3600056ba38f65894285b89d266c494b210d8f40d754f6883bf42439e5",
+ "size": 1262040
},
"python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "81b0e7ee271a24ac155731b02e5d3fdc1b00e6f86afedcfb657eb3331006ad56",
- "size": 136384
+ "mode": 416,
+ "sha256": "60842713081ba77c08871ddf37799b26c0934615d1e3d49d22dd677be8eaf830",
+ "size": 1065288
},
"python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "c4f596d81eee41ac1d11bbe74ff388c37ea88eab61d51148280cdfa2648af8a9",
- "size": 133752
+ "mode": 416,
+ "sha256": "1b468f0e37f7647d4b3597f945b13bcdb6261277c9ee82dde1e4db0c01ae1777",
+ "size": 1065400
},
"python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d76998ffe1a7250545dc2dd79c7f0463a16c1370000273dd6eea3acd6be7b154",
- "size": 67872
+ "mode": 416,
+ "sha256": "dc3f21fdd7dfc4baa6acfc884716a56de0f66974a51df1fbb8ba977b5f103ed8",
+ "size": 999720
},
"python/iqpilot_private/konn3kt/hephaestus/sentry_body.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "2c50d0a84df01de778666ea92969e28226a91b87d6da7e6ac0d419ba25743598",
- "size": 137120
+ "mode": 416,
+ "sha256": "bdcb85c47639922960d380b6dd2fbf68883b4fc76de48da4d9fd93668b38cb45",
+ "size": 1065224
},
"python/iqpilot_private/konn3kt/hephaestus/sentry_imu.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "673812cb76a3dc353e7726bf940d68ee8c2b9ff3316dbddce0de972e72233150",
- "size": 135816
+ "mode": 416,
+ "sha256": "0d69eab9b5c27d88d0c99a0cb6e9bfb9aa50310d4b823753644b3e3eb200ad3d",
+ "size": 1196440
},
"python/iqpilot_private/konn3kt/hephaestus/sentry_media.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "734054caeac7fe5b17f69ee5d2ec917a93463a562cac50d584b8b6da5a7ab4ed",
- "size": 204080
+ "mode": 416,
+ "sha256": "cda5e46e4746b54d25012ea5966da9778e396d74ef4bb6eac762fba4c17b6fae",
+ "size": 1065224
},
"python/iqpilot_private/konn3kt/hephaestus/sentry_policy.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "3ba641937f57ce36a3663eb85df9facb97bfb5c637a776d81b63e7e74482c2d5",
- "size": 201912
+ "mode": 416,
+ "sha256": "4f3666dc01af607a8860b7d26689f4790797b25633bbe5049f7a2cc3f75ea68f",
+ "size": 1196472
},
"python/iqpilot_private/konn3kt/hephaestus/sentry_upload.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "1aa10dad0406f6d71298fb017d1cb32f4516db52eb5fe97018d220171f81048e",
- "size": 205248
+ "mode": 416,
+ "sha256": "b836c3f257c530f56882c15ea0b836c47bc270e57eb38424cfb763a2509a1f70",
+ "size": 1130696
+ },
+ "python/iqpilot_private/konn3kt/hephaestus/session_bridge.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "e4805ad4be15e8ba8fd3856b87161e95e627687275b97dd68f8ac8a84ae1c38d",
+ "size": 1065352
},
"python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "1fbb0bb01ce46805ca383b3ba207bf40b08647a047336fe0d00936518f241945",
- "size": 135584
+ "mode": 416,
+ "sha256": "4c27b4ad1ee41a5fbd854392fc5541f9532071d87a333e2fef43a253f1c78cc1",
+ "size": 1065368
},
"python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "2ae7c4618618edc31b56a8720ffcf5dea7772fd6c00ce5a68703a58990757732",
- "size": 269536
+ "mode": 416,
+ "sha256": "badeebe1aea6c0be071bebc7bffa3368d528a48b517e5a079b40f9c8579803d7",
+ "size": 1130920
+ },
+ "python/iqpilot_private/konn3kt/registration/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/iqpilot_private/konn3kt/registration/reg_core.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "338b657e8e87de063ca1d04600315f4cf30ffae34be5029e6415e52d6cd4b786",
+ "size": 1065304
},
"python/iqpilot_private/konn3kt/telemetry/__init__.py": {
"mode": 420,
@@ -565,9 +516,9 @@
"size": 0
},
"python/iqpilot_private/konn3kt/telemetry/iqtelemetryd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "be7294d1034251717952a68a6831902d5ce8ea903f69b16fc3bbf7d232043d05",
- "size": 540624
+ "mode": 416,
+ "sha256": "11cee4ff4e4041bbbb8ba76a0846a5eedbaee7fc172c7abba855a09e394915a1",
+ "size": 1393240
},
"python/iqpilot_private/konn3kt/uploaderd/__init__.py": {
"mode": 420,
@@ -575,9 +526,9 @@
"size": 0
},
"python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "ea3c6f1c89a3411773fcbab54e73f5b6997765a42c5fbe6d12249eccc321d31e",
- "size": 336264
+ "mode": 416,
+ "sha256": "2b6d4963a7370a2cac157b3ab44c02f251d7be221fbf7ec33df0b096a0ad3e3d",
+ "size": 1262072
},
"runtime": {
"entries": {
@@ -601,6 +552,14 @@
"path": "python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so",
"type": "python-module"
},
+ "iqpilot_private.konn3kt.hephaestus.iqsentryd": {
+ "path": "python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so",
+ "type": "python-module"
+ },
+ "iqpilot_private.konn3kt.hephaestus.k3wgd": {
+ "path": "python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so",
+ "type": "python-module"
+ },
"iqpilot_private.konn3kt.hephaestus.manage_hephaestusd": {
"path": "python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so",
"type": "python-module"
@@ -624,36 +583,38 @@
}
},
"signatures": {
- "python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "YpZLvLgHCf+QVH7Yo1fml62OiJu7oUVZkvT/4VScxphS4ul8GjBe+wQ+r2pFE+E1DkZ3h+QLQoI9WgqeC0HRDg==",
- "python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "TVUDTKJVz5koYuF2SxNScKNChgHO3adhQdhpZtPE3IVC+zmOr/BVxGJpykyYaFx1J541DydjM3pYeiVbkBtDCA==",
- "python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "zFIzelMLy+b9fNs3EDCwnc5/9R4KxGQyhACAbjqGogKDuo18t1FfDnQHzoa4vYqrkLcvVTRwgVP27/CWR5sNAA==",
- "python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "RImusUqqBvtJLKEgQbzOLZRn8izo9Pzll7BaFbqqfeJCaqRGBcWzNc70YcJQ5wodEaQzaY2DLIsu07l/o0ScAQ==",
- "python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "MCSdZIEfeTNKhHrL/vQChDUxRx51miixM0UAMJJ4SKkvbvmVwN56Y34B6A/aLRJvVDH8YXyUZnUCQaR8+UhgCg==",
- "python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "YP/Op7Ol87glLrYv77mMifZyBM6d4Dj/X3wiNKIcB8ECaDCtbhzTmOwlh2eO+HZIWdxT57PCu1FEhdWlb7RaCA==",
- "python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "uNYw3cE1Ffda+71eLKrU3sjFrpG7xNuhfTNf8X8ubGYFhp3tmAf2sp1+hTyvef/Mi+QjtF00CE0rnZ/dra98CQ==",
- "python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "xibUheiHQQs9Cx7ygWhYYjr7/CGBMvsSv1E0IhLyejZifAnUQQ1tK0wWcfdwFrzw3iFTgO9hp55fDHXPJV2qDw==",
- "python/iqpilot_private/konn3kt/hephaestus/ble_connection_parameters.cpython-312-aarch64-linux-gnu.so": "WguojStOM6IoIAZ3IFCmUcIJowx8UtuL8+qgn61BzvfnF2/AVS/9a+vdVvKZXwXMuZsAkKAd+GZWt4rLPUFXBw==",
- "python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "NqiUDgwj60ZdB6SYVIPkGh0HwFIQv2z0p0BKKingy5EyFThcKfbELMk86wYRbXAZnjkCUdTWrhxsxp9sIrSEAw==",
- "python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "+xL+bgAX904lJa3+fesZC3e0FAf+CbXByelJpRIjlz94eGG3h5qVgozeFalGUZYdhZpJb42SyAze4DcpaxPUAg==",
- "python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "kmmmMdiMMIIslmInzMjniLfmp/VP5scfuAbRAvNA9QRbmxxw4ko5nnya0VhTjS7d8hM0rRZ/McI4qAJByPpYCw==",
- "python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "LLGPCnUzDdyF22dJ/qhI6gYPU8hx3xeL9LeyOEqP//gzoa4jNCJeI+HWYLz0teOSoJssiGwLJ+ZuAtuT7MIpCQ==",
- "python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "kHEBEwEVZAodATQ/+/wSNyKJ5S7p2AiTqZ+kfPAmmb3ihr4xVyuy94fAkV87WIzaO1SzInB8x6SGg6UghsZiBQ==",
- "python/iqpilot_private/konn3kt/hephaestus/driving_settings.cpython-312-aarch64-linux-gnu.so": "03FhZYwHUlbfbOxYDX8CwC2gIwl6KDGRaYmCGdIx6gcg4IdtAdVyrgIw7OWDIqKuHXqlckDnf+Jh3KOClpTOAw==",
- "python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "v6huZjXXSNqSNprbiFaUgC30HDcoIiyEC7A105rut+dM3KCBisvYe/adu4p4PQVS8T2qNbqnHGboZd2NHtKQCA==",
- "python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so": "aFGz1KAWpY4O/+4xXZ+/Yfq9GLQ7IEZqNNQjEtRVjxDE7XEukXK3s4dXWpeZ9pg5j1NhmcCn/bnGaR+p9JWUBQ==",
- "python/iqpilot_private/konn3kt/hephaestus/iqsentryvisiond.cpython-312-aarch64-linux-gnu.so": "gmK55AoOUqaRL4qlpQFtSYGrPkaZ+2VtN+0XqHhwY8NH8gXZUEevFhNi/yne5qdw9TPW7k1xM0kE43cNpKAEBA==",
- "python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so": "+0CBcRpRn/sXdSxnPRIV8Hv/HO0/3+dsoIbkGePdunOxWujxmPbaFYh6jADa8vrhh4AL+UDaaJQGfMjy/3CzCg==",
- "python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "8CbpnBxEs/4IEKRupI+K2b//RlL8wGmmR99RUIa40XtOrq0Y2oZkk4qQWqWx0Leth3tj2euz3eSdm0OwrYerAg==",
- "python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "+b6HQ5D++9rppi1E1mHVQjaMXy0tB0KLb036r4sX8JslK3pXuiP1HYP4kqpugNEBhUTvI5shcsYndKi0PBRvDA==",
- "python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "1qSKc1Ge2UVIevFR4gCR+f8N3zlRoK+kRU+Db56lfzEB0f7S72mrMQpE3QD0ZRfQ+IngyYj71TueRAYTuUOZCA==",
- "python/iqpilot_private/konn3kt/hephaestus/sentry_body.cpython-312-aarch64-linux-gnu.so": "FMnBSL3lt83BfmjqfHGGdGl3Ivq4D5GwCXoULBsiq4uEKBO7v2ADru0poTJzjRCrPTTv8nnteWT+lGffEXO4DQ==",
- "python/iqpilot_private/konn3kt/hephaestus/sentry_imu.cpython-312-aarch64-linux-gnu.so": "Qp3+AE9b6G1bw6IhSGAPY9NZJeqqzsVf0vLVhdQ+CWkIesNIBAWTNixW9xLZ6bxVnw4Jvk4k5n404iaOP2iPDw==",
- "python/iqpilot_private/konn3kt/hephaestus/sentry_media.cpython-312-aarch64-linux-gnu.so": "yK5QgYja6wGUAEDAjvnhZr7kvS72pekXRGKuDfKPFUSehFVJVx+HNwWvKNLZDE60UNyeNzCf0XG40zxsN+ufDQ==",
- "python/iqpilot_private/konn3kt/hephaestus/sentry_policy.cpython-312-aarch64-linux-gnu.so": "DvORZ1fUs4uW/eRe6RiN8jp2tUJU2Mnn/y9M9XbNSXieKKWFsPgyvYAQS4TvhZc2+S1+cNbgoqqihu8EcFu7BA==",
- "python/iqpilot_private/konn3kt/hephaestus/sentry_upload.cpython-312-aarch64-linux-gnu.so": "Hu5AB59owBNkFqKmvfshrOXBSDa0I02ydUifnHjiq+7tocZNrOmjnsBPbcQgSuHYPgaux5W1r0fBmwpGLs9PAA==",
- "python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "CEXfY5TOPY7JyHubxBeyW+WS0mrCU6nH3HB2hglnA/xdIWrRxNo0DzkegS1LRDhNCvwqThMLX3c6DY8yR8urCw==",
- "python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "Wo9GEpl8kt2tPyy/7lX9d7a2gMI0gL1XrLKtTIkhJbq78qqp/TtzZOuXty2y9NvGnoXmJdRSCRfOMBFZkY/mAA==",
- "python/iqpilot_private/konn3kt/telemetry/iqtelemetryd.cpython-312-aarch64-linux-gnu.so": "gYuyE8q9g3GsKXMfMoXrLCYZvz2XJDicuriUutLPgz1KBKEZDgsAVNUxfR5oztOV+OSAb11by5nb9gi9vo62BA==",
- "python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "ZCpdDtvv/E9G2tkoFT027BhNaA6ZBtlwU9m7lyKiY3nXiD0hk5m2mkVnbwKXolNXBCyCiQa3YmL5fKmPDqUqBg=="
+ "python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "0qFnstW83THKbKdU/6x895gXoJLMF5kp9PhxhSoja0t/D/kQmp1QI0u7zhi3idsk8pwYzZCN8YMIRBrVALFsCg==",
+ "python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "eFqA9ShbE8zeqXxEaEqV0KOXKbCP/9SQifNXvRR3Y+of29oxKT+CEK5OyOdNnEjaEG+u+gTXMOd3oSsk/L+wAQ==",
+ "python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "Qx5fS+scNnelKqICvs1iRFY8httSCmDCRQLpB95hK4Riakydm5pwLTJF1YUR0Oz0cf4it+INwKcoUmVkAG9kBw==",
+ "python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "R9j9s8waY7kThuL5491x9r7C/9ROkVZWigxKaThgKC8s0Je9xIb/EFuO58reyJUVx6P37rXVU2XxSm+MvZdLCg==",
+ "python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "UkT0VaaRxweoTRjfzP3E38fwVRgEI1Fj/HMl6HPxuiBrOJhOSVWlJHwHKtYgFrzVBhUwNqnHrmAbdLy6ZN9LBg==",
+ "python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "2AnnmUawhW1vWt76WabuiXHM779pEE8hC5xHVEdZ7cHN34RJDg+Q1ZyO/5arv2ntek2Vp5v3F8dlRm6FpfQSBA==",
+ "python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "Aq7CLbLUNrZlTq6XWlexzlKS/31+9H+XRKzZRI7VVBpiD9VEtlcB7u49RRdcEH5gfpGZFLDIMrfS1J01yoggAg==",
+ "python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "gXWjTUUp8r9dDyUMaXBpEUBZCTFH70Gwb8SEj+rWFV69iB1wOijDeW9P7danSn8iw3y68l6AcVV7u3y8F+trDg==",
+ "python/iqpilot_private/konn3kt/hephaestus/ble_connection_parameters.cpython-312-aarch64-linux-gnu.so": "HqaR/WsSKt1UgAFo9cg4plqPGK7719drj6AE6GwZriK6vrLZrC2KZRwNTul7RT39RsatksN3J4oNw85I5LCPAw==",
+ "python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "ygAT4jof7Gu4vS2X+8GMic0D2QCTXCAUdU9waYi8vlyi+6hmrgbWMM/BDjDdIJoYMxjnn3zRYdN0Z6BD9PrcBg==",
+ "python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "1yQX0yOs4u8K08p5nlyQT+1UwpH96PgMTYZlpCvNwbC8o5xex4lQJyI6lirgpHXfYltVmEgSgw8N1i0GA7yGCQ==",
+ "python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "s83uFeK7C19GQKrq5R+L3poMHRo3VCj4NMq46gWrC+ofOoRmPRgkaGSqGrDGr8m/EIMbj4bpYInkxqbjqy3AAQ==",
+ "python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "ETryY+pFucqOyF5iTwE4g6mnf/luxyM3VljIioWLLppDpf8WN9WVv2Qj0r4GVsAg7iwf37HOW/dvj5EtfEiPBA==",
+ "python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "HK2d7XJdB/NScUdK0kcpOOEwlehCjXKDiz0t3qul7TSGTw/TP7fQRfottuWhynWk3WseDpDruerOM9XZNJQzDw==",
+ "python/iqpilot_private/konn3kt/hephaestus/driving_settings.cpython-312-aarch64-linux-gnu.so": "u8BScZvFva8KW03yPS1XI1VwGjLGcjMy0UMLL7X9GgksKdBKEJ/DQLU9wx3FunBk8yRQ8L/K5mq3TTGUpLn+BQ==",
+ "python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "6V6EGUYF2qEA6tfQeUs9oTMLDuAOkx1OpDrS3LJNNHuyw+QbK33iYGAiH07eNs2GjL8psL3zS1tbn2x4p6udAw==",
+ "python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so": "QUrRj3RbxO8WaTZGn5AZBTAGQYrviI8Q9IKlX7DRyVd+kq1GwAgftTS6tDLimQNHHhLXj1ZN/wnxVqKoBrGrAg==",
+ "python/iqpilot_private/konn3kt/hephaestus/iqsentryvisiond.cpython-312-aarch64-linux-gnu.so": "e0UybV5I5fAtvmF2L+1059qtu9dXCnkWUb+kdOpnUl+YXdWRwOL7TdR9TSitdNfGFRmxPfh5DwdNSToe2ZlwAw==",
+ "python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so": "GKxP/VjKIdlFW7EIDu/83MOOjKhcgjk7GqsIJzuEwlsCIWlir9cOjbJB/pgwE/mnW+BPZwKMzYeeVdUXCK+rDw==",
+ "python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "7TUFhgkXVyx4JT5aFzlK+DIm7WxF4taRsSVabHHITFbon7XXBv7YjhTsaBTZL4d83eSfj4B7qU+1oHzFclTOBQ==",
+ "python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "9CBMXV4OHwtRfxXpoXvq6xJ0rb4jE54xMZUEJz47JjOBctahb3xBKpo02pNbVKnZqZEiNmPKIDqAoHCkwtbODA==",
+ "python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "2mJaygzUnf3XXy8z3/yrFc6OtZf8MDnRE2bn3gXx3EFo/otVuBHjyVu1Mv0H8SymIRN7I/Fn+ao+lpM7ILTWCQ==",
+ "python/iqpilot_private/konn3kt/hephaestus/sentry_body.cpython-312-aarch64-linux-gnu.so": "ZeAj6MuMrsp+mlzimai1v31Jalb7yuClrRSsDIL2Hyh76efNxajwPOx5evu0Zk1jVznu3GLtXIE8qFwWqrXXBQ==",
+ "python/iqpilot_private/konn3kt/hephaestus/sentry_imu.cpython-312-aarch64-linux-gnu.so": "EJiZdjHFhyFThk1YZAYRa28cjbEa0jkSDmjNoMonT2yGyMVaxfkdU5OI86GR+LY+kEFxhRPsIlO8YSAp7PpqDA==",
+ "python/iqpilot_private/konn3kt/hephaestus/sentry_media.cpython-312-aarch64-linux-gnu.so": "IaXakjeDlvwMDS7NUG0f6E/3WTqv8FwgryUtRuXYXP67bmQfrzBI/kT8e+7FH4i49fu+CyrGbS42+JhsXEpwAA==",
+ "python/iqpilot_private/konn3kt/hephaestus/sentry_policy.cpython-312-aarch64-linux-gnu.so": "30KviVv+3hNyDLKPF+IvFbWoSH7JGgL8MKQLLR7Rrndxff6xsIw9fjFrjJuWA8q/RAkO/zw/ptJOCBFLGgTQDw==",
+ "python/iqpilot_private/konn3kt/hephaestus/sentry_upload.cpython-312-aarch64-linux-gnu.so": "Qw7U90kMvbmWcIaQl4AwLY/NesGZmFEDT5DpRw2t+IC//yr1aZqH5NteA8oEcLnnVxlqmVkP1DMu/5Dn4aBiDA==",
+ "python/iqpilot_private/konn3kt/hephaestus/session_bridge.cpython-312-aarch64-linux-gnu.so": "8xFCNu6XKY52fe6xOxPM0jDOC1fkn6G/dDVct9t/WsnScSLBb7tOaPJKl+G3zk4xlttrztdK9haPoDOFlApuBw==",
+ "python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "xBhqIugWTyGqS1Qd8hXTcL2BX4dwY5EhO2taJEBgwKhU/o17zv7+7GufJiIsAHambrVWI06fnmUA9mZXEBSRDw==",
+ "python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "cHJU8JNjRlQ9mdxUsW7VU8hr7yFohd5S2xRjIF8n4mLtfA+IiwTBPWj58HOHK9QbMG2O5uyiQnGd+pINcaF2Cg==",
+ "python/iqpilot_private/konn3kt/registration/reg_core.cpython-312-aarch64-linux-gnu.so": "Pbz/pYFEjYy2Ywx8HLxYfSZt447S+X7of2ADDrPDjWR4qwdy3mUSUgmOR20jm0SeDlhe9GtI3kbc5I3F6GrdBw==",
+ "python/iqpilot_private/konn3kt/telemetry/iqtelemetryd.cpython-312-aarch64-linux-gnu.so": "hMIg0GAGId13kARPXKz3kUDi20oF707izEkG2CWymliKdG+1L/8AYn6ib4whIl9NBszjy7vnmAzznbjTbmfgBA==",
+ "python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "Vj5Encl2ybZctTrlmZ9f5puYEBIm6ESgscF9Ee414GN88pyQnsUUhqzCeN6IaAJ9T/2tt7n/gEKSyptHCVOUCg=="
}
}
diff --git a/artifacts/iqpilot_hephaestusd_private/manifest.json.sig b/artifacts/iqpilot_hephaestusd_private/manifest.json.sig
index 60541a10..1c57efdc 100644
--- a/artifacts/iqpilot_hephaestusd_private/manifest.json.sig
+++ b/artifacts/iqpilot_hephaestusd_private/manifest.json.sig
@@ -1 +1 @@
-+bka3mSd+pxAt1QcXidY5AAFJXqrazscCuWg0zNSJmjsEk7/LaSXKaHcTCOpBOLN/oMF5nQCsmHnoJUwOEVsDQ==
+FdPsL8Lon52Th92plPCF4TsZpz+7qfhZ7yFhrfKKVDknEnF3n+t9LchpGq3n4KuCnhfCO+dszLtoNQfSq/qkDA==
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/_vendored.json
index 08cc6239..7403813f 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/_vendored.json
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/_vendored.json
@@ -1,18 +1,14 @@
[
"iqpilot.common.api",
"iqpilot.common.api.base",
- "iqpilot.common.api.comma_connect",
- "iqpilot.common.atlas_alerts",
+ "iqpilot.common.api.konn3kt",
"iqpilot.common.basedir",
- "iqpilot.common.constants",
"iqpilot.common.file_helpers",
"iqpilot.common.git_creds",
"iqpilot.common.gpio",
"iqpilot.common.i2c",
- "iqpilot.common.issue_debug",
"iqpilot.common.logging_extra",
"iqpilot.common.realtime",
- "iqpilot.common.spinner",
"iqpilot.common.swaglog",
"iqpilot.common.time_helpers",
"iqpilot.common.utils",
@@ -28,10 +24,6 @@
"iqpilot.selfdrive.iqmodeld.model_bundle_downloader",
"iqpilot.selfdrive.iqmodeld.models.fetcher",
"iqpilot.selfdrive.iqmodeld.models.helpers",
- "iqpilot.selfdrive.locationd.calibrationd",
- "iqpilot.selfdrive.selfdrived.alertmanager",
- "iqpilot.selfdrive.selfdrived.events",
- "iqpilot.selfdrive.ui.feedback.feedbackd",
"iqpilot.system.hardware",
"iqpilot.system.hardware.base",
"iqpilot.system.hardware.hw",
@@ -47,7 +39,6 @@
"iqpilot.system.loggerd.xattr_cache",
"iqpilot.system.manager.process",
"iqpilot.system.manager.process_config",
- "iqpilot.system.micd",
"iqpilot.system.phonegps.bridge",
"iqpilot.system.sentry",
"iqpilot.system.ui.lib.networkmanager",
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/__init__.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/__init__.py
index 7c7b26fc..b7e90dac 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/__init__.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/__init__.py
@@ -1,9 +1,9 @@
-import iqpilot.common.api.comma_connect
+import iqpilot.common.api.konn3kt
class Api:
def __init__(self, dongle_id):
- self.service = iqpilot.common.api.comma_connect.CommaConnectApi(dongle_id)
+ self.service = iqpilot.common.api.konn3kt.Konn3ktApi(dongle_id)
def request(self, method, endpoint, **params):
return self.service.request(method, endpoint, **params)
@@ -19,8 +19,8 @@ class Api:
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
- return iqpilot.common.api.comma_connect.CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
- return iqpilot.common.api.comma_connect.CommaConnectApi(None).get_key_pair()
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).get_key_pair()
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/base.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/base.py
index 371a8ab4..0aef9c35 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/base.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/base.py
@@ -7,10 +7,44 @@ from functools import lru_cache
from iqpilot.system.hardware.hw import Paths
from iqpilot.system.version import get_version
-# name: jwt signature algorithm
KEYS = {"id_rsa": "RS256",
"id_ecdsa": "ES256"}
+_CUSTODIAN_KEY = object()
+_IQKEEPER_SOCKET = "/run/iqkeeper/iqkeeper.sock"
+
+
+def _custodian_available() -> bool:
+ import stat
+ try:
+ return stat.S_ISSOCK(os.stat(_IQKEEPER_SOCKET).st_mode)
+ except OSError:
+ return False
+
+
+def _custodian_sign_jwt(dongle_id: str, payload: dict, alg: str) -> str:
+ import base64
+ import json
+ import socket
+ nonce = base64.b64encode(os.urandom(16)).decode()
+ packet = json.dumps({
+ "v": 1,
+ "op": "sign",
+ "dongle_id": dongle_id,
+ "payload": payload,
+ "alg": alg,
+ "nonce": nonce,
+ }).encode()
+ with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as sock:
+ sock.settimeout(25.0)
+ sock.connect(_IQKEEPER_SOCKET)
+ sock.send(packet)
+ reply_data = sock.recv(8192)
+ reply = json.loads(reply_data) if reply_data else {}
+ if reply.get("nonce") != nonce or not reply.get("ok"):
+ raise PermissionError(f"custodian sign failed: {reply.get('error', 'unknown')}")
+ return reply["token"]
+
@lru_cache(maxsize=4)
def load_signing_key(private_key: str):
@@ -60,6 +94,9 @@ class BaseApi:
}
if payload_extra is not None:
payload.update(payload_extra)
+ if self.private_key is _CUSTODIAN_KEY:
+ serializable = {k: v.isoformat() if isinstance(v, datetime) else v for k, v in payload.items()}
+ return _custodian_sign_jwt(self.dongle_id, serializable, 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):
@@ -73,9 +110,25 @@ class BaseApi:
return self.request(method, endpoint, timeout, access_token, session=session, json=json, **params)
@staticmethod
- def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
+ def get_key_pair() -> tuple[str, str | object, str] | tuple[None, None, None]:
for key in KEYS:
- if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
- with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
- return KEYS[key], private.read(), public.read()
+ priv_path = Paths.persist_root() + f'/comma/{key}'
+ pub_path = priv_path + '.pub'
+ if not os.path.isfile(priv_path) or not os.path.isfile(pub_path):
+ continue
+ try:
+ with open(pub_path) as public:
+ pub = public.read()
+ except OSError:
+ continue
+ try:
+ with open(priv_path) as private:
+ return KEYS[key], private.read(), pub
+ except PermissionError:
+ import time
+ for _attempt in range(10):
+ if _custodian_available():
+ return KEYS[key], _CUSTODIAN_KEY, pub
+ time.sleep(0.5)
+ continue
return None, None, None
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/comma_connect.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
similarity index 75%
rename from artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/comma_connect.py
rename to artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
index bc16d40e..cae7ea46 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/comma_connect.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
@@ -1,3 +1,4 @@
+# Copyright (c) 2026 IQ.Lvbs. All rights reserved.
import os
from iqpilot.common.api.base import BaseApi
@@ -5,7 +6,7 @@ from iqpilot.common.api.base import BaseApi
API_HOST = os.getenv('API_HOST', 'https://api-iqlabs.konn3kt.com')
-class CommaConnectApi(BaseApi):
+class Konn3ktApi(BaseApi):
def __init__(self, dongle_id):
super().__init__(dongle_id, API_HOST)
self.user_agent = "openpilot-"
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/atlas_alerts.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/atlas_alerts.py
deleted file mode 100644
index e2d28626..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/atlas_alerts.py
+++ /dev/null
@@ -1,277 +0,0 @@
-from __future__ import annotations
-
-from abc import ABC, abstractmethod
-from bisect import insort
-from collections.abc import Callable, Iterable
-from dataclasses import dataclass, field
-from enum import IntEnum
-
-import iqpilot.cereal.messaging as messaging
-from iqpilot.cereal import car, log
-from iqpilot.common.realtime import DT_CTRL
-from iqpilot.system.hardware import HARDWARE
-
-AlertSize = log.SelfdriveState.AlertSize
-AlertStatus = log.SelfdriveState.AlertStatus
-VisualAlert = car.CarControl.HUDControl.VisualAlert
-AudibleAlert = car.CarControl.HUDControl.AudibleAlert
-
-
-def _frames_for(seconds: float) -> int:
- return int(seconds / DT_CTRL)
-
-
-class Tier(IntEnum):
- LOWEST = 0
- LOWER = 1
- LOW = 2
- MID = 3
- HIGH = 4
- HIGHEST = 5
-
-
-class Tags:
- ENABLE = "enable"
- PRE_ENABLE = "preEnable"
- OVERRIDE_LATERAL = "overrideLateral"
- OVERRIDE_LONGITUDINAL = "overrideLongitudinal"
- NO_ENTRY = "noEntry"
- WARNING = "warning"
- USER_DISABLE = "userDisable"
- SOFT_DISABLE = "softDisable"
- IMMEDIATE_DISABLE = "immediateDisable"
- PERMANENT = "permanent"
-
-
-@dataclass(slots=True)
-class AlertCard:
- alert_text_1: str
- alert_text_2: str
- alert_status: log.SelfdriveState.AlertStatus
- alert_size: log.SelfdriveState.AlertSize
- priority: Tier
- visual_alert: car.CarControl.HUDControl.VisualAlert
- audible_alert: car.CarControl.HUDControl.AudibleAlert
- duration: int
- creation_delay: float = 0.0
- alert_type: str = field(default="", init=False)
- event_type: str | None = field(default=None, init=False)
-
- def __init__(self,
- alert_text_1: str,
- alert_text_2: str,
- alert_status: log.SelfdriveState.AlertStatus,
- alert_size: log.SelfdriveState.AlertSize,
- priority: Tier,
- visual_alert: car.CarControl.HUDControl.VisualAlert,
- audible_alert: car.CarControl.HUDControl.AudibleAlert,
- duration: float,
- creation_delay: float = 0.0):
- self.alert_text_1 = alert_text_1
- self.alert_text_2 = alert_text_2
- self.alert_status = alert_status
- self.alert_size = alert_size
- self.priority = priority
- self.visual_alert = visual_alert
- self.audible_alert = audible_alert
- self.duration = _frames_for(duration)
- self.creation_delay = creation_delay
- self.alert_type = ""
- self.event_type = None
-
- def __str__(self) -> str:
- return f"{self.alert_text_1}/{self.alert_text_2} {self.priority} {self.visual_alert} {self.audible_alert}"
-
-
-AlertFactory = Callable[[car.CarParams, car.CarState, messaging.SubMaster, bool, int, log.ControlsState], AlertCard]
-
-
-def car_mode_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> AlertCard:
- del CS, sm, metric, soft_disable_time, personality
- headline = "Enable Adaptive Cruise to Engage"
- if CP.brand == "honda":
- headline = "Enable Main Switch to Engage"
- return NoEntryCard(headline)
-
-
-class EventBook(ABC):
- def __init__(self):
- self._live_names: list[int] = []
- self._latched_names: list[int] = []
- self.event_counters: dict[int, int] = {}
-
- @property
- def events(self) -> list[int]:
- return self._live_names
-
- @events.setter
- def events(self, values: list[int]) -> None:
- self._live_names = values
-
- @property
- def static_events(self) -> list[int]:
- return self._latched_names
-
- @static_events.setter
- def static_events(self, values: list[int]) -> None:
- self._latched_names = values
-
- @property
- def names(self) -> list[int]:
- return list(self._live_names)
-
- def __len__(self) -> int:
- return len(self._live_names)
-
- def add(self, event_name: int, static: bool = False) -> None:
- if static:
- insort(self._latched_names, event_name)
- insort(self._live_names, event_name)
-
- def clear(self) -> None:
- refreshed: dict[int, int] = {}
- for event_name, frames_seen in self.event_counters.items():
- refreshed[event_name] = frames_seen + 1 if event_name in self._live_names else 0
- self.event_counters = refreshed
- self._live_names = list(self._latched_names)
-
- def contains(self, event_type: str) -> bool:
- board = self.get_events_mapping()
- return any(event_type in board.get(event_name, {}) for event_name in self._live_names)
-
- def has(self, event_name: int) -> bool:
- return event_name in self._live_names
-
- def contains_in_list(self, events_list: list[int]) -> bool:
- return any(event_name in self._live_names for event_name in events_list)
-
- def remove(self, event_name: int, static: bool = False) -> None:
- if static and event_name in self._latched_names:
- self._latched_names.remove(event_name)
-
- if event_name in self._live_names:
- self.event_counters[event_name] = self.event_counters.get(event_name, 0) + 1
- self._live_names.remove(event_name)
-
- def add_from_msg(self, events: Iterable) -> None:
- for event in events:
- insort(self._live_names, event.name.raw)
-
- def to_msg(self):
- board = self.get_events_mapping()
- outbound = []
- for event_name in self._live_names:
- msg = self.get_event_msg_type().new_message()
- msg.name = event_name
- for event_kind in board.get(event_name, {}):
- setattr(msg, event_kind, True)
- outbound.append(msg)
- return outbound
-
- def create_alerts(self, event_types: list[str], callback_args=None):
- callback_args = [] if callback_args is None else callback_args
- board = self.get_events_mapping()
- spawned: list[AlertCard] = []
- for event_name in self._live_names:
- variants = board.get(event_name, {})
- for event_type in event_types:
- chosen = variants.get(event_type)
- if chosen is None:
- continue
- alert = self._realize(chosen, callback_args)
- age_frames = self.event_counters.get(event_name, 0) + 1
- if age_frames * DT_CTRL < alert.creation_delay:
- continue
- alert.alert_type = f"{self.get_event_name(event_name)}/{event_type}"
- alert.event_type = event_type
- spawned.append(alert)
- return spawned
-
- @staticmethod
- def _realize(candidate: AlertCard | AlertFactory, callback_args: list) -> AlertCard:
- return candidate if isinstance(candidate, AlertCard) else candidate(*callback_args)
-
- @abstractmethod
- def get_events_mapping(self) -> dict[int, dict[str, AlertCard | AlertFactory]]:
- raise NotImplementedError
-
- @abstractmethod
- def get_event_name(self, event: int) -> str:
- raise NotImplementedError
-
- @abstractmethod
- def get_event_msg_type(self):
- raise NotImplementedError
-
-
-def _mici_reframe(primary: str, secondary: str) -> tuple[str, str, log.SelfdriveState.AlertSize]:
- if HARDWARE.get_device_type() == "mici":
- return secondary, primary, AlertSize.small
- return primary, secondary, AlertSize.mid
-
-
-class NoEntryCard(AlertCard):
- def __init__(self,
- alert_text_2: str,
- alert_text_1: str = "IQ.Pilot Unavailable",
- visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none,
- priority: Tier = Tier.LOW):
- primary, secondary, size = _mici_reframe(alert_text_1, alert_text_2)
- super().__init__(primary, secondary, AlertStatus.normal, size, priority, visual_alert, AudibleAlert.refuse, 3.0)
-
-
-class GentleDisableCard(AlertCard):
- def __init__(self, alert_text_2: str):
- super().__init__(
- "TAKE CONTROL IMMEDIATELY",
- alert_text_2,
- AlertStatus.userPrompt,
- AlertSize.full,
- Tier.MID,
- VisualAlert.steerRequired,
- AudibleAlert.warningSoft,
- 2.0,
- )
-
-
-class PendingDisableCard(GentleDisableCard):
- def __init__(self, alert_text_2: str):
- super().__init__(alert_text_2)
- self.alert_text_1 = "IQ.Pilot will disengage"
-
-
-class HardDisableCard(AlertCard):
- def __init__(self, alert_text_2: str):
- super().__init__(
- "TAKE CONTROL IMMEDIATELY",
- alert_text_2,
- AlertStatus.critical,
- AlertSize.full,
- Tier.HIGHEST,
- VisualAlert.steerRequired,
- AudibleAlert.warningImmediate,
- 4.0,
- )
-
-
-class ChimeCard(AlertCard):
- def __init__(self, audible_alert: car.CarControl.HUDControl.AudibleAlert):
- super().__init__("", "", AlertStatus.normal, AlertSize.none, Tier.MID, VisualAlert.none, audible_alert, 0.2)
-
-
-class BannerCard(AlertCard):
- def __init__(self, alert_text_1: str, alert_text_2: str = "", duration: float = 0.2, priority: Tier = Tier.LOWER, creation_delay: float = 0.0):
- size = AlertSize.mid if alert_text_2 else AlertSize.small
- super().__init__(alert_text_1, alert_text_2, AlertStatus.normal, size, priority, VisualAlert.none, AudibleAlert.none, duration, creation_delay)
-
-
-class BootCard(AlertCard):
- def __init__(self, alert_text_1: str, alert_text_2: str = "Always keep hands on wheel and eyes on road", alert_status=AlertStatus.normal):
- if HARDWARE.get_device_type() == "mici":
- compact_secondary = "" if alert_text_2 == "Always keep hands on wheel and eyes on road" else alert_text_2
- super().__init__(alert_text_1, compact_secondary, alert_status, AlertSize.small, Tier.LOWER, VisualAlert.none, AudibleAlert.none, 5.0)
- else:
- super().__init__(alert_text_1, alert_text_2, alert_status, AlertSize.mid, Tier.LOWER, VisualAlert.none, AudibleAlert.none, 5.0)
-
-
-NULL_ALERT = AlertCard("", "", AlertStatus.normal, AlertSize.none, Tier.LOWEST, VisualAlert.none, AudibleAlert.none, 0.0)
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/constants.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/constants.py
deleted file mode 100644
index 7ca425c4..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/constants.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import numpy as np
-
-# conversions
-class CV:
- # Speed
- MPH_TO_KPH = 1.609344
- KPH_TO_MPH = 1. / MPH_TO_KPH
- MS_TO_KPH = 3.6
- KPH_TO_MS = 1. / MS_TO_KPH
- MS_TO_MPH = MS_TO_KPH * KPH_TO_MPH
- MPH_TO_MS = MPH_TO_KPH * KPH_TO_MS
- MS_TO_KNOTS = 1.9438
- KNOTS_TO_MS = 1. / MS_TO_KNOTS
-
- # Angle
- DEG_TO_RAD = np.pi / 180.
- RAD_TO_DEG = 1. / DEG_TO_RAD
-
- # Mass
- LB_TO_KG = 0.453592
-
-
-ACCELERATION_DUE_TO_GRAVITY = 9.81 # m/s^2
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/issue_debug.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/issue_debug.py
deleted file mode 100644
index 85ce98fc..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/issue_debug.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import threading
-import time
-from datetime import datetime
-from pathlib import Path
-
-from iqpilot.system.hardware import PC
-from iqpilot.system.hardware.hw import Paths
-
-
-DEBUG_FILENAME = "iqpilot_issue_debug.txt"
-DEBUG_PATH = Path(Paths.comma_home()) / "community" / DEBUG_FILENAME if PC else Path("/data/community") / DEBUG_FILENAME
-
-_lock = threading.Lock()
-_last_log_times: dict[str, float] = {}
-
-
-def log_issue(tag: str, message: str) -> None:
- try:
- DEBUG_PATH.parent.mkdir(parents=True, exist_ok=True)
- with _lock:
- with open(DEBUG_PATH, "a", encoding="utf-8") as f:
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
- f.write(f"[{timestamp}] [{tag}] {message}\n")
- except OSError:
- pass
-
-
-def log_issue_limited(key: str, tag: str, message: str, interval_sec: float = 1.0) -> None:
- now = time.monotonic()
- with _lock:
- last = _last_log_times.get(key, 0.0)
- if now - last < interval_sec:
- return
- _last_log_times[key] = now
-
- log_issue(tag, message)
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/spinner.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/spinner.py
deleted file mode 100644
index b823d09a..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/spinner.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import os
-import subprocess
-from iqpilot.common.basedir import BASEDIR
-
-
-class Spinner:
- def __init__(self):
- try:
- self.spinner_proc = subprocess.Popen(["./spinner.py"],
- stdin=subprocess.PIPE,
- cwd=os.path.join(BASEDIR, "iqpilot", "system", "ui"),
- close_fds=True)
- except OSError:
- self.spinner_proc = None
-
- def __enter__(self):
- return self
-
- def update(self, spinner_text: str):
- if self.spinner_proc is not None:
- self.spinner_proc.stdin.write(spinner_text.encode('utf8') + b"\n")
- try:
- self.spinner_proc.stdin.flush()
- except BrokenPipeError:
- pass
-
- def update_progress(self, cur: float, total: float):
- self.update(str(round(100 * cur / total)))
-
- def close(self):
- if self.spinner_proc is not None:
- self.spinner_proc.kill()
- try:
- self.spinner_proc.communicate(timeout=2.)
- except subprocess.TimeoutExpired:
- print("WARNING: failed to kill spinner")
- self.spinner_proc = None
-
- def __del__(self):
- self.close()
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.close()
-
-
-if __name__ == "__main__":
- import time
- with Spinner() as s:
- s.update("Spinner text")
- time.sleep(5.0)
- print("gone")
- time.sleep(5.0)
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/utils.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/utils.py
index a80270bb..005d75b8 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/utils.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/common/utils.py
@@ -31,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/konn3kt/registration.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/konn3kt/registration.py
index 11e4fc30..a3720bfa 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/konn3kt/registration.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/konn3kt/registration.py
@@ -1,18 +1,12 @@
#!/usr/bin/env python3
-import os
-import time
-import json
-import jwt
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
import re
import secrets
-from typing import cast
from pathlib import Path
-from datetime import datetime, timedelta, UTC
-from iqpilot.common.api import api_get, get_key_pair
from iqpilot.common.params import Params
-from iqpilot.common.spinner import Spinner
-from iqpilot.system.hardware import HARDWARE
from iqpilot.system.hardware.hw import Paths
from iqpilot.common.swaglog import cloudlog
@@ -35,6 +29,10 @@ def _read_persist_dongle_id() -> str | None:
return None
+def is_valid_dongle_id(dongle_id: str | None) -> bool:
+ return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
+
+
def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str | None:
ro = _read_persist_dongle_id()
if is_valid_dongle_id(ro):
@@ -46,8 +44,8 @@ def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = T
if v and v != UNREGISTERED_DONGLE_ID:
return v.lower() if is_valid_dongle_id(v) else v
return ro or None
-def is_valid_dongle_id(dongle_id: str | None) -> bool:
- return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
+
+
def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str:
p = Params() if params is None else params
dongle_id = get_cached_dongle_id(p, prefer_readonly=prefer_readonly)
@@ -57,164 +55,32 @@ def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool
p.put("DongleId", dongle_id)
cloudlog.warning(f"generated new DongleId={dongle_id} (no readonly dongle_id found)")
return dongle_id
+
+
+_impl = None
+
+
+def _registration_impl():
+ global _impl
+ if _impl is None:
+ from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+ try:
+ _impl = load_private_module(__name__, "iqpilot_private.konn3kt.registration.reg_core")
+ except ProprietaryModuleMissing:
+ from konn3kt_private.registration import reg_core as _impl
+ return _impl
+
+
def ensure_dev_pairing_identity(params: Params | None = None, force_reset: bool = False) -> dict[str, str]:
- p = Params() if params is None else params
-
- persist_dir = Path(Paths.persist_root()) / "comma"
- persist_dir.mkdir(parents=True, exist_ok=True)
-
- dongle_path = persist_dir / "dongle_id"
- priv_path = persist_dir / "id_rsa"
- pub_path = persist_dir / "id_rsa.pub"
-
- if force_reset:
- for fp in (dongle_path, priv_path, pub_path):
- try:
- fp.unlink(missing_ok=True)
- except Exception:
- cloudlog.exception(f"failed to remove {fp}")
- try:
- (persist_dir / "konn3kt_prime_type").unlink(missing_ok=True)
- except Exception:
- pass
- try:
- p.remove("PrimeType")
- except Exception:
- pass
- forced_dongle = os.getenv("KONN3KT_DEV_DONGLE_ID")
- dongle_id = forced_dongle.strip().lower() if forced_dongle else None
- if dongle_id and not is_valid_dongle_id(dongle_id):
- cloudlog.error("KONN3KT_DEV_DONGLE_ID must be 16 hex chars")
- dongle_id = None
- if dongle_id is None:
- existing = None
- try:
- existing = dongle_path.read_text().strip().lower() if dongle_path.is_file() else None
- except Exception:
- cloudlog.exception("failed reading existing dev dongle_id")
- dongle_id = existing if is_valid_dongle_id(existing) else secrets.token_hex(8)
- try:
- dongle_path.write_text(dongle_id)
- except Exception:
- cloudlog.exception("failed writing dev dongle_id")
- p.put("DongleId", dongle_id)
- p.put("HardwareSerial", p.get("HardwareSerial") or f"DEV-{dongle_id}")
- if force_reset or (not priv_path.is_file()) or (not pub_path.is_file()):
- try:
- from cryptography.hazmat.primitives import serialization
- from cryptography.hazmat.primitives.asymmetric import rsa
- key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
- priv_bytes = key.private_bytes(
- encoding=serialization.Encoding.PEM,
- format=serialization.PrivateFormat.TraditionalOpenSSL,
- encryption_algorithm=serialization.NoEncryption(),
- )
- pub_bytes = key.public_key().public_bytes(
- encoding=serialization.Encoding.PEM,
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
- )
- priv_path.write_bytes(priv_bytes)
- pub_path.write_bytes(pub_bytes)
- except Exception:
- cloudlog.exception("failed generating dev RSA keys")
- raise
- return {
- "dongle_id": dongle_id,
- "serial": p.get("HardwareSerial") or f"DEV-{dongle_id}",
- "persist_dir": str(persist_dir),
- }
-def _normalize_imei(value: str | None) -> str:
- return value or ""
+ return _registration_impl().ensure_dev_pairing_identity(params, force_reset)
def get_registration_identifiers(wait_timeout: float = IMEI_WAIT_TIMEOUT, show_spinner: bool = False) -> tuple[str, str, str]:
- serial = HARDWARE.get_serial()
- spinner = Spinner() if show_spinner else None
- start_time = time.monotonic()
- imei1: str | None = None
- imei2: str | None = None
-
- while time.monotonic() - start_time < wait_timeout:
- try:
- imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
- if imei1 or imei2:
- break
- except RuntimeError as e:
- if "no modems" in str(e).lower():
- cloudlog.warning("No cellular modem available, proceeding without IMEI")
- break
- cloudlog.exception("Error getting imei, trying again...")
- except Exception:
- cloudlog.exception("Error getting imei, trying again...")
- time.sleep(1)
-
- imei1 = _normalize_imei(imei1)
- imei2 = _normalize_imei(imei2)
-
- if not imei1 and not imei2:
- cloudlog.warning(f"proceeding with serial-only registration for serial={serial}")
- if spinner is not None:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
- spinner.close()
-
- return serial, imei1, imei2
+ return _registration_impl().get_registration_identifiers(wait_timeout, show_spinner)
def register(show_spinner=False) -> str | None:
- params = Params()
-
- dongle_id: str | None = get_cached_dongle_id(params, prefer_readonly=True)
- if dongle_id in ("", UNREGISTERED_DONGLE_ID):
- dongle_id = None
-
- jwt_algo, private_key, public_key = get_key_pair()
-
- if not public_key:
- dongle_id = UNREGISTERED_DONGLE_ID
- cloudlog.warning("missing public key")
- elif dongle_id is None:
- if show_spinner:
- spinner = Spinner()
- spinner.update("registering device")
-
- serial, imei1, imei2 = get_registration_identifiers(wait_timeout=IMEI_WAIT_TIMEOUT, show_spinner=False)
-
- backoff = 0
- start_time = time.monotonic()
- while True:
- try:
- register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)},
- cast(str, private_key), algorithm=jwt_algo)
- cloudlog.info("getting pilotauth")
- cloudlog.info("getting pilotauth")
- resp = api_get("v2/pilotauth/", method='POST', timeout=15,
- imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
-
- if resp.status_code in (400, 402, 403):
- cloudlog.info(f"Unable to register device, got {resp.status_code}")
- dongle_id = UNREGISTERED_DONGLE_ID
- else:
- dongleauth = json.loads(resp.text)
- dongle_id = dongleauth["dongle_id"]
- break
- except Exception:
- cloudlog.exception("failed to authenticate")
- backoff = min(backoff + 1, 15)
- time.sleep(backoff)
-
- if time.monotonic() - start_time > 60 and show_spinner:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})")
- return UNREGISTERED_DONGLE_ID
-
- if show_spinner:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
- spinner.close()
-
- if dongle_id:
- params.put("DongleId", dongle_id)
- from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
- set_offroad_alert("Offroad_UnregisteredHardware", False)
- return dongle_id
+ return _registration_impl().register(show_spinner)
if __name__ == "__main__":
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/car/vehicle_catalog.json b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/car/vehicle_catalog.json
index e7048978..79367200 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/car/vehicle_catalog.json
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/car/vehicle_catalog.json
@@ -3941,6 +3941,86 @@
],
"req": "All"
},
+ "TESLA_MODEL_S_HW1|2014-2016": {
+ "label": "Tesla Model S (with HW1) 2014-16",
+ "id": "TESLA_MODEL_S_HW1",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model S (with HW1)",
+ "yrs": [
+ "2014",
+ "2015",
+ "2016"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_S_HW2|2017-2019": {
+ "label": "Tesla Model S (with HW2) 2017-19",
+ "id": "TESLA_MODEL_S_HW2",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model S (with HW2)",
+ "yrs": [
+ "2017",
+ "2018",
+ "2019"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_S_HW3|2020-2023": {
+ "label": "Tesla Model S (with HW3) 2020-23",
+ "id": "TESLA_MODEL_S_HW3",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model S (with HW3)",
+ "yrs": [
+ "2020",
+ "2021",
+ "2022",
+ "2023"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_X_HW1|2014-2016": {
+ "label": "Tesla Model X (with HW1) 2014-16",
+ "id": "TESLA_MODEL_X_HW1",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model X (with HW1)",
+ "yrs": [
+ "2014",
+ "2015",
+ "2016"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_X_HW2|2016-2019": {
+ "label": "Tesla Model X (with HW2) 2016-19",
+ "id": "TESLA_MODEL_X_HW2",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model X (with HW2)",
+ "yrs": [
+ "2016",
+ "2017",
+ "2018",
+ "2019"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_X_HW3|2021-2023": {
+ "label": "Tesla Model X (with HW3) 2021-23",
+ "id": "TESLA_MODEL_X_HW3",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model X (with HW3)",
+ "yrs": [
+ "2021",
+ "2022",
+ "2023"
+ ],
+ "req": "All"
+ },
"TESLA_MODEL_X|2024": {
"label": "Tesla Model X (with HW4) 2024",
"id": "TESLA_MODEL_X",
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
index 1749bfe0..6ba1c352 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
@@ -139,7 +139,18 @@ def egpu_oob_pkl_path(meta: dict) -> str:
def egpu_model_oob_pkl_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths
- return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob.pkl")
+ return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob2.pkl")
+
+
+def drop_frozen_model_oob(meta: dict) -> None:
+ from iqpilot.system.hardware.hw import Paths
+ # warp-on-dock bundles saved under the old name were built before the prune fix and replay capture-time feeds
+ stale = os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob.pkl")
+ try:
+ os.remove(stale)
+ cloudlog.warning(f"removed frozen-feed warp-on-dock artifact {stale}")
+ except OSError:
+ pass
def onnx_cache_path(meta: dict) -> str:
@@ -288,6 +299,28 @@ def patch_tinygrad_fetch_fw() -> None:
helpers.fetch_fw = fetch_fw
+def patch_tinygrad_prune_views() -> None:
+ from tinygrad.engine import jit
+ from tinygrad.uop.ops import Ops
+ if getattr(jit.prune_linear, "_iq_patched", False):
+ return
+
+ # upstream prune only sees bare BUFFER args, so a kernel reading an input through a SLICE view ran once at capture and froze that input
+ def prune_linear(linear, needed):
+ kept, onetime = [], []
+ for si in linear.src:
+ si_bufs = {b for src in si.src[1:] for b in src.toposort() if b.op is Ops.BUFFER}
+ if not si_bufs.isdisjoint(needed):
+ kept.append(si)
+ needed |= si_bufs
+ else:
+ onetime.append(si)
+ return linear.replace(src=tuple(kept)), linear.replace(src=tuple(onetime))
+
+ prune_linear._iq_patched = True
+ jit.prune_linear = prune_linear
+
+
def patch_usb_signal_wait() -> None:
import time
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
index b8f4ccfc..7b75718a 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
@@ -3,7 +3,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
"""
from __future__ import annotations
-import functools
import hashlib
import json
import os
@@ -24,26 +23,29 @@ RANGE_RETRIES = 4
PARALLEL_PARTS = max(1, int(os.environ.get("IQ_DOWNLOAD_PARTS", "3")))
-def _requests_auth():
+def _git_auth():
import importlib
for mod in ("iqpilot_private.models.git_auth", "iqpilot.models_private_src.git_auth",
"iqpilot.selfdrive.iqmodeld.models.git_auth"):
try:
- return importlib.import_module(mod).get_requests_auth()
+ return importlib.import_module(mod)
except Exception:
continue
return None
-def _hf():
- import importlib
- for mod in ("iqpilot_private.models.git_auth", "iqpilot.selfdrive.iqmodeld.models.git_auth"):
- try:
- m = importlib.import_module(mod)
- return m.get_hf_headers(), m.hf_resolve_url
- except Exception:
- continue
- return None, None
+def _resolve_hf(hf_path: str) -> tuple[str | None, dict]:
+ m = _git_auth()
+ if m is None:
+ return None, {}
+ return m.resolve_hf(hf_path)
+
+
+def requests_get(url: str, **kwargs):
+ m = _git_auth()
+ if m is None:
+ raise RuntimeError("no asset broker available")
+ return m.requests_get(url, **kwargs)
class RangesNotHonoured(Exception):
@@ -189,10 +191,9 @@ def _verify_file(tmp: str, sha256: str, size: int) -> None:
def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
- headers, resolve = _hf()
- if resolve is None:
+ url, headers = _resolve_hf(hf_path)
+ if url is None:
raise RuntimeError("no HF credentials available")
- url = resolve(hf_path)
os.makedirs(os.path.dirname(dst), exist_ok=True)
tmp = dst + ".hfpart"
last_error: Exception | None = None
@@ -213,8 +214,10 @@ def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb
os.remove(leftover)
except Exception as e:
last_error = e
- for _attempt in range(STREAM_RETRIES):
+ for attempt in range(STREAM_RETRIES):
try:
+ if attempt:
+ url, headers = _resolve_hf(hf_path)
have = os.path.getsize(tmp) if os.path.isfile(tmp) else 0
if size and have > size:
os.remove(tmp)
@@ -242,26 +245,6 @@ def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb
raise RuntimeError(f"HF download failed: {last_error}")
-def _lfs_endpoint(base_url: str) -> str:
- return base_url.split("/raw/", 1)[0] + ".git/info/lfs"
-
-
-def _resolve_oid(session, base_url: str, oid: str, size: int, auth):
- import requests
- batch = session.post(f"{_lfs_endpoint(base_url)}/objects/batch",
- data=json.dumps({"operation": "download", "transfers": ["basic"],
- "objects": [{"oid": oid, "size": size}]}),
- headers={"Content-Type": "application/vnd.git-lfs+json",
- "Accept": "application/vnd.git-lfs+json"},
- auth=auth, timeout=HTTP_TIMEOUT_S)
- batch.raise_for_status()
- entry = batch.json()["objects"][0]
- if "actions" not in entry:
- raise requests.RequestException(f"LFS object unavailable: {entry.get('error', oid)}")
- action = entry["actions"]["download"]
- return action["href"], action.get("header", {})
-
-
def _part_path(dst: str, oid: str) -> str:
return os.path.join(dst + ".parts", oid)
@@ -276,19 +259,28 @@ def _part_complete(path: str, oid: str, size: int) -> bool:
return digest.hexdigest() == oid
-def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) -> None:
+def _fetch_part(session, obj: dict, path: str, progress) -> None:
size = int(obj["size"])
have = os.path.getsize(path) if os.path.isfile(path) else 0
if have > size:
os.remove(path)
have = 0
- href, headers = _resolve_oid(session, base_url, obj["oid"], size, auth)
- obj_auth = None if headers.get("Authorization") else auth
+ m = _git_auth()
+ if m is None:
+ raise RuntimeError("no asset broker available for LFS parts")
+ target = m.broker_lfs_url(obj["oid"], size)
+ headers = m.broker_headers() if target else {}
+ if not (target and headers):
+ raise RuntimeError("no device identity for the konn3kt asset broker")
+ _stream_part(session, target, headers, path, have, progress)
+
+
+def _stream_part(session, href: str, headers: dict, path: str, have: int, progress) -> None:
# LFS parts are content-addressed (oid == sha256), so a half-written part can be resumed with a
# Range request and verified afterwards instead of being thrown away on every restart.
if have:
headers = {**headers, "Range": f"bytes={have}-"}
- with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S, auth=obj_auth) as r:
+ with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S) as r:
r.raise_for_status()
if have and r.status_code != 206:
have = 0
@@ -300,7 +292,6 @@ def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) ->
def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
- auth = _requests_auth()
os.makedirs(dst + ".parts", exist_ok=True)
total = int(size) or sum(int(o["size"]) for o in objects)
done_bytes = sum(int(o["size"]) for o in objects if _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"])))
@@ -318,30 +309,26 @@ def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progres
with lock:
progress(n)
- def fetch(base_url: str, obj: dict) -> None:
+ def fetch(obj: dict) -> None:
path = _part_path(dst, obj["oid"])
if _part_complete(path, obj["oid"], int(obj["size"])):
return
- _fetch_part(requests.Session(), base_url, obj, path, auth, locked_progress)
+ _fetch_part(requests.Session(), obj, path, locked_progress)
if not _part_complete(path, obj["oid"], int(obj["size"])):
if os.path.getsize(path) >= int(obj["size"]):
os.remove(path)
raise RuntimeError(f"part {obj['oid'][:12]} incomplete or failed verification")
- for base_url in MODELS_BASE_URLS:
- for _attempt in range(STREAM_RETRIES):
- try:
- pending = [o for o in objects if not _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))]
- got[0] = total - sum(int(o["size"]) for o in pending)
- with ThreadPoolExecutor(max_workers=max(1, min(PARALLEL_PARTS, len(pending) or 1))) as pool:
- for _ in pool.map(functools.partial(fetch, base_url), pending):
- pass
- break
- except Exception as e:
- last_error = e
- else:
- continue
- break
+ for _attempt in range(STREAM_RETRIES):
+ try:
+ pending = [o for o in objects if not _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))]
+ got[0] = total - sum(int(o["size"]) for o in pending)
+ with ThreadPoolExecutor(max_workers=max(1, min(PARALLEL_PARTS, len(pending) or 1))) as pool:
+ for _ in pool.map(fetch, pending):
+ pass
+ break
+ except Exception as e:
+ last_error = e
else:
raise RuntimeError(f"model bundle download failed: {last_error}")
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
index 05cce298..ee2eb77f 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
@@ -305,7 +305,40 @@ def get_runtime_bundle_upgrade(bundle, params: Params = None, available_bundles=
return _find_runtime_upgrade(bundle, params, available_bundles)
+_forced_bundle = None
+
+
+def load_default_model_session():
+ """Stage the shipped default model files and return a decoded default bundle
+ WITHOUT persisting it as the active selection, so the next boot retries the
+ user's own model instead of staying on the default forever."""
+ bundle_dict = _load_default_bundle_dict()
+ ensure_default_model_files(bundle_dict)
+ return ModelBundle(**bundle_dict)
+
+
+class forced_active_bundle:
+ """Make get_active_bundle return `bundle` for the duration of the block, used to
+ build a runner for a session-only fallback bundle without touching Params."""
+ def __init__(self, bundle):
+ self._bundle = bundle
+
+ def __enter__(self):
+ global _forced_bundle
+ self._prev = _forced_bundle
+ _forced_bundle = self._bundle
+ return self._bundle
+
+ def __exit__(self, *exc):
+ global _forced_bundle
+ _forced_bundle = self._prev
+ return False
+
+
def get_active_bundle(params: Params = None):
+ if _forced_bundle is not None:
+ return _forced_bundle
+
params = Params() if params is None else params
try:
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/locationd/calibrationd.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/locationd/calibrationd.py
deleted file mode 100644
index 3e3ec6d4..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/locationd/calibrationd.py
+++ /dev/null
@@ -1,367 +0,0 @@
-#!/usr/bin/env python3
-'''
-This process finds calibration values. More info on what these calibration values
-are can be found here https://github.com/commaai/openpilot/tree/master/common/transformations
-While the roll calibration is a real value that can be estimated, here we assume it's zero,
-and the image input into the neural network is not corrected for roll.
-'''
-
-import os
-import capnp
-import numpy as np
-from typing import NoReturn
-
-from iqpilot.cereal import log, car
-import iqpilot.cereal.messaging as messaging
-from iqpilot.common.constants import CV
-from iqpilot.common.params import Params
-from iqpilot.common.issue_debug import log_issue_limited
-from iqpilot.common.realtime import config_realtime_process
-from iqpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
-from iqpilot.common.swaglog import cloudlog
-from iqpilot.system.hardware import HARDWARE
-
-MIN_SPEED_FILTER = 15 * CV.MPH_TO_MS
-MAX_VEL_ANGLE_STD = np.radians(0.25)
-MAX_YAW_RATE_FILTER = np.radians(2) # per second
-
-MAX_HEIGHT_STD = np.exp(-3.5)
-
-# This is at model frequency, blocks needed for efficiency
-SMOOTH_CYCLES = 10
-BLOCK_SIZE = 100
-INPUTS_NEEDED = 5 # Minimum blocks needed for valid calibration
-INPUTS_WANTED = 50 # We want a little bit more than we need for stability
-MAX_ALLOWED_YAW_SPREAD = np.radians(2)
-MAX_ALLOWED_PITCH_SPREAD = np.radians(4)
-TICI_FAMILY_PITCH_SPREAD_RESET = np.radians(3)
-RPY_INIT = np.array([0.0,0.0,0.0])
-WIDE_FROM_DEVICE_EULER_INIT = np.array([0.0, 0.0, 0.0])
-HEIGHT_INIT = np.array([1.22])
-HEIGHT_SANE_MIN, HEIGHT_SANE_MAX = 0.9, 2.0
-DEVICE_IS_TICI_FAMILY = HARDWARE.get_device_type() in ("tici", "tizi")
-
-# These values are needed to accommodate the model frame in the narrow cam
-if HARDWARE.get_device_type() == 'mici':
- PITCH_LIMITS = np.array([-0.143101, 0.22235988])
-else:
- PITCH_LIMITS = np.array([-0.09074112085129739, 0.17])
-YAW_LIMITS = np.array([-0.06912048084718224, 0.06912048084718235])
-DEBUG = os.getenv("DEBUG") is not None
-
-def is_calibration_valid(rpy: np.ndarray) -> bool:
- return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1])
-
-
-def sanity_clip(rpy: np.ndarray) -> np.ndarray:
- if np.isnan(rpy).any():
- rpy = RPY_INIT
- return np.array([rpy[0],
- np.clip(rpy[1], PITCH_LIMITS[0] - .005, PITCH_LIMITS[1] + .005),
- np.clip(rpy[2], YAW_LIMITS[0] - .005, YAW_LIMITS[1] + .005)])
-
-def moving_avg_with_linear_decay(prev_mean: np.ndarray, new_val: np.ndarray, idx: int, block_size: float) -> np.ndarray:
- return (idx*prev_mean + (block_size - idx) * new_val) / block_size
-
-class Calibrator:
- def __init__(self, param_put: bool = False):
- self.param_put = param_put
-
- self.not_car = False
- self.stable_rpy = RPY_INIT.copy()
- self.stable_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT.copy()
- self.stable_height = HEIGHT_INIT.copy()
- self.has_stable_snapshot = False
-
- # Read saved calibration
- self.params = Params()
- calibration_params = self.params.get("CalibrationParams")
- rpy_init = RPY_INIT
- wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
- height = HEIGHT_INIT
- valid_blocks = 0
- self.cal_status = log.ExtrinsicsCalibration.Status.uncalibrated
-
- if param_put and calibration_params:
- try:
- with log.Event.from_bytes(calibration_params) as msg:
- rpy_init = np.array(msg.extrinsicsCalibration.rpyCalib)
- valid_blocks = msg.extrinsicsCalibration.validBlocks
- wide_from_device_euler = np.array(msg.extrinsicsCalibration.wideFromDeviceEuler)
- height = np.array(msg.extrinsicsCalibration.height)
- except Exception:
- cloudlog.exception("Error reading cached CalibrationParams")
-
- self.reset(rpy_init, valid_blocks, wide_from_device_euler, height)
- self.update_status()
-
- # If saved calibration is immediately invalid (e.g. bad params from a previous
- # bootstrap bug or device remount), auto-clear it so we recalibrate from scratch
- # instead of getting permanently stuck in the "Calibration Invalid" state.
- if self.cal_status == log.ExtrinsicsCalibration.Status.invalid:
- cloudlog.warning("calibrationd: saved CalibrationParams are invalid, clearing and starting fresh")
- if param_put:
- self.params.remove("CalibrationParams")
- self.reset()
- self.update_status()
-
- def _remember_stable_solution(self) -> None:
- self.stable_rpy = self.rpy.copy()
- self.stable_wide_from_device_euler = self.wide_from_device_euler.copy()
- self.stable_height = self.height.copy()
- self.has_stable_snapshot = True
-
- def reset(self, rpy_init: np.ndarray = RPY_INIT,
- valid_blocks: int = 0,
- wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT,
- height_init: np.ndarray = HEIGHT_INIT,
- smooth_from: np.ndarray | None = None) -> None:
- if not np.isfinite(rpy_init).all():
- self.rpy = RPY_INIT.copy()
- else:
- self.rpy = rpy_init.copy()
-
- if not np.isfinite(height_init).all() or len(height_init) != 1:
- self.height = HEIGHT_INIT.copy()
- else:
- self.height = height_init.copy()
-
- if not np.isfinite(wide_from_device_euler_init).all() or len(wide_from_device_euler_init) != 3:
- self.wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT.copy()
- else:
- self.wide_from_device_euler = wide_from_device_euler_init.copy()
-
- if not np.isfinite(valid_blocks) or valid_blocks < 0:
- self.valid_blocks = 0
- else:
- self.valid_blocks = valid_blocks
-
- self.rpys = np.tile(self.rpy, (INPUTS_WANTED, 1))
- self.wide_from_device_eulers = np.tile(self.wide_from_device_euler, (INPUTS_WANTED, 1))
- self.heights = np.tile(self.height, (INPUTS_WANTED, 1))
-
- self.idx = 0
- self.block_idx = 0
- self.v_ego = 0.0
-
- if smooth_from is None:
- self.old_rpy = RPY_INIT
- self.old_rpy_weight = 0.0
- else:
- self.old_rpy = smooth_from
- self.old_rpy_weight = 1.0
-
- def get_valid_idxs(self) -> list[int]:
- # exclude current block_idx from validity window
- before_current = list(range(self.block_idx))
- after_current = list(range(min(self.valid_blocks, self.block_idx + 1), self.valid_blocks))
- return before_current + after_current
-
- def update_status(self) -> None:
- valid_idxs = self.get_valid_idxs()
- if valid_idxs:
- self.wide_from_device_euler = np.mean(self.wide_from_device_eulers[valid_idxs], axis=0)
- self.height = np.mean(self.heights[valid_idxs], axis=0)
- rpys = self.rpys[valid_idxs]
- self.rpy = np.mean(rpys, axis=0)
- max_rpy_calib = np.array(np.max(rpys, axis=0))
- min_rpy_calib = np.array(np.min(rpys, axis=0))
- self.calib_spread = np.abs(max_rpy_calib - min_rpy_calib)
- else:
- self.calib_spread = np.zeros(3)
-
- if self.valid_blocks < INPUTS_NEEDED:
- if self.cal_status == log.ExtrinsicsCalibration.Status.recalibrating:
- self.cal_status = log.ExtrinsicsCalibration.Status.recalibrating
- else:
- self.cal_status = log.ExtrinsicsCalibration.Status.uncalibrated
- elif is_calibration_valid(self.rpy):
- self.cal_status = log.ExtrinsicsCalibration.Status.calibrated
- else:
- self.cal_status = log.ExtrinsicsCalibration.Status.invalid
-
- # If spread is too high, assume mounting was changed and reset to last block.
- # Make the transition smooth. Abrupt transitions are not good for feedback loop through supercombo model.
- # TODO: add height spread check with smooth transition too
- pitch_spread_limit = TICI_FAMILY_PITCH_SPREAD_RESET if DEVICE_IS_TICI_FAMILY else MAX_ALLOWED_PITCH_SPREAD
- spread_too_high = self.calib_spread[1] > pitch_spread_limit or self.calib_spread[2] > MAX_ALLOWED_YAW_SPREAD
- if self.cal_status == log.ExtrinsicsCalibration.Status.calibrated and not spread_too_high:
- self._remember_stable_solution()
-
- if spread_too_high and self.cal_status == log.ExtrinsicsCalibration.Status.calibrated:
- use_stable_snapshot = DEVICE_IS_TICI_FAMILY and self.has_stable_snapshot
- if use_stable_snapshot:
- reset_rpy = self.stable_rpy
- reset_wide = self.stable_wide_from_device_euler
- reset_height = self.stable_height
- else:
- reset_rpy = self.rpys[self.block_idx - 1]
- reset_wide = self.wide_from_device_eulers[self.block_idx - 1]
- reset_height = self.heights[self.block_idx - 1]
-
- log_issue_limited(
- "calibrationd_reset_spread",
- "calibration",
- f"calibrationd reset unstable solution pitchSpread={self.calib_spread[1]:.6f} "
- f"yawSpread={self.calib_spread[2]:.6f} pitchLimit={pitch_spread_limit:.6f} "
- f"use_stable_snapshot={use_stable_snapshot} rpy={self.rpy.tolist()}",
- interval_sec=0.5,
- )
- self.reset(reset_rpy, valid_blocks=1, wide_from_device_euler_init=reset_wide,
- height_init=reset_height, smooth_from=self.stable_rpy if use_stable_snapshot else self.rpy)
- self.cal_status = log.ExtrinsicsCalibration.Status.recalibrating
-
- write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5)
- if self.param_put and write_this_cycle:
- self.params.put_nonblocking("CalibrationParams", self.get_msg(True).to_bytes())
-
- def handle_v_ego(self, v_ego: float) -> None:
- self.v_ego = v_ego
-
- def get_smooth_rpy(self) -> np.ndarray:
- if self.old_rpy_weight > 0:
- return self.old_rpy_weight * self.old_rpy + (1.0 - self.old_rpy_weight) * self.rpy
- else:
- return self.rpy
-
- def handle_cam_odom(self, trans: list[float],
- rot: list[float],
- wide_from_device_euler: list[float],
- trans_std: list[float],
- road_transform_trans: list[float],
- road_transform_trans_std: list[float]) -> np.ndarray | None:
- self.old_rpy_weight = max(0.0, self.old_rpy_weight - 1/SMOOTH_CYCLES)
-
- fast_enough = self.v_ego > MIN_SPEED_FILTER
- motion_speed = max(float(self.v_ego), float(trans[0]))
- cam_fast_enough = motion_speed > MIN_SPEED_FILTER
- yaw_ok = abs(rot[2]) < MAX_YAW_RATE_FILTER
- straight_and_fast = fast_enough and cam_fast_enough and yaw_ok
- angle_std_threshold = MAX_VEL_ANGLE_STD
- height_std_threshold = MAX_HEIGHT_STD
- rpy_certain = np.arctan2(trans_std[1], motion_speed) < angle_std_threshold
- if len(road_transform_trans_std) == 3:
- height_certain = road_transform_trans_std[2] < height_std_threshold
- else:
- height_certain = True
-
- certain_if_calib = rpy_certain
- if not (straight_and_fast and certain_if_calib):
- log_issue_limited(
- "calibrationd_rejected_sample",
- "calibration",
- f"calibrationd rejected sample vEgo={self.v_ego:.2f} trans0={trans[0]:.2f} yawRate={rot[2]:.4f} "
- f"fast_enough={fast_enough} cam_fast_enough={cam_fast_enough} motion_speed={motion_speed:.2f} yaw_ok={yaw_ok} "
- f"rpy_certain={rpy_certain} height_certain={height_certain} valid_blocks={self.valid_blocks} idx={self.idx}",
- interval_sec=1.0,
- )
- return None
-
- observed_rpy = np.array([0,
- -np.arctan2(trans[2], trans[0]),
- np.arctan2(trans[1], trans[0])])
- new_rpy = euler_from_rot(rot_from_euler(self.get_smooth_rpy()).dot(rot_from_euler(observed_rpy)))
- new_rpy = sanity_clip(new_rpy)
-
- if len(wide_from_device_euler) == 3:
- new_wide_from_device_euler = np.array(wide_from_device_euler)
- else:
- new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
-
- if len(road_transform_trans) == 3 and HEIGHT_SANE_MIN <= road_transform_trans[2] <= HEIGHT_SANE_MAX:
- new_height = np.array([road_transform_trans[2]])
- else:
- new_height = HEIGHT_INIT
-
- self.rpys[self.block_idx] = moving_avg_with_linear_decay(self.rpys[self.block_idx], new_rpy, self.idx, float(BLOCK_SIZE))
- self.wide_from_device_eulers[self.block_idx] = moving_avg_with_linear_decay(self.wide_from_device_eulers[self.block_idx],
- new_wide_from_device_euler, self.idx, float(BLOCK_SIZE))
- self.heights[self.block_idx] = moving_avg_with_linear_decay(self.heights[self.block_idx], new_height, self.idx, float(BLOCK_SIZE))
-
- self.idx = (self.idx + 1) % BLOCK_SIZE
- if self.idx == 0:
- self.block_idx += 1
- self.valid_blocks = max(self.block_idx, self.valid_blocks)
- self.block_idx = self.block_idx % INPUTS_WANTED
-
- self.update_status()
-
- if self.idx == 0:
- log_issue_limited(
- "calibrationd_progress_block",
- "calibration",
- f"calibrationd progress status={int(self.cal_status)} valid_blocks={self.valid_blocks} "
- f"calPerc={min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100)} "
- f"rpy={self.rpy.tolist()} spread={self.calib_spread.tolist()}",
- interval_sec=0.5,
- )
-
- return new_rpy
-
- def get_msg(self, valid: bool) -> capnp.lib.capnp._DynamicStructBuilder:
- smooth_rpy = self.get_smooth_rpy()
-
- msg = messaging.new_message('extrinsicsCalibration')
- msg.valid = valid
-
- extrinsicsCalibration = msg.extrinsicsCalibration
- extrinsicsCalibration.validBlocks = self.valid_blocks
- extrinsicsCalibration.calStatus = self.cal_status
- extrinsicsCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100)
- extrinsicsCalibration.rpyCalib = smooth_rpy.tolist()
- extrinsicsCalibration.rpyCalibSpread = self.calib_spread.tolist()
- extrinsicsCalibration.wideFromDeviceEuler = self.wide_from_device_euler.tolist()
- extrinsicsCalibration.height = self.height.tolist()
-
- return msg
-
- def send_data(self, pm: messaging.PubMaster, valid: bool) -> None:
- pm.send('extrinsicsCalibration', self.get_msg(valid))
-
-
-def main() -> NoReturn:
- config_realtime_process([0, 1, 2, 3], 5)
-
- pm = messaging.PubMaster(['extrinsicsCalibration'])
- sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll='cameraOdometry')
-
- params_reader = Params()
- CP = messaging.log_from_bytes(params_reader.get("CarParams", block=True), car.CarParams)
-
- calibrator = Calibrator(param_put=True)
- calibrator.not_car = CP.notCar
-
- while 1:
- timeout = 0 if sm.frame == -1 else 100
- sm.update(timeout)
-
- if sm.updated['cameraOdometry']:
- calibrator.handle_v_ego(sm['carState'].vEgo)
- new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans,
- sm['cameraOdometry'].rot,
- sm['cameraOdometry'].wideFromDeviceEuler,
- sm['cameraOdometry'].transStd,
- sm['cameraOdometry'].roadTransformTrans,
- sm['cameraOdometry'].roadTransformTransStd)
-
- if DEBUG and new_rpy is not None:
- print('got new rpy', new_rpy)
-
- # 4Hz driven by cameraOdometry
- if sm.frame % 5 == 0:
- checks_ok = sm.all_checks()
- if not checks_ok:
- ft = sm.freq_tracker
- recv_hz = {s: (round(1.0 / ft[s].avg_dt.get_average(), 2) if ft[s].avg_dt.count else None) for s in sm.services}
- log_issue_limited(
- "calibrationd_checks_failed",
- "calibration",
- f"calibrationd all_checks failed alive={sm.alive} freq_ok={sm.freq_ok} valid={sm.valid} "
- f"seen={sm.seen} recv_hz={recv_hz}",
- interval_sec=5.0,
- )
- calibrator.send_data(pm, checks_ok)
-
-
-if __name__ == "__main__":
- main()
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/alertmanager.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/alertmanager.py
deleted file mode 100644
index 0ffe2943..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/alertmanager.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import copy
-import os
-import json
-from collections import defaultdict
-from dataclasses import dataclass
-
-from iqpilot.common.basedir import BASEDIR
-from iqpilot.common.params import Params
-from iqpilot.selfdrive.selfdrived.events import Alert
-from iqpilot.common.atlas_alerts import NULL_ALERT as EmptyAlert
-
-
-with open(os.path.join(BASEDIR, "iqpilot/selfdrive/selfdrived/alerts_offroad.json")) as f:
- OFFROAD_ALERTS = json.load(f)
-
-
-def set_offroad_alert(alert: str, show_alert: bool, extra_text: str | None = None) -> None:
- if show_alert:
- a = copy.copy(OFFROAD_ALERTS[alert])
- a['extra'] = extra_text or ''
- Params().put(alert, a)
- else:
- Params().remove(alert)
-
-
-@dataclass
-class AlertEntry:
- alert: Alert | None = None
- start_frame: int = -1
- end_frame: int = -1
- added_frame: int = -1
-
- def active(self, frame: int) -> bool:
- return frame <= self.end_frame
-
- def just_added(self, frame: int) -> bool:
- return self.active(frame) and frame == (self.added_frame + 1)
-
-class AlertManager:
- def __init__(self):
- self.alerts: dict[str, AlertEntry] = defaultdict(AlertEntry)
- self.current_alert = EmptyAlert
-
- def add_many(self, frame: int, alerts: list[Alert]) -> None:
- for alert in alerts:
- entry = self.alerts[alert.alert_type]
- entry.alert = alert
- if not entry.just_added(frame):
- entry.start_frame = frame
- min_end_frame = entry.start_frame + alert.duration
- entry.end_frame = max(frame + 1, min_end_frame)
- entry.added_frame = frame
-
- def process_alerts(self, frame: int, clear_event_types: set):
- ae = AlertEntry()
- for v in self.alerts.values():
- if not v.alert:
- continue
-
- if v.alert.event_type in clear_event_types:
- v.end_frame = -1
-
- # sort by priority first and then by start_frame
- greater = ae.alert is None or (v.alert.priority, v.start_frame) > (ae.alert.priority, ae.start_frame)
- if v.active(frame) and greater:
- ae = v
-
- self.current_alert = ae.alert if ae.alert is not None else EmptyAlert
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py
deleted file mode 100644
index aeaa9eb9..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py
+++ /dev/null
@@ -1,892 +0,0 @@
-#!/usr/bin/env python3
-import math
-
-from iqpilot.cereal import log, car
-import iqpilot.cereal.messaging as messaging
-from iqpilot.common.constants import CV
-from iqpilot.common.realtime import DT_CTRL
-from iqpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
-from iqpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
-from iqpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
-from iqpilot.system.hardware import HARDWARE
-
-from iqpilot.common.atlas_alerts import EventBook as EventsBase, Tier as Priority, Tags as ET, AlertCard as Alert, \
- NoEntryCard as NoEntryAlert, GentleDisableCard as SoftDisableAlert, PendingDisableCard as UserSoftDisableAlert, \
- HardDisableCard as ImmediateDisableAlert, ChimeCard as EngagementAlert, BannerCard as NormalPermanentAlert, \
- BootCard as StartupAlert, AlertFactory as AlertCallbackType, car_mode_entry_alert as wrong_car_mode_alert
-
-AlertSize = log.SelfdriveState.AlertSize
-AlertStatus = log.SelfdriveState.AlertStatus
-VisualAlert = car.CarControl.HUDControl.VisualAlert
-AudibleAlert = car.CarControl.HUDControl.AudibleAlert
-EventName = log.OnroadEvent.EventName
-
-EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()}
-
-class Events(EventsBase):
- def __init__(self):
- super().__init__()
- self.event_counters = dict.fromkeys(EVENTS.keys(), 0)
-
- def get_events_mapping(self) -> dict[int, dict[str, Alert | AlertCallbackType]]:
- return EVENTS
-
- def get_event_name(self, event: int):
- return EVENT_NAME[event]
-
- def get_event_msg_type(self):
- return log.OnroadEvent
-
-def get_display_speed(speed_ms: float, metric: bool) -> str:
- speed = int(round(speed_ms * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH)))
- unit = 'km/h' if metric else 'mph'
- return f"{speed} {unit}"
-
-def soft_disable_alert(alert_text_2: str) -> AlertCallbackType:
- def func(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- if soft_disable_time < int(0.5 / DT_CTRL):
- return ImmediateDisableAlert(alert_text_2)
- return SoftDisableAlert(alert_text_2)
- return func
-
-def user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType:
- def func(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- if soft_disable_time < int(0.5 / DT_CTRL):
- return ImmediateDisableAlert(alert_text_2)
- return UserSoftDisableAlert(alert_text_2)
- return func
-
-def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- return NoEntryAlert(f"Drive above {get_display_speed(CP.minEnableSpeed, metric)} to engage")
-
-
-def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- return Alert(
- f"Steer Assist Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4)
-
-
-def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- first_word = 'Recalibrating' if sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.recalibrating else 'Calibrating'
- return Alert(
- f"{first_word}: {sm['extrinsicsCalibration'].calPerc:.0f}%",
- f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}",
- AlertStatus.normal, AlertSize.mid,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
-
-
-def audio_feedback_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- duration = FEEDBACK_MAX_DURATION - ((sm['audioFeedback'].blockNum + 1) * SAMPLE_BUFFER / SAMPLE_RATE)
- return NormalPermanentAlert(
- "Recording Audio Feedback",
- f"{round(duration)} second{'s' if round(duration) != 1 else ''} remaining. Press again to save early.",
- priority=Priority.LOW)
-
-def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- full_perc = round(100. - sm['deviceState'].freeSpacePercent)
- return NormalPermanentAlert("Out of Storage", f"{full_perc}% full")
-
-
-def posenet_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- if sm.frame * DT_CTRL < 10.:
- return NoEntryAlert("IQModel is starting up", alert_text_1="Please Wait")
- mdl = sm['modelV2'].velocity.x[0] if len(sm['modelV2'].velocity.x) else math.nan
- err = CS.vEgo - mdl
- msg = f"Speed Error: {err:.1f} m/s"
- return NoEntryAlert(msg, alert_text_1="Posenet Speed Invalid")
-
-
-def process_not_running_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- not_running = [p.name for p in sm['managerState'].processes if not p.running and p.shouldBeRunning]
- msg = ', '.join(not_running)
- return NoEntryAlert(msg, alert_text_1="Process Not Running")
-
-
-def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
- bad_cams = [s.replace('State', '') for s in all_cams if s in sm.data.keys() and not sm.all_checks([s, ])]
- return NormalPermanentAlert("Camera Malfunction", ', '.join(bad_cams))
-
-
-def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- rpy = sm['extrinsicsCalibration'].rpyCalib
- yaw = math.degrees(rpy[2] if len(rpy) == 3 else math.nan)
- pitch = math.degrees(rpy[1] if len(rpy) == 3 else math.nan)
- angles = f"Remount Device (Pitch: {pitch:.1f}°, Yaw: {yaw:.1f}°)"
- return NormalPermanentAlert("Calibration Invalid", angles)
-
-
-def paramsd_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- if not sm['vehicleParameters'].angleOffsetValid:
- angle_offset_deg = sm['vehicleParameters'].angleOffsetDeg
- title = "Steering misalignment detected"
- text = f"Angle offset too high (Offset: {angle_offset_deg:.1f}°)"
- elif not sm['vehicleParameters'].steerRatioValid:
- steer_ratio = sm['vehicleParameters'].steerRatio
- title = "Steer ratio mismatch"
- text = f"Steering rack geometry may be off (Ratio: {steer_ratio:.1f})"
- elif not sm['vehicleParameters'].stiffnessFactorValid:
- stiffness_factor = sm['vehicleParameters'].stiffnessFactor
- title = "Abnormal tire stiffness"
- text = f"Check tires, pressure, or alignment (Factor: {stiffness_factor:.1f})"
- else:
- return NoEntryAlert("paramsd Temporary Error")
-
- return NoEntryAlert(alert_text_1=title, alert_text_2=text)
-
-def overheat_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- cpu = max(sm['deviceState'].cpuTempC, default=0.)
- gpu = max(sm['deviceState'].gpuTempC, default=0.)
- temp = max((cpu, gpu, sm['deviceState'].memoryTempC))
- return NormalPermanentAlert("System Overheated", f"{temp:.0f} °C")
-
-
-def low_memory_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- return NormalPermanentAlert("Low Memory", f"{sm['deviceState'].memoryUsagePercent}% used")
-
-
-def modeld_lagging_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- return NormalPermanentAlert("Driving Model Lagging", f"{sm['modelV2'].frameDropPerc:.1f}% frames dropped")
-
-
-def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- gb = sm['carControl'].actuators.accel / 4.
- if CP.steerControlType in (car.CarParams.SteerControlType.angle, car.CarParams.SteerControlType.curvatureDEPRECATED):
- steer = sm['carControl'].actuators.steeringAngleDeg
- vals = f"Gas: {round(gb * 100.)}%, Angle: {round(steer, 1)}°"
- else:
- steer = sm['carControl'].actuators.torque
- vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%"
- return NormalPermanentAlert("Joystick Mode", vals)
-
-
-def longitudinal_maneuver_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- ad = sm['alertDebug']
- audible_alert = AudibleAlert.prompt if 'Active' in ad.alertText1 else AudibleAlert.none
- alert_status = AlertStatus.userPrompt if 'Active' in ad.alertText1 else AlertStatus.normal
- alert_size = AlertSize.mid if ad.alertText2 else AlertSize.small
- return Alert(ad.alertText1, ad.alertText2,
- alert_status, alert_size,
- Priority.LOW, VisualAlert.none, audible_alert, 0.2)
-
-
-def personality_changed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- personality = str(personality).title()
- return NormalPermanentAlert(f"Driving Personality: {personality}", duration=1.5)
-
-
-def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
- title = "Invalid LKAS setting"
- text = "Toggle stock LKAS on or off to engage"
- if CP.brand == "tesla":
- title = "Dashcam Mode"
- text = "FSD / Autosteer is active"
- elif CP.brand == "mazda":
- text = "Enable your car's LKAS to engage"
- elif CP.brand == "nissan":
- text = "Disable your car's stock LKAS to engage"
- return NormalPermanentAlert(title, text)
-
-
-def invalid_lkas_setting_no_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster,
- metric: bool, soft_disable_time: int, personality) -> Alert:
- if CP.brand == "tesla":
- return NoEntryAlert("FSD / Autosteer is active", alert_text_1="Dashcam Mode")
- return NoEntryAlert("Invalid LKAS setting")
-
-
-
-EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
- EventName.stockFcw: {},
- EventName.actuatorsApiUnavailable: {},
-
- EventName.joystickDebug: {
- ET.WARNING: joystick_alert,
- ET.PERMANENT: NormalPermanentAlert("Joystick Mode"),
- },
-
- EventName.longitudinalManeuver: {
- ET.WARNING: longitudinal_maneuver_alert,
- ET.PERMANENT: NormalPermanentAlert("Longitudinal Maneuver Mode",
- "Ensure road ahead is clear"),
- },
-
- EventName.bigModelLoading: {
- ET.NO_ENTRY: NoEntryAlert("Big Model Loading"),
- },
-
- EventName.bigModelFailed: {
- ET.SOFT_DISABLE: soft_disable_alert("Big Model Failed"),
- ET.PERMANENT: NormalPermanentAlert("Big Model Failed ", "Restart the car to retry,\nsmall model is still available", duration=20.),
- },
-
- EventName.lateralManeuver: {
- ET.WARNING: longitudinal_maneuver_alert,
- ET.PERMANENT: NormalPermanentAlert("Lateral Maneuver Mode"),
- },
-
- EventName.selfdriveInitializing: {
- ET.NO_ENTRY: NoEntryAlert("IQ.Pilot Initializing"),
- },
-
- EventName.startup: {
- ET.PERMANENT: StartupAlert("Welcome to IQ.Pilot!")
- },
-
- EventName.startupMaster: {
- ET.PERMANENT: StartupAlert("Welcome to IQ.Pilot!"),
- },
-
- EventName.startupNoControl: {
- ET.PERMANENT: StartupAlert("Dashcam mode"),
- ET.NO_ENTRY: NoEntryAlert("Dashcam mode"),
- },
-
- EventName.startupNoCar: {
- ET.PERMANENT: StartupAlert("IQ.Pilot Dashcam mode: car unrecognized"),
- },
-
- EventName.startupNoSecOcKey: {
- ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
- "TSK Security Key Not Available",
- priority=Priority.HIGH),
- },
-
- EventName.dashcamMode: {
- ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
- priority=Priority.LOWEST),
- },
-
- EventName.invalidLkasSetting: {
- ET.PERMANENT: invalid_lkas_setting_alert,
- ET.NO_ENTRY: invalid_lkas_setting_no_entry_alert,
- },
-
- EventName.cruiseMismatch: {
- #ET.PERMANENT: ImmediateDisableAlert("openpilot failed to cancel cruise"),
- },
-
- EventName.carUnrecognized: {
- ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
- "Car Unrecognized",
- priority=Priority.LOWEST),
- },
-
- EventName.aeb: {
- ET.PERMANENT: Alert(
- "BRAKE!",
- "Emergency Braking: Risk of Collision",
- AlertStatus.critical, AlertSize.full,
- Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 2.),
- ET.NO_ENTRY: NoEntryAlert("AEB: Risk of Collision"),
- },
-
- EventName.stockAeb: {
- ET.PERMANENT: Alert(
- "BRAKE!",
- "Stock AEB: Risk of Collision",
- AlertStatus.critical, AlertSize.full,
- Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 2.),
- ET.NO_ENTRY: NoEntryAlert("Stock AEB: Risk of Collision"),
- },
-
- EventName.stockLkas: {
- ET.PERMANENT: Alert(
- "Stock LKAS: Lane Departure Detected",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.),
- ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"),
- },
-
- EventName.fcw: {
- ET.PERMANENT: Alert(
- "BRAKE!",
- "Risk of Collision",
- AlertStatus.critical, AlertSize.full,
- Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.warningSoft, 2.),
- },
-
- EventName.ldw: {
- ET.PERMANENT: Alert(
- "Lane Departure Detected",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.),
- },
- EventName.steerTempUnavailableSilent: {
- ET.WARNING: Alert(
- "Steering Assist Temporarily Unavailable",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8),
- },
-
- EventName.preDriverDistracted: {
- ET.PERMANENT: Alert(
- "Pay Attention",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.MID, VisualAlert.none, AudibleAlert.none, .1),
- },
-
- EventName.promptDriverDistracted: {
- ET.PERMANENT: Alert(
- "Pay Attention",
- "Driver Distracted",
- AlertStatus.userPrompt, AlertSize.mid,
- Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1),
- },
-
- EventName.driverDistracted: {
- ET.PERMANENT: Alert(
- "DISENGAGE IMMEDIATELY",
- "Driver Distracted",
- AlertStatus.critical, AlertSize.full,
- Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1),
- },
-
- EventName.preDriverUnresponsive: {
- ET.PERMANENT: Alert(
- "Touch Steering Wheel: No Face Detected",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .1),
- },
-
- EventName.promptDriverUnresponsive: {
- ET.PERMANENT: Alert(
- "Touch Steering Wheel",
- "Driver Unresponsive",
- AlertStatus.userPrompt, AlertSize.mid,
- Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1),
- },
-
- EventName.driverUnresponsive: {
- ET.PERMANENT: Alert(
- "DISENGAGE IMMEDIATELY",
- "Driver Unresponsive",
- AlertStatus.critical, AlertSize.full,
- Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1),
- },
-
- EventName.manualRestart: {
- ET.WARNING: Alert(
- "TAKE CONTROL",
- "Resume Driving Manually",
- AlertStatus.userPrompt, AlertSize.mid,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
- },
-
- EventName.resumeRequired: {
- ET.WARNING: Alert(
- "Press Resume to Exit Standstill",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
- },
-
- EventName.belowSteerSpeed: {
- ET.WARNING: below_steer_speed_alert,
- },
-
- EventName.preLaneChangeLeft: {
- ET.WARNING: Alert(
- "Steer Left to Start Lane Change Once Safe",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
- },
-
- EventName.preLaneChangeRight: {
- ET.WARNING: Alert(
- "Steer Right to Start Lane Change Once Safe",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
- },
-
- EventName.laneChangeBlocked: {
- ET.WARNING: Alert(
- "Car Detected in Blindspot",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1),
- },
-
- EventName.laneChange: {
- ET.WARNING: Alert(
- "Changing Lanes",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
- },
-
- EventName.steerSaturated: {
- ET.WARNING: Alert(
- "Take Control",
- "Turn Exceeds Steering Limit",
- AlertStatus.userPrompt, AlertSize.mid,
- Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 2.),
- },
-
- EventName.fanMalfunction: {
- ET.PERMANENT: NormalPermanentAlert("Fan Malfunction", "Likely Hardware Issue"),
- },
-
- EventName.cameraMalfunction: {
- ET.PERMANENT: camera_malfunction_alert,
- ET.SOFT_DISABLE: soft_disable_alert("Camera Malfunction"),
- ET.NO_ENTRY: NoEntryAlert("Camera Malfunction: Reboot Your Device"),
- },
- EventName.cameraFrameRate: {
- ET.PERMANENT: NormalPermanentAlert("Camera Frame Rate Low", "Reboot your Device"),
- ET.SOFT_DISABLE: soft_disable_alert("Camera Frame Rate Low"),
- ET.NO_ENTRY: NoEntryAlert("Camera Frame Rate Low: Reboot Your Device"),
- },
- EventName.locationdTemporaryError: {
- ET.NO_ENTRY: NoEntryAlert("locationd Temporary Error"),
- ET.SOFT_DISABLE: soft_disable_alert("locationd Temporary Error"),
- },
-
- EventName.locationdPermanentError: {
- ET.NO_ENTRY: NoEntryAlert("locationd Permanent Error"),
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("locationd Permanent Error"),
- ET.PERMANENT: NormalPermanentAlert("locationd Permanent Error"),
- },
-
- EventName.paramsdTemporaryError: {
- ET.NO_ENTRY: paramsd_invalid_alert,
- ET.SOFT_DISABLE: soft_disable_alert("paramsd Temporary Error"),
- },
-
- EventName.paramsdPermanentError: {
- ET.NO_ENTRY: NoEntryAlert("paramsd Permanent Error"),
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("paramsd Permanent Error"),
- ET.PERMANENT: NormalPermanentAlert("paramsd Permanent Error"),
- },
- EventName.pcmEnable: {
- ET.ENABLE: EngagementAlert(AudibleAlert.engage),
- },
-
- EventName.buttonEnable: {
- ET.ENABLE: EngagementAlert(AudibleAlert.engage),
- },
-
- EventName.pcmDisable: {
- ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
- },
-
- EventName.buttonCancel: {
- ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
- ET.NO_ENTRY: NoEntryAlert("Cancel Pressed"),
- },
-
- EventName.brakeHold: {
- ET.WARNING: Alert(
- "Press Resume to Exit Brake Hold",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
- },
-
- EventName.parkBrake: {
- ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
- ET.NO_ENTRY: NoEntryAlert("Parking Brake Engaged"),
- },
-
- EventName.pedalPressed: {
- ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
- ET.NO_ENTRY: NoEntryAlert("Pedal Pressed",
- visual_alert=VisualAlert.brakePressed),
- },
-
- EventName.steerDisengage: {
- ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
- ET.NO_ENTRY: NoEntryAlert("Steering Pressed"),
- },
-
- EventName.preEnableStandstill: {
- ET.PRE_ENABLE: Alert(
- "Release Brake to Engage",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.),
- },
-
- EventName.gasPressedOverride: {
- ET.OVERRIDE_LONGITUDINAL: Alert(
- "",
- "",
- AlertStatus.normal, AlertSize.none,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1),
- },
-
- EventName.steerOverride: {
- ET.OVERRIDE_LATERAL: Alert(
- "",
- "",
- AlertStatus.normal, AlertSize.none,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1),
- },
-
- EventName.wrongCarMode: {
- ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
- ET.NO_ENTRY: wrong_car_mode_alert,
- },
-
- EventName.resumeBlocked: {
- ET.NO_ENTRY: NoEntryAlert("Press Set to Engage"),
- },
-
- EventName.wrongCruiseMode: {
- ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
- ET.NO_ENTRY: NoEntryAlert("Adaptive Cruise Disabled"),
- },
-
- EventName.steerTempUnavailable: {
- ET.SOFT_DISABLE: soft_disable_alert("Steering Assist Temporarily Unavailable"),
- ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable"),
- },
-
- EventName.steerTimeLimit: {
- ET.SOFT_DISABLE: soft_disable_alert("Vehicle Steering Time Limit"),
- ET.NO_ENTRY: NoEntryAlert("Vehicle Steering Time Limit"),
- },
-
- EventName.outOfSpace: {
- ET.PERMANENT: out_of_space_alert,
- ET.NO_ENTRY: NoEntryAlert("Out of Storage"),
- },
-
- EventName.belowEngageSpeed: {
- ET.NO_ENTRY: below_engage_speed_alert,
- },
-
- EventName.sensorDataInvalid: {
- ET.PERMANENT: Alert(
- "Sensor Data Invalid",
- "Possible Hardware Issue",
- AlertStatus.normal, AlertSize.mid,
- Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=1.),
- ET.NO_ENTRY: NoEntryAlert("Sensor Data Invalid"),
- ET.SOFT_DISABLE: soft_disable_alert("Sensor Data Invalid"),
- },
-
- EventName.noGps: {
- },
-
- EventName.tooDistracted: {
- ET.NO_ENTRY: NoEntryAlert("Distraction Level Too High"),
- },
-
- EventName.excessiveActuation: {
- ET.SOFT_DISABLE: soft_disable_alert("Excessive Actuation"),
- ET.NO_ENTRY: NoEntryAlert("Excessive Actuation"),
- },
-
- EventName.overheat: {
- ET.PERMANENT: overheat_alert,
- ET.SOFT_DISABLE: soft_disable_alert("System Overheated"),
- ET.NO_ENTRY: NoEntryAlert("System Overheated"),
- },
-
- EventName.wrongGear: {
- ET.SOFT_DISABLE: Alert(
- "",
- "",
- AlertStatus.normal, AlertSize.none,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, 0.),
- ET.NO_ENTRY: Alert(
- "",
- "",
- AlertStatus.normal, AlertSize.none,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, 0.),
- },
-
- EventName.calibrationInvalid: {
- ET.PERMANENT: calibration_invalid_alert,
- ET.SOFT_DISABLE: soft_disable_alert("Calibration Invalid: Remount Device & Recalibrate"),
- ET.NO_ENTRY: NoEntryAlert("Calibration Invalid: Remount Device & Recalibrate"),
- },
-
- EventName.calibrationIncomplete: {
- ET.PERMANENT: calibration_incomplete_alert,
- ET.SOFT_DISABLE: soft_disable_alert("Calibration Incomplete"),
- ET.NO_ENTRY: NoEntryAlert("Calibration in Progress"),
- },
-
- EventName.calibrationRecalibrating: {
- ET.PERMANENT: calibration_incomplete_alert,
- ET.SOFT_DISABLE: soft_disable_alert("Device Remount Detected: Recalibrating"),
- ET.NO_ENTRY: NoEntryAlert("Remount Detected: Recalibrating"),
- },
-
- EventName.doorOpen: {
- ET.SOFT_DISABLE: user_soft_disable_alert("Door Open"),
- ET.NO_ENTRY: NoEntryAlert("Door Open"),
- },
-
- EventName.seatbeltNotLatched: {
- ET.SOFT_DISABLE: user_soft_disable_alert("Seatbelt Unlatched"),
- ET.NO_ENTRY: NoEntryAlert("Seatbelt Unlatched"),
- },
-
- EventName.espDisabled: {
- ET.SOFT_DISABLE: soft_disable_alert("Electronic Stability Control Disabled"),
- ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Disabled"),
- },
-
- EventName.lowBattery: {
- ET.SOFT_DISABLE: soft_disable_alert("Low Battery"),
- ET.NO_ENTRY: NoEntryAlert("Low Battery"),
- },
-
- EventName.commIssue: {
- },
- EventName.commIssueAvgFreq: {
- },
-
- EventName.selfdrivedLagging: {
- },
-
- EventName.processNotRunning: {
- ET.NO_ENTRY: process_not_running_alert,
- ET.SOFT_DISABLE: soft_disable_alert("Process Not Running"),
- },
-
- EventName.radarFault: {
- ET.SOFT_DISABLE: soft_disable_alert("Radar Error: Restart the Car"),
- ET.NO_ENTRY: NoEntryAlert("Radar Error: Restart the Car"),
- },
-
- EventName.radarTempUnavailable: {
- ET.SOFT_DISABLE: soft_disable_alert("Radar Temporarily Unavailable"),
- ET.NO_ENTRY: NoEntryAlert("Radar Temporarily Unavailable"),
- },
-
- EventName.modeldLagging: {
- ET.SOFT_DISABLE: soft_disable_alert("Driving Model Lagging"),
- ET.NO_ENTRY: NoEntryAlert("Driving Model Lagging"),
- ET.PERMANENT: modeld_lagging_alert,
- },
-
- EventName.posenetInvalid: {
- ET.SOFT_DISABLE: soft_disable_alert("Posenet Speed Invalid"),
- ET.NO_ENTRY: posenet_invalid_alert,
- },
-
- EventName.deviceFalling: {
- ET.SOFT_DISABLE: soft_disable_alert("Device Fell Off Mount"),
- ET.NO_ENTRY: NoEntryAlert("Device Fell Off Mount"),
- },
-
- EventName.lowMemory: {
- ET.SOFT_DISABLE: soft_disable_alert("Low Memory: Reboot Your Device"),
- ET.PERMANENT: low_memory_alert,
- ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"),
- },
-
- EventName.accFaulted: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"),
- ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"),
- ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"),
- },
-
- EventName.cruiseFaultLateralAllowed: {
- ET.PERMANENT: NormalPermanentAlert("Cruise Faulted", "Lane Assist remains available", duration=5.0, priority=Priority.LOW),
- },
-
- EventName.espActive: {
- ET.SOFT_DISABLE: soft_disable_alert("Electronic Stability Control Active"),
- ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Active"),
- },
-
- EventName.controlsMismatch: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Controls Mismatch"),
- ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"),
- },
-
- EventName.usbError: {
- ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"),
- ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device"),
- ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"),
- },
-
- EventName.canError: {
- ET.PERMANENT: Alert(
- "CAN Error",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
- },
-
- EventName.canBusMissing: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Bus Disconnected"),
- ET.PERMANENT: Alert(
- "CAN Bus Disconnected: Likely Faulty Cable",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
- ET.NO_ENTRY: NoEntryAlert("CAN Bus Disconnected: Check Connections"),
- },
-
- EventName.steerUnavailable: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("LKAS Fault: Restart the Car"),
- ET.PERMANENT: NormalPermanentAlert("LKAS Fault: Restart the car to engage"),
- ET.NO_ENTRY: NoEntryAlert("LKAS Fault: Restart the Car"),
- },
-
- EventName.reverseGear: {
- ET.PERMANENT: Alert(
- "Reverse\nGear",
- "",
- AlertStatus.normal, AlertSize.full,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2, creation_delay=0.5),
- ET.USER_DISABLE: ImmediateDisableAlert("Reverse Gear"),
- ET.NO_ENTRY: NoEntryAlert("Reverse Gear"),
- },
-
- EventName.cruiseDisabled: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Is Off"),
- },
-
- EventName.relayMalfunction: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Harness Relay Malfunction"),
- ET.PERMANENT: NormalPermanentAlert("Harness Relay Malfunction", "Check Hardware"),
- ET.NO_ENTRY: NoEntryAlert("Harness Relay Malfunction"),
- },
-
- EventName.speedTooLow: {
- ET.IMMEDIATE_DISABLE: Alert(
- "IQ.Pilot Canceled",
- "Speed too low",
- AlertStatus.normal, AlertSize.mid,
- Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.),
- },
-
- EventName.speedTooHigh: {
- ET.WARNING: Alert(
- "Speed Too High",
- "Model uncertain at this speed",
- AlertStatus.userPrompt, AlertSize.mid,
- Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 4.),
- ET.NO_ENTRY: NoEntryAlert("Slow down to engage"),
- },
-
- EventName.vehicleSensorsInvalid: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Vehicle Sensors Invalid"),
- ET.PERMANENT: NormalPermanentAlert("Vehicle Sensors Calibrating", "Drive to Calibrate"),
- ET.NO_ENTRY: NoEntryAlert("Vehicle Sensors Calibrating"),
- },
-
- EventName.personalityChanged: {
- ET.WARNING: personality_changed_alert,
- },
-
- EventName.userBookmark: {
- ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5),
- },
-
- EventName.audioFeedback: {
- ET.PERMANENT: audio_feedback_alert,
- },
-}
-
-
-if HARDWARE.get_device_type() == 'mici':
- EVENTS.update({
- EventName.preDriverDistracted: {
- ET.PERMANENT: Alert(
- "Pay Attention",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.MID, VisualAlert.none, AudibleAlert.none, 2),
- },
- EventName.promptDriverDistracted: {
- ET.PERMANENT: Alert(
- "Pay Attention",
- "Driver Distracted",
- AlertStatus.userPrompt, AlertSize.mid,
- Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, 1),
- },
- EventName.resumeRequired: {
- ET.WARNING: Alert(
- "Press Resume",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
- },
- EventName.preLaneChangeLeft: {
- ET.WARNING: Alert(
- "Steer Left",
- "Confirm Lane Change",
- AlertStatus.normal, AlertSize.mid,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
- },
- EventName.preLaneChangeRight: {
- ET.WARNING: Alert(
- "Steer Right",
- "Confirm Lane Change",
- AlertStatus.normal, AlertSize.mid,
- Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
- },
- EventName.laneChangeBlocked: {
- ET.WARNING: Alert(
- "Car in Blindspot",
- "",
- AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1),
- },
- EventName.steerSaturated: {
- ET.WARNING: Alert(
- "take control",
- "turn exceeds limit",
- AlertStatus.userPrompt, AlertSize.mid,
- Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 2.),
- },
- EventName.calibrationIncomplete: {
- ET.PERMANENT: calibration_incomplete_alert,
- ET.SOFT_DISABLE: soft_disable_alert("Calibration Incomplete"),
- ET.NO_ENTRY: NoEntryAlert("Calibrating"),
- },
- EventName.reverseGear: {
- ET.PERMANENT: Alert(
- "Reverse",
- "",
- AlertStatus.normal, AlertSize.full,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2, creation_delay=0.5),
- ET.USER_DISABLE: ImmediateDisableAlert("Reverse"),
- ET.NO_ENTRY: NoEntryAlert("Reverse"),
- },
- })
-
-
-if __name__ == '__main__':
- from iqpilot.cereal.services import SERVICE_LIST
- from collections import defaultdict
-
- event_names = {v: k for k, v in EventName.schema.enumerants.items()}
- alerts_by_type: dict[str, dict[Priority, list[str]]] = defaultdict(lambda: defaultdict(list))
-
- CP = car.CarParams.new_message()
- CS = car.CarState.new_message()
- sm = messaging.SubMaster(list(SERVICE_LIST.keys()))
-
- for i, alerts in EVENTS.items():
- for et, alert in alerts.items():
- if callable(alert):
- alert = alert(CP, CS, sm, False, 1, log.LongitudinalPersonality.standard)
- alerts_by_type[et][alert.priority].append(event_names[i])
-
- all_alerts: dict[str, list[tuple[Priority, list[str]]]] = {}
- for et, priority_alerts in alerts_by_type.items():
- all_alerts[et] = sorted(priority_alerts.items(), key=lambda x: x[0], reverse=True)
-
- for status, evs in sorted(all_alerts.items(), key=lambda x: x[0]):
- print(f"**** {status} ****")
- for p, alert_list in evs:
- print(f" {repr(p)}:")
- print(" ", ', '.join(alert_list), "\n")
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/ui/feedback/feedbackd.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/ui/feedback/feedbackd.py
deleted file mode 100644
index 0412edd3..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/ui/feedback/feedbackd.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python3
-import iqpilot.cereal.messaging as messaging
-from iqpilot.common.params import Params
-from iqpilot.common.swaglog import cloudlog
-from iqpilot.cereal import car
-from iqpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
-
-FEEDBACK_MAX_DURATION = 10.0
-ButtonType = car.CarState.ButtonEvent.Type
-
-
-def main():
- params = Params()
- pm = messaging.PubMaster(['userBookmark', 'audioFeedback'])
- sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton'])
- should_record_audio = False
- block_num = 0
- waiting_for_release = False
- early_stop_triggered = False
-
- while True:
- sm.update()
- should_send_bookmark = False
-
- if False and sm.updated['carState'] and sm['carState'].canValid and not sm['iqState'].aol.available:
- for be in sm['carState'].buttonEvents:
- if be.type == ButtonType.lkas:
- if be.pressed:
- if not should_record_audio:
- if params.get_bool("RecordAudioFeedback"):
- should_record_audio = True
- block_num = 0
- waiting_for_release = False
- early_stop_triggered = False
- cloudlog.info("LKAS button pressed - starting 10-second audio feedback")
- else:
- should_send_bookmark = True
- cloudlog.info("LKAS button pressed - bookmarking")
- elif should_record_audio and not waiting_for_release:
- waiting_for_release = True
- elif waiting_for_release:
- waiting_for_release = False
- early_stop_triggered = True
- cloudlog.info("LKAS button released - ending recording early")
-
- if should_record_audio and sm.updated['rawAudioData']:
- raw_audio = sm['rawAudioData']
- msg = messaging.new_message('audioFeedback', valid=True)
- msg.audioFeedback.audio.data = raw_audio.data
- msg.audioFeedback.audio.sampleRate = raw_audio.sampleRate
- msg.audioFeedback.blockNum = block_num
- block_num += 1
- if (block_num * SAMPLE_BUFFER / SAMPLE_RATE) >= FEEDBACK_MAX_DURATION or early_stop_triggered:
- should_send_bookmark = True
- should_record_audio = False
- early_stop_triggered = False
- cloudlog.info("10-second recording completed or second button press - stopping audio feedback")
- pm.send('audioFeedback', msg)
-
- if sm.updated['bookmarkButton']:
- cloudlog.info("Bookmark button pressed!")
- should_send_bookmark = True
-
- if should_send_bookmark:
- msg = messaging.new_message('userBookmark', valid=True)
- pm.send('userBookmark', msg)
-
-
-if __name__ == '__main__':
- main()
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
index 48a34ea4..c38e33b9 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
@@ -125,16 +125,32 @@ class Amplifier:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: list[AmpConfig]) -> bool:
+ import threading
+
tries = 15
backoff = 0.
for i in range(tries):
- try:
- self._set_configs(configs)
- return True
- except OSError:
+ result = [None]
+ error = [None]
+ def _worker():
+ try:
+ self._set_configs(configs)
+ result[0] = True
+ except OSError as e:
+ error[0] = e
+ t = threading.Thread(target=_worker, daemon=True)
+ t.start()
+ t.join(timeout=2)
+ if t.is_alive():
backoff += 0.1
time.sleep(backoff)
- print(f"Failed to set amp config, {tries - i - 1} retries left")
+ print(f"Failed to set amp config (timeout), {tries - i - 1} retries left")
+ continue
+ if result[0]:
+ return True
+ backoff += 0.1
+ time.sleep(backoff)
+ print(f"Failed to set amp config, {tries - i - 1} retries left")
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
index 2f71ed63..2506fb9e 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
@@ -469,8 +469,10 @@ class Tici(HardwareBase):
if self.amplifier is not None:
self.amplifier.initialize_configuration(self.get_device_type())
- # Allow hardwared to write engagement status to kmsg
- os.system("sudo chmod a+w /dev/kmsg")
+ try:
+ subprocess.run(["sudo", "-n", "chmod", "a+w", "/dev/kmsg"], timeout=2, capture_output=True, check=False)
+ except Exception:
+ pass
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
@@ -514,10 +516,10 @@ class Tici(HardwareBase):
affine_irq(3, "xhci-hcd:usb1")
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
- subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
- subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessException as e:
- print(str(e))
+ subprocess.run(["sudo", "-n", "chrt", "-f", "-p", "1", pid], timeout=2, capture_output=True, check=False)
+ subprocess.run(["sudo", "-n", "taskset", "-pc", "3", pid], timeout=2, capture_output=True, check=False)
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+ pass
def configure_modem(self):
from iqpilot.common.params import Params
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process.py
index 31434b81..17d97196 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process.py
@@ -34,36 +34,33 @@ except ModuleNotFoundError:
return _VERIFIED_RUNNER_PATH
-def launcher(proc: str, name: str) -> None:
+def launcher(proc: str, name: str, env: dict[str, str] | None = None) -> None:
try:
- # import the process
+ if env:
+ os.environ.update(env)
+
mod = importlib.import_module(proc)
- # rename the process
setproctitle(proc)
- # create new context since we forked
messaging.reset_context()
- # add daemon name tag to logs
cloudlog.bind(daemon=name)
sentry.set_tag("daemon", name)
- # exec the process
mod.main()
except KeyboardInterrupt:
cloudlog.warning(f"child {proc} got SIGINT")
except Exception:
- # can't install the crash handler because sys.excepthook doesn't play nice
- # with threads, so catch it here.
sentry.capture_exception()
raise
-def nativelauncher(pargs: list[str], cwd: str, name: str) -> None:
+def nativelauncher(pargs: list[str], cwd: str, name: str, env: dict[str, str] | None = None) -> None:
+ if env:
+ os.environ.update(env)
os.environ['MANAGER_DAEMON'] = name
- # exec the process
os.chdir(cwd)
os.environ['PWD'] = cwd
os.execvp(pargs[0], pargs)
@@ -162,7 +159,7 @@ class ManagerProcess(ABC):
class NativeProcess(ManagerProcess):
- def __init__(self, name, cwd, cmdline, should_run, enabled=True, sigkill=False, restart_if_crash=False):
+ def __init__(self, name, cwd, cmdline, should_run, enabled=True, sigkill=False, restart_if_crash=False, env=None):
self.name = name
self.cwd = cwd
self.cmdline = cmdline
@@ -171,12 +168,12 @@ class NativeProcess(ManagerProcess):
self.sigkill = sigkill
self.launcher = nativelauncher
self.restart_if_crash = restart_if_crash
+ self.env = env
def prepare(self) -> None:
pass
def start(self) -> None:
- # In case we only tried a non blocking stop we need to stop it before restarting
if self.shutting_down:
self.stop()
@@ -185,7 +182,7 @@ class NativeProcess(ManagerProcess):
cwd = os.path.join(BASEDIR, self.cwd)
cloudlog.info(f"starting process {self.name}")
- self.proc = Process(name=self.name, target=self.launcher, args=(self.cmdline, cwd, self.name))
+ self.proc = Process(name=self.name, target=self.launcher, args=(self.cmdline, cwd, self.name, self.env))
self.proc.start()
self.shutting_down = False
@@ -217,7 +214,7 @@ def _normalize_bundle_modes(bundle: str) -> None:
class BundleProcess(NativeProcess):
- def __init__(self, name, bundle, entry, should_run, enabled=True, sigkill=False, restart_if_crash=False):
+ def __init__(self, name, bundle, entry, should_run, enabled=True, sigkill=False, restart_if_crash=False, env=None):
self.bundle = bundle
self.entry = entry
runner_path = preferred_runner_path()
@@ -237,6 +234,7 @@ class BundleProcess(NativeProcess):
enabled=enabled,
sigkill=sigkill,
restart_if_crash=restart_if_crash,
+ env=env,
)
def start(self) -> None:
@@ -249,7 +247,7 @@ class BundleProcess(NativeProcess):
class PythonProcess(ManagerProcess):
- def __init__(self, name, module, should_run, enabled=True, sigkill=False, restart_if_crash=False):
+ def __init__(self, name, module, should_run, enabled=True, sigkill=False, restart_if_crash=False, env=None):
self.name = name
self.module = module
self.should_run = should_run
@@ -257,6 +255,7 @@ class PythonProcess(ManagerProcess):
self.sigkill = sigkill
self.launcher = launcher
self.restart_if_crash = restart_if_crash
+ self.env = env
def prepare(self) -> None:
if self.enabled:
@@ -264,7 +263,6 @@ class PythonProcess(ManagerProcess):
importlib.import_module(self.module)
def start(self) -> None:
- # In case we only tried a non blocking stop we need to stop it before restarting
if self.shutting_down:
self.stop()
@@ -272,7 +270,7 @@ class PythonProcess(ManagerProcess):
return
cloudlog.info(f"starting python {self.module}")
- self.proc = Process(name=self.name, target=self.launcher, args=(self.module, self.name))
+ self.proc = Process(name=self.name, target=self.launcher, args=(self.module, self.name, self.env))
self.proc.start()
self.shutting_down = False
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process_config.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process_config.py
index 6908a78d..d5a8b37e 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process_config.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/manager/process_config.py
@@ -166,6 +166,8 @@ def big_model_enabled(started, params, CP: car.CarParams) -> bool:
def hephaestus_ready_shim(started, params, CP: car.CarParams) -> bool:
return hephaestus_ready(params)
+_IQCORE_ARMED_ENV = {"IQCORE_ARMED": "1"}
+
def not_low_power(started: bool, params: Params, CP: car.CarParams) -> bool:
# FastSleep deep standby: heavy processes are shed offroad while DevicePowerState is low_power
return started or params.get("DevicePowerState") != "low_power"
@@ -185,7 +187,8 @@ def and_(*fns):
procs = [
NativeProcess("loggerd", "iqpilot/system/loggerd", ["./loggerd"], logging),
NativeProcess("encoderd", "iqpilot/system/loggerd", ["./encoderd"], only_onroad),
- NativeProcess("stream_encoderd", "iqpilot/system/loggerd", ["./encoderd", "--stream"], or_(notcar, livestream, sentry_recording)),
+ NativeProcess("stream_encoderd", "iqpilot/system/loggerd", ["./encoderd", "--stream"], or_(notcar, livestream, sentry_recording),
+ restart_if_crash=True),
PythonProcess("logmessaged", "iqpilot.system.logmessaged", always_run, restart_if_crash=True),
NativeProcess("camerad", "iqpilot/system/camerad", ["./camerad"], or_(driverview, livestream, sentry_recording), restart_if_crash=True),
@@ -194,13 +197,11 @@ procs = [
PythonProcess("micd", "iqpilot.system.micd", or_(iscar, livestream)),
PythonProcess("timed", "iqpilot.system.timed", always_run, enabled=not PC),
PythonProcess("wireguardd", "iqpilot.system.wireguard.wireguardd", wireguard_installed, enabled=TICI, restart_if_crash=True),
- BundleProcess("k3wgd", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.hephaestus.k3wgd", always_run, enabled=TICI,
- restart_if_crash=True),
BundleProcess("iqsentryd", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.hephaestus.iqsentryd", sentry_enabled,
enabled=TICI, restart_if_crash=True),
BundleProcess("navassistd", "iqpilot_navassist_private", "iqpilot_private.navassist.daemon", nav_assist, enabled=TICI, restart_if_crash=True),
- PythonProcess("dmonitoringmodeld", "iqpilot.selfdrive.dmonitoringmodeld.dmonitoringmodeld", driver_monitoring, enabled=not PC),
+ PythonProcess("dmonitoringmodeld", "iqpilot.selfdrive.dmonitoringmodeld.dmonitoringmodeld", driver_monitoring, enabled=not PC, env=_IQCORE_ARMED_ENV),
PythonProcess("sensord", "iqpilot.system.sensord.sensord", only_onroad, enabled=not PC),
PythonProcess("ui", "iqpilot.selfdrive.ui.ui", not_low_power, restart_if_crash=True),
@@ -235,7 +236,7 @@ procs = [
# debug procs
NativeProcess("bridge", "iqpilot/cereal/messaging", ["./bridge"], notcar),
- PythonProcess("webrtcd", "iqpilot.system.webrtc.webrtcd", or_(iscar, livestream)),
+ PythonProcess("webrtcd", "iqpilot.system.webrtc.webrtcd", or_(iscar, livestream), restart_if_crash=True),
PythonProcess("canlived", "iqpilot.konn3kt.canlive.canlived", canlive),
]
@@ -243,16 +244,16 @@ procs = [
procs += [
# Models
BundleProcess("models_manager", "iqpilot_model_selector_private", "iqpilot_private.models.manager", and_(only_offroad, not_low_power), restart_if_crash=True),
- NativeProcess("iqmodeld", "iqpilot/selfdrive/iqmodeld", ["./iqmodeld"], and_(only_onroad, is_tinygrad_model), restart_if_crash=True),
+ NativeProcess("iqmodeld", "iqpilot/selfdrive/iqmodeld", ["./iqmodeld"], and_(only_onroad, is_tinygrad_model), restart_if_crash=True, env=_IQCORE_ARMED_ENV),
# big-model backends: iqmodeld self-demotes to the small channel worker when
# either backend is enabled; the selector publishes, and exactly one big
# worker (Mac or eGPU, eMac wins) feeds the BIG channel
PythonProcess("modeld_selector", "iqpilot.selfdrive.iqmodeld.modeld_selector",
and_(only_onroad, and_(is_tinygrad_model, big_model_enabled)), restart_if_crash=True),
BundleProcess("maciqmodeld", "iqpilot_emac_private", "iqpilot_private.emac.maciqmodeld",
- and_(only_onroad, and_(is_tinygrad_model, emac_enabled)), restart_if_crash=True),
+ and_(only_onroad, and_(is_tinygrad_model, emac_enabled)), restart_if_crash=True, env=_IQCORE_ARMED_ENV),
PythonProcess("iqegpumodeld", "iqpilot.selfdrive.iqmodeld.iqegpumodeld",
- and_(only_onroad, and_(is_tinygrad_model, egpu_enabled)), restart_if_crash=True),
+ and_(only_onroad, and_(is_tinygrad_model, egpu_enabled)), restart_if_crash=True, env=_IQCORE_ARMED_ENV),
PythonProcess("egpu_prefetch", "iqpilot.selfdrive.iqmodeld.egpu_prefetch",
and_(only_offroad, and_(is_tinygrad_model, egpu_prefetch_enabled)), restart_if_crash=True),
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/micd.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/micd.py
deleted file mode 100644
index 5c8d0559..00000000
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/micd.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env python3
-import numpy as np
-import os
-import time
-from functools import cache
-import threading
-
-from iqpilot.cereal import messaging
-from iqpilot.common.params import Params
-from iqpilot.common.realtime import Ratekeeper
-from iqpilot.common.utils import retry
-from iqpilot.common.swaglog import cloudlog
-
-RATE = 10
-FFT_SAMPLES = 1600 # 100ms
-REFERENCE_SPL = 2e-5 # newtons/m^2
-SAMPLE_RATE = 16000
-SAMPLE_BUFFER = 800 # 50ms
-
-
-@cache
-def get_a_weighting_filter():
- # Calculate the A-weighting filter
- # https://en.wikipedia.org/wiki/A-weighting
- freqs = np.fft.fftfreq(FFT_SAMPLES, d=1 / SAMPLE_RATE)
- A = 12194 ** 2 * freqs ** 4 / ((freqs ** 2 + 20.6 ** 2) * (freqs ** 2 + 12194 ** 2) * np.sqrt((freqs ** 2 + 107.7 ** 2) * (freqs ** 2 + 737.9 ** 2)))
- return A / np.max(A)
-
-
-def calculate_spl(measurements):
- # https://www.engineeringtoolbox.com/sound-pressure-d_711.html
- sound_pressure = np.sqrt(np.mean(measurements ** 2)) # RMS of amplitudes
- if sound_pressure > 0:
- sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) # dB
- else:
- sound_pressure_level = 0
- return sound_pressure, sound_pressure_level
-
-
-def apply_a_weighting(measurements: np.ndarray) -> np.ndarray:
- # Generate a Hanning window of the same length as the audio measurements
- measurements_windowed = measurements * np.hanning(len(measurements))
-
- # Apply the A-weighting filter to the signal
- return np.abs(np.fft.ifft(np.fft.fft(measurements_windowed) * get_a_weighting_filter()))
-
-
-class Mic:
- def __init__(self):
- self.rk = Ratekeeper(RATE)
- self.pm = messaging.PubMaster(['soundPressure', 'rawAudioData'])
- self.params = Params()
-
- self.measurements = np.empty(0)
-
- self.sound_pressure = 0
- self.sound_pressure_weighted = 0
- self.sound_pressure_level_weighted = 0
-
- self.lock = threading.Lock()
- self.callback_count = 0
- self.last_audio_rms = 0.0
- self.last_audio_peak = 0.0
- self.last_device = None
- self.last_status = None
-
- def update(self):
- with self.lock:
- sound_pressure = self.sound_pressure
- sound_pressure_weighted = self.sound_pressure_weighted
- sound_pressure_level_weighted = self.sound_pressure_level_weighted
- callback_count = self.callback_count
- audio_rms = self.last_audio_rms
- audio_peak = self.last_audio_peak
- device_name = self.last_device
- status = self.last_status
-
- msg = messaging.new_message('soundPressure', valid=True)
- msg.soundPressure.soundPressure = float(sound_pressure)
- msg.soundPressure.soundPressureWeighted = float(sound_pressure_weighted)
- msg.soundPressure.soundPressureWeightedDb = float(sound_pressure_level_weighted)
-
- self.pm.send('soundPressure', msg)
- if callback_count % RATE == 0:
- cloudlog.info(
- f"micd health: callbacks={callback_count} rms={audio_rms:.6f} peak={audio_peak:.6f} "
- f"device={device_name} status={status!r} livestream={self.params.get_bool('IsLiveStreaming')}"
- )
- self.rk.keep_time()
-
- def callback(self, indata, frames, time, status):
- """
- Using amplitude measurements, calculate an uncalibrated sound pressure and sound pressure level.
- Then apply A-weighting to the raw amplitudes and run the same calculations again.
-
- Logged A-weighted equivalents are rough approximations of the human-perceived loudness.
- """
- msg = messaging.new_message('rawAudioData', valid=True)
- audio_data_int_16 = (indata[:, 0] * 32767).astype(np.int16)
- msg.rawAudioData.data = audio_data_int_16.tobytes()
- msg.rawAudioData.sampleRate = SAMPLE_RATE
- self.pm.send('rawAudioData', msg)
-
- with self.lock:
- self.callback_count += 1
- self.last_audio_rms = float(np.sqrt(np.mean(np.square(indata[:, 0]))))
- self.last_audio_peak = float(np.max(np.abs(indata[:, 0])))
- self.last_status = str(status) if status else None
- self.measurements = np.concatenate((self.measurements, indata[:, 0]))
-
- while self.measurements.size >= FFT_SAMPLES:
- measurements = self.measurements[:FFT_SAMPLES]
-
- self.sound_pressure, _ = calculate_spl(measurements)
- measurements_weighted = apply_a_weighting(measurements)
- self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted)
-
- self.measurements = self.measurements[FFT_SAMPLES:]
-
- @retry(attempts=10, delay=3)
- def get_stream(self, sd):
- # reload sounddevice to reinitialize portaudio
- sd._terminate()
- sd._initialize()
- requested_device = os.environ.get("MICD_DEVICE")
- device = int(requested_device) if requested_device is not None else None
- return sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER, device=device)
-
- def micd_thread(self):
- # sounddevice must be imported after forking processes
- import sounddevice as sd
-
- device = sd.default.device
- if os.environ.get("MICD_DEVICE") is not None:
- device = int(os.environ["MICD_DEVICE"])
- sd.default.device = (device, device)
- 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}")
-
- 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():
- mic = Mic()
- mic.micd_thread()
-
-
-if __name__ == "__main__":
- main()
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/ui/lib/wifi_manager.py b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/ui/lib/wifi_manager.py
index a52cdab8..ae32de15 100644
--- a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/ui/lib/wifi_manager.py
+++ b/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/system/ui/lib/wifi_manager.py
@@ -177,7 +177,7 @@ class WifiManager:
# State
self._connecting_to_ssid: str = ""
self._ipv4_address: str = ""
- self._current_network_metered: MeteredType = MeteredType.UNKNOWN
+ self._current_network_metered: MeteredType = MeteredType.NO
self._tethering_password: str = ""
self._ipv4_forward = False
self._tethering_transition = False
@@ -568,6 +568,7 @@ class WifiManager:
'type': ('s', '802-11-wireless'),
'uuid': ('s', str(uuid.uuid4())),
'id': ('s', f'iqpilot connection {ssid}'),
+ 'metered': ('i', int(MeteredType.NO)),
'autoconnect-retries': ('i', 0),
},
'802-11-wireless': {
@@ -802,7 +803,7 @@ class WifiManager:
cloudlog.warning("No WiFi device found")
return
- self._current_network_metered = MeteredType.UNKNOWN
+ self._current_network_metered = MeteredType.NO
for active_conn in self._get_active_connections():
conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1]
@@ -821,8 +822,14 @@ class WifiManager:
metered_prop = settings['connection'].get('metered', ('i', 0))[1]
if metered_prop == MeteredType.YES:
self._current_network_metered = MeteredType.YES
- elif metered_prop == MeteredType.NO:
+ else:
self._current_network_metered = MeteredType.NO
+ if metered_prop != MeteredType.NO:
+ settings['connection']['metered'] = ('i', int(MeteredType.NO))
+ settings_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE)
+ reply = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'Update', 'a{sa{sv}}', (settings,)))
+ if reply.header.message_type == MessageType.error:
+ cloudlog.warning(f'Failed to default Wi-Fi connection to unmetered: {reply}')
return
def set_current_network_metered(self, metered: MeteredType):
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 4f1e13fe..708c3562
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 2483f835..9343868a
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e9048d77..c4de6837
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 78010022..01756052
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e69b12e4..3f6b662b
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 18f45c78..fe14357b
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index b0ac025e..0f9eee0c
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip
index 53a8424c..db9c9ffb 100644
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 5efa11d6..c7c2849a
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_connection_parameters.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_connection_parameters.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index fe55babe..6f511566
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_connection_parameters.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_connection_parameters.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index be25b936..1ad12c26
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 04398ed6..e957340e
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 0c1d4991..2ecb5b40
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index f5f4f6c9..fb13f5b3
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index bd376df0..34709087
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/driving_settings.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/driving_settings.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 1b637aca..a042db3b
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/driving_settings.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/driving_settings.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 8211b261..b807c376
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index a5695239..7a93c8ab
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryvisiond.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryvisiond.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index c7234da0..4d56b10b
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryvisiond.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/iqsentryvisiond.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 6e65f8db..39f3fb6f
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/k3wgd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index cd4baf5c..30528cc7
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index eab513bb..34288596
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 2c67e071..6bac9663
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_body.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_body.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 57e74985..c294e80f
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_body.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_body.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_imu.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_imu.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 8e91068b..3b5c0054
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_imu.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_imu.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_media.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_media.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 7dc6ffd4..0e9a325d
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_media.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_media.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_policy.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_policy.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 990f9bed..e454658e
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_policy.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_policy.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_upload.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_upload.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 7e744602..41273130
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_upload.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/sentry_upload.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/session_bridge.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/session_bridge.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..fe54cde2
Binary files /dev/null and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/session_bridge.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 12f40270..5d37480b
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 57ba3ffe..69adbd5c
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/locationd/__init__.py b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/registration/__init__.py
similarity index 100%
rename from artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/locationd/__init__.py
rename to artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/registration/__init__.py
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/registration/reg_core.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/registration/reg_core.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..43349d79
Binary files /dev/null and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/registration/reg_core.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/telemetry/iqtelemetryd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/telemetry/iqtelemetryd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 2d7c379d..e96b93e3
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/telemetry/iqtelemetryd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/telemetry/iqtelemetryd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 30f7583d..178d4773
Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_iqvd_private/manifest.json b/artifacts/iqpilot_iqvd_private/manifest.json
index 9a480028..d648e1ed 100644
--- a/artifacts/iqpilot_iqvd_private/manifest.json
+++ b/artifacts/iqpilot_iqvd_private/manifest.json
@@ -1,4 +1,5 @@
{
+ "bundle_version": 1792551438,
"python/_iqclosure/_vendored.json": {
"mode": 420,
"sha256": "b5eb6863ec038e9d3dbd166ebd4ebce4e1bfdba80e399cc818660daa39121105",
@@ -41,8 +42,8 @@
},
"python/_iqclosure/iqpilot/common/utils.py": {
"mode": 420,
- "sha256": "103fa6c24f8b963576c482a34d7ecb020168fd3e85156493ae4432ee3b7b2c06",
- "size": 5159
+ "sha256": "c39b89a1ab34511dfa5f9efac783fde0e6d616963a416cf472bd1f604b57f84d",
+ "size": 5892
},
"python/_iqclosure/iqpilot/system/__init__.py": {
"mode": 420,
@@ -81,13 +82,13 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
"mode": 420,
- "sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
- "size": 7585
+ "sha256": "38e521356c56ac85f35543b9457d36639e55ed2ac916f30ea3b86948e806541f",
+ "size": 8016
},
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
"mode": 420,
- "sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db",
- "size": 25601
+ "sha256": "da5ecb280f7af14169903260dc941e3bfac033c93b184cb0abea0f1494c34582",
+ "size": 25731
},
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
"mode": 420,
@@ -109,25 +110,15 @@
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
- "python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "ebe6862e0d71e64c708adcf0b30980d68a881fae903270451513c3c36ac19cec",
- "size": 67640
- },
"python/iqpilot_private/iqvd/__init__.py": {
"mode": 420,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
"python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "35a3ff4fc87a93e7b83b5abefdb3b25aff40f55c57e1d6c94b706580da97472a",
- "size": 201088
- },
- "python/iqpilot_private/iqvd/offload/__init__.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "4aa8810cb7deb908a17727fe9070caa6e89fad22ace74ea66cab52cf3a9d8ee3",
- "size": 67640
+ "mode": 416,
+ "sha256": "56ce5ad31b607b188eb8cf8e7b512953484420d2cb7a27568b78e139cc92df74",
+ "size": 1130920
},
"python/iqpilot_private/iqvd/offload/__init__.py": {
"mode": 420,
@@ -135,29 +126,29 @@
"size": 0
},
"python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "2819b633eada9ca81ebf5c2b70b09c59e75fc39ad5fe1d3354e6937af2ebf671",
- "size": 135248
+ "mode": 416,
+ "sha256": "fdd2f2f8d6008780de923a81dfd2a855c6974f8630250e368a278f677d966fd7",
+ "size": 1065224
},
"python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "24520726197b90091a7be02751425a2d443a8030d9fdcaf2cbf9753cb54712e1",
- "size": 68232
+ "mode": 416,
+ "sha256": "ea45fc96d4f8ae45961799d7ed57f927fb16106c9db8307db6d395dac3d0b509",
+ "size": 1065224
},
"python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "4743a191fc66df0ab47c912dd91f66db2a52eb5602bbeacbe958045477f97074",
- "size": 135928
+ "mode": 416,
+ "sha256": "3e8f2c8fda7666847d407daf8cc8ca6a7005cb9f6fd870b860ddf1f53060bbd5",
+ "size": 1130840
},
"python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "c960f9ea3d8d418979173f2f38af254cbe5f7215bc730ab32d00867c972e2b78",
- "size": 68120
+ "mode": 416,
+ "sha256": "c9621673c8d4f1dd32dace6a8c4e95f51cadc21d683208254474e9f080b5c4ac",
+ "size": 1130904
},
"python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "40009c25a3870406c2d525b89f9dd1d3a4a2b5d87167aaaf40539f0a04e2d154",
- "size": 270104
+ "mode": 416,
+ "sha256": "8d8e37fcc2f64c3d6c198626479d3cca36dd959e568ab64355c65362fc5ff360",
+ "size": 1196344
},
"runtime": {
"entries": {
@@ -168,13 +159,11 @@
}
},
"signatures": {
- "python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": "O0ruQnmy0kYo9kDP497IqO1qv8vbu7MxQPJ3NYOtzE7wGMVFXNPY7XYsuwLSeMWpvyulR84VyESKdw1XxAJ+Cg==",
- "python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": "eC8ltU6gkLw2jtoMNaE1rlWZQvR41aUJC2UwRXJzpzPzMdwvDWwmQ6pjdM+NMrlAH9+hTfspmzl/I+41hl+EDg==",
- "python/iqpilot_private/iqvd/offload/__init__.cpython-312-aarch64-linux-gnu.so": "51199f2smtzQKSMN2O1P2RqIKxcTusT/tHSImFHuluWr9Ln9EeoY52tFkY8o8ghKvRAwyRKhd8aBtXVWDk5lDw==",
- "python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so": "rHFp8fRDzB4p8BALQU3H8lIdgfWiDZB9hLhaPTt7VxBRa/8LqYb0J2Nn9rPAVKlE/Ge+CpWsDvjZRSvI1gGxAw==",
- "python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so": "v30o1QIBwUNtguaChR0gIvZc31sxsm5sggTvwFj5fPc15Gdn2hV4xSpvwny2AzhgXMIsQRodoP/beMUJUR/lBA==",
- "python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so": "W6ikO5pIopJ6hF5/7TH8wscx3SpLdnzbTvqqiaUJnph+y3VazBXRppg/+hW5cMf1aTUSKXQvMEPb9eM5VHMcAQ==",
- "python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so": "JUDqK6O2H7/nSx7uLBetzvzenw5fQepaxeAnKVuxL7K5T8jUhWsTCVxbwtzTB/VajwBcQcLUUqv4qxtbr6lLBQ==",
- "python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": "UyYQcqQ9VPw8Met6ec2peNVz19jSbo3mHPygM8NbKQ4ghc134lpvco1J0paNKx4m1O4wf0jxH2wgjn/kfmwqDg=="
+ "python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": "nQeUBh5VBNq1B4ygfO7q78KCI9kadSY/PlCco1Dpm9+VwIIsnBCRsr1rLN6wdb4pKACizXei1tuquFnKfDklCg==",
+ "python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so": "VHHNoJPyndUJRKnkl7gCvmBa03UG8zLmqBL5hChQkPrwIxGTJVy6VIpQirxD5d/8tCUkolL9MtTudyzVcr5oAw==",
+ "python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so": "4pN2rWVV39iTQ4NCSotKGvdcjsyv2nebQiiWVhz8eg/i0TYT+rglCiK/K/CRvRz6TtN8p+gvO6kvTNX8qhsuDw==",
+ "python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so": "Xeu//0EZs72tK76JU1miYeLSSxHu+jGZGK0gZfB8fosX3o4LaECdoMFpxCmvsiWNiWpcHvyU1l80N0ZIMoyiAg==",
+ "python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so": "DUHqu0XBxEFlZxMJ+haVhC/J8rS9S5QYoIx5MOOh9CxExSwRQalCnWJ5nUmyS66VNLs2LRXa5XUPZQ+wqxDtDw==",
+ "python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": "P/Kyh/f8spyn1p/Rq17lKu2IfYv5Y1boFJw6UEXB0vD13ra69GLShOoqDs4L7RR8S2QByFuCfLm1ifmqkuiPCQ=="
}
}
diff --git a/artifacts/iqpilot_iqvd_private/manifest.json.sig b/artifacts/iqpilot_iqvd_private/manifest.json.sig
index fd71d4c6..90266fe1 100644
--- a/artifacts/iqpilot_iqvd_private/manifest.json.sig
+++ b/artifacts/iqpilot_iqvd_private/manifest.json.sig
@@ -1 +1 @@
-4i8PuAK3YOHd51HjPmUofy1Va2zgK8QsJWSY9zkROZpkNBOudH+xv/3nizVt+3v74p9lPP9xzMo+RpV72ZFMBg==
+RASbMiHfyPugPQ0ry9f9syHR2qjdE1JQ8X8rcFTMwuJjgvQewwTyclwZ2i54V50lvhvi60RpHtfvXETO4UjSDQ==
diff --git a/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/common/utils.py b/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/common/utils.py
index a80270bb..005d75b8 100644
--- a/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/common/utils.py
+++ b/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/common/utils.py
@@ -31,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
diff --git a/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py b/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
index 48a34ea4..c38e33b9 100644
--- a/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
+++ b/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
@@ -125,16 +125,32 @@ class Amplifier:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: list[AmpConfig]) -> bool:
+ import threading
+
tries = 15
backoff = 0.
for i in range(tries):
- try:
- self._set_configs(configs)
- return True
- except OSError:
+ result = [None]
+ error = [None]
+ def _worker():
+ try:
+ self._set_configs(configs)
+ result[0] = True
+ except OSError as e:
+ error[0] = e
+ t = threading.Thread(target=_worker, daemon=True)
+ t.start()
+ t.join(timeout=2)
+ if t.is_alive():
backoff += 0.1
time.sleep(backoff)
- print(f"Failed to set amp config, {tries - i - 1} retries left")
+ print(f"Failed to set amp config (timeout), {tries - i - 1} retries left")
+ continue
+ if result[0]:
+ return True
+ backoff += 0.1
+ time.sleep(backoff)
+ print(f"Failed to set amp config, {tries - i - 1} retries left")
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
diff --git a/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
index 2f71ed63..2506fb9e 100644
--- a/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
+++ b/artifacts/iqpilot_iqvd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
@@ -469,8 +469,10 @@ class Tici(HardwareBase):
if self.amplifier is not None:
self.amplifier.initialize_configuration(self.get_device_type())
- # Allow hardwared to write engagement status to kmsg
- os.system("sudo chmod a+w /dev/kmsg")
+ try:
+ subprocess.run(["sudo", "-n", "chmod", "a+w", "/dev/kmsg"], timeout=2, capture_output=True, check=False)
+ except Exception:
+ pass
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
@@ -514,10 +516,10 @@ class Tici(HardwareBase):
affine_irq(3, "xhci-hcd:usb1")
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
- subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
- subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessException as e:
- print(str(e))
+ subprocess.run(["sudo", "-n", "chrt", "-f", "-p", "1", pid], timeout=2, capture_output=True, check=False)
+ subprocess.run(["sudo", "-n", "taskset", "-pc", "3", pid], timeout=2, capture_output=True, check=False)
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+ pass
def configure_modem(self):
from iqpilot.common.params import Params
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index 27500f5a..00000000
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 894f46ab..b123d3f3
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/__init__.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index c2f67e69..00000000
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/__init__.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index ed964de5..cfa073f2
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/client.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index b107df75..0df741b4
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/geometry.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e9cd5d1b..f2c250c6
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/perception.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e12e4c81..fa7a84b9
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/offload/protocol.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 967ea97f..bd535ee6
Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_maps_private/manifest.json b/artifacts/iqpilot_maps_private/manifest.json
index d09b8d50..909be7c0 100644
--- a/artifacts/iqpilot_maps_private/manifest.json
+++ b/artifacts/iqpilot_maps_private/manifest.json
@@ -1,4 +1,5 @@
{
+ "bundle_version": 1792551456,
"python/_iqclosure/_vendored.json": {
"mode": 420,
"sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
@@ -9,20 +10,15 @@
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
- "python/iqpilot_private/maps/__init__.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "b4f12341a5ebeb6263226cd03391c6505c708279c9c4ff4c0057326f6325e8ab",
- "size": 67616
- },
"python/iqpilot_private/maps/__init__.py": {
"mode": 420,
"sha256": "5128931b54d4f1ff131185e7711d56a788b5e2cb71e3c8154a2f79fef184b452",
"size": 118
},
"python/iqpilot_private/maps/git_auth.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "84600544b6ca61637b1d332d4881a00083ef9001efba65ac45cca79a2a526c33",
- "size": 67856
+ "mode": 416,
+ "sha256": "8626c26d5b891985bc2a0ea84e3e51169447aebc6a88a154e82e962ab2c93c10",
+ "size": 1130840
},
"runtime": {
"entries": {
@@ -33,7 +29,6 @@
}
},
"signatures": {
- "python/iqpilot_private/maps/__init__.cpython-312-aarch64-linux-gnu.so": "f9iG6goRpO0HKZpoQUoZ7KGcO7YBqV3oBtZxtES4IjeOqUl7zPaVAMxGYD0jkkjtC5HKBH2fldpGGOAoQvhbBw==",
- "python/iqpilot_private/maps/git_auth.cpython-312-aarch64-linux-gnu.so": "63rRKgkKdIVzRaDNRFYj+X/NRNnZrwra43QGZF8VcafRopvwXnXrSgjxmgLPIRZQVk5YPcn9cgHXAuU2UJjdDw=="
+ "python/iqpilot_private/maps/git_auth.cpython-312-aarch64-linux-gnu.so": "rAXhCCEeW9flkDTlDRHAGiU/wrQ6YWtdWaUdPjoEDUgTkdvvA5a4QW+I/ongdPkO55y+J2crx/LXOz8KPtHHAg=="
}
}
diff --git a/artifacts/iqpilot_maps_private/manifest.json.sig b/artifacts/iqpilot_maps_private/manifest.json.sig
index 18a66bbb..35e0bca9 100644
--- a/artifacts/iqpilot_maps_private/manifest.json.sig
+++ b/artifacts/iqpilot_maps_private/manifest.json.sig
@@ -1 +1 @@
-UrGrwApvCiaSCaGrD+6aKwLNTm5TaQ02X2dS4DVbM9Gw6qvQ3w6/ql/mfbr/i0Y1SgPIDfKx8fCOdcCWIwJtBA==
+Y4lco3biglBKOOqFOD1X1894CCwb2uPKgYMRTV2om+t1vTXYwZ1ge8pjRMbZfXdLewmxQ2pNqkffWNqUITymDg==
diff --git a/artifacts/iqpilot_maps_private/python/iqpilot_private/maps/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_maps_private/python/iqpilot_private/maps/__init__.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index f9dc9ccb..00000000
Binary files a/artifacts/iqpilot_maps_private/python/iqpilot_private/maps/__init__.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_maps_private/python/iqpilot_private/maps/git_auth.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_maps_private/python/iqpilot_private/maps/git_auth.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 2be9e39c..476383b8
Binary files a/artifacts/iqpilot_maps_private/python/iqpilot_private/maps/git_auth.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_maps_private/python/iqpilot_private/maps/git_auth.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/manifest.json b/artifacts/iqpilot_model_selector_private/manifest.json
index ee2bf108..a38b5f45 100644
--- a/artifacts/iqpilot_model_selector_private/manifest.json
+++ b/artifacts/iqpilot_model_selector_private/manifest.json
@@ -1,8 +1,9 @@
{
+ "bundle_version": 1792551178,
"python/_iqclosure/_vendored.json": {
"mode": 420,
- "sha256": "1b0c676a7151287ba1aaaf1f80fbb49de1eed48bd6e979a3dffa23f01230af86",
- "size": 920
+ "sha256": "8756c85acb0d6b34f059386aee6177bf9a5d8fb01b509dd81c8848441ef7929b",
+ "size": 942
},
"python/_iqclosure/iqpilot/__init__.py": {
"mode": 420,
@@ -16,13 +17,18 @@
},
"python/_iqclosure/iqpilot/common/api/__init__.py": {
"mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
+ "sha256": "1dd848ec6e867836a321a93f9ac218415c19528aa356a3e80969503285316b54",
+ "size": 900
},
"python/_iqclosure/iqpilot/common/api/base.py": {
"mode": 420,
- "sha256": "86b834e02e24a005c903f1ca059b1a572e37c17a87271726c90918cd04b6133b",
- "size": 3451
+ "sha256": "5b312ec45e673d9337a89ea82a157152d1f64e5e48518807a592247787087609",
+ "size": 4921
+ },
+ "python/_iqclosure/iqpilot/common/api/konn3kt.py": {
+ "mode": 420,
+ "sha256": "2282e19909079bfd77a089f480df5a229191c35f8523f005d20e2ea468a6b0bc",
+ "size": 313
},
"python/_iqclosure/iqpilot/common/basedir.py": {
"mode": 420,
@@ -46,8 +52,8 @@
},
"python/_iqclosure/iqpilot/common/realtime.py": {
"mode": 420,
- "sha256": "016be524079ef32df03b2c135eab8f83da60a46fc7f143620b836c65f33e39a3",
- "size": 4423
+ "sha256": "0190cf1f239f8a2bda573524a72a4b6a74e7ed70aa4c1e5f72d1f1159d59cd60",
+ "size": 4926
},
"python/_iqclosure/iqpilot/common/swaglog.py": {
"mode": 420,
@@ -61,18 +67,8 @@
},
"python/_iqclosure/iqpilot/common/utils.py": {
"mode": 420,
- "sha256": "103fa6c24f8b963576c482a34d7ecb020168fd3e85156493ae4432ee3b7b2c06",
- "size": 5159
- },
- "python/_iqclosure/iqpilot/konn3kt/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/konn3kt/cloud_client.py": {
- "mode": 420,
- "sha256": "4d58e62f8d628a8570881f2b87c82c801b46928fbb84f8dab8a81a9b55ba4e15",
- "size": 493
+ "sha256": "c39b89a1ab34511dfa5f9efac783fde0e6d616963a416cf472bd1f604b57f84d",
+ "size": 5892
},
"python/_iqclosure/iqpilot/selfdrive/__init__.py": {
"mode": 420,
@@ -91,8 +87,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_cache.py": {
"mode": 420,
- "sha256": "1838b6ab0e9b71729397f0feda85a959e7796d3196aa68de9ca774f8fe3c473c",
- "size": 2559
+ "sha256": "2b3664ae0cfbab6ddbc624d4f0376dee74ccf0b43b6089b022aa7c9362009908",
+ "size": 2560
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/__init__.py": {
"mode": 420,
@@ -106,8 +102,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py": {
"mode": 420,
- "sha256": "2fad977c3816ff824c126a92605f866f3bc0aa989ed8397e818bed481a41192f",
- "size": 14051
+ "sha256": "9628f88e71f7fae2c3d62dd17618367b3c7d12749cfe2a4737410288888cd3bd",
+ "size": 15008
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/temporal_state.py": {
"mode": 420,
@@ -151,13 +147,13 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
"mode": 420,
- "sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
- "size": 7585
+ "sha256": "38e521356c56ac85f35543b9457d36639e55ed2ac916f30ea3b86948e806541f",
+ "size": 8016
},
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
"mode": 420,
- "sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db",
- "size": 25601
+ "sha256": "da5ecb280f7af14169903260dc941e3bfac033c93b184cb0abea0f1494c34582",
+ "size": 25731
},
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
"mode": 420,
@@ -184,50 +180,60 @@
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
- "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "4256dfb6e41630d1650932bda2b0b34249a4f68cc66038c8c4880f02758ecc0c",
- "size": 67640
- },
"python/iqpilot_private/models/__init__.py": {
"mode": 420,
"sha256": "5128931b54d4f1ff131185e7711d56a788b5e2cb71e3c8154a2f79fef184b452",
"size": 118
},
+ "python/iqpilot_private/models/_enroll.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "d230702120b8a84b0b122844d17741269e9c04c963d0a2d50949c53e7e4a0392",
+ "size": 999928
+ },
+ "python/iqpilot_private/models/_iqcore_binding.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "fe3b6c3827b9a17a200cf289f8cb8592e472b5e7e1829e8a09bc8cac78d2991b",
+ "size": 1065144
+ },
"python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "c1af60adcc1e2bb13c12282fddcca774cc622269923a577ad46861147848f919",
- "size": 133872
+ "mode": 416,
+ "sha256": "b6c67773d2b22dc0c552f32791ff99c1c884ca6afad5f7cfc91434b8f3b5228b",
+ "size": 1065304
},
"python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "3ebab4e08c7c75f678a3c810bffc185092157b2a88e7ebc23e19eae8e6e129eb",
- "size": 68144
+ "mode": 416,
+ "sha256": "f096e15790592e54907f86e65df9c2302419189f5c43f8b88d3073527ae18238",
+ "size": 1065256
},
"python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "b6efc87125fb6d350f5b1346b1542d3e744c699fc45f75df99e5402edb9a21e9",
- "size": 134096
+ "mode": 416,
+ "sha256": "74ff49ceb972b20578e66a2853f15e180fa9b428cdbf37c8f647c08c8aa15892",
+ "size": 1065368
},
"python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "a024d244743cd899ad359c1177aee2315ba9e5a12b1f8708f4d76c9cc46bea2c",
- "size": 201752
+ "mode": 416,
+ "sha256": "888aeabbe88c05c1e1d11def322f32bd399e5f558d49303eabae19b3c09fca37",
+ "size": 1130920
},
"python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "ec73d0435e8e974d3d3c7b310c96a6cee18d1110e9bf5b8b72717f150487c3db",
- "size": 68080
+ "mode": 416,
+ "sha256": "777bd6decfcd2a36c43c853f176db156fd650bf2d0f9b458b62a69525dd041cb",
+ "size": 1065352
},
"python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "7ec2fbb273c8baa91974ed4f1387b56578e17a89e166560fe276d4bd22b2834c",
- "size": 203576
+ "mode": 416,
+ "sha256": "6b1417618b09387feb90cebf2ad369d93529ba5a7cd566d9dbe14974217f5338",
+ "size": 1131000
+ },
+ "python/iqpilot_private/models/iqc.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "f54f3dfbdf3abb6629243bef3cd0da8c5374fa179040d2cf40ab10ba79210129",
+ "size": 1327704
},
"python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "391d2e774fd645d126011bbb4a036fa1f25647b2ef006faa421d042120cabc55",
- "size": 339984
+ "mode": 416,
+ "sha256": "a087f40591c917c8ed0341ea10a47f8adc8452f3b566084550175e667d6459f1",
+ "size": 1327512
},
"runtime": {
"entries": {
@@ -238,13 +244,15 @@
}
},
"signatures": {
- "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "8Oas7JZZ7zmVIMRIENhqnWB9SbrCDlLbhouUn7EnLpCBiatVBCwNVa9z42al4oT32XllAxv3NxYKoCs7iVO3AQ==",
- "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "FbTHsGldom1DcQHjJ5a6V4tnbg1DSHpTVE6oYDQDuoeZX8hsp3FoNURK+4DW/jpDojZsTLU4LQW6PFOBwLtHAg==",
- "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "2saywleDwtIVnGvYySpHwLhUQqDaj27LU3fj6UPwoOWJSx+YGiksNZlG5xlSGe53j1M7qyNSMPUMB8AyYFG1Aw==",
- "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "PMnolJ5vV1lbrkOdnbvc+hXsaKaH4/Ocxv2jr4L+Uc5eZwqhX60yerFtS/JgMSZNowoaYvBYyod5NJCQl3ZXBg==",
- "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "vZvAPdHliUZ6TiI4Wa7yn5KwQWIzGlstyUlj35su+qLbkvFZuvpwJkKKpgcADI1AyUVXPCg1OFxyrUWEq3HvBQ==",
- "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "kDfRCaG/n6H+2Qi3iX2JKyTRzglN/uy72UzhOsQ4rtaLD/I8jJoL5UMdQuI6hh6bafFAH3Ory42bVye60ZCyBA==",
- "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "X1zNt2VT3wMGiAp/cgUi/glUNE44zIGSBOOYlO1wN1YL5OoPz/ad14zE3QXmyxOkvsIsC9w0+VNCKkr4m6nvBA==",
- "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "B1jVEO0cjOqMoG3Fbkrca4eMPyU5s2EcW+rtMMhS7dH/l1Jp5bcYWehFJcKTx0O4zmmeUOZzIR4XavMG2Qp1AQ=="
+ "python/iqpilot_private/models/_enroll.cpython-312-aarch64-linux-gnu.so": "okulq02oEg6c/AjfK6ldwGokYY5VjEWvLwM1b2nW4VT95M/nzfPBX7feWjLepB0vThiRa76Qf3bBmk7Zcv/2CQ==",
+ "python/iqpilot_private/models/_iqcore_binding.cpython-312-aarch64-linux-gnu.so": "eHe88UatX5GonlzYAjPEMZBTvuRduNZayvvKLpNfJmU2TtXBmpQTn0Nvwzw+UTzDrsy7vHkzA4mmVbTcFjRaBQ==",
+ "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "5VQ4Tz0Twma+VsCUCfDEQ7dPIV1GUH3xzkNRstWgPeky2MbFWpnjRFk6MUxLj1a7sPSs1s+oSZjj9Jh6jXMbDw==",
+ "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "Ih4+8VDbepxfCupUOktXYjQu1hkQxdBNZ5yp58LCv0pTwjQVBEDqGeVrOaPVZr5wcfc067sx6k6c391JIDmsBg==",
+ "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "9S+eMQgb5p2TbXL2Msn+1oawSSoxKfzjm9n6GsCzrERFFKACpEjPeflBnnZFeTkVGogVgPe/YDXIExt9HU1yCw==",
+ "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "H7qadyNEqpmq9ICZwDbBwirJkZZ8uNPr3qq4b6ppQWseoil7kXkXVaQEbgjbkFfs6fXgT8miz9TuV7MkCUFtCQ==",
+ "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "DLbub0F37+i718kJS8o4utxkQlc0JnVLwLbixexRfWWayI7xpqlhyYYtjkU1OAJ4V7di0d9vT5rrrnNwcBCYDg==",
+ "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "9WFTiHzwW4flxL996vGCem/NfcwrAvJ0iVY4PnhVkOmdUgZI5feazGl4px/5BqDFAsmzpv2X7GDddRfnje0BBA==",
+ "python/iqpilot_private/models/iqc.cpython-312-aarch64-linux-gnu.so": "Wy7zbJ3Uq/zKPYur4llisNV78dD1Br0k4RaGRE6LmYBZhP7E5UPh/PkiGN35a4KQw7coVTz7k6QNmhkBwV/QBA==",
+ "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "T1upWFYQnUx6h50DChQYVvUe8cU0Y1SIGtTWqi9bBbRHJrUiyF5eTU/2tyCRAfhXUNmej4jDC3GXen3LbZ/aCA=="
}
}
diff --git a/artifacts/iqpilot_model_selector_private/manifest.json.sig b/artifacts/iqpilot_model_selector_private/manifest.json.sig
index 18c70554..7c82e5b7 100644
--- a/artifacts/iqpilot_model_selector_private/manifest.json.sig
+++ b/artifacts/iqpilot_model_selector_private/manifest.json.sig
@@ -1 +1 @@
-lMc1eIwvOZFGqSSv1vD2DViOTsO8E/B5QQOHOXeHimq1QAd0OBb+GvdEtAWIVurZJQD0UAJJAKdLoWX0xr5+DQ==
+ygr/GU1zFptQyHq7psL+DQ3j+AeLjH+CDWNPoCgknpaFjg3RTwwxSwmt7HoTwHf2GIoLjO5IAAphORP4ARqwDg==
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_model_selector_private/python/_iqclosure/_vendored.json
index 7a4e1822..af09850a 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/_vendored.json
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/_vendored.json
@@ -1,5 +1,7 @@
[
+ "iqpilot.common.api",
"iqpilot.common.api.base",
+ "iqpilot.common.api.konn3kt",
"iqpilot.common.basedir",
"iqpilot.common.gpio",
"iqpilot.common.i2c",
@@ -8,7 +10,6 @@
"iqpilot.common.swaglog",
"iqpilot.common.time_helpers",
"iqpilot.common.utils",
- "iqpilot.konn3kt.cloud_client",
"iqpilot.selfdrive.iqmodeld.config",
"iqpilot.selfdrive.iqmodeld.model_cache",
"iqpilot.selfdrive.iqmodeld.models.fetcher",
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/__init__.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/__init__.py
index e69de29b..b7e90dac 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/__init__.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/__init__.py
@@ -0,0 +1,26 @@
+import iqpilot.common.api.konn3kt
+
+
+class Api:
+ def __init__(self, dongle_id):
+ self.service = iqpilot.common.api.konn3kt.Konn3ktApi(dongle_id)
+
+ def request(self, method, endpoint, **params):
+ return self.service.request(method, endpoint, **params)
+
+ def get(self, *args, **kwargs):
+ return self.service.get(*args, **kwargs)
+
+ def post(self, *args, **kwargs):
+ return self.service.post(*args, **kwargs)
+
+ def get_token(self, payload_extra=None, expiry_hours=1):
+ return self.service.get_token(payload_extra, expiry_hours)
+
+
+def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
+
+
+def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).get_key_pair()
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/base.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/base.py
index 371a8ab4..0aef9c35 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/base.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/base.py
@@ -7,10 +7,44 @@ from functools import lru_cache
from iqpilot.system.hardware.hw import Paths
from iqpilot.system.version import get_version
-# name: jwt signature algorithm
KEYS = {"id_rsa": "RS256",
"id_ecdsa": "ES256"}
+_CUSTODIAN_KEY = object()
+_IQKEEPER_SOCKET = "/run/iqkeeper/iqkeeper.sock"
+
+
+def _custodian_available() -> bool:
+ import stat
+ try:
+ return stat.S_ISSOCK(os.stat(_IQKEEPER_SOCKET).st_mode)
+ except OSError:
+ return False
+
+
+def _custodian_sign_jwt(dongle_id: str, payload: dict, alg: str) -> str:
+ import base64
+ import json
+ import socket
+ nonce = base64.b64encode(os.urandom(16)).decode()
+ packet = json.dumps({
+ "v": 1,
+ "op": "sign",
+ "dongle_id": dongle_id,
+ "payload": payload,
+ "alg": alg,
+ "nonce": nonce,
+ }).encode()
+ with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as sock:
+ sock.settimeout(25.0)
+ sock.connect(_IQKEEPER_SOCKET)
+ sock.send(packet)
+ reply_data = sock.recv(8192)
+ reply = json.loads(reply_data) if reply_data else {}
+ if reply.get("nonce") != nonce or not reply.get("ok"):
+ raise PermissionError(f"custodian sign failed: {reply.get('error', 'unknown')}")
+ return reply["token"]
+
@lru_cache(maxsize=4)
def load_signing_key(private_key: str):
@@ -60,6 +94,9 @@ class BaseApi:
}
if payload_extra is not None:
payload.update(payload_extra)
+ if self.private_key is _CUSTODIAN_KEY:
+ serializable = {k: v.isoformat() if isinstance(v, datetime) else v for k, v in payload.items()}
+ return _custodian_sign_jwt(self.dongle_id, serializable, 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):
@@ -73,9 +110,25 @@ class BaseApi:
return self.request(method, endpoint, timeout, access_token, session=session, json=json, **params)
@staticmethod
- def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
+ def get_key_pair() -> tuple[str, str | object, str] | tuple[None, None, None]:
for key in KEYS:
- if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
- with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
- return KEYS[key], private.read(), public.read()
+ priv_path = Paths.persist_root() + f'/comma/{key}'
+ pub_path = priv_path + '.pub'
+ if not os.path.isfile(priv_path) or not os.path.isfile(pub_path):
+ continue
+ try:
+ with open(pub_path) as public:
+ pub = public.read()
+ except OSError:
+ continue
+ try:
+ with open(priv_path) as private:
+ return KEYS[key], private.read(), pub
+ except PermissionError:
+ import time
+ for _attempt in range(10):
+ if _custodian_available():
+ return KEYS[key], _CUSTODIAN_KEY, pub
+ time.sleep(0.5)
+ continue
return None, None, None
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/konn3kt.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
new file mode 100644
index 00000000..cae7ea46
--- /dev/null
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/api/konn3kt.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2026 IQ.Lvbs. All rights reserved.
+import os
+
+from iqpilot.common.api.base import BaseApi
+
+API_HOST = os.getenv('API_HOST', 'https://api-iqlabs.konn3kt.com')
+
+
+class Konn3ktApi(BaseApi):
+ def __init__(self, dongle_id):
+ super().__init__(dongle_id, API_HOST)
+ self.user_agent = "openpilot-"
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/realtime.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/realtime.py
index 72578964..ca79bbab 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/realtime.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/realtime.py
@@ -62,10 +62,23 @@ def config_background_thread() -> None:
set_core_affinity(list(range(os.cpu_count() or 1)))
+def _swap_active() -> bool:
+ try:
+ with open("/proc/swaps") as f:
+ return len(f.read().splitlines()) > 1
+ except Exception:
+ return False
+
+
def lock_memory() -> None:
- """mlockall this process so memory reclaim/compaction can't stall it. RT control
- procs only (locking ui/modeld would worsen pressure). Best-effort."""
- if sys.platform != 'linux' or PC:
+ """mlockall this process so swap can't evict its pages and stall the RT loop.
+ RT control procs only (locking ui/modeld would worsen pressure). Best-effort.
+
+ No-op without active swap: with none, anonymous pages (thread stacks, heap)
+ are already unreclaimable, so mlockall buys nothing — it only pins every 8 MB
+ thread stack and MCL_FUTURE allocation in full (~400 MB/proc here) and, being
+ non-migratable, starves CMA. The device runs swapless by design (iqos-zram)."""
+ if sys.platform != 'linux' or PC or not _swap_active():
return
try:
import ctypes
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/utils.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/utils.py
index a80270bb..005d75b8 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/utils.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/common/utils.py
@@ -31,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/konn3kt/__init__.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/konn3kt/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py
deleted file mode 100644
index 2897609e..00000000
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/konn3kt/cloud_client.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
-Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
-"""
-
-
-import os
-from iqpilot.common.api.base import BaseApi
-API_HOST = os.getenv('KONN3KT_API_HOST', 'https://api-iqlabs.konn3kt.com')
-
-class Konn3ktApi(BaseApi):
-
- def __init__(self, dongle_id):
- super().__init__(dongle_id, API_HOST)
- self.user_agent = "konn3kt-device-"
-
- def get_token(self, expiry_hours=1):
- return super()._get_token(expiry_hours=expiry_hours)
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_cache.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_cache.py
index 5c104245..0b1cba59 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_cache.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_cache.py
@@ -4,7 +4,7 @@ from pathlib import Path
_RUNTIME_ARTIFACT_PATTERNS = (
- re.compile(r"egpu_.+_amd_(?:tinygrad|policy|policy_oob|model_oob)\.pkl$"),
+ re.compile(r"egpu_.+_amd_(?:tinygrad|policy|policy_oob|model_oob2)\.pkl$"),
re.compile(r"emac_warp_\d+x\d+_tinygrad\.pkl$"),
re.compile(r".+_usbemac\.pkl$"),
re.compile(r".+\.onnx$"),
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
index 05cce298..ee2eb77f 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/models/helpers.py
@@ -305,7 +305,40 @@ def get_runtime_bundle_upgrade(bundle, params: Params = None, available_bundles=
return _find_runtime_upgrade(bundle, params, available_bundles)
+_forced_bundle = None
+
+
+def load_default_model_session():
+ """Stage the shipped default model files and return a decoded default bundle
+ WITHOUT persisting it as the active selection, so the next boot retries the
+ user's own model instead of staying on the default forever."""
+ bundle_dict = _load_default_bundle_dict()
+ ensure_default_model_files(bundle_dict)
+ return ModelBundle(**bundle_dict)
+
+
+class forced_active_bundle:
+ """Make get_active_bundle return `bundle` for the duration of the block, used to
+ build a runner for a session-only fallback bundle without touching Params."""
+ def __init__(self, bundle):
+ self._bundle = bundle
+
+ def __enter__(self):
+ global _forced_bundle
+ self._prev = _forced_bundle
+ _forced_bundle = self._bundle
+ return self._bundle
+
+ def __exit__(self, *exc):
+ global _forced_bundle
+ _forced_bundle = self._prev
+ return False
+
+
def get_active_bundle(params: Params = None):
+ if _forced_bundle is not None:
+ return _forced_bundle
+
params = Params() if params is None else params
try:
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
index 48a34ea4..c38e33b9 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
@@ -125,16 +125,32 @@ class Amplifier:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: list[AmpConfig]) -> bool:
+ import threading
+
tries = 15
backoff = 0.
for i in range(tries):
- try:
- self._set_configs(configs)
- return True
- except OSError:
+ result = [None]
+ error = [None]
+ def _worker():
+ try:
+ self._set_configs(configs)
+ result[0] = True
+ except OSError as e:
+ error[0] = e
+ t = threading.Thread(target=_worker, daemon=True)
+ t.start()
+ t.join(timeout=2)
+ if t.is_alive():
backoff += 0.1
time.sleep(backoff)
- print(f"Failed to set amp config, {tries - i - 1} retries left")
+ print(f"Failed to set amp config (timeout), {tries - i - 1} retries left")
+ continue
+ if result[0]:
+ return True
+ backoff += 0.1
+ time.sleep(backoff)
+ print(f"Failed to set amp config, {tries - i - 1} retries left")
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
diff --git a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
index 2f71ed63..2506fb9e 100644
--- a/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
+++ b/artifacts/iqpilot_model_selector_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
@@ -469,8 +469,10 @@ class Tici(HardwareBase):
if self.amplifier is not None:
self.amplifier.initialize_configuration(self.get_device_type())
- # Allow hardwared to write engagement status to kmsg
- os.system("sudo chmod a+w /dev/kmsg")
+ try:
+ subprocess.run(["sudo", "-n", "chmod", "a+w", "/dev/kmsg"], timeout=2, capture_output=True, check=False)
+ except Exception:
+ pass
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
@@ -514,10 +516,10 @@ class Tici(HardwareBase):
affine_irq(3, "xhci-hcd:usb1")
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
- subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
- subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessException as e:
- print(str(e))
+ subprocess.run(["sudo", "-n", "chrt", "-f", "-p", "1", pid], timeout=2, capture_output=True, check=False)
+ subprocess.run(["sudo", "-n", "taskset", "-pc", "3", pid], timeout=2, capture_output=True, check=False)
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+ pass
def configure_modem(self):
from iqpilot.common.params import Params
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index b3c2f010..00000000
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/_enroll.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/_enroll.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..f78cd01f
Binary files /dev/null and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/_enroll.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/_iqcore_binding.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/_iqcore_binding.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..81c379f3
Binary files /dev/null and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/_iqcore_binding.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index ebe494fb..057b0cd5
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 5ab33e1c..c7ccd7ff
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index c6d235ef..8d4377ab
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index ab1ef9a8..3c455c92
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 1049ed09..a965699b
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index d33fdc5d..d2a0ceaa
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/iqc.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/iqc.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..d5652741
Binary files /dev/null and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/iqc.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 7cac5421..b6c094ac
Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navassist_private/manifest.json b/artifacts/iqpilot_navassist_private/manifest.json
index 8d538a0b..bc1b02d4 100644
--- a/artifacts/iqpilot_navassist_private/manifest.json
+++ b/artifacts/iqpilot_navassist_private/manifest.json
@@ -1,158 +1,54 @@
{
+ "bundle_version": 1792268600,
"python/_iqclosure/_vendored.json": {
"mode": 420,
- "sha256": "6435d4e0f3a9354a6a2e9421f503ccfc8156432a4be70c9b6703515d10169c9b",
- "size": 577
- },
- "python/_iqclosure/iqpilot/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/common/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/common/gpio.py": {
- "mode": 420,
- "sha256": "01ba8cb32a644a2df545e764ba19bf1c7a320a217f983f8bac73b45d3a6891cf",
- "size": 2408
- },
- "python/_iqclosure/iqpilot/common/i2c.py": {
- "mode": 420,
- "sha256": "4af8f9da7504a548d15f18b91d71c1379cb7c05c84be031448764cc49920ce68",
- "size": 2449
- },
- "python/_iqclosure/iqpilot/common/logging_extra.py": {
- "mode": 420,
- "sha256": "ce9b3b3d8498d76e1c4c0352a3e98e72759bb7f367ff8629c1329c46040595c3",
- "size": 6628
- },
- "python/_iqclosure/iqpilot/common/realtime.py": {
- "mode": 420,
- "sha256": "f137ef62c1602a1f7c329731883dc518c62288bd25596891858d460ae3209ae2",
- "size": 3966
- },
- "python/_iqclosure/iqpilot/common/swaglog.py": {
- "mode": 420,
- "sha256": "0a133b4ef49f80b8890f115e81273f3f3e21351ae79f92dc02abc033e6d24027",
- "size": 5274
- },
- "python/_iqclosure/iqpilot/common/time_helpers.py": {
- "mode": 420,
- "sha256": "c14cda1f84149d5ffa63a2adec5ca24126f77c1412a78412e5e3e68a6fd12bcf",
- "size": 474
- },
- "python/_iqclosure/iqpilot/common/utils.py": {
- "mode": 420,
- "sha256": "6475ad94bc89252398c41e9eabf7929ba5eb8fa23a08ad7545e7a67b400af9f3",
- "size": 8090
- },
- "python/_iqclosure/iqpilot/system/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/system/hardware/__init__.py": {
- "mode": 420,
- "sha256": "889391bffca493e74bfc108cae2976a53b644caca13ab600061fd29e9622c627",
- "size": 695
- },
- "python/_iqclosure/iqpilot/system/hardware/base.py": {
- "mode": 420,
- "sha256": "a56293f7c23e503ff62d0d3ed39f1831b5544a65b37378165934361a5ddcd304",
- "size": 4966
- },
- "python/_iqclosure/iqpilot/system/hardware/hw.py": {
- "mode": 420,
- "sha256": "af504d08f49754fa49aa3040c6bfcc3d122c2948e50d4d481625ae8f00aa5256",
- "size": 3398
- },
- "python/_iqclosure/iqpilot/system/hardware/pc/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/system/hardware/pc/hardware.py": {
- "mode": 420,
- "sha256": "2520895597c375ae570bd04d5f189e874c89d642f3f07bdbeb20388cb6691433",
- "size": 258
- },
- "python/_iqclosure/iqpilot/system/hardware/tici/__init__.py": {
- "mode": 420,
- "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "size": 0
- },
- "python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
- "mode": 420,
- "sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
- "size": 7585
- },
- "python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
- "mode": 420,
- "sha256": "f1ddffa9bdb5071a49fd65eb4b49958b60b1873e1ec70e6f3f2e2b2501abce2f",
- "size": 25584
- },
- "python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
- "mode": 420,
- "sha256": "d22858982705531732d5a20a45a73795496e5bbd9e23028c24609a7e58ac7aed",
- "size": 890
- },
- "python/_iqclosure/iqpilot/system/hardware/tici/lpa.py": {
- "mode": 420,
- "sha256": "2bffb84aef35892c4788e80b6d937bf2eebd15e226b72a82c4f39217667c1ca0",
- "size": 53692
- },
- "python/_iqclosure/iqpilot/system/hardware/tici/pins.py": {
- "mode": 420,
- "sha256": "b57ba4262e6a6a79f2db15c27356acf3e54f579a7edd13b483b2e16d140aca6d",
- "size": 678
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
+ "size": 3
},
"python/iqpilot_private/__init__.py": {
"mode": 420,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
- "python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "2aaa1930828be7ea5e108c9fd723bcb8cf29a9b8e40a11f2544579d022e57fec",
- "size": 67640
- },
"python/iqpilot_private/navassist/__init__.py": {
"mode": 420,
"sha256": "ee4237375e199e74a35b5c1851f32eec3d0799966b7f1211c86ce00a0f756088",
"size": 112
},
+ "python/iqpilot_private/navassist/_cipher.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "e6b8cdc774506ce0f3b416111fd9522a22f488286b5449d121936ccf682412c7",
+ "size": 1064536
+ },
"python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "aa0ac6242e25d57f16ea5b668c935805e9f62f40450844c67ce49c3d13537874",
- "size": 135392
+ "mode": 416,
+ "sha256": "7483113a98a51a567f1d5487f8fd6d9fa3f0652f1fc8b5b8be2d2a0f3f17d1eb",
+ "size": 1064568
},
"python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "528c0bc888f65784c123cfcf07fd677a0cc61f8931eaa033486a60c592ed6412",
- "size": 201704
+ "mode": 416,
+ "sha256": "2168c0e7d825a4c9b5fde5b353c5097f95ac8ece2b42d632527849e66c1e0cd0",
+ "size": 1064616
},
"python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "6f8999f04cfdc4c264743d537b59904a5c874c5503dd82c936f4b7f80dcb6e6e",
- "size": 134016
+ "mode": 416,
+ "sha256": "519d14e719328449cf673607b790a88a356d025fac01d162cb0b53398ba02d49",
+ "size": 1130072
},
"python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d78fbdbb8773f39f19508ec20c0c55114ab15dddbbbf563873453e89fbf7c8ca",
- "size": 203288
+ "mode": 416,
+ "sha256": "594cb881a71598e8d96e0152df4c71be09ceb037a37a6498723853293c5ab5a3",
+ "size": 1130168
},
"python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "4e58188a2a984431825c8071cb7cfbd49dc87b83887020c5883baf119a6afce7",
- "size": 68240
+ "mode": 416,
+ "sha256": "4d2c0fa7dcf9a77dc8998b508683098c48c786a276957e07e296510ff7df9d5c",
+ "size": 1130232
},
"python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "6d5c4dffbc09cc2fcab1114750b6e3905c98baee24f8df2bbd5891d6764740fc",
- "size": 69656
+ "mode": 416,
+ "sha256": "422b9bb95559bf6732e31d82a8b3bf435a9bd7bd917788ec0e2654b865205310",
+ "size": 999096
},
"runtime": {
"entries": {
@@ -163,12 +59,12 @@
}
},
"signatures": {
- "python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so": "+TJsarV/gTM+PeWfwHOteKjOmhgp9W1f9adooEzjEtofVRVTB3mLZMY36kjRKgk1cMxMSdezyQi6r0ES4zdVAQ==",
- "python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so": "Ra8I1sD/cUTsohgsfY54zCi2Kh4xh2JR4DqgHuhlJUyrIETd4XsQoxuD5AWOkhshh75ZsN0PWpjEJ9TAZaGwAw==",
- "python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so": "GpsdQhg4y2Eo6tr6MLFNPJ0NkKz47rqdGArLQTJypz8QK1RMv7wX64AeWol9M7eUnymuYOvfTbn5Wsn6iJfoBA==",
- "python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so": "i7TCM/c4Igl6hT1jcW00mzCnSnt3vCeaYgfluN4eHD1QUCGdswQZU1lZohA3stywXNzmvZriGuv7kpeT10k6Cg==",
- "python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so": "befiXfF18Kz/KIDXc5XzIlO+B6YnOc6E3XGbGDVKzAphlIkhko+bHBSz0D0tid5bVWtzR8I3xhA9X9CJdaQTBA==",
- "python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so": "JE88scCtxeGCkLiAtZInFwkv3WzVj/JStHn7jKXoHc/Zx9/TxOPfnS57tLlEb4i+lAOpbOY8bQlOnKeCyjQeAw==",
- "python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so": "JiqxEb7qnDP+w7KEK1Aapc6l65gJwndMGVbUaJCrqK7FBTBFb4gw4EDo/VsbYC6b6py6ScTs9v3iQqOztIXSAw=="
+ "python/iqpilot_private/navassist/_cipher.cpython-312-aarch64-linux-gnu.so": "z/ruMHSCKDVJTNgbP41sdxyIEu/SZSJYnAziOqY93F/SeO9q48/pN27QgCvEd6WusKuPmpmFqfWgkwY9zTqfCw==",
+ "python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so": "n2ZFnpKhJd5hz0XE1j4SjuTHcloy5ypK3Q5XHyhvFAtcqgzgywInzv+mem+saG12zIvX4tqh3s1Tks+uhMy/CA==",
+ "python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so": "XMYEFgSlBmBJtNkBRKJ4BsoIHpnGeRRjjsPIAHd/+8rcwrhlx9waFZ3LSBy44PfA28HlR/c3WWtIr9fkzfGNCw==",
+ "python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so": "5owuYV1oSSdF4PNIWHmcWAzdg84TY8jXuxqNYAlROYGnA9VUS7g3dSot8x5vanjYUg2ZmKsjl1/AZAWpbZMjDA==",
+ "python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so": "YEM143l5lVV6gQQaM2n9r8FN2ex6ycgXrcrugtmh1hvfZTYcZcWqN8zUID0+czrSmW1w61RMlgbRVRKv8OfFAA==",
+ "python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so": "g6nnT1AgmvvZ7FrJ8jq0mpORaGwE4SursrT5D26I/C/vgIen1iXg4REKNAlAoZ/4T6O6eeLV7bqbAMKMl3X2AA==",
+ "python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so": "64eeb2HJCNvxBD3Ii2BtDKX2TuUPWUjcXxNq3Yy7zJri6KseSINCKxUupVCiBNn1aN5LZgDJ5tSHWsKm8PGGAA=="
}
}
diff --git a/artifacts/iqpilot_navassist_private/manifest.json.sig b/artifacts/iqpilot_navassist_private/manifest.json.sig
index 8909e544..e956c41f 100644
--- a/artifacts/iqpilot_navassist_private/manifest.json.sig
+++ b/artifacts/iqpilot_navassist_private/manifest.json.sig
@@ -1 +1 @@
-tcQKs1d9JKU5JaobF9ma1hEtVlT+lXR5cnE9oa8aaSpd6/E3WLjwB4K7BIdDcPbZdiGUuSOyYPrj3KuSkeX4AA==
+COO530ld0nPfZgPLWyKO8N2OWHm9TfpmtUz5xtAO+oKFPvJqv5a7peQ3nTJXGGUrNnhWAsG7KfxbXdEB27ImDA==
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_navassist_private/python/_iqclosure/_vendored.json
index f535c129..fe51488c 100644
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/_vendored.json
+++ b/artifacts/iqpilot_navassist_private/python/_iqclosure/_vendored.json
@@ -1,19 +1 @@
-[
- "iqpilot.common.gpio",
- "iqpilot.common.i2c",
- "iqpilot.common.logging_extra",
- "iqpilot.common.realtime",
- "iqpilot.common.swaglog",
- "iqpilot.common.time_helpers",
- "iqpilot.common.utils",
- "iqpilot.system.hardware",
- "iqpilot.system.hardware.base",
- "iqpilot.system.hardware.hw",
- "iqpilot.system.hardware.pc.hardware",
- "iqpilot.system.hardware.tici",
- "iqpilot.system.hardware.tici.amplifier",
- "iqpilot.system.hardware.tici.hardware",
- "iqpilot.system.hardware.tici.iwlist",
- "iqpilot.system.hardware.tici.lpa",
- "iqpilot.system.hardware.tici.pins"
-]
+[]
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/__init__.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/__init__.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/logging_extra.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/logging_extra.py
deleted file mode 100644
index ceaf083c..00000000
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/logging_extra.py
+++ /dev/null
@@ -1,249 +0,0 @@
-import io
-import os
-import sys
-import copy
-import json
-import time
-import uuid
-import socket
-import logging
-import traceback
-import numpy as np
-from threading import local
-from collections import OrderedDict
-from contextlib import contextmanager
-
-LOG_TIMESTAMPS = "LOG_TIMESTAMPS" in os.environ
-
-def json_handler(obj):
- if isinstance(obj, np.bool_):
- return bool(obj)
- # if isinstance(obj, (datetime.date, datetime.time)):
- # return obj.isoformat()
- return repr(obj)
-
-def json_robust_dumps(obj):
- return json.dumps(obj, default=json_handler)
-
-class NiceOrderedDict(OrderedDict):
- def __str__(self):
- return json_robust_dumps(self)
-
-class SwagFormatter(logging.Formatter):
- def __init__(self, swaglogger):
- logging.Formatter.__init__(self, None, '%a %b %d %H:%M:%S %Z %Y')
-
- self.swaglogger = swaglogger
- self.host = socket.gethostname()
-
- def format_dict(self, record):
- record_dict = NiceOrderedDict()
-
- if isinstance(record.msg, dict):
- record_dict['msg'] = record.msg
- else:
- try:
- record_dict['msg'] = record.getMessage()
- except (ValueError, TypeError):
- record_dict['msg'] = [record.msg]+record.args
-
- record_dict['ctx'] = self.swaglogger.get_ctx()
-
- if record.exc_info:
- record_dict['exc_info'] = self.formatException(record.exc_info)
-
- record_dict['level'] = record.levelname
- record_dict['levelnum'] = record.levelno
- record_dict['name'] = record.name
- record_dict['filename'] = record.filename
- record_dict['lineno'] = record.lineno
- record_dict['pathname'] = record.pathname
- record_dict['module'] = record.module
- record_dict['funcName'] = record.funcName
- record_dict['host'] = self.host
- record_dict['process'] = record.process
- record_dict['thread'] = record.thread
- record_dict['threadName'] = record.threadName
- record_dict['created'] = record.created
-
- return record_dict
-
- def format(self, record):
- if self.swaglogger is None:
- raise Exception("must set swaglogger before calling format()")
- return json_robust_dumps(self.format_dict(record))
-
-class SwagLogFileFormatter(SwagFormatter):
- def fix_kv(self, k, v):
- # append type to names to preserve legacy naming in logs
- # avoids overlapping key namespaces with different types
- # e.g. log.info() creates 'msg' -> 'msg$s'
- # log.event() creates 'msg.health.logMonoTime' -> 'msg.health.logMonoTime$i'
- # because overlapping namespace 'msg' caused problems
- if isinstance(v, (str, bytes)):
- k += "$s"
- elif isinstance(v, float):
- k += "$f"
- elif isinstance(v, bool):
- k += "$b"
- elif isinstance(v, int):
- k += "$i"
- elif isinstance(v, dict):
- nv = {}
- for ik, iv in v.items():
- ik, iv = self.fix_kv(ik, iv)
- nv[ik] = iv
- v = nv
- elif isinstance(v, list):
- k += "$a"
- return k, v
-
- def format(self, record):
- if isinstance(record, str):
- v = json.loads(record)
- else:
- v = self.format_dict(record)
-
- mk, mv = self.fix_kv('msg', v['msg'])
- del v['msg']
- v[mk] = mv
- v['id'] = uuid.uuid4().hex
-
- return json_robust_dumps(v)
-
-class SwagErrorFilter(logging.Filter):
- def filter(self, record):
- return record.levelno < logging.ERROR
-
-def _tmpfunc():
- return 0
-
-def _srcfile():
- return os.path.normcase(_tmpfunc.__code__.co_filename)
-
-class SwagLogger(logging.Logger):
- def __init__(self):
- logging.Logger.__init__(self, "swaglog")
-
- self.global_ctx = {}
-
- self.log_local = local()
- self.log_local.ctx = {}
-
- def local_ctx(self):
- try:
- return self.log_local.ctx
- except AttributeError:
- self.log_local.ctx = {}
- return self.log_local.ctx
-
- def get_ctx(self):
- return dict(self.local_ctx(), **self.global_ctx)
-
- @contextmanager
- def ctx(self, **kwargs):
- old_ctx = self.local_ctx()
- self.log_local.ctx = copy.copy(old_ctx) or {}
- self.log_local.ctx.update(kwargs)
- try:
- yield
- finally:
- self.log_local.ctx = old_ctx
-
- def bind(self, **kwargs):
- self.local_ctx().update(kwargs)
-
- def bind_global(self, **kwargs):
- self.global_ctx.update(kwargs)
-
- def event(self, event, *args, **kwargs):
- evt = NiceOrderedDict()
- evt['event'] = event
- if args:
- evt['args'] = args
- evt.update(kwargs)
- if 'error' in kwargs:
- self.error(evt)
- elif 'debug' in kwargs:
- self.debug(evt)
- else:
- self.info(evt)
-
- def timestamp(self, event_name):
- if LOG_TIMESTAMPS:
- t = time.monotonic()
- tstp = NiceOrderedDict()
- tstp['timestamp'] = NiceOrderedDict()
- tstp['timestamp']["event"] = event_name
- tstp['timestamp']["time"] = t*1e9
- self.debug(tstp)
-
- def findCaller(self, stack_info=False, stacklevel=1):
- """
- Find the stack frame of the caller so that we can note the source
- file name, line number and function name.
- """
- f = sys._getframe(3)
- #On some versions of IronPython, currentframe() returns None if
- #IronPython isn't run with -X:Frames.
- if f is not None:
- f = f.f_back
- orig_f = f
- while f and stacklevel > 1:
- f = f.f_back
- stacklevel -= 1
- if not f:
- f = orig_f
- rv = "(unknown file)", 0, "(unknown function)", None
- while hasattr(f, "f_code"):
- co = f.f_code
- filename = os.path.normcase(co.co_filename)
-
- if filename == _srcfile:
- f = f.f_back
- continue
- sinfo = None
- if stack_info:
- sio = io.StringIO()
- sio.write('Stack (most recent call last):\n')
- traceback.print_stack(f, file=sio)
- sinfo = sio.getvalue()
- if sinfo[-1] == '\n':
- sinfo = sinfo[:-1]
- sio.close()
- rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
- break
- return rv
-
-if __name__ == "__main__":
- log = SwagLogger()
-
- stdout_handler = logging.StreamHandler(sys.stdout)
- stdout_handler.setLevel(logging.INFO)
- stdout_handler.addFilter(SwagErrorFilter())
- log.addHandler(stdout_handler)
-
- stderr_handler = logging.StreamHandler(sys.stderr)
- stderr_handler.setLevel(logging.ERROR)
- log.addHandler(stderr_handler)
-
- log.info("asdasd %s", "a")
- log.info({'wut': 1})
- log.warning("warning")
- log.error("error")
- log.critical("critical")
- log.event("test", x="y")
-
- with log.ctx():
- stdout_handler.setFormatter(SwagFormatter(log))
- stderr_handler.setFormatter(SwagFormatter(log))
- log.bind(user="some user")
- log.info("in req")
- print("")
- log.warning("warning")
- print("")
- log.error("error")
- print("")
- log.critical("critical")
- print("")
- log.event("do_req", a=1, b="c")
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/realtime.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/realtime.py
deleted file mode 100644
index 9fd2f695..00000000
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/realtime.py
+++ /dev/null
@@ -1,134 +0,0 @@
-"""Utilities for reading real time clocks and keeping soft real time constraints."""
-import gc
-import os
-import sys
-import time
-
-from setproctitle import getproctitle
-
-from iqpilot.common.utils import MovingAverage
-from iqpilot.system.hardware import PC
-
-
-# time step for each process
-DT_CTRL = 0.01 # controlsd
-DT_MDL = 0.05 # model
-DT_HW = 0.5 # hardwared and manager
-DT_DMON = 0.05 # driver monitoring
-
-
-class Priority:
- # CORE 2
- # - modeld = 55
- # - camerad = 54
- CTRL_LOW = 51 # plannerd & radard
-
- # CORE 3
- # - pandad = 55
- CTRL_HIGH = 53
-
-
-def set_core_affinity(cores: list[int]) -> None:
- if sys.platform == 'linux' and not PC:
- os.sched_setaffinity(0, cores)
-
-
-def config_realtime_process(cores: int | list[int], priority: int) -> None:
- gc.disable()
- if sys.platform == 'linux' and not PC:
- os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(priority))
- c = cores if isinstance(cores, list) else [cores, ]
- set_core_affinity(c)
-
-
-def config_background_thread() -> None:
- if sys.platform == 'linux' and not PC:
- os.sched_setscheduler(0, os.SCHED_OTHER, os.sched_param(0))
- set_core_affinity(list(range(os.cpu_count() or 1)))
-
-
-def lock_memory() -> None:
- """mlockall this process so memory reclaim/compaction can't stall it. RT control
- procs only (locking ui/modeld would worsen pressure). Best-effort."""
- if sys.platform != 'linux' or PC:
- return
- try:
- import ctypes
- import resource
- resource.setrlimit(resource.RLIMIT_MEMLOCK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
- MCL_CURRENT, MCL_FUTURE = 0x1, 0x2
- libc = ctypes.CDLL("libc.so.6", use_errno=True)
- if libc.mlockall(MCL_CURRENT | MCL_FUTURE) != 0:
- raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
- except Exception as e:
- try:
- from iqpilot.common.swaglog import cloudlog
- cloudlog.warning(f"lock_memory (mlockall) failed: {e}")
- except Exception:
- pass
-
-
-class Ratekeeper:
- def __init__(self, rate: float, print_delay_threshold: float | None = 0.0) -> None:
- """Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative."""
- self._interval = 1. / rate
- self._print_delay_threshold = print_delay_threshold
- self._frame = 0
- self._remaining = 0.0
- self._process_name = getproctitle()
- self._last_monitor_time = -1.
- self._next_frame_time = -1.
-
- self.avg_dt = MovingAverage(100)
- self.avg_dt.add_value(self._interval)
-
- def reset(self) -> None:
- self._remaining = 0.0
- self._last_monitor_time = -1.
- self._next_frame_time = -1.
- self.avg_dt = MovingAverage(100)
- self.avg_dt.add_value(self._interval)
-
- @property
- def frame(self) -> int:
- return self._frame
-
- @property
- def remaining(self) -> float:
- return self._remaining
-
- @property
- def lag(self) -> float:
- return max(0., -self._remaining)
-
- @property
- def lagging(self) -> bool:
- expected_dt = self._interval * (1 / 0.9)
- return self.avg_dt.get_average() > expected_dt
-
- # Maintain loop rate by calling this at the end of each loop
- def keep_time(self) -> bool:
- lagged = self.monitor_time()
- if self._remaining > 0:
- time.sleep(self._remaining)
- return lagged
-
- # Monitors the cumulative lag, but does not enforce a rate
- def monitor_time(self) -> bool:
- if self._last_monitor_time < 0:
- self._next_frame_time = time.monotonic() + self._interval
- self._last_monitor_time = time.monotonic()
-
- prev = self._last_monitor_time
- self._last_monitor_time = time.monotonic()
- self.avg_dt.add_value(self._last_monitor_time - prev)
-
- lagged = False
- remaining = self._next_frame_time - time.monotonic()
- self._next_frame_time += self._interval
- if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold:
- print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms")
- lagged = True
- self._frame += 1
- self._remaining = remaining
- return lagged
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/swaglog.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/swaglog.py
deleted file mode 100644
index f8fd9d39..00000000
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/common/swaglog.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import logging
-import os
-import sys
-import time
-import warnings
-from pathlib import Path
-from logging.handlers import BaseRotatingHandler
-
-import zmq
-
-from iqpilot.common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter
-from iqpilot.system.hardware.hw import Paths
-
-
-def get_file_handler():
- Path(Paths.swaglog_root()).mkdir(parents=True, exist_ok=True)
- base_filename = os.path.join(Paths.swaglog_root(), "swaglog")
- handler = SwaglogRotatingFileHandler(base_filename)
- return handler
-
-class SwaglogRotatingFileHandler(BaseRotatingHandler):
- def __init__(self, base_filename, interval=60, max_bytes=1024*256, backup_count=2500, encoding=None):
- super().__init__(base_filename, mode="a", encoding=encoding, delay=True)
- self.base_filename = base_filename
- self.interval = interval # seconds
- self.max_bytes = max_bytes
- self.backup_count = backup_count
- self.log_files = self.get_existing_logfiles()
- log_indexes = [f.split(".")[-1] for f in self.log_files]
- self.last_file_idx = max([int(i) for i in log_indexes if i.isdigit()] or [-1])
- self.last_rollover = None
- self.doRollover()
-
- def _open(self):
- self.last_rollover = time.monotonic()
- self.last_file_idx += 1
- next_filename = f"{self.base_filename}.{self.last_file_idx:010}"
- stream = open(next_filename, self.mode, encoding=self.encoding)
- self.log_files.insert(0, next_filename)
- return stream
-
- def get_existing_logfiles(self):
- log_files = list()
- base_dir = os.path.dirname(self.base_filename)
- for fn in os.listdir(base_dir):
- fp = os.path.join(base_dir, fn)
- if fp.startswith(self.base_filename) and os.path.isfile(fp):
- log_files.append(fp)
- return sorted(log_files)
-
- def shouldRollover(self, record):
- size_exceeded = self.max_bytes > 0 and self.stream.tell() >= self.max_bytes
- time_exceeded = self.interval > 0 and self.last_rollover + self.interval <= time.monotonic()
- return size_exceeded or time_exceeded
-
- def doRollover(self):
- if self.stream:
- self.stream.close()
- self.stream = self._open()
-
- if self.backup_count > 0:
- while len(self.log_files) > self.backup_count:
- to_delete = self.log_files.pop()
- if os.path.exists(to_delete): # just being safe, should always exist
- os.remove(to_delete)
-
-class UnixDomainSocketHandler(logging.Handler):
- def __init__(self, formatter):
- logging.Handler.__init__(self)
- self.setFormatter(formatter)
- self.pid = None
-
- self.zctx = None
- self.sock = None
-
- def __del__(self):
- self.close()
-
- def close(self):
- if self.sock is not None:
- self.sock.close()
- if self.zctx is not None:
- self.zctx.term()
-
- def connect(self):
- self.zctx = zmq.Context()
- self.sock = self.zctx.socket(zmq.PUSH)
- self.sock.setsockopt(zmq.LINGER, 10)
- self.sock.connect(Paths.swaglog_ipc())
- self.pid = os.getpid()
-
- def emit(self, record):
- if os.getpid() != self.pid:
- # TODO suppresses warning about forking proc with zmq socket, fix root cause
- warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*")
- self.connect()
-
- msg = self.format(record).rstrip('\n')
- # print("SEND".format(repr(msg)))
- try:
- s = chr(record.levelno)+msg
- self.sock.send(s.encode('utf8'), zmq.NOBLOCK)
- except zmq.error.Again:
- # drop :/
- pass
-
-
-class ForwardingHandler(logging.Handler):
- def __init__(self, target_logger):
- super().__init__()
- self.target_logger = target_logger
-
- def emit(self, record):
- self.target_logger.handle(record)
-
-
-def add_file_handler(log):
- """
- Function to add the file log handler to swaglog.
- This can be used to store logs when logmessaged is not running.
- """
- handler = get_file_handler()
- handler.setFormatter(SwagLogFileFormatter(log))
- log.addHandler(handler)
-
-
-cloudlog = log = SwagLogger()
-log.setLevel(logging.DEBUG)
-
-
-class PrettyConsoleFormatter(logging.Formatter):
- # StreamHandler writes to stderr, so tty-gate on that
- _COLOR = sys.stderr.isatty() and os.environ.get('NO_COLOR') is None
-
- def format(self, record):
- msg = record.getMessage()
- if not self._COLOR:
- return f"{record.filename}: {msg}"
- lvl = record.levelno
- if lvl >= 50: lc, ln = "\033[1;38;5;196m", "CRIT"
- elif lvl >= 40: lc, ln = "\033[1;38;5;203m", " ERR"
- elif lvl >= 30: lc, ln = "\033[38;5;214m", "WARN"
- elif lvl >= 20: lc, ln = "\033[38;5;110m", "info"
- else: lc, ln = "\033[38;5;244m", " dbg"
- body = f"\033[1;38;5;210m{msg}\033[0m" if lvl >= 40 else msg
- src = "" if record.filename == "(unknown file)" else f"\033[2m{record.filename}\033[0m "
- return f"{lc}{ln:>4}\033[0m {src}{body}"
-
-
-outhandler = logging.StreamHandler()
-outhandler.setFormatter(PrettyConsoleFormatter())
-
-print_level = os.environ.get('LOGPRINT', 'warning')
-if print_level == 'debug':
- outhandler.setLevel(logging.DEBUG)
-elif print_level == 'info':
- outhandler.setLevel(logging.INFO)
-elif print_level == 'warning':
- outhandler.setLevel(logging.WARNING)
-
-ipchandler = UnixDomainSocketHandler(SwagFormatter(log))
-
-log.addHandler(outhandler)
-# logs are sent through IPC before writing to disk to prevent disk I/O blocking
-log.addHandler(ipchandler)
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/__init__.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/hw.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/hw.py
deleted file mode 100644
index 42409a97..00000000
--- a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/hw.py
+++ /dev/null
@@ -1,126 +0,0 @@
-import os
-import platform
-from pathlib import Path
-
-from iqpilot.system.hardware import PC
-
-DEFAULT_DOWNLOAD_CACHE_ROOT = "/tmp/comma_download_cache"
-
-class Paths:
- _persist_root_cache: str | None = None
-
- @staticmethod
- def _is_writable_persist_root(path: str) -> bool:
- try:
- os.makedirs(path, exist_ok=True)
- comma_dir = os.path.join(path, "comma")
- os.makedirs(comma_dir, exist_ok=True)
-
- probe_path = os.path.join(comma_dir, ".rw_probe")
- with open(probe_path, "w") as f:
- f.write("1")
- os.remove(probe_path)
- return True
- except OSError:
- return False
-
- @staticmethod
- def comma_home() -> str:
- return os.path.join(str(Path.home()), ".comma" + os.environ.get("OPENPILOT_PREFIX", ""))
-
- @staticmethod
- def params() -> str:
- if os.environ.get("PARAMS_ROOT"):
- return os.environ["PARAMS_ROOT"]
- return os.path.join(Paths.comma_home(), "params") if PC else "/data/params"
-
- @staticmethod
- def log_root() -> str:
- if os.environ.get('LOG_ROOT', False):
- return os.environ['LOG_ROOT']
- elif PC:
- return str(Path(Paths.comma_home()) / "media" / "0" / "realdata")
- else:
- return '/data/media/0/realdata/'
-
- @staticmethod
- def swaglog_root() -> str:
- if PC:
- return os.path.join(Paths.comma_home(), "log")
- else:
- return "/data/log/"
-
- @staticmethod
- def swaglog_ipc() -> str:
- return "ipc:///tmp/logmessage" + os.environ.get("OPENPILOT_PREFIX", "")
-
- @staticmethod
- def download_cache_root() -> str:
- if os.environ.get('COMMA_CACHE', False):
- return os.environ['COMMA_CACHE'] + "/"
- return DEFAULT_DOWNLOAD_CACHE_ROOT + os.environ.get("OPENPILOT_PREFIX", "") + "/"
-
- @staticmethod
- def persist_root() -> str:
- if PC:
- return os.path.join(Paths.comma_home(), "persist")
-
- if Paths._persist_root_cache is not None:
- return Paths._persist_root_cache
-
- for candidate in ("/persist", "/data/persist"):
- if Paths._is_writable_persist_root(candidate):
- Paths._persist_root_cache = candidate
- return candidate
-
- # Keep previous behavior as a last resort.
- Paths._persist_root_cache = "/persist"
- return Paths._persist_root_cache
-
- @staticmethod
- def stats_root() -> str:
- if PC:
- return str(Path(Paths.comma_home()) / "stats")
- else:
- return "/data/stats/"
-
- @staticmethod
- def config_root() -> str:
- if PC:
- return Paths.comma_home()
- else:
- return "/tmp/.comma"
-
- @staticmethod
- def shm_path() -> str:
- if PC and platform.system() == "Darwin":
- return "/tmp" # This is not really shared memory on macOS, but it's the closest we can get
- return "/dev/shm"
-
- @staticmethod
- def model_root() -> str:
- if PC:
- return str(Path(Paths.comma_home()) / "media" / "0" / "models")
- else:
- return "/data/media/0/models"
-
- @staticmethod
- def crash_log_root() -> str:
- if PC:
- return str(Path(Paths.comma_home()) / "community" / "crashes")
- else:
- return "/data/community/crashes"
-
- @staticmethod
- def mapd_root() -> str:
- if PC:
- return str(Path(Paths.comma_home()) / "media" / "0" / "osm")
- else:
- return "/data/media/0/osm"
-
- @staticmethod
- def screen_recordings_root() -> str:
- if PC:
- return str(Path(Paths.comma_home()) / "media" / "0" / "screen_recordings")
- else:
- return "/data/media/0/screen_recordings"
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/pc/__init__.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/pc/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/__init__.py b/artifacts/iqpilot_navassist_private/python/_iqclosure/iqpilot/system/hardware/tici/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index 051c26fc..00000000
Binary files a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/_cipher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/_cipher.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..1d30756b
Binary files /dev/null and b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/_cipher.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 4c39c93d..972af2f4
Binary files a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e5991bea..34f16ae1
Binary files a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 9bd8c6a3..d72636b0
Binary files a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 63896dc2..5a8dfb1c
Binary files a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index ecf1679d..630bce6f
Binary files a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 8dd31365..bbfa5dce
Binary files a/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navassist_private/python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/manifest.json b/artifacts/iqpilot_navd_private/manifest.json
index 6e29720e..c4d1e880 100644
--- a/artifacts/iqpilot_navd_private/manifest.json
+++ b/artifacts/iqpilot_navd_private/manifest.json
@@ -1,8 +1,9 @@
{
+ "bundle_version": 1792551877,
"python/_iqclosure/_vendored.json": {
"mode": 420,
- "sha256": "f66c056f33c5e8bd3c5add8e8ec397f3015278e2bb254c0c8e7a7645eb642d9e",
- "size": 955
+ "sha256": "47b758362d75bf5ad7db835875d9d49d9cbac1769f767123065189fa57c41cf8",
+ "size": 1128
},
"python/_iqclosure/iqpilot/__init__.py": {
"mode": 420,
@@ -51,8 +52,8 @@
},
"python/_iqclosure/iqpilot/common/realtime.py": {
"mode": 420,
- "sha256": "016be524079ef32df03b2c135eab8f83da60a46fc7f143620b836c65f33e39a3",
- "size": 4423
+ "sha256": "0190cf1f239f8a2bda573524a72a4b6a74e7ed70aa4c1e5f72d1f1159d59cd60",
+ "size": 4926
},
"python/_iqclosure/iqpilot/common/slc_utilities.py": {
"mode": 420,
@@ -81,8 +82,8 @@
},
"python/_iqclosure/iqpilot/common/utils.py": {
"mode": 420,
- "sha256": "103fa6c24f8b963576c482a34d7ecb020168fd3e85156493ae4432ee3b7b2c06",
- "size": 5159
+ "sha256": "c39b89a1ab34511dfa5f9efac783fde0e6d616963a416cf472bd1f604b57f84d",
+ "size": 5892
},
"python/_iqclosure/iqpilot/iq_maps/__init__.py": {
"mode": 420,
@@ -109,6 +110,36 @@
"sha256": "1ae07960497ecb76e79c56978d3d2df0d0b11f71dcdb2e7dd10fc446514ef95d",
"size": 1242
},
+ "python/_iqclosure/iqpilot/selfdrive/controls/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/selfdrive/controls/lib/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/__init__.py": {
+ "mode": 420,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_change.py": {
+ "mode": 420,
+ "sha256": "0e4b4c54856d651d89e1c8896ebf8bb4f0a2efc0b8788a585094e06ecd6616d7",
+ "size": 8461
+ },
+ "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py": {
+ "mode": 420,
+ "sha256": "6dd380acb469c52b16516c245f0409fc0288d187fb71c9320cdf68d46edab619",
+ "size": 4343
+ },
+ "python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lateral_edge_guard.py": {
+ "mode": 420,
+ "sha256": "6bb6bcb8e5323480bf1b992549279a7d4c17a1bd1bd260863cf7b61b887fa735",
+ "size": 10227
+ },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/__init__.py": {
"mode": 420,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
@@ -156,13 +187,13 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py": {
"mode": 420,
- "sha256": "98f46f8705259b00efae7274186b4e68201aad467a19de4f16d5f850d10ab0b5",
- "size": 7585
+ "sha256": "38e521356c56ac85f35543b9457d36639e55ed2ac916f30ea3b86948e806541f",
+ "size": 8016
},
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
"mode": 420,
- "sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db",
- "size": 25601
+ "sha256": "da5ecb280f7af14169903260dc941e3bfac033c93b184cb0abea0f1494c34582",
+ "size": 25731
},
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
"mode": 420,
@@ -186,153 +217,158 @@
},
"python/iqpilot_private/navd/.source_sha": {
"mode": 420,
- "sha256": "835566c5efad9c2b4206c6969a534e10bceb77609737f687a3a96f88a85724d4",
+ "sha256": "cc19b30f292809d81a61e0141ea5f3c2529c577cc00912e21ad685696e7c1cec",
"size": 41
},
- "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "cbccf68e4dbfda59ba84584a470c31c18e0822fb218b5946764526f441f88a82",
- "size": 67640
- },
"python/iqpilot_private/navd/__init__.py": {
"mode": 420,
"sha256": "3faeaa3363b2f50fa135ad4c1f2466853d684d562120425f77a58039837fd8fb",
"size": 69
},
+ "python/iqpilot_private/navd/action_engine.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "f6e3fb2d21f01109565d5c60bfdc9ca2dd0039a2d50a133c8ab7b1e2bf84a929",
+ "size": 1130856
+ },
"python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d22bfd9057cff7ca07acc8fec86d34cbfbed7fc05138ee68f05dd064950646b6",
- "size": 269896
+ "mode": 416,
+ "sha256": "44fa3ed9c2cebb5de4c101631ed48eec18d5eab022ec99068d309ba4a83ce887",
+ "size": 1261944
},
"python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "534df527471f2166f331ae895ea3570d86e7c8743f852f15bb5e05f0908ab82d",
- "size": 202104
+ "mode": 416,
+ "sha256": "337962c364a5a14b06fe0c67c8e6472bee0e7c1e89b89c5b59d4c8d6a047c7cf",
+ "size": 1130792
},
"python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "4d17f2ae1a59ad729b6b8e18b570269b67442220e9afd01f883f434628b1e852",
- "size": 136952
+ "mode": 416,
+ "sha256": "85db538d9ed70f76f0baa4ba767e0acc2e109be23518490630ab3df85bec8c8b",
+ "size": 1065192
},
"python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "945779adb944d2a04ee8bd81ffd67915e87bd8229f75dba4a25be36521fb45a4",
- "size": 135896
+ "mode": 416,
+ "sha256": "55a38939124fec29f39eb60397e855aac57793260522c7197d540edeb91aa9a5",
+ "size": 1130728
},
"python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "fe651ff34a3d4d31113aec6304444095832098e02709d1a9ae6fc3a290491ead",
- "size": 135464
+ "mode": 416,
+ "sha256": "c5e48e55287bef7351daed6632b6a6558a877827039fccbb0592e6f2ccd8cbe0",
+ "size": 1065240
},
"python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "3a671a256a65c9931861a79b58d6003ed1491d9d275186b51821f5222c7b0596",
- "size": 135560
+ "mode": 416,
+ "sha256": "e1c1e39349f8a2e7e2448ea7ab5cbe31407cd96fb7c17bb01f80b2be411a0738",
+ "size": 1130792
},
"python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d53b2c8e61ed8783049871cd14f82213d4d17a3d2f2ee32a54ce4700368dd9e3",
- "size": 69704
+ "mode": 416,
+ "sha256": "ada6dca804de71fbb35eb31dfea2b1dfe7b922eb028baec3023cd9bb60442327",
+ "size": 1065208
},
"python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "4723241d05218af09d00f870f075a4b6107b5b0a4ebcb521236ad30ec3197748",
- "size": 68144
+ "mode": 416,
+ "sha256": "014092a1ea036ddff81616d3cc073e304a15711eb4a9db85a202166b3e2ebb96",
+ "size": 1065256
},
"python/iqpilot_private/navd/model_map_raster_v3.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "a8804639cc1e7303a8fb3bd1902df2441ac8f6290b069516413d210ac3666766",
- "size": 335360
+ "mode": 416,
+ "sha256": "8cf1d67439aa68aa73651acbb319f9e17850e757de71149aabe2ff0f30690c66",
+ "size": 1261976
},
"python/iqpilot_private/navd/model_map_tile.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "27000992b85df0915171bd60f0b99bd955ea41df9a341521652d2bf4d19f18fc",
- "size": 268840
+ "mode": 416,
+ "sha256": "71c78d9cf3c2061e62fa3a7857ae6ddaf4a5df8b01e27ba47e38724aaf6c34b7",
+ "size": 1196440
},
"python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "37a465ecb6bd44a663cc270b8a56d2ccff58146bbff0881d9974a6835af469cb",
- "size": 203032
+ "mode": 416,
+ "sha256": "260ed46054104fa512f5984c67ed0bf78f96fcfcd393c9b2d27fd9249a9cb658",
+ "size": 1065336
+ },
+ "python/iqpilot_private/navd/nav_model_input.cpython-312-aarch64-linux-gnu.so": {
+ "mode": 416,
+ "sha256": "b56cbd876dbf6efea8e41d7e4ac71cb493c46ea1f29c857c60b7f356641fc6b6",
+ "size": 2114264
},
"python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "565774a388e7ff053cc37a6938bf54c2b580515a420595fb1074ff555dac9d83",
- "size": 135512
+ "mode": 416,
+ "sha256": "13c0d407851b8ce6ed440e47b185a7bbe644576a47c5f795c4cfd68ac4a790bd",
+ "size": 1130712
},
"python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "0386767a5ac5dfbf7d99aea5cc3ea87e14839c9c4913c7a64acd554331937f02",
- "size": 202288
+ "mode": 416,
+ "sha256": "3fc18b853d11b3a64206b1971c88b3dbc521b6adfdbc2dbaf900dc21bd3be8f4",
+ "size": 1065240
},
"python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d4e1cc13a1fad7c620df105b8e9b789b5aaf2315a14abefeda80449f37a48b81",
- "size": 928696
+ "mode": 416,
+ "sha256": "be61f91e3393d7221c8254fdead520e692a612faa593fc5c2fecccf825010fb3",
+ "size": 1917656
},
"python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "9a4f05ed613b665acf3828699f0b917b8358e1619226c3cd410ebf278fb4d1e3",
- "size": 135232
+ "mode": 416,
+ "sha256": "551ce0787afc94a9f78ce9048dbfa12c07bc7da50fd19f243a40b1abad90c66b",
+ "size": 1065192
},
"python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "864a31c370a9fc3fcc950dfefccc3281ca55fb9eec691c4946009bc5f64c9082",
- "size": 135520
+ "mode": 416,
+ "sha256": "b314184d8bd4e618615e50aa99dd70a492037134fadd5a97366870258547375c",
+ "size": 999912
},
"python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "c86dd543303e7a53428a3d8d94dc99e4c25cfec2aba8dc1d81236066bba5063b",
- "size": 267848
+ "mode": 416,
+ "sha256": "002def8baa4a55884b5bf9256304e1d4bc848270fd61d6d7b7ccfa6a62012321",
+ "size": 1130904
},
"python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "a98b7628ee0080dba75a69d72e4ff2d7be28699f965a16215fde480295bb7bc1",
- "size": 136120
+ "mode": 416,
+ "sha256": "4adbc74a85e2c652ec900be6106bc36b70efb9cd650d73b7480f6b1908c36d4f",
+ "size": 1065224
},
"python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "2501e3ed0ede544726bd1cd8ff8a957d7d3482913976882cd1da13dafdf50375",
- "size": 203096
+ "mode": 416,
+ "sha256": "1b19a6b4184d2a03069647b5e862e09f8efee2bd642240291aafe7e784e668a4",
+ "size": 1065352
},
"python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "1f3c62a505946d73ac3b8aa6596ab774db185047a6792417e33ada76955f7fd5",
- "size": 1211416
+ "mode": 416,
+ "sha256": "3bbba164bdcd92f3a6abeb646405f26d23ee5693393c96be479b723f971246a1",
+ "size": 2114392
},
"python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "af2ca23713905a10763e354b1345adbf50c8ddd48694ce263410248351461b2a",
- "size": 135792
+ "mode": 416,
+ "sha256": "d809748b9a573d8ab3623f94f5dbb1ab7387904337af7de88b6cbf5220e8201f",
+ "size": 1130824
},
"python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "ba9dfae7e7c44b6123a2d0fd4c354570a97f7ae42c2b458adac20c76cf06cf82",
- "size": 69664
+ "mode": 416,
+ "sha256": "fa02a76f7e33007f0091509583bb540242eb8843ab499895eee124374e29870c",
+ "size": 999688
},
"python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "84a5fbbe909c3e72ad6265ea1059eea56b9e36cc0ba7372851a0eb9c348f67d1",
- "size": 135896
+ "mode": 416,
+ "sha256": "a3b65415ad2516c5478bfd3789a259b2584e3016644a86b63f4931407199b992",
+ "size": 1065192
},
"python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "4649ee7042cf35802d015c73e4a94889a66363c1f85d7b21655b1b8ce25f8f13",
- "size": 136912
+ "mode": 416,
+ "sha256": "a989e86db4f1bacf33016be0409b514254c1459d70380155122b581425d9faf5",
+ "size": 1130824
},
"python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "af9519fc7b9d768a6fdd980478feda5fa79765d0dd67433ce5308743d91a4b1e",
- "size": 135464
+ "mode": 416,
+ "sha256": "39e1aa4d9df8b61c3631522dcaee29ce05275ec92994efea7700fda8455ada65",
+ "size": 1130888
},
"python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "ad17fd15c08ce235db8e2ccbbfb99ecc646a5b1a6505180e2de70d75b6c10f61",
- "size": 204048
+ "mode": 416,
+ "sha256": "50479c5b4967705c8149fcfa9474eaa1ee982de256b0fe7d7744782bf6254908",
+ "size": 1196440
},
"python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "6d3ffce1ac886cbface7e3fa5b1e5961fc80d6d7a38e2f4843c13933b0c508db",
- "size": 266880
+ "mode": 416,
+ "sha256": "dc3f401bfc363d4e8bf49d5aed3cd77235f2ddba1239df85a579c7d76552acca",
+ "size": 1196408
},
"runtime": {
"entries": {
@@ -371,33 +407,34 @@
}
},
"signatures": {
- "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "yX9sd4NqftVEIwGAF39dVoM6VnzqbqlwZtRWWjyfrEADTRhYpjtevLk8IoKUJpV37o+9GlU3+PFw4cBeW651AA==",
- "python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": "7l2QNuXu52aKUDdiDmpnVIHi/4SLoikAgypmE5xcojd7ZLtEqMSHpKO73xzHujd2NU8FqhXvXAhBHLbRrCKJCw==",
- "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "4QxYI2YF6IaWbDAimEQNSfU7NX/0cmZKWy2FJcB+yZ/lPPefRXhPAZ6wtAUGp4nKyRIzn9OZXBtdFDTlgMyTBQ==",
- "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "qxPjAup8c18smc/fHOKf//aXjKwvNbHblXzZM58Ltq/cHOcU0b0/lzCgnB+PvhwTC63Ldz0Se2l19BP0HLd+BQ==",
- "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "P7VLBQnUntVhX5od5L547jVbRXLiuL6u2Q+qjh73Q0dEkBdRGe7cBgxsB46ffJqPj9p6xJrCn5bDiDYqHsaeAA==",
- "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "EKExNNbK33T19cWYXEZKrs2PGj+YBv7lso5B8pWxFca3P42iAzinxeOctQS0TjSjhNQWsb+rYtJLCblyko0kBQ==",
- "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "GPnWEDRYUblywR3FcrCnyTOkaeWAL/ftUDiHKjetIFYlkrKDSyognQVfeNzZ6HKfIFnpOTqjuR4zkiRoycEYBQ==",
- "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "qvynUnnwxo/1/5TnboG6voiSkwZ00YUIidcckTm8beR7VmE1Eg3RW00ntqbW7MC91Czs9JQR1ayewVSbGC7KDQ==",
- "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "aKptFZ+DALBXiYSrO9EikwY62WT9seEMfmrUAo9sdR1CoSdGScGhykYpYlUhFDb0zf3+WlI5KcwVbV32SwmkBw==",
- "python/iqpilot_private/navd/model_map_raster_v3.cpython-312-aarch64-linux-gnu.so": "DjSk2azxFA7ZAa2O1+g3WzDELbOQZ+0MlxqsSUx6DyqXTVf7CBeoOF/B/LrY9Sql5XeXIz3EFxxLsbVaVCdXBQ==",
- "python/iqpilot_private/navd/model_map_tile.cpython-312-aarch64-linux-gnu.so": "MiQzkKQfwOevWoXY+3SiyZg7Dej9bM14n8kBpGnJxwz1NPDLTKnZJm/WZZRz4u32CAR5Hn4Epsg+Jc2yYKCgCg==",
- "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "AfjKysW4o67PwDzBsJr53ASxZTyjmzFsaydy32f9kn0ZC5vxb1wPZTXP2aDfl3nexAx29+nuPK1zHW1JxknhBA==",
- "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "nSmQGWc+aKKbPr2ZW5lhyxV0P4H+lrdWJ5D5nKxTX2uStaDHbz4w4jddNxUuCU2fyuRWeNstXPagCMHQLUteAg==",
- "python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so": "DJvMIUqtDXa1a7HVlJS+7yYwtwI46cRMPneFdXkIDR2LfPEgLmi1VysUeRS5Da5Ym1RxmipsrVktEz/iKYceDg==",
- "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "f9HGBUc7TxkOEDwSYPyDoGrKbNRuTtrZLStq6VTRsXok0Xg1HXc2fkCMoqYBHpVRWTallQ3mC58AfRUQxia4Dg==",
- "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "pw4tv47FUqcGiNSSxBgEyyWn6FCWNUefUL0BJa9Zji8iuQx72VPIlrg967p1jWbAGleNzBPfP7M2fefzwyLiDg==",
- "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "1+eSagkgdKZcQX3LPgwfsikdVn0NQOzR6OqsTMvAAODavqHoeeGwOvYxZJrF/WwkaLna8PzAOfIDg1j++yGlCQ==",
- "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "RhkyJ0iqvSaj9B0MwiEFIa3VgvZbF/F3uJ/8kucXymwoC0+ceszFmqAQRK47ZZal3De4F7xY2eDHYI7pzUTbCw==",
- "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "13dDmGrDfxtSkWRCTyNigelgQhthLHJ0VoISNCUY6OcLua84xXxA6tNyK4Hi2ltu3diPyvvkw4n5JXM9x6kLCg==",
- "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "+zYcT09rCxpOuU06PfyVQu3A0Vq3LXDYX7TjrJSfhx3oWJ/AroilWe8cPYe35+81r4RR8Ck/hG5KJnglS5G8AA==",
- "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "J3LzxN/QzuaYF0Cj8ZJmHnAA2CPsrumbZddyqumJGLIyfA2/69RmMzwtTkHMk+Lfly655rZPK9qMrfyv0xt+Bg==",
- "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "0J8G7awbYfdOP4reUCU2gzWwV3hn+xL7Bl9VWmHQM73QjOG6RDpZtLIbswqNHSG4BT3UPfLuEj4pmTN7pJlKBQ==",
- "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "PhHUekKd65AbwvgJsPjIbeJ4LuK5UoK+og+spL+8Z7V5bSuyDD93riSSTJT+FhF+wOxZMJynX3+wwBynlTu9DQ==",
- "python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so": "13j7/ucICrQFB8qJbhrI1kV2aglF9qutVK/pZrosV9rgVYfO4iAnIJ4LVd8lQ/XK7jqL8k6TYsC2/NB1ED2JBw==",
- "python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so": "bp9ef/Pnp+QijE9kp+Af8px1szN8XVkbn5oXJ4GoO2pI9+ADvD+vUOblEx8R9k1F0t9RtJ6REbhCfBIfX1uwAA==",
- "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "u2RdJ8NBPrJdt3lT/a235y3RyYP2VlmeQbPpfep6/pkoiOlgGUz/WMW+c9XQdxjirZzJmzkH8QM+RO/HltUFCQ==",
- "python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so": "FHv6YD7eNg3jn9FU2bkWJ3VYdGXT8UWNnHgouIDinnsr9frKHvbRN5n5drB0pqwSzAiJGhTcB9vlMlWDzewNDw==",
- "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "pX7b/E+cHfdPVuYCBB8kG8DVW1t+Wz/CPf8r+nxg5BX0PWZyvxk66Eu+oGpdzDAQf3a6hjBoh5fg4qdbDMm9DA=="
+ "python/iqpilot_private/navd/action_engine.cpython-312-aarch64-linux-gnu.so": "F25IiQTQJ+1SY2eGZJ5VBId72iU68SJtUpwiV1jangm7hw2vE9t7bQe0YCvok8G0xhVW8vOFDstIooN5Dat9DQ==",
+ "python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": "bZE0Y0HJ5XNADty9I4Vty5eE9NOP6YQHvG7+PPrvnOKxMRqAiUV6QJuFFwUbR1oKNwWXb5hpCFXq9WP3UgViBQ==",
+ "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "u5yZ3JamaJ3VtmzAjAuzjuFRocb37Jje9ev9JlEmEqN0qb5Jtj1FHGp60/MIKQMUsM9ZuOWWHu92FbEcv55YAQ==",
+ "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "a4ATJSFgAxYIT3wU1rT1aMShpOvYtleb4cl6jtjoo6snZsk9DntHPh5WWEHYv4xVuxLkuyS1ZCucTmvb7K/VAg==",
+ "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "TPo/qs1I8m1nzCaJIJJBFWBaXjDohg7wwiKmkQuc9hUVLa0DR/5ZV7amH0kr9zsp94oIFSnw4IU8Mdvd5DvcDw==",
+ "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "h5rbPaJEyyRVxcK1sQc8VnDLSY5JW4Hbc3USHMlOGbhdF+pSzSDcxfAtDvLNpmkX2uFJ9FMH9+fK9I9Yv3HoCQ==",
+ "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "FBWkcmDky7gY5I1ryD+HU4YU2k8kPNyWMxT+N0aP0lYCNIqP5eiNDZxuUHgW6wwOSI7RPvGtxvYD+JKZg+o2Cw==",
+ "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "7p1ughocaGojOFcvk2Ha+rw6UQZQdi+krlAisXUJTqZN8kiTv9zLuAvk9aHwUBoXm8jkjupZAwlm/UsdONxXBQ==",
+ "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "MJ61UvRbZegK1KTZkTzsmRRqVjuf+T87t/S9Cp9kO1zNVylimLXkgROHwhbRZT1oqVTb7Nk55D178Bqkv9UdDw==",
+ "python/iqpilot_private/navd/model_map_raster_v3.cpython-312-aarch64-linux-gnu.so": "8NDLi9SR9qK0BEgSq70qanx4Kmx18bxl7U4A7tUOx3fcSxCn9Etxu0WjCW3GdeM2bo3Q8+fhgcGJa39etVOQDw==",
+ "python/iqpilot_private/navd/model_map_tile.cpython-312-aarch64-linux-gnu.so": "F9U6kNARHQOQqvBHlg4ZRfL0Mq/x4kfecYZvKzuCR6+8eALW+01d53f4h0jxcm28GtH7nYR70P8QSFTz8IWMAg==",
+ "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "zuUaBeiys3yYItmHQFlvU+rF3PzutPaPgICCBDFhYQLo6BBLkbDAFCUrovIgWKB0PXUR7IjrNQT/8fOYpwTzAg==",
+ "python/iqpilot_private/navd/nav_model_input.cpython-312-aarch64-linux-gnu.so": "vttzuUiF4Y0gx51NOriCHBVyQUaPw04CVhswPVB0SNcM4nC97fW+ndFlG7+JxydIRm2Q3MOrLKjIBnSvcLqGAA==",
+ "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "Qd6LBQrIYM0vgUaT942pMobqZVW128Wt4VCpEvWUkv/u+DusG5bYQ/avShBKFQZt0nPvtDdPAn2QPtnoyx69Dg==",
+ "python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so": "xERABVRg2pZeXx3LT3bVnBMhVhdnrdicnpVsgHs3mtGfjcCWFld1WJ3IelRlhHbwkZQczpvM6JaeguaKEGlVAQ==",
+ "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "Ktnh+A4Z79a21si4cRSDK34KvrWzmOtw9D3UBV0nScfc8l74oiIltHYE2AID1vLWRb6V6G7NASN6hh9AmgirDA==",
+ "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "G+ZThPhlEb12Tyr0iV8wn66SLHXG9zOjqv+cuuFEzrPQDBjo7mYZOlVlYJYHxSqG6hI893b0DlhOLLFt+CN5AA==",
+ "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "e1wxGgHryVbHl4eHCEyuxbCrMD79yPo5BL44f/WJqi2SuStJuM6SCdww3spPGACtjJdgPah1A+6jy8SIpu/cBg==",
+ "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "8LXmLX7Ut3OwB9xEspgneyqI3H0TuIxlARYmgyll9lT+HQ/O6nDBi+/sAn9DR9uIQjefe5BXWEopfLbk0oZSBA==",
+ "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "tO1fXQJX7MiXmtkzxy9sIiX1vvYQkxmUSdsaU7UmHG+p43U70h1IDo0bbZEL1eqOy2pA+YC3Eo8XKuRipmQfCg==",
+ "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "FfHZRP5sG1ujkbwNO/B7gzIKYLHzc7SUP0gYocahsEketuU2hoBMoUSInOY0rYqgSth/w9NyLKxo61ITYQagCA==",
+ "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "fTHOaZdAPhvE0KDFLphOsYKpwY+FmeGX3B1yNT/X+v5RAZEk4jLALmZQyW1N/C0YF0wXNFSnfRfdVLD6mK8YDA==",
+ "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "wbveJfZ1Ils6rOwJj6QL2AVWrGh3mgq8nAG9l+S3fXY25b4auler6B3YvMgaEiH+1f1zTFEGqg1IuzMyX/bQAA==",
+ "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "5ZA6DOEtJLDtdIiltra49wsj1QyHDigPsgBHe1dHlseIk6hMy2pKP7ExbG7xmKmqoix3H5Z5F0G/fX+i4kFvDg==",
+ "python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so": "BEyGPH6vF9MMMGTxOCkNEaXcFRrqvM1iiXiUdcK9pZZrIqx1PQivC3eCNnITsscKqOpdvLiYtiL4XCZJIJL0Dg==",
+ "python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so": "ELbFb6mMCSwm28q/MnqU9bRZKT9KAnalxL8oj/jCGAGNEmCvTgg+tocj/aSI6sn0i7oiZstmtomUwdeG1zpbAQ==",
+ "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "P5pI2GdvQObiLF9s319H2OvHmAq+cMgs9zdljhdb2HUkBQfK2sMbstgbrJ5omMYjp6ZcDFhydrXFhUsZGbGyCQ==",
+ "python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so": "oNdyumR4l7seqk3PcSzAt2/w9f3fHxTml4TGeiIXBhev98DACg1lnWmqBQPVaa7nW533J6SCDWHPkDNIz9KpCg==",
+ "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "VOldQ5K+jWexchjOLGty4l8goCHgYMRuSbdKs/oDNhtfhYPO9HHmcw1Agjkl1Rn8MiNJRYpaLf2+MopyHDIjCw=="
}
}
diff --git a/artifacts/iqpilot_navd_private/manifest.json.sig b/artifacts/iqpilot_navd_private/manifest.json.sig
index f36a0b5f..168b2295 100644
--- a/artifacts/iqpilot_navd_private/manifest.json.sig
+++ b/artifacts/iqpilot_navd_private/manifest.json.sig
@@ -1 +1 @@
-jF2L6FQ5zgdd+okjdZFb89Lb7SNzdYU7CfXxMotSFMQu4zqJvKQ8Y+fkeqY1tyLLRDn2Je4Thmyoi/qdN7K8Aw==
+AD5fLVMfNodHrJG6Q9EouN2kU8XSDsY2D/XdSA0Z4+6tWeeUwTCCxH9VadyHvlUifg4MmPBDedSdzB7+2YBcDg==
diff --git a/artifacts/iqpilot_navd_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_navd_private/python/_iqclosure/_vendored.json
index 6cef27bc..bfc65682 100644
--- a/artifacts/iqpilot_navd_private/python/_iqclosure/_vendored.json
+++ b/artifacts/iqpilot_navd_private/python/_iqclosure/_vendored.json
@@ -16,6 +16,9 @@
"iqpilot.iq_maps",
"iqpilot.selfdrive.car.cruise",
"iqpilot.selfdrive.car.long_increments",
+ "iqpilot.selfdrive.controls.lib.helpers.lane_change",
+ "iqpilot.selfdrive.controls.lib.helpers.lane_turn",
+ "iqpilot.selfdrive.controls.lib.helpers.lateral_edge_guard",
"iqpilot.selfdrive.iqmodeld.config",
"iqpilot.system",
"iqpilot.system.hardware",
diff --git a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/realtime.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/realtime.py
index 72578964..ca79bbab 100644
--- a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/realtime.py
+++ b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/realtime.py
@@ -62,10 +62,23 @@ def config_background_thread() -> None:
set_core_affinity(list(range(os.cpu_count() or 1)))
+def _swap_active() -> bool:
+ try:
+ with open("/proc/swaps") as f:
+ return len(f.read().splitlines()) > 1
+ except Exception:
+ return False
+
+
def lock_memory() -> None:
- """mlockall this process so memory reclaim/compaction can't stall it. RT control
- procs only (locking ui/modeld would worsen pressure). Best-effort."""
- if sys.platform != 'linux' or PC:
+ """mlockall this process so swap can't evict its pages and stall the RT loop.
+ RT control procs only (locking ui/modeld would worsen pressure). Best-effort.
+
+ No-op without active swap: with none, anonymous pages (thread stacks, heap)
+ are already unreclaimable, so mlockall buys nothing — it only pins every 8 MB
+ thread stack and MCL_FUTURE allocation in full (~400 MB/proc here) and, being
+ non-migratable, starves CMA. The device runs swapless by design (iqos-zram)."""
+ if sys.platform != 'linux' or PC or not _swap_active():
return
try:
import ctypes
diff --git a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/utils.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/utils.py
index a80270bb..005d75b8 100644
--- a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/utils.py
+++ b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/common/utils.py
@@ -31,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/__init__.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/__init__.py
similarity index 100%
rename from artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/__init__.py
rename to artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/__init__.py
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/ui/__init__.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/__init__.py
similarity index 100%
rename from artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/ui/__init__.py
rename to artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/__init__.py
diff --git a/artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/ui/feedback/__init__.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/__init__.py
similarity index 100%
rename from artifacts/iqpilot_hephaestusd_private/python/_iqclosure/iqpilot/selfdrive/ui/feedback/__init__.py
rename to artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/__init__.py
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_change.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_change.py
similarity index 100%
rename from artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_change.py
rename to artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_change.py
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
similarity index 91%
rename from artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
rename to artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
index c2cf85e5..6dfb043d 100644
--- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
+++ b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
@@ -16,7 +16,6 @@ TurnDirection = custom.IQTurnSignalDirection
TURN_TRIGGER_MPS = 20 * CV.MPH_TO_MS
TURN_SPEED_GATE_MPS = TURN_TRIGGER_MPS
LANE_CHANGE_SPEED_MIN = TURN_SPEED_GATE_MPS
-TURN_SPEED_CEILING_MPS = 35 * CV.MPH_TO_MS
@dataclass
@@ -27,13 +26,6 @@ class _TurnGateState:
refresh_tick: int = 0
-def _mph_param_to_mps(raw_value) -> float:
- try:
- return float(raw_value) * CV.MPH_TO_MS
- except (TypeError, ValueError):
- return TURN_TRIGGER_MPS
-
-
def _resolve_signal_choice(speed_mps: float,
speed_limit_mps: float,
left_signal: bool,
@@ -59,9 +51,8 @@ class TurnSignalPlanner:
self.reload_setup()
def _refresh_from_params(self) -> None:
- requested_gate = _mph_param_to_mps(self._params.get("IQLaneTurnValue", return_default=True))
self._state.active = self._params.get_bool("IQLaneTurnDesire")
- self._state.speed_limit_mps = min(TURN_SPEED_CEILING_MPS, requested_gate)
+ self._state.speed_limit_mps = TURN_TRIGGER_MPS
def _consume_legacy_kwargs(self, **legacy) -> tuple[bool, bool, bool, bool, float]:
return (
diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lateral_edge_guard.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lateral_edge_guard.py
similarity index 100%
rename from artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lateral_edge_guard.py
rename to artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lateral_edge_guard.py
diff --git a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
index 48a34ea4..c38e33b9 100644
--- a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
+++ b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/amplifier.py
@@ -125,16 +125,32 @@ class Amplifier:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: list[AmpConfig]) -> bool:
+ import threading
+
tries = 15
backoff = 0.
for i in range(tries):
- try:
- self._set_configs(configs)
- return True
- except OSError:
+ result = [None]
+ error = [None]
+ def _worker():
+ try:
+ self._set_configs(configs)
+ result[0] = True
+ except OSError as e:
+ error[0] = e
+ t = threading.Thread(target=_worker, daemon=True)
+ t.start()
+ t.join(timeout=2)
+ if t.is_alive():
backoff += 0.1
time.sleep(backoff)
- print(f"Failed to set amp config, {tries - i - 1} retries left")
+ print(f"Failed to set amp config (timeout), {tries - i - 1} retries left")
+ continue
+ if result[0]:
+ return True
+ backoff += 0.1
+ time.sleep(backoff)
+ print(f"Failed to set amp config, {tries - i - 1} retries left")
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
diff --git a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
index 2f71ed63..2506fb9e 100644
--- a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
+++ b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py
@@ -469,8 +469,10 @@ class Tici(HardwareBase):
if self.amplifier is not None:
self.amplifier.initialize_configuration(self.get_device_type())
- # Allow hardwared to write engagement status to kmsg
- os.system("sudo chmod a+w /dev/kmsg")
+ try:
+ subprocess.run(["sudo", "-n", "chmod", "a+w", "/dev/kmsg"], timeout=2, capture_output=True, check=False)
+ except Exception:
+ pass
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
@@ -514,10 +516,10 @@ class Tici(HardwareBase):
affine_irq(3, "xhci-hcd:usb1")
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
- subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
- subprocess.call(["sudo", "taskset", "-pc", "3", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessException as e:
- print(str(e))
+ subprocess.run(["sudo", "-n", "chrt", "-f", "-p", "1", pid], timeout=2, capture_output=True, check=False)
+ subprocess.run(["sudo", "-n", "taskset", "-pc", "3", pid], timeout=2, capture_output=True, check=False)
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+ pass
def configure_modem(self):
from iqpilot.common.params import Params
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/.source_sha b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/.source_sha
index e4a64c95..64c809df 100644
--- a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/.source_sha
+++ b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/.source_sha
@@ -1 +1 @@
-e704fff8c426fe807222f8997f5b3b510777eaa0
+22cda719a4b809517acf792ee41d968a605ffb9f
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index 5294c73c..00000000
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/action_engine.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/action_engine.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..4544bc73
Binary files /dev/null and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/action_engine.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 768ec72d..3220d6d8
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 863dab23..57eb3e4b
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 91062cbe..65cc7ae7
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 5f9fd02c..58e42097
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 304bb8a0..55df8881
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e4643213..627e6dc7
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 1b77c6f7..b3f7e315
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 83692ea1..5e3aeabd
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_raster_v3.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_raster_v3.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 9a68fc5d..48caa850
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_raster_v3.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_raster_v3.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_tile.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_tile.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index ad0610a1..535cfd66
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_tile.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/model_map_tile.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index c3b7d88a..81f2e423
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_model_input.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_model_input.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..54831579
Binary files /dev/null and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_model_input.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 6c787101..af71aa80
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 5e20f984..0c029f54
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 3cc9ce65..1d298ea8
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index f258f541..89372d4d
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 1be26efd..7e846e70
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index f387dbd0..18dbbabc
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index cb3d10a0..1df0d416
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 2fa70060..03f2f1d9
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index aa681d5a..3b281b6b
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 126e28cd..08567ebf
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 06038f91..cacbfad0
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 026dc50a..271a8af0
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 42795a37..b0169501
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 65018ac3..6a2aba0d
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 70e3b147..4b96a203
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index e7b0e7af..5d449693
Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/iqpilot_updater_private/manifest.json b/artifacts/iqpilot_updater_private/manifest.json
index d484b464..e0af9ceb 100644
--- a/artifacts/iqpilot_updater_private/manifest.json
+++ b/artifacts/iqpilot_updater_private/manifest.json
@@ -1,4 +1,5 @@
{
+ "bundle_version": 1792366342,
"python/_iqclosure/_vendored.json": {
"mode": 420,
"sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
@@ -14,25 +15,10 @@
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
- "python/iqpilot_private/updater/_hf_maps_blob.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "69b858142aeb82cf730f042f6bc354dabb19579654872eaed3f73d482a856802",
- "size": 67616
- },
- "python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "d8d9ccc3059b6239f2be41437e97b160b96278e419d84f9c0c243a2ba3ebf94c",
- "size": 67616
- },
- "python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "cea69f651768732bcbbd1a7edff668876dcf6787c332e859f6f01c945d74cbe0",
- "size": 67616
- },
"python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so": {
- "mode": 493,
- "sha256": "384782451d53a5ca89809f05bf5c68577596dfbaca3f2f82bb0a972663897491",
- "size": 136608
+ "mode": 416,
+ "sha256": "4747080855bf483d044614d3676a2241d2decff1f285aa4592c48d8156ce8ed5",
+ "size": 1065176
},
"runtime": {
"entries": {
@@ -43,9 +29,6 @@
}
},
"signatures": {
- "python/iqpilot_private/updater/_hf_maps_blob.cpython-312-aarch64-linux-gnu.so": "2hlUdv1cuGpzNrKoSx3VqRGdaVsgkkGhiHKBirfYkEwJmQJizQ966d6iZQoIzh8LEmRdhi+77iLFXQiEWApSDQ==",
- "python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so": "EHoKO6/esRwuP7oxR9a5FXiQVPjh+3CmtXS1jNQor7yOpra/LijNPHvvu423y+vlOn1KkVCbhZgaHrsB/3oJDA==",
- "python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so": "oqWDyKXImMpVEHwBohGW1nL7oZaNMu12aP/zMfowkjDAsHZJOM+TOicUNketIi990nJMbhApcJk3zpoM+1nGCw==",
- "python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so": "xp2MN37LX95Rbt3zGC/ZysyTCv8wUi6xpzbHoTLMwRfq4cDpWafItWgFiypcn5ANsAQA7weqMbihmtq6EbV3Bg=="
+ "python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so": "QVSnJ2ij0v+VkQ557zcAGKhCM9k2k3F+idMIWlzOpgahoMi8w7yKAtmRx/hNUbcZabHDoCHDvYEMd2oM7piMCw=="
}
}
diff --git a/artifacts/iqpilot_updater_private/manifest.json.sig b/artifacts/iqpilot_updater_private/manifest.json.sig
index 3c650df7..9920230f 100644
--- a/artifacts/iqpilot_updater_private/manifest.json.sig
+++ b/artifacts/iqpilot_updater_private/manifest.json.sig
@@ -1 +1 @@
-Q62ipKl3jmGXHvKYVnhQY7sk4ZSS7AmhXf23DY4sYqoTP7hn9WDDlQD82bMhQJdcY+Obcr9fRn1EGmGTuMUjDw==
+geWGPMi5tiN+dzvastdSeQphlF5Yrf7P+YZQlxTuoEGtgE7T58YpG/1Y4Y4NzVOtwXl2hNhCmBTrQUANRB8GBA==
diff --git a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_hf_maps_blob.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_hf_maps_blob.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index aabbbf8e..00000000
Binary files a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_hf_maps_blob.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index 168c4883..00000000
Binary files a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_iqos_pat_blob.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so
deleted file mode 100755
index b4bc7150..00000000
Binary files a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/_pat_blob.cpython-312-aarch64-linux-gnu.so and /dev/null differ
diff --git a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so
old mode 100755
new mode 100644
index 372c9b9e..e37a7811
Binary files a/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_updater_private/python/iqpilot_private/updater/git_remote.cpython-312-aarch64-linux-gnu.so differ
diff --git a/artifacts/package_sources/iqdbc/iqdbc/can/parser.py b/artifacts/package_sources/iqdbc/iqdbc/can/parser.py
index 0db42b18..e1d4e5a3 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/can/parser.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/can/parser.py
@@ -236,7 +236,8 @@ class CANParser:
if src != self.bus:
continue
bus_empty = False
- if self.enable_capture:
+ # frames before controls are ready predate the ECU knockout and safety-mode forwarding change
+ if self.controls_ready and self.enable_capture:
self.seen_addresses.add(address)
state = self.message_states.get(address)
if state is None or len(dat) > 64:
diff --git a/artifacts/package_sources/iqdbc/iqdbc/can/tests/test_packer_parser.py b/artifacts/package_sources/iqdbc/iqdbc/can/tests/test_packer_parser.py
index f6ea3922..fb83c97b 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/can/tests/test_packer_parser.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/can/tests/test_packer_parser.py
@@ -10,10 +10,13 @@ MAX_BAD_COUNTER = 5
class TestCanParserPacker:
def test_seen_addresses(self):
parser = CANParser(TEST_DBC, [], 0)
- parser.update([0, [(0x123, b'\x00', 0), (0x124, b'\x00', 1)]])
+ parser.update([0, [(0x122, b'\x00', 0)]])
+ assert parser.seen_addresses == set()
+ parser.controls_ready = True
+ parser.update([1, [(0x123, b'\x00', 0), (0x124, b'\x00', 1)]])
assert parser.seen_addresses == {0x123}
parser.enable_capture = False
- parser.update([1, [(0x125, b'\x00', 0)]])
+ parser.update([2, [(0x125, b'\x00', 0)]])
assert parser.seen_addresses == {0x123}
def test_packer(self):
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/__init__.py b/artifacts/package_sources/iqdbc/iqdbc/car/__init__.py
index a77c8212..bcffa309 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/__init__.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/__init__.py
@@ -91,6 +91,7 @@ class Bus(StrEnum):
main = auto()
party = auto()
ap_party = auto()
+ ap_pt = auto()
def rate_limit(new_value, last_value, dw_step, up_step):
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/car.capnp b/artifacts/package_sources/iqdbc/iqdbc/car/car.capnp
index 48a71b75..feb54b6c 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/car.capnp
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/car.capnp
@@ -727,6 +727,11 @@ struct CarParams {
fcaGiorgio @32;
rivian @33;
volkswagenMeb @34;
+ byd @35;
+ volvo @36;
+ bmw @37;
+ mg @38;
+ teslaLegacy @39;
}
enum SteerControlType {
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/docs_definitions.py b/artifacts/package_sources/iqdbc/iqdbc/car/docs_definitions.py
index 8950e687..5e3a153b 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/docs_definitions.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/docs_definitions.py
@@ -143,6 +143,10 @@ class CarHarness(EnumBase):
tesla_a = BaseCarHarness("Tesla A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
tesla_b = BaseCarHarness("Tesla B connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
tesla_c = BaseCarHarness("Tesla C connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
+ tesla_model_s_hw1 = BaseCarHarness("Tesla Model S HW1 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
+ tesla_model_x_hw1 = BaseCarHarness("Tesla Model X HW1 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
+ tesla_model_sx_hw2 = BaseCarHarness("Tesla Model S/X HW2 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
+ tesla_model_sx_hw3 = BaseCarHarness("Tesla Model S/X HW3 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
psa_a = BaseCarHarness("PSA A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable])
# custom harness
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carcontroller.py b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carcontroller.py
index 773084f3..77cc9d8b 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carcontroller.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carcontroller.py
@@ -482,7 +482,8 @@ class CarController(CarControllerBase):
# LFA and HDA icons
if self.frame % 5 == 0 and (not hda2 or hda2_long or camera_scc):
- can_sends.extend(hyundaicanfd.create_lfahda_cluster(self.packer, CS, self.CAN, CC.longActive, CC.latActive))
+ can_sends.extend(hyundaicanfd.create_lfahda_cluster(self.packer, CS, self.CAN, CC.longActive, CC.latActive,
+ synthesize=hda2_long and not camera_scc))
if not camera_scc:
can_sends.extend(hyundaicanfd.create_lfa_icon_non_camera_scc(self.packer, CS, self.CAN, CC))
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carstate.py b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carstate.py
index 3a18f887..bf245406 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carstate.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/carstate.py
@@ -217,6 +217,8 @@ class CarState(CarStateBase):
parser._add_message(name, ignore_counter = ignore_counter) # ← 이름으로 등록
def add_and_cache(parser, name: str, attr: str, ignore_counter: bool = False):
+ if parser is None:
+ return False
add_if_seen(parser, name, ignore_counter)
if name in parser.vl: # 등록 성공했을 때만
setattr(self, attr, parser.vl[name])
@@ -227,7 +229,7 @@ class CarState(CarStateBase):
self.cp.controls_ready = self.cp_cam.controls_ready = True
if self.cp_alt is not None:
self.cp_alt.controls_ready = True
- elif self.controls_ready_count == 100:
+ elif self.controls_ready_count == 127:
self.cp.enable_capture = self.cp_cam.enable_capture = False
if self.cp_alt is not None:
self.cp_alt.enable_capture = False
@@ -278,8 +280,11 @@ class CarState(CarStateBase):
add_and_cache(self.cp, self.cruise_btns_msg_canfd, "cruise_buttons_msg")
if not add_and_cache(self.cp_cam, "CAM_0x362", "cam_0x362") and self.cp_alt is not None:
add_and_cache(self.cp_alt, "CAM_0x362", "cam_0x362")
- if not add_and_cache(self.cp_alt, "CAM_0x2a4", "cam_0x2a4", ignore_counter=True) and self.cp_cam is not None:
- add_and_cache(self.cp_cam, "CAM_0x2a4", "cam_0x2a4", ignore_counter=True)
+ # Standard HDA2 A-CAN loses its startup copy when Panda enables safety.
+ camera_first = self.CP.flags & HyundaiFlags.CANFD_HDA2 and not self.CP.flags & HyundaiFlags.CAMERA_SCC
+ primary, fallback = (self.cp_cam, self.cp_alt) if camera_first else (self.cp_alt, self.cp_cam)
+ if not add_and_cache(primary, "CAM_0x2a4", "cam_0x2a4", ignore_counter=True):
+ add_and_cache(fallback, "CAM_0x2a4", "cam_0x2a4", ignore_counter=True)
elif self.controls_ready_count == 125:
add_and_cache(self.cp, "MANUAL_SPEED_LIMIT_ASSIST", "manual_speed_limit_assist", ignore_counter = True)
if self.gear_msg_canfd == "ACCELERATOR":
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/hyundaicanfd.py b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/hyundaicanfd.py
index cd462ede..9f81fca9 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/hyundaicanfd.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/hyundaicanfd.py
@@ -4,7 +4,7 @@ import numpy as np
from iqdbc.car import CanBusBase
from iqdbc.car.carlog import carlog
from iqdbc.car.crc import CRC16_XMODEM
-from iqdbc.car.hyundai.values import HyundaiFlags, HyundaiExtFlags
+from iqdbc.car.hyundai.values import Buttons, HyundaiFlags, HyundaiExtFlags
from iqpilot.common.params import Params
from iqdbc.car.common.conversions import Conversions as CV
from iqpilot.cereal import log
@@ -334,18 +334,15 @@ def create_acc_cancel(packer, CP, CAN, cruise_info_copy):
})
return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values)
-def create_lfahda_cluster(packer, CS, CAN, long_active, lat_active):
-
-
+def create_lfahda_cluster(packer, CS, CAN, long_active, lat_active, synthesize=False):
if CS.lfahda_cluster is not None:
values = copy.copy(CS.lfahda_cluster)
rx_counter = values.pop("COUNTER", None)
+ elif synthesize:
+ values = {"HDA_OptUsmSta": 2}
+ rx_counter = None
else:
return []
- values = {}
- rx_counter = None
- values["LFA_OptUsmSta"] = 2
- values["HDA_OptUsmSta"] = 2
values["HDA_CntrlModSta"] = 2 if long_active else 0
values["HDA_LFA_SymSta"] = 2 if lat_active else 0
return [packer.make_can_msg("LFAHDA_CLUSTER", CAN.ECAN, values, rx_counter=rx_counter)]
@@ -588,7 +585,7 @@ def create_adrv_messages(CP, packer, CAN, frame):
ret = []
if not CP.flags & HyundaiFlags.CAMERA_SCC.value:
- values = {}
+ ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, {}))
ret.extend(create_fca_warning_light(CP, packer, CAN, frame))
if frame % 5 == 0:
@@ -770,6 +767,9 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
values = copy.copy(CS.cruise_buttons_msg)
# Keep the physical long press on ECAN for CarState, but don't forward it to CAM.
values["NORMAL_CRUISE_MAIN_BTN"] = 0
+ # the camera's own SCC cancels when SET- is held ~2 s; openpilot sets speed from the ECAN copy
+ if CC.enabled and values["CRUISE_BUTTONS"] in (Buttons.RES_ACCEL, Buttons.SET_DECEL):
+ values["CRUISE_BUTTONS"] = Buttons.NONE
if HDA_LFA_SymSta == 0 and 0 < frame % 200 < 12:
values["LFA_BTN"] = 1
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_camera_frame_source.py b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_camera_frame_source.py
new file mode 100644
index 00000000..ec01b13e
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_camera_frame_source.py
@@ -0,0 +1,115 @@
+import pytest
+
+from iqdbc.can import CANPacker
+from iqdbc.car import Bus, gen_empty_fingerprint, get_safety_config, structs
+from iqdbc.car.hyundai.interface import CarInterface
+from iqdbc.car.hyundai.values import CAR, CAMERA_SCC_CAR, DBC, HyundaiFlags
+from iqpilot.common.params import Params
+
+CAMERA_FRAME = 0x2A4
+CANFD_CARS = tuple(sorted(CAR.with_flags(HyundaiFlags.CANFD)))
+
+
+def build(candidate, hda2, camera_scc, alpha_long, monkeypatch, tmp_path):
+ monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
+ params = Params()
+ params.put_bool("ControlsReady", True)
+ params.put_int("HyundaiCameraSCC", camera_scc)
+ fingerprint = gen_empty_fingerprint()
+ if hda2:
+ fingerprint[2 if camera_scc == 0 else 1][0x50] = 16
+ fingerprint[2][CAMERA_FRAME] = 24
+ cp = CarInterface.get_params(candidate, fingerprint, [], alpha_long, False, False)
+ cp_iq = CarInterface.get_params_iq(cp, candidate, fingerprint, [], alpha_long, False, False)
+ return CarInterface(cp, cp_iq)
+
+
+def register(interface, seen=(Bus.cam, Bus.alt)):
+ for bus in seen:
+ interface.can_parsers[bus].seen_addresses.add(CAMERA_FRAME)
+ interface.CS.controls_ready_count = 123
+ interface.CS.monitor_fingerprint(interface.can_parsers, True)
+
+
+@pytest.mark.parametrize("candidate", CANFD_CARS, ids=str)
+@pytest.mark.parametrize("alpha_long", (False, True))
+@pytest.mark.parametrize("hda2,camera_scc,expected", (
+ (True, 0, Bus.cam), (True, 1, Bus.alt), (True, 2, Bus.alt),
+ (False, 0, Bus.alt), (False, 1, Bus.alt),
+))
+def test_source_preference_across_canfd_platforms(candidate, alpha_long, hda2, camera_scc, expected, monkeypatch, tmp_path):
+ interface = build(candidate, hda2, camera_scc, alpha_long, monkeypatch, tmp_path)
+ register(interface)
+ # Factory camera-SCC platforms retain their original A-CAN preference too.
+ if candidate in CAMERA_SCC_CAR:
+ expected = Bus.alt
+ for bus in (Bus.cam, Bus.alt):
+ assert (CAMERA_FRAME in interface.can_parsers[bus].addresses) == (bus == expected)
+ parser = interface.can_parsers[expected]
+ assert interface.CS.cam_0x2a4 is parser.vl["CAM_0x2a4"]
+ state = parser.message_states[CAMERA_FRAME]
+ assert not state.ignore_alive
+ assert not state.ignore_checksum
+
+
+@pytest.mark.parametrize("hda2,camera_scc", ((True, 0), (True, 1), (False, 0)))
+@pytest.mark.parametrize("seen", ((Bus.cam,), (Bus.alt,), ()))
+def test_single_source_and_absent_frame(hda2, camera_scc, seen, monkeypatch, tmp_path):
+ interface = build(CAR.KIA_EV6, hda2, camera_scc, False, monkeypatch, tmp_path)
+ register(interface, seen)
+ for bus in (Bus.cam, Bus.alt):
+ assert (CAMERA_FRAME in interface.can_parsers[bus].addresses) == (bus in seen)
+
+
+def test_startup_copy_disappears_but_camera_stays_valid(monkeypatch, tmp_path):
+ interface = build(CAR.KIA_EV6, True, 0, False, monkeypatch, tmp_path)
+ register(interface)
+ camera = interface.can_parsers[Bus.cam]
+ alternate = interface.can_parsers[Bus.alt]
+ packer = CANPacker(DBC[CAR.KIA_EV6][Bus.pt])
+ for frame in range(200):
+ nanos = (frame + 1) * 50_000_000
+ camera_msg = packer.make_can_msg("CAM_0x2a4", camera.bus, {"COUNTER": frame % 256})
+ frames = [camera_msg]
+ if frame < 20:
+ frames.append((camera_msg[0], camera_msg[1], alternate.bus))
+ for parser in (camera, alternate):
+ parser.update([(nanos, frames)])
+ assert parser.can_valid
+ # A real loss on the selected source must still fail validation.
+ for frame in range(50):
+ camera.update([(nanos + (frame + 1) * 50_000_000, [])])
+ valid = camera.can_valid
+ assert not valid
+
+
+def test_missing_alternate_parser_uses_camera(monkeypatch, tmp_path):
+ interface = build(CAR.KIA_EV6, True, 0, False, monkeypatch, tmp_path)
+ del interface.can_parsers[Bus.alt]
+ register(interface, (Bus.cam,))
+ assert CAMERA_FRAME in interface.can_parsers[Bus.cam].addresses
+
+
+@pytest.mark.parametrize("camera_scc", (0, 1))
+def test_alternate_steering_camera_frame_is_unchanged(camera_scc, monkeypatch, tmp_path):
+ interface = build(CAR.KIA_EV6, True, camera_scc, False, monkeypatch, tmp_path)
+ interface.CP.flags |= HyundaiFlags.CANFD_HDA2_ALT_STEERING.value
+ for bus in (Bus.cam, Bus.alt):
+ interface.can_parsers[bus].seen_addresses.add(0x362)
+ register(interface, ())
+ assert 0x362 in interface.can_parsers[Bus.cam].addresses
+ assert 0x362 not in interface.can_parsers[Bus.alt].addresses
+ assert interface.CS.cam_0x362 is interface.can_parsers[Bus.cam].vl["CAM_0x362"]
+
+
+def test_standard_hda2_with_second_panda(monkeypatch, tmp_path):
+ interface = build(CAR.KIA_EV6, True, 0, False, monkeypatch, tmp_path)
+ safety = interface.CP.safetyConfigs[0]
+ interface.CP.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.noOutput),
+ get_safety_config(safety.safetyModel, safety.safetyParam)]
+ interface = CarInterface(interface.CP, interface.CP_IQ)
+ assert interface.can_parsers[Bus.cam].bus == 6
+ assert interface.can_parsers[Bus.alt].bus == 4
+ register(interface)
+ assert CAMERA_FRAME in interface.can_parsers[Bus.cam].addresses
+ assert CAMERA_FRAME not in interface.can_parsers[Bus.alt].addresses
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_camera_scc_button_forwarding.py b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_camera_scc_button_forwarding.py
new file mode 100644
index 00000000..96146228
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_camera_scc_button_forwarding.py
@@ -0,0 +1,72 @@
+import pytest
+
+from iqdbc.can.parser import get_raw_value
+from iqdbc.car import Bus, gen_empty_fingerprint, structs
+from iqdbc.car.hyundai.hyundaicanfd import CanBus
+from iqdbc.car.hyundai.interface import CarInterface
+from iqdbc.car.hyundai.values import CAR, Buttons, HyundaiFlags
+from iqpilot.common.params import Params
+
+CRUISE_BUTTONS = 0x1CF
+CAMERA_SCC_EV6 = HyundaiFlags.CANFD | HyundaiFlags.CANFD_CAMERA_SCC | HyundaiFlags.EV
+
+
+def camera_scc_ev6(monkeypatch, tmp_path):
+ monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
+ Params().put_bool("ControlsReady", True)
+ fingerprint = gen_empty_fingerprint()
+ cp = CarInterface.get_params(CAR.KIA_EV6, fingerprint, [], True, False, False)
+ cp.flags = int(CAMERA_SCC_EV6)
+ cp.openpilotLongitudinalControl = True
+ cp_iq = CarInterface.get_params_iq(cp, CAR.KIA_EV6, fingerprint, [], True, False, False)
+ interface = CarInterface(cp, cp_iq)
+ interface.update([(10_000_000, [])])
+ buttons = {sig: 0 for sig in interface.can_parsers[Bus.pt].dbc.name_to_msg["CRUISE_BUTTONS"].sigs}
+ interface.CS.cruise_buttons_msg = buttons
+ interface.CS.cruise_btns_msg_canfd = "CRUISE_BUTTONS"
+ interface.CS.MainMode_ACC = True
+ interface.CS.ACCMode = 1
+ return interface
+
+
+def buttons_sent_to_camera(interface, enabled, driver_button, frames=40):
+ cc = structs.CarControl()
+ cc.enabled = enabled
+ cc = cc.as_reader()
+ cc_iq = structs.IQCarControl()
+ interface.CS.cruise_buttons_msg["CRUISE_BUTTONS"] = driver_button
+ signal = interface.can_parsers[Bus.pt].dbc.name_to_msg["CRUISE_BUTTONS"].sigs["CRUISE_BUTTONS"]
+ camera_bus = CanBus(interface.CP).CAM
+ seen = []
+ t = 10_000_000
+ for _ in range(frames):
+ t += 10_000_000
+ _, sends = interface.apply(cc, cc_iq, t)
+ seen += [get_raw_value(dat, signal) for addr, dat, bus in sends if addr == CRUISE_BUTTONS and bus == camera_bus]
+ return seen
+
+
+@pytest.mark.parametrize("held", [Buttons.SET_DECEL, Buttons.RES_ACCEL])
+def test_engaged_camera_scc_never_sees_a_held_set_speed_button(held, monkeypatch, tmp_path):
+ interface = camera_scc_ev6(monkeypatch, tmp_path)
+ seen = buttons_sent_to_camera(interface, True, held)
+ assert seen
+ assert held not in seen
+
+
+@pytest.mark.parametrize("button", [Buttons.CANCEL, Buttons.GAP_DIST])
+def test_engaged_camera_scc_still_gets_cancel_and_gap(button, monkeypatch, tmp_path):
+ interface = camera_scc_ev6(monkeypatch, tmp_path)
+ assert set(buttons_sent_to_camera(interface, True, button)) == {button}
+
+
+def test_disengaged_camera_scc_gets_the_driver_buttons(monkeypatch, tmp_path):
+ interface = camera_scc_ev6(monkeypatch, tmp_path)
+ assert Buttons.SET_DECEL in buttons_sent_to_camera(interface, False, Buttons.SET_DECEL)
+
+
+def test_engaged_camera_scc_keeps_openpilots_own_rearm_press(monkeypatch, tmp_path):
+ interface = camera_scc_ev6(monkeypatch, tmp_path)
+ interface.CS.ACCMode = 4
+ interface.CS.out.vEgo = 20.0
+ assert Buttons.SET_DECEL in buttons_sent_to_camera(interface, True, Buttons.NONE, frames=220)
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_hda2_long_adas_replacement.py b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_hda2_long_adas_replacement.py
new file mode 100644
index 00000000..8597b2c7
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_hda2_long_adas_replacement.py
@@ -0,0 +1,77 @@
+import pytest
+
+from iqdbc.car import gen_empty_fingerprint, structs
+from iqdbc.car.hyundai.hyundaicanfd import CanBus
+from iqdbc.car.hyundai.interface import CarInterface
+from iqdbc.car.hyundai.values import CAR, HyundaiFlags, HyundaiSafetyFlags
+from iqdbc.safety.tests.libsafety import libsafety_py
+from iqpilot.common.params import Params
+
+ADRV_0x51 = 0x51
+LFAHDA_CLUSTER = 0x1E0
+HDA2_EV = HyundaiFlags.CANFD | HyundaiFlags.CANFD_HDA2 | HyundaiFlags.EV
+
+
+def ev6(monkeypatch, tmp_path, flags=HDA2_EV, openpilot_long=True):
+ monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
+ Params().put_bool("ControlsReady", True)
+ fingerprint = gen_empty_fingerprint()
+ cp = CarInterface.get_params(CAR.KIA_EV6, fingerprint, [], openpilot_long, False, False)
+ cp.flags = int(flags)
+ cp.openpilotLongitudinalControl = openpilot_long
+ cp_iq = CarInterface.get_params_iq(cp, CAR.KIA_EV6, fingerprint, [], openpilot_long, False, False)
+ return CarInterface(cp, cp_iq)
+
+
+def sent_frames(interface, frames=100):
+ cc = structs.CarControl().as_reader()
+ cc_iq = structs.IQCarControl()
+ out = []
+ t = 0
+ for _ in range(frames):
+ t += 10_000_000
+ interface.update([(t, [])])
+ _, sends = interface.apply(cc, cc_iq, t)
+ out.extend(sends)
+ return out
+
+
+def test_hda2_long_replaces_the_silenced_adas_heartbeat_and_cluster_status(monkeypatch, tmp_path):
+ interface = ev6(monkeypatch, tmp_path)
+ can = CanBus(interface.CP)
+ sends = sent_frames(interface)
+
+ heartbeat = [s for s in sends if s[0] == ADRV_0x51]
+ assert len(heartbeat) == 100
+ assert {s[2] for s in heartbeat} == {can.ACAN}
+
+ cluster = [s for s in sends if s[0] == LFAHDA_CLUSTER]
+ assert len(cluster) == 20
+ assert {s[2] for s in cluster} == {can.ECAN}
+
+
+@pytest.mark.parametrize("flags,openpilot_long", [
+ (HDA2_EV, False),
+ (HDA2_EV | HyundaiFlags.CAMERA_SCC, True),
+])
+def test_cars_that_keep_their_adas_ecu_send_neither(flags, openpilot_long, monkeypatch, tmp_path):
+ interface = ev6(monkeypatch, tmp_path, flags, openpilot_long)
+ addresses = {s[0] for s in sent_frames(interface)}
+
+ assert ADRV_0x51 not in addresses
+ if not openpilot_long:
+ assert LFAHDA_CLUSTER not in addresses
+
+
+def test_panda_safety_transmits_the_replacements_for_this_ev6(monkeypatch, tmp_path):
+ interface = ev6(monkeypatch, tmp_path)
+ sends = [s for s in sent_frames(interface) if s[0] in (ADRV_0x51, LFAHDA_CLUSTER)]
+ safety = libsafety_py.libsafety
+ param = HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.CANFD_LKA_STEERING
+ assert param == 21
+ assert safety.set_safety_hooks(structs.CarParams.SafetyModel.hyundaiCanfd, param) == 0
+ safety.init_tests()
+
+ assert sends
+ for addr, dat, bus in sends:
+ assert safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, dat)), (hex(addr), bus)
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_pre_ready_frames.py b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_pre_ready_frames.py
new file mode 100644
index 00000000..0d045185
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/hyundai/tests/test_pre_ready_frames.py
@@ -0,0 +1,58 @@
+import pytest
+
+from iqdbc.can import CANPacker
+from iqdbc.car import Bus, gen_empty_fingerprint
+from iqdbc.car.hyundai.hyundaicanfd import CanBus
+from iqdbc.car.hyundai.interface import CarInterface
+from iqdbc.car.hyundai.values import CAR, DBC, HyundaiFlags
+from iqpilot.common.params import Params
+
+HDA2_EV = HyundaiFlags.CANFD | HyundaiFlags.CANFD_HDA2 | HyundaiFlags.EV
+DT_NS = 10_000_000
+
+
+def hda2_ev6(openpilot_long, monkeypatch, tmp_path):
+ monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
+ Params().put_bool("ControlsReady", False)
+ fingerprint = gen_empty_fingerprint()
+ cp = CarInterface.get_params(CAR.KIA_EV6, fingerprint, [], openpilot_long, False, False)
+ cp.flags = int(HDA2_EV)
+ cp.openpilotLongitudinalControl = openpilot_long
+ cp_iq = CarInterface.get_params_iq(cp, CAR.KIA_EV6, fingerprint, [], openpilot_long, False, False)
+ return CarInterface(cp, cp_iq)
+
+
+def drive(interface, frames_before_ready, frames_after_ready, steps_before=60, steps_after=200):
+ t = 0
+ for _ in range(steps_before):
+ t += DT_NS
+ interface.update([(t, frames_before_ready)])
+ Params().put_bool("ControlsReady", True)
+ for _ in range(steps_after):
+ t += DT_NS
+ interface.update([(t, frames_after_ready)])
+
+
+def frame(packer, name, bus):
+ addr, dat, _ = packer.make_can_msg(name, bus, {})
+ return addr, dat, bus
+
+
+@pytest.mark.parametrize("openpilot_long", [True, False])
+def test_frames_that_stop_at_controls_ready_are_never_registered(openpilot_long, monkeypatch, tmp_path):
+ interface = hda2_ev6(openpilot_long, monkeypatch, tmp_path)
+ can = CanBus(interface.CP)
+ packer = CANPacker(DBC[CAR.KIA_EV6][Bus.pt])
+ scc_on_ecan = frame(packer, "SCC_CONTROL", can.ECAN)
+ lane_on_acan = frame(packer, "CAM_0x2a4", can.ACAN)
+ lane_on_cam = frame(packer, "CAM_0x2a4", can.CAM)
+
+ before = [scc_on_ecan, lane_on_acan, lane_on_cam]
+ after = [lane_on_cam] if openpilot_long else [scc_on_ecan, lane_on_cam]
+ drive(interface, before, after)
+
+ parsers = interface.can_parsers
+ assert lane_on_cam[0] in parsers[Bus.cam].addresses
+ assert lane_on_acan[0] not in parsers[Bus.alt].addresses
+ assert (scc_on_ecan[0] in parsers[Bus.pt].addresses) == (not openpilot_long)
+ assert interface.CS.cam_0x2a4 is not None
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carcontroller.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carcontroller.py
index 5b91e937..99a94871 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carcontroller.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carcontroller.py
@@ -5,7 +5,8 @@ from iqdbc.car.lateral import apply_steer_angle_limits_vm
from iqdbc.car.interfaces import CarControllerBase
from iqdbc.car.tesla import TESLA_BLINKERS
from iqdbc.car.tesla.teslacan import TeslaCAN
-from iqdbc.car.tesla.values import CarControllerParams
+from iqdbc.car.tesla.teslacan_legacy import TeslaCANLegacy
+from iqdbc.car.tesla.values import CAR, CarControllerParams, LEGACY_CARS, LEGACY_HW1_CARS, get_legacy_canbus
from iqdbc.car.vehicle_model import VehicleModel
from iqpilot.selfdrive.car.enhanced_stock_longitudinal_control import get_set_speed_kph_from_params
from iqdbc.lvbs.car.tesla.torque_blend import TorqueBlendController
@@ -36,7 +37,46 @@ class CarController(CarControllerBase):
self.blinker_request_prev = False
self.blinker_cancel_frame = 0
+ self.legacy = CP.carFingerprint in LEGACY_CARS
+ if self.legacy:
+ from iqdbc.car.tesla.interface import CarInterface
+ canbus = get_legacy_canbus(CP.carFingerprint)
+ packers = {canbus.party: CANPacker(dbc_names[Bus.party]), canbus.powertrain: CANPacker(dbc_names[Bus.pt])}
+ self.tesla_can_legacy = TeslaCANLegacy(packers, canbus)
+ self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW3))
+
+ def update_legacy(self, CC, CS):
+ actuators = CC.actuators
+ can_sends = []
+
+ lat_active = CC.latActive and CS.hands_on_level < 3
+
+ if self.frame % CarControllerParams.STEER_STEP == 0:
+ self.apply_angle_last = apply_steer_angle_limits_vm(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
+ lat_active, CarControllerParams, self.VM)
+ counter = (self.frame // CarControllerParams.STEER_STEP) % 16
+ can_sends.append(self.tesla_can_legacy.create_steering_control(counter, self.apply_angle_last, lat_active))
+
+ if self.frame % 10 == 0 and self.CP.carFingerprint not in LEGACY_HW1_CARS:
+ counter = (self.frame // 10) % 16
+ can_sends.append(self.tesla_can_legacy.create_steering_allowed(counter))
+
+ if self.frame % 4 == 0:
+ state = 13 if CC.cruiseControl.cancel else 4
+ accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
+ counter = (self.frame // 4) % 8
+ can_sends.append(self.tesla_can_legacy.create_longitudinal_command(state, accel, counter, CS.out.vEgo, CC.longActive, CS.out.gasPressed))
+
+ new_actuators = actuators.as_builder()
+ new_actuators.steeringAngleDeg = self.apply_angle_last
+
+ self.frame += 1
+ return new_actuators, can_sends
+
def update(self, CC, CC_IQ, CS, now_nanos):
+ if self.legacy:
+ return self.update_legacy(CC, CS)
+
actuators = CC.actuators
can_sends = []
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carstate.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carstate.py
index 172836ed..a1622bad 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carstate.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/carstate.py
@@ -4,7 +4,8 @@ from iqdbc.car import Bus, structs
from iqdbc.car.common.conversions import Conversions as CV
from iqdbc.car.interfaces import CarStateBase
from iqdbc.car.tesla import TESLA_BLINKERS
-from iqdbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD, TeslaFlags
+from iqdbc.car.tesla.values import (DBC, CANBUS, CAR, GEAR_MAP, LEGACY_CARS, LEGACY_HW3_CARS, STEER_THRESHOLD, TeslaFlags,
+ get_legacy_canbus)
from iqdbc.lvbs.car.tesla.iq_carstate import IQCarState
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ
@@ -24,12 +25,10 @@ def stock_autosteer_invalid(CP, CP_IQ, autopilot_state: int, autosteer_enabled:
class CarState(CarStateBase, IQCarState):
def __init__(self, CP, CP_IQ):
- from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
- iq_lvbs_alc = import_verified_module("iqpilot_alc_private", "iqpilot_private.konn3kt.iqlvbs.alc")
CarStateBase.__init__(self, CP, CP_IQ)
IQCarState.__init__(self, CP, CP_IQ)
+ self.legacy = CP.carFingerprint in LEGACY_CARS
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
- self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"]
self.summon = False
self.summon_prev = False
@@ -38,9 +37,25 @@ class CarState(CarStateBase, IQCarState):
self.hands_on_level = 0
self.das_control = None
self.das_body_controls_dat = b""
- self._odometer_store = iq_lvbs_alc.create_vehicle_odometer_store(CP, Params())
self.cruise_override = False
+ if self.legacy:
+ self.can_defines = {
+ **self.can_define.dv,
+ **CANDefine(DBC[CP.carFingerprint][Bus.pt]).dv,
+ **CANDefine(DBC[CP.carFingerprint][Bus.chassis]).dv,
+ }
+ self.shifter_values = self.can_defines["DI_torque2"]["DI_gear"]
+ self._odometer_store = None
+ else:
+ self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"]
+ try:
+ from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
+ iq_lvbs_alc = import_verified_module("iqpilot_alc_private", "iqpilot_private.konn3kt.iqlvbs.alc")
+ self._odometer_store = iq_lvbs_alc.create_vehicle_odometer_store(CP, Params())
+ except Exception:
+ self._odometer_store = None
+
def update_summon_state(self, summon_state: str, cruise_enabled: bool):
summon_now = summon_state in ("ACTIVE", "COMPLETE", "SELFPARK_STARTED")
if summon_now and not self.summon_prev and not self.cruise_enabled_prev:
@@ -50,7 +65,85 @@ class CarState(CarStateBase, IQCarState):
self.summon_prev = summon_now
self.cruise_enabled_prev = cruise_enabled
+ def update_legacy(self, can_parsers) -> tuple[structs.CarState, structs.IQCarState]:
+ cp_party = can_parsers[Bus.party]
+ cp_ap_party = can_parsers[Bus.ap_party]
+ cp_pt = can_parsers[Bus.pt]
+ cp_ap_pt = can_parsers[Bus.ap_pt]
+ cp_chassis = can_parsers[Bus.chassis]
+ ret = structs.CarState()
+ ret_iq = structs.IQCarState()
+
+ ret.vEgoRaw = cp_chassis.vl["ESP_B"]["ESP_vehicleSpeed"] * CV.KPH_TO_MS
+ ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+
+ ret.gasPressed = cp_pt.vl["DI_torque1"]["DI_pedalPos"] > 0
+
+ ret.brakePressed = cp_chassis.vl["BrakeMessage"]["driverBrakeStatus"] == 2
+
+ if self.CP.carFingerprint in LEGACY_HW3_CARS:
+ epas_status = cp_party.vl["EPAS_sysStatus"]
+ else:
+ epas_status = cp_chassis.vl["EPAS_sysStatus"]
+ self.hands_on_level = epas_status["EPAS_handsOnLevel"]
+ ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"]
+ ret.steeringRateDeg = -cp_chassis.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"]
+ ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"]
+
+ ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
+
+ eac_status = self.can_defines["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None)
+ ret.steerFaultPermanent = eac_status == "EAC_FAULT"
+ ret.steerFaultTemporary = eac_status == "EAC_INHIBITED"
+
+ eac_error_code = self.can_defines["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None)
+ ret.steeringDisengage = self.hands_on_level >= 3 or (eac_status == "EAC_INHIBITED" and
+ eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY")
+
+ cruise_state = self.can_defines["DI_state"]["DI_cruiseState"].get(int(cp_chassis.vl["DI_state"]["DI_cruiseState"]), None)
+ speed_units = self.can_defines["DI_state"]["DI_speedUnits"].get(int(cp_chassis.vl["DI_state"]["DI_speedUnits"]), None)
+
+ cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
+
+ ret.cruiseState.enabled = cruise_enabled
+ if speed_units == "KPH":
+ ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_digitalSpeed"] * CV.KPH_TO_MS, 1e-3)
+ elif speed_units == "MPH":
+ ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_digitalSpeed"] * CV.MPH_TO_MS, 1e-3)
+ ret.cruiseState.available = cruise_state == "STANDBY" or ret.cruiseState.enabled
+ ret.cruiseState.standstill = False
+ ret.standstill = cruise_state == "STANDSTILL"
+ ret.accFaulted = cruise_state == "FAULT"
+
+ ret.gearShifter = GEAR_MAP[self.can_defines["DI_torque2"]["DI_gear"].get(int(cp_chassis.vl["DI_torque2"]["DI_gear"]), "DI_GEAR_INVALID")]
+
+ doors = ["DOOR_STATE_FL", "DOOR_STATE_FR", "DOOR_STATE_RL", "DOOR_STATE_RR", "DOOR_STATE_FrontTrunk", "BOOT_STATE"]
+ ret.doorOpen = any(self.can_defines["GTW_carState"][door].get(int(cp_chassis.vl["GTW_carState"][door]), "OPEN") == "OPEN" for door in doors)
+
+ if self.CP.carFingerprint == CAR.TESLA_MODEL_X_HW1:
+ ret.leftBlinker = cp_chassis.vl["STW_ACTN_RQ"]["TurnIndLvr_Stat"] == 1
+ ret.rightBlinker = cp_chassis.vl["STW_ACTN_RQ"]["TurnIndLvr_Stat"] == 2
+ else:
+ ret.leftBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorLStatus"] == 1
+ ret.rightBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorRStatus"] == 1
+
+ if self.CP.flags & TeslaFlags.NO_SDM1:
+ ret.seatbeltUnlatched = cp_chassis.vl["RCM_status"]["RCM_buckleDriverStatus"] != 1
+ else:
+ ret.seatbeltUnlatched = cp_chassis.vl["SDM1"]["SDM_bcklDrivStatus"] != 1
+
+ ret.stockAeb = cp_ap_pt.vl["DAS_control"]["DAS_aebEvent"] == 1
+
+ ret.stockLkas = cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 2
+
+ self.das_control = copy.copy(cp_ap_pt.vl["DAS_control"])
+
+ return ret, ret_iq
+
def update(self, can_parsers) -> tuple[structs.CarState, structs.IQCarState]:
+ if self.legacy:
+ return self.update_legacy(can_parsers)
+
cp_party = can_parsers[Bus.party]
cp_ap_party = can_parsers[Bus.ap_party]
ret = structs.CarState()
@@ -188,6 +281,16 @@ class CarState(CarStateBase, IQCarState):
@staticmethod
def get_can_parsers(CP, CP_IQ):
+ if CP.carFingerprint in LEGACY_CARS:
+ canbus = get_legacy_canbus(CP.carFingerprint)
+ return {
+ Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], canbus.party),
+ Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], canbus.autopilot_party),
+ Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], canbus.powertrain),
+ Bus.ap_pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], canbus.autopilot_powertrain),
+ Bus.chassis: CANParser(DBC[CP.carFingerprint][Bus.chassis], [], canbus.chassis),
+ }
+
parsers = {
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party),
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/fingerprints.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/fingerprints.py
index 39373440..2a0c8888 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/fingerprints.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/fingerprints.py
@@ -55,4 +55,31 @@ FW_VERSIONS = {
b'TeM3_SP_XP002p2_0.0.0 (36),XPR003.10.0',
],
},
+ CAR.TESLA_MODEL_X_HW1: {
+ (Ecu.eps, 0x730, None): [
+ b'1057658-00-00B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ },
+ CAR.TESLA_MODEL_X_HW2: {
+ (Ecu.eps, 0x730, None): [
+ b'\x10#\x81',
+ ],
+ },
+ CAR.TESLA_MODEL_S_HW1: {
+ (Ecu.eps, 0x730, None): [
+ b'1016704-00-HAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\x10\x00A',
+ ],
+ },
+ CAR.TESLA_MODEL_S_HW2: {
+ (Ecu.eps, 0x730, None): [
+ b'\x10#\x01',
+ ],
+ },
+ CAR.TESLA_MODEL_S_HW3: {
+ (Ecu.eps, 0x730, None): [
+ b'SX_0.0.0 (99),S013.7',
+ b'SX_0.0.0 (99),SR013.7',
+ ],
+ },
}
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/interface.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/interface.py
index a25089aa..cf8a40c8 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/interface.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/interface.py
@@ -2,7 +2,8 @@ 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, Ecu, is_legacy_das_steering
+from iqdbc.car.tesla.values import (TeslaSafetyFlags, TeslaFlags, TeslaLegacySafetyFlags, CANBUS, CAR, DBC, Ecu, LEGACY_CARS,
+ LEGACY_HW1_CARS, LEGACY_HW2_CARS, is_legacy_das_steering)
from iqdbc.car.tesla.radar_interface import RadarInterface, RADAR_START_ADDR
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ
@@ -15,6 +16,9 @@ class CarInterface(CarInterfaceBase):
@staticmethod
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
+ if candidate in LEGACY_CARS:
+ return CarInterface._get_params_legacy(ret, candidate, fingerprint)
+
ret.brand = "tesla"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
@@ -63,10 +67,45 @@ class CarInterface(CarInterfaceBase):
return ret
+ @staticmethod
+ def _get_params_legacy(ret: structs.CarParams, candidate, fingerprint) -> structs.CarParams:
+ ret.brand = "tesla"
+
+ if not any(0x201 in messages for messages in fingerprint.values()):
+ ret.flags |= TeslaFlags.NO_SDM1.value
+
+ safety_model = structs.CarParams.SafetyModel.teslaLegacy
+ if candidate in LEGACY_HW1_CARS:
+ ret.safetyConfigs = [
+ get_safety_config(safety_model, int(TeslaLegacySafetyFlags.HW1)),
+ ]
+ else:
+ hw = TeslaLegacySafetyFlags.HW2 if candidate in LEGACY_HW2_CARS else TeslaLegacySafetyFlags.HW3
+ ret.safetyConfigs = [
+ get_safety_config(safety_model, int(hw)),
+ get_safety_config(safety_model, int(hw | TeslaLegacySafetyFlags.EXTERNAL_PANDA)),
+ ]
+
+ ret.steerLimitTimer = 0.4
+ ret.steerActuatorDelay = 0.1
+ ret.steerAtStandstill = True
+
+ ret.steerControlType = structs.CarParams.SteerControlType.angle
+ ret.radarUnavailable = candidate == CAR.TESLA_MODEL_S_HW2
+
+ ret.alphaLongitudinalAvailable = True
+ ret.openpilotLongitudinalControl = True
+ ret.safetyConfigs[0].safetyParam |= TeslaLegacySafetyFlags.LONG_CONTROL.value
+
+ return ret
+
@staticmethod
def _get_params_iq(stock_cp: structs.CarParams, ret: structs.IQCarParams, candidate, fingerprint: dict[int, dict[int, int]],
car_fw: list[structs.CarParams.CarFw], alpha_long: bool, is_release_iq: bool, docs: bool) -> structs.IQCarParams:
+ if candidate in LEGACY_CARS:
+ return ret
+
stock_cp.enableBsm = True
if candidate == CAR.TESLA_MODEL_X:
@@ -74,7 +113,7 @@ class CarInterface(CarInterfaceBase):
# Vehicle-bus messages can be slow enough to miss the initial capture window.
# Accept either the established 0x3DF marker or the absolute odometer frame.
- vehicle_bus_seen = any(0x3DF in bus or 0x3B6 in bus for bus in fingerprint.values())
+ vehicle_bus_seen = any(address in fingerprint[CANBUS.vehicle] for address in (0x3DF, 0x3B6))
if vehicle_bus_seen:
ret.flags |= TeslaFlagsIQ.HAS_VEHICLE_BUS.value
ret.iqSafetyFlags |= TeslaSafetyFlagsIQ.HAS_VEHICLE_BUS
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/radar_interface.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/radar_interface.py
index d21d0228..b1dad859 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/radar_interface.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/radar_interface.py
@@ -1,35 +1,67 @@
from iqdbc.can import CANParser
from iqdbc.car import Bus, structs
from iqdbc.car.interfaces import RadarInterfaceBase
-from iqdbc.car.tesla.values import DBC
+from iqdbc.car.tesla.values import DBC, LEGACY_BOSCH_RADAR_CARS, LEGACY_CARS, get_legacy_canbus
RADAR_START_ADDR = 0x410
RADAR_MSG_COUNT = 80 # 40 points * 2 messages each
+BOSCH_NUM_POINTS = 32
+BOSCH_RADAR_POINT_FREQ = 8
+BOSCH_TRIGGER_MSG = 878
+BOSCH_MAX_RANGE = 250.0
+BOSCH_MIN_PROB_EXIST = 50.0
+
+
+def _is_bosch_radar(CP):
+ return CP.carFingerprint in LEGACY_BOSCH_RADAR_CARS
+
+
+def _radar_bus(CP):
+ if CP.carFingerprint in LEGACY_CARS:
+ return get_legacy_canbus(CP.carFingerprint).radar
+ return 1
+
def get_radar_can_parser(CP):
if Bus.radar not in DBC[CP.carFingerprint]:
return None
- messages = [('RadarStatus', 16)]
- for i in range(RADAR_MSG_COUNT // 2):
+ if _is_bosch_radar(CP):
+ messages = [('TeslaRadarSguInfo', 8)]
+ num_points = BOSCH_NUM_POINTS
+ freq = BOSCH_RADAR_POINT_FREQ
+ else:
+ messages = [('RadarStatus', 16)]
+ num_points = RADAR_MSG_COUNT // 2
+ freq = 16
+
+ for i in range(num_points):
messages.extend([
- (f'RadarPoint{i}_A', 16),
- (f'RadarPoint{i}_B', 16),
+ (f'RadarPoint{i}_A', freq),
+ (f'RadarPoint{i}_B', freq),
])
- return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, 1)
+ return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, _radar_bus(CP))
class RadarInterface(RadarInterfaceBase):
def __init__(self, CP, CP_IQ):
super().__init__(CP, CP_IQ)
self.updated_messages = set()
- self.trigger_msg = RADAR_START_ADDR + RADAR_MSG_COUNT - 1
self.track_id = 0
+ self.bosch_radar = _is_bosch_radar(CP)
+ if self.bosch_radar:
+ self.trigger_msg = BOSCH_TRIGGER_MSG
+ self.num_points = BOSCH_NUM_POINTS
+ else:
+ self.trigger_msg = RADAR_START_ADDR + RADAR_MSG_COUNT - 1
+ self.num_points = RADAR_MSG_COUNT // 2
+
self.radar_off_can = CP.radarUnavailable
self.rcp = get_radar_can_parser(CP)
+ self.last_radar_data: structs.RadarData | None = None
def update(self, can_strings):
if self.radar_off_can or self.rcp is None:
@@ -39,10 +71,13 @@ class RadarInterface(RadarInterfaceBase):
self.updated_messages.update(vls)
if self.trigger_msg not in self.updated_messages:
+ if self.bosch_radar:
+ return self.last_radar_data
return None
rr = self._update(self.updated_messages)
self.updated_messages.clear()
+ self.last_radar_data = rr
return rr
@@ -54,13 +89,17 @@ class RadarInterface(RadarInterfaceBase):
if not self.rcp.can_valid:
ret.errors.canError = True
- radar_status = self.rcp.vl['RadarStatus']
- if radar_status['shortTermUnavailable']:
- ret.errors.radarUnavailableTemporary = True
- if radar_status['sensorBlocked'] or radar_status['vehDynamicsError']:
- ret.errors.radarFault = True
+ if self.bosch_radar:
+ if self.rcp.vl['TeslaRadarSguInfo']['RADC_HWFail']:
+ ret.errors.radarFault = True
+ else:
+ radar_status = self.rcp.vl['RadarStatus']
+ if radar_status['shortTermUnavailable']:
+ ret.errors.radarUnavailableTemporary = True
+ if radar_status['sensorBlocked'] or radar_status['vehDynamicsError']:
+ ret.errors.radarFault = True
- for i in range(RADAR_MSG_COUNT // 2):
+ for i in range(self.num_points):
msg_a = self.rcp.vl[f'RadarPoint{i}_A']
msg_b = self.rcp.vl[f'RadarPoint{i}_B']
@@ -73,6 +112,11 @@ class RadarInterface(RadarInterfaceBase):
del self.pts[i]
continue
+ if self.bosch_radar and (msg_a['LongDist'] > BOSCH_MAX_RANGE or msg_a['LongDist'] <= 0 or msg_a['ProbExist'] < BOSCH_MIN_PROB_EXIST):
+ if i in self.pts:
+ del self.pts[i]
+ continue
+
if i not in self.pts:
self.pts[i] = structs.RadarData.RadarPoint()
self.pts[i].trackId = self.track_id
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/teslacan_legacy.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/teslacan_legacy.py
new file mode 100644
index 00000000..590da5bf
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/teslacan_legacy.py
@@ -0,0 +1,72 @@
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
+"""
+from iqdbc.car.common.conversions import Conversions as CV
+from iqdbc.car.interfaces import V_CRUISE_MAX
+from iqdbc.car.tesla.values import CarControllerParams
+
+
+class TeslaCANLegacy:
+ def __init__(self, packers, canbus):
+ self.packers = packers
+ self.canbus = canbus
+ self.CCP = CarControllerParams
+ self.jerk_upper = self.CCP.JERK_LIMIT_MAX
+ self.jerk_lower = self.CCP.JERK_LIMIT_MIN
+
+ @staticmethod
+ def checksum(msg_id, dat):
+ ret = (msg_id & 0xFF) + ((msg_id >> 8) & 0xFF)
+ ret += sum(dat)
+ return ret & 0xFF
+
+ def create_steering_control(self, counter, angle, enabled):
+ values = {
+ "DAS_steeringControlCounter": counter,
+ "DAS_steeringAngleRequest": -angle,
+ "DAS_steeringHapticRequest": 0,
+ "DAS_steeringControlType": 1 if enabled else 0,
+ }
+
+ packer = self.packers[self.canbus.party]
+ data = packer.make_can_msg("DAS_steeringControl", self.canbus.party, values)[1]
+ values["DAS_steeringControlChecksum"] = self.checksum(0x488, data[:3])
+ return packer.make_can_msg("DAS_steeringControl", self.canbus.party, values)
+
+ def create_longitudinal_command(self, acc_state, accel, counter, v_ego, active, gas_pressed):
+ set_speed = max(v_ego * CV.MS_TO_KPH, 0)
+ if active:
+ set_speed = 0 if accel < 0 else V_CRUISE_MAX
+
+ if gas_pressed:
+ self.jerk_upper = self.jerk_lower = 0.0
+ else:
+ self.jerk_lower = max(self.jerk_lower - self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MIN)
+ self.jerk_upper = min(self.jerk_upper + self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MAX)
+
+ values = {
+ "DAS_setSpeed": set_speed,
+ "DAS_accState": acc_state,
+ "DAS_aebEvent": 0,
+ "DAS_jerkMin": self.jerk_lower,
+ "DAS_jerkMax": self.jerk_upper,
+ "DAS_accelMin": accel,
+ "DAS_accelMax": max(accel, 0),
+ "DAS_controlCounter": counter,
+ }
+
+ packer = self.packers[self.canbus.powertrain]
+ data = packer.make_can_msg("DAS_control", self.canbus.powertrain, values)[1]
+ values["DAS_controlChecksum"] = self.checksum(0x2b9, data[:7])
+ return packer.make_can_msg("DAS_control", self.canbus.powertrain, values)
+
+ def create_steering_allowed(self, counter):
+ values = {
+ "APS_eacMonitorCounter": counter,
+ "APS_eacAllow": 1,
+ }
+
+ packer = self.packers[self.canbus.party]
+ data = packer.make_can_msg("APS_eacMonitor", self.canbus.party, values)[1]
+ values["APS_eacMonitorChecksum"] = self.checksum(0x27d, data[:2])
+ return packer.make_can_msg("APS_eacMonitor", self.canbus.party, values)
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/tests/test_tesla.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/tests/test_tesla.py
index 6a0d64ca..3eac82da 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/tests/test_tesla.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/tests/test_tesla.py
@@ -13,7 +13,7 @@ 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.carstate import CarState, stock_autosteer_invalid
-from iqdbc.car.tesla.values import (CANBUS, CAR, FW_PATTERN, LEGACY_DAS_STEERING_FW, TeslaFlags, TeslaSafetyFlags,
+from iqdbc.car.tesla.values import (CANBUS, CAR, FW_PATTERN, LEGACY_CARS, LEGACY_DAS_STEERING_FW, TeslaFlags, TeslaSafetyFlags,
get_platform_codes, is_ascii_version_fw, is_hw4_gen2_fw, is_legacy_das_steering)
from iqdbc.lvbs.car.interfaces import _apply_tesla_options
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ
@@ -44,6 +44,8 @@ def fw_match(fw: bytes):
class TestTeslaFwPattern:
def test_all_known_fw_parses(self):
for car, ecus in FW_VERSIONS.items():
+ if car in LEGACY_CARS:
+ continue
for fws in ecus.values():
for fw in fws:
if not is_ascii_version_fw(fw):
@@ -53,6 +55,8 @@ class TestTeslaFwPattern:
def test_only_hw4_gen2_ships_a_binary_fw(self):
# a binary FW can only ever match exactly, so it must not creep into other platforms
for car, ecus in FW_VERSIONS.items():
+ if car in LEGACY_CARS:
+ continue
for fws in ecus.values():
for fw in fws:
if is_ascii_version_fw(fw):
@@ -217,6 +221,31 @@ class TestStockAutosteerInvalid:
class TestTeslaFingerprint:
+ @pytest.mark.parametrize("candidate", [CAR.TESLA_MODEL_3, CAR.TESLA_MODEL_Y, CAR.TESLA_MODEL_X])
+ @pytest.mark.parametrize("bus", [CANBUS.party, CANBUS.vehicle, CANBUS.autopilot_party])
+ @pytest.mark.parametrize("marker", [0x3DF, 0x3B6])
+ def test_vehicle_bus_addon_requires_marker_on_vehicle_bus(self, candidate, bus, marker):
+ fingerprint = gen_empty_fingerprint()
+ fingerprint[bus][marker] = 8
+ CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False)
+ CP_IQ = CarInterface.get_params_iq(CP, candidate, fingerprint, [], False, False, False)
+ addon_present = bus == CANBUS.vehicle
+
+ assert bool(CP_IQ.flags & TeslaFlagsIQ.HAS_VEHICLE_BUS) == addon_present
+ assert bool(CP_IQ.iqSafetyFlags & TeslaSafetyFlagsIQ.HAS_VEHICLE_BUS) == addon_present
+ assert (Bus.adas in CarState.get_can_parsers(CP, CP_IQ)) == addon_present
+
+ def test_standard_harness_with_mirrored_odometer_has_no_vehicle_bus_parser(self):
+ fingerprint = gen_empty_fingerprint()
+ for bus in (CANBUS.party, CANBUS.autopilot_party):
+ fingerprint[bus][0x3B6] = 6
+ CP = CarInterface.get_params(CAR.TESLA_MODEL_3, fingerprint, [], False, False, False)
+ CP_IQ = CarInterface.get_params_iq(CP, CAR.TESLA_MODEL_3, fingerprint, [], False, False, False)
+
+ assert not CP_IQ.flags & TeslaFlagsIQ.HAS_VEHICLE_BUS
+ assert not CP_IQ.iqSafetyFlags & TeslaSafetyFlagsIQ.HAS_VEHICLE_BUS
+ assert Bus.adas not in CarState.get_can_parsers(CP, CP_IQ)
+
def test_radar_detection(self):
# Test radar availability detection for cars with radar DBC defined
for radar in (True, False):
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/tests/test_tesla_legacy.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/tests/test_tesla_legacy.py
new file mode 100644
index 00000000..bd9eb9c9
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/tests/test_tesla_legacy.py
@@ -0,0 +1,564 @@
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
+"""
+import pytest
+
+from iqdbc.can import CANPacker, CANParser
+from iqdbc.car import Bus, gen_empty_fingerprint, structs
+from iqdbc.car.fw_versions import match_fw_to_car
+from iqdbc.car.structs import CarParams
+from iqdbc.car.tesla.carcontroller import CarController
+from iqdbc.car.tesla.carstate import CarState
+from iqdbc.car.tesla.fingerprints import FW_VERSIONS
+from iqdbc.car.tesla.interface import CarInterface
+from iqdbc.car.tesla.radar_interface import BOSCH_TRIGGER_MSG, RadarInterface
+from iqdbc.car.tesla.teslacan_legacy import TeslaCANLegacy
+from iqdbc.car.tesla.values import (CAR, DBC, LEGACY_CARS, LEGACY_HW1_CARS, LEGACY_HW2_CARS, TeslaFlags,
+ TeslaLegacySafetyFlags, get_legacy_canbus)
+
+Ecu = CarParams.Ecu
+NANOS = 1_000_000_000
+
+
+def make_params(candidate, fingerprint=None):
+ fingerprint = fingerprint or gen_empty_fingerprint()
+ CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False)
+ CP_IQ = CarInterface.get_params_iq(CP, candidate, fingerprint, [], False, False, False)
+ return CP, CP_IQ
+
+
+def checksum(addr, dat):
+ return ((addr & 0xFF) + ((addr >> 8) & 0xFF) + sum(dat)) & 0xFF
+
+
+class TestLegacyParams:
+ @pytest.mark.parametrize("candidate", LEGACY_CARS)
+ def test_safety_configs(self, candidate):
+ CP, CP_IQ = make_params(candidate)
+
+ assert CP.brand == "tesla"
+ assert CP.steerControlType == structs.CarParams.SteerControlType.angle
+ assert CP.openpilotLongitudinalControl
+ assert all(cfg.safetyModel == CarParams.SafetyModel.teslaLegacy for cfg in CP.safetyConfigs)
+ assert CP.safetyConfigs[0].safetyParam & TeslaLegacySafetyFlags.LONG_CONTROL
+ assert CP_IQ.flags == 0
+ assert not CP.enableBsm
+
+ if candidate in LEGACY_HW1_CARS:
+ assert len(CP.safetyConfigs) == 1
+ assert CP.safetyConfigs[0].safetyParam & TeslaLegacySafetyFlags.HW1
+ assert not CP.safetyConfigs[0].safetyParam & TeslaLegacySafetyFlags.EXTERNAL_PANDA
+ else:
+ hw = TeslaLegacySafetyFlags.HW2 if candidate in LEGACY_HW2_CARS else TeslaLegacySafetyFlags.HW3
+ assert len(CP.safetyConfigs) == 2
+ assert CP.safetyConfigs[0].safetyParam & hw
+ assert not CP.safetyConfigs[0].safetyParam & TeslaLegacySafetyFlags.EXTERNAL_PANDA
+ assert CP.safetyConfigs[1].safetyParam & hw
+ assert CP.safetyConfigs[1].safetyParam & TeslaLegacySafetyFlags.EXTERNAL_PANDA
+
+ def test_hw3_safety_param_values_match_safety_header(self):
+ CP, _ = make_params(CAR.TESLA_MODEL_S_HW3)
+ assert [cfg.safetyParam for cfg in CP.safetyConfigs] == [33, 36]
+
+ def test_no_sdm1_flag(self):
+ fingerprint = gen_empty_fingerprint()
+ CP, _ = make_params(CAR.TESLA_MODEL_S_HW3, fingerprint)
+ assert CP.flags & TeslaFlags.NO_SDM1
+
+ fingerprint[0][0x201] = 5
+ CP, _ = make_params(CAR.TESLA_MODEL_S_HW3, fingerprint)
+ assert not CP.flags & TeslaFlags.NO_SDM1
+
+ def test_radar_unavailable_matches_xnor(self):
+ unavailable = {c for c in LEGACY_CARS if make_params(c)[0].radarUnavailable}
+ assert unavailable == {CAR.TESLA_MODEL_S_HW2}
+
+ def test_modern_platforms_unchanged(self):
+ CP, _ = make_params(CAR.TESLA_MODEL_Y)
+ assert CP.safetyConfigs[0].safetyModel == CarParams.SafetyModel.tesla
+ assert len(CP.safetyConfigs) == 1
+
+
+class TestLegacyBuses:
+ @pytest.mark.parametrize("candidate, expected", [
+ (CAR.TESLA_MODEL_S_HW1, {Bus.party: 0, Bus.ap_party: 2, Bus.pt: 0, Bus.ap_pt: 2, Bus.chassis: 0}),
+ (CAR.TESLA_MODEL_X_HW1, {Bus.party: 0, Bus.ap_party: 2, Bus.pt: 0, Bus.ap_pt: 2, Bus.chassis: 0}),
+ (CAR.TESLA_MODEL_S_HW2, {Bus.party: 0, Bus.ap_party: 2, Bus.pt: 4, Bus.ap_pt: 6, Bus.chassis: 0}),
+ (CAR.TESLA_MODEL_X_HW2, {Bus.party: 0, Bus.ap_party: 2, Bus.pt: 4, Bus.ap_pt: 6, Bus.chassis: 0}),
+ (CAR.TESLA_MODEL_S_HW3, {Bus.party: 0, Bus.ap_party: 2, Bus.pt: 4, Bus.ap_pt: 6, Bus.chassis: 1}),
+ (CAR.TESLA_MODEL_X_HW3, {Bus.party: 0, Bus.ap_party: 2, Bus.pt: 4, Bus.ap_pt: 6, Bus.chassis: 1}),
+ ])
+ def test_parser_buses(self, candidate, expected):
+ CP, CP_IQ = make_params(candidate)
+ parsers = CarState.get_can_parsers(CP, CP_IQ)
+ assert {bus: parser.bus for bus, parser in parsers.items()} == expected
+
+ def test_hw3_radar_is_on_the_external_panda(self):
+ assert get_legacy_canbus(CAR.TESLA_MODEL_S_HW3).radar == 5
+ assert get_legacy_canbus(CAR.TESLA_MODEL_X_HW3).radar == 5
+ assert get_legacy_canbus(CAR.TESLA_MODEL_S_HW2).radar == 1
+
+ def test_dbc_map(self):
+ assert DBC[CAR.TESLA_MODEL_S_HW3][Bus.party] == 'tesla_raven_party'
+ assert DBC[CAR.TESLA_MODEL_S_HW3][Bus.pt] == 'tesla_powertrain'
+ assert DBC[CAR.TESLA_MODEL_S_HW3][Bus.chassis] == 'tesla_can'
+ assert DBC[CAR.TESLA_MODEL_S_HW3][Bus.radar] == 'tesla_radar_continental_generated'
+ assert DBC[CAR.TESLA_MODEL_S_HW1][Bus.pt] == 'tesla_can'
+ assert DBC[CAR.TESLA_MODEL_S_HW2][Bus.pt] == 'tesla_powertrain'
+ assert DBC[CAR.TESLA_MODEL_S_HW1][Bus.radar] == 'tesla_radar_bosch_generated'
+
+
+class TestLegacyFingerprint:
+ @pytest.mark.parametrize("candidate", [c for c in LEGACY_CARS if c in FW_VERSIONS])
+ def test_exact_match(self, candidate):
+ for fw in FW_VERSIONS[candidate][(Ecu.eps, 0x730, None)]:
+ car_fw = [CarParams.CarFw(ecu=Ecu.eps, fwVersion=fw, address=0x730, subAddress=0, brand='tesla')]
+ exact, matches = match_fw_to_car(car_fw, '0' * 17, log=False)
+ assert exact
+ assert matches == {candidate}
+
+ def test_hw3_fw_is_distinct_from_modern_platforms(self):
+ modern = set()
+ for car, ecus in FW_VERSIONS.items():
+ if car not in LEGACY_CARS:
+ modern |= {fw for fws in ecus.values() for fw in fws}
+ for fw in FW_VERSIONS[CAR.TESLA_MODEL_S_HW3][(Ecu.eps, 0x730, None)]:
+ assert fw not in modern
+
+
+def feed(parsers, *groups):
+ messages = [msg for group in groups for msg in group]
+ for parser in parsers.values():
+ parser.update([(NANOS, messages)])
+
+
+def pack(dbc, bus, **messages):
+ packer = CANPacker(dbc)
+ return [packer.make_can_msg(name, bus, values) for name, values in messages.items()]
+
+
+class TestLegacyCarState:
+ @staticmethod
+ def car_state(candidate, fingerprint=None):
+ CP, CP_IQ = make_params(candidate, fingerprint)
+ cs = CarState(CP, CP_IQ)
+ parsers = CarState.get_can_parsers(CP, CP_IQ)
+ cs.update(parsers)
+ return cs, parsers
+
+ def test_hw3_decodes_every_source(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW3)
+
+ feed(
+ parsers,
+ pack('tesla_raven_party', 0, EPAS_sysStatus={
+ "EPAS_internalSAS": 10.0, "EPAS_handsOnLevel": 1, "EPAS_torsionBarTorque": 0.5, "EPAS_eacStatus": 2}),
+ pack('tesla_can', 1,
+ ESP_B={"ESP_vehicleSpeed": 72.0},
+ BrakeMessage={"driverBrakeStatus": 2},
+ DI_state={"DI_cruiseState": 2, "DI_speedUnits": 1, "DI_digitalSpeed": 90},
+ DI_torque2={"DI_gear": 4},
+ GTW_carState={"BC_indicatorLStatus": 1, "DOOR_STATE_FL": 0},
+ STW_ANGLHP_STAT={"StW_AnglHP_Spd": 12.0},
+ RCM_status={"RCM_buckleDriverStatus": 1}),
+ pack('tesla_powertrain', 4, DI_torque1={"DI_pedalPos": 40}),
+ pack('tesla_powertrain', 6, DAS_control={"DAS_aebEvent": 1}),
+ pack('tesla_raven_party', 2, DAS_steeringControl={"DAS_steeringControlType": 2}),
+ )
+ ret, ret_iq = cs.update(parsers)
+
+ assert ret.vEgoRaw == pytest.approx(20.0, abs=0.01)
+ assert ret.brakePressed
+ assert ret.gasPressed
+ assert ret.steeringAngleDeg == pytest.approx(-10.0, abs=0.1)
+ assert ret.steeringTorque == pytest.approx(-0.5, abs=0.01)
+ assert ret.steeringRateDeg == pytest.approx(-12.0, abs=0.5)
+ assert ret.cruiseState.enabled
+ assert ret.cruiseState.available
+ assert ret.cruiseState.speed == pytest.approx(25.0, abs=0.01)
+ assert ret.gearShifter == structs.CarState.GearShifter.drive
+ assert ret.leftBlinker
+ assert not ret.rightBlinker
+ assert not ret.doorOpen
+ assert not ret.seatbeltUnlatched
+ assert ret.stockAeb
+ assert ret.stockLkas
+ assert cs.hands_on_level == 1
+ assert cs.das_control["DAS_aebEvent"] == 1
+
+ def test_model_x_hw3_reads_the_same_sources_as_model_s_hw3(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_X_HW3)
+ feed(
+ parsers,
+ pack('tesla_raven_party', 0, EPAS_sysStatus={"EPAS_internalSAS": 4.0, "EPAS_handsOnLevel": 0, "EPAS_eacStatus": 2}),
+ pack('tesla_can', 1, ESP_B={"ESP_vehicleSpeed": 36.0}, DI_state={"DI_cruiseState": 2, "DI_speedUnits": 1, "DI_digitalSpeed": 40}),
+ pack('tesla_powertrain', 4, DI_torque1={"DI_pedalPos": 0}),
+ )
+ ret, _ = cs.update(parsers)
+ assert ret.vEgoRaw == pytest.approx(10.0, abs=0.01)
+ assert ret.steeringAngleDeg == pytest.approx(-4.0, abs=0.1)
+ assert ret.cruiseState.enabled
+
+ def test_hw3_door_open(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW3)
+ for door in ("DOOR_STATE_FL", "DOOR_STATE_FR", "DOOR_STATE_RL", "DOOR_STATE_RR", "DOOR_STATE_FrontTrunk", "BOOT_STATE"):
+ feed(parsers, pack('tesla_can', 1, GTW_carState={door: 1}))
+ ret, _ = cs.update(parsers)
+ assert ret.doorOpen, door
+
+ feed(parsers, pack('tesla_can', 1, GTW_carState={"DOOR_STATE_FL": 0}))
+ ret, _ = cs.update(parsers)
+ assert not ret.doorOpen
+
+ def test_hw3_cruise_states(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW3)
+ expectations = {1: (False, True, False), 2: (True, True, False), 3: (True, True, True), 5: (False, False, False)}
+ for state, (enabled, available, standstill) in expectations.items():
+ feed(parsers, pack('tesla_can', 1, DI_state={"DI_cruiseState": state, "DI_speedUnits": 0, "DI_digitalSpeed": 50}))
+ ret, _ = cs.update(parsers)
+ assert ret.cruiseState.enabled == enabled, state
+ assert ret.cruiseState.available == available, state
+ assert ret.standstill == standstill, state
+ assert ret.accFaulted == (state == 5)
+ assert ret.cruiseState.speed == pytest.approx(50 * 0.44704, abs=0.01)
+
+ def test_hw3_steer_faults(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW3)
+ feed(parsers, pack('tesla_raven_party', 0, EPAS_sysStatus={"EPAS_eacStatus": 3, "EPAS_handsOnLevel": 0}))
+ ret, _ = cs.update(parsers)
+ assert ret.steerFaultPermanent and not ret.steerFaultTemporary
+
+ feed(parsers, pack('tesla_raven_party', 0, EPAS_sysStatus={"EPAS_eacStatus": 0, "EPAS_eacErrorCode": 9}))
+ ret, _ = cs.update(parsers)
+ assert ret.steerFaultTemporary
+ assert ret.steeringDisengage
+
+ feed(parsers, pack('tesla_raven_party', 0, EPAS_sysStatus={"EPAS_eacStatus": 2, "EPAS_handsOnLevel": 3}))
+ ret, _ = cs.update(parsers)
+ assert ret.steeringDisengage
+
+ def test_hw3_seatbelt_with_sdm1(self):
+ fingerprint = gen_empty_fingerprint()
+ fingerprint[1][0x201] = 5
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW3, fingerprint)
+ feed(parsers, pack('tesla_can', 1, SDM1={"SDM_bcklDrivStatus": 0}))
+ ret, _ = cs.update(parsers)
+ assert ret.seatbeltUnlatched
+
+ feed(parsers, pack('tesla_can', 1, SDM1={"SDM_bcklDrivStatus": 1}))
+ ret, _ = cs.update(parsers)
+ assert not ret.seatbeltUnlatched
+
+ def test_hw3_seatbelt_without_sdm1(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW3)
+ feed(parsers, pack('tesla_can', 1, RCM_status={"RCM_buckleDriverStatus": 0}))
+ ret, _ = cs.update(parsers)
+ assert ret.seatbeltUnlatched
+
+ feed(parsers, pack('tesla_can', 1, RCM_status={"RCM_buckleDriverStatus": 1}))
+ ret, _ = cs.update(parsers)
+ assert not ret.seatbeltUnlatched
+
+ def test_hw1_reads_everything_from_party_bus(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW1)
+ feed(
+ parsers,
+ pack('tesla_can', 0,
+ EPAS_sysStatus={"EPAS_internalSAS": -5.0, "EPAS_handsOnLevel": 0, "EPAS_eacStatus": 2},
+ ESP_B={"ESP_vehicleSpeed": 36.0},
+ BrakeMessage={"driverBrakeStatus": 1},
+ DI_state={"DI_cruiseState": 2, "DI_speedUnits": 1, "DI_digitalSpeed": 40},
+ DI_torque1={"DI_pedalPos": 0},
+ DI_torque2={"DI_gear": 1},
+ GTW_carState={"BC_indicatorRStatus": 1},
+ SDM1={"SDM_bcklDrivStatus": 1},
+ STW_ANGLHP_STAT={"StW_AnglHP_Spd": 0}),
+ pack('tesla_can', 2,
+ DAS_control={"DAS_aebEvent": 0},
+ DAS_steeringControl={"DAS_steeringControlType": 0}),
+ )
+ ret, _ = cs.update(parsers)
+
+ assert ret.vEgoRaw == pytest.approx(10.0, abs=0.01)
+ assert not ret.brakePressed
+ assert not ret.gasPressed
+ assert ret.steeringAngleDeg == pytest.approx(5.0, abs=0.1)
+ assert ret.gearShifter == structs.CarState.GearShifter.park
+ assert ret.rightBlinker and not ret.leftBlinker
+ assert not ret.stockAeb and not ret.stockLkas
+
+ def test_model_x_hw1_blinkers_come_from_stalk(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_X_HW1)
+ feed(parsers, pack('tesla_can', 0, STW_ACTN_RQ={"TurnIndLvr_Stat": 2}))
+ ret, _ = cs.update(parsers)
+ assert ret.rightBlinker and not ret.leftBlinker
+
+ feed(parsers, pack('tesla_can', 0, STW_ACTN_RQ={"TurnIndLvr_Stat": 1}))
+ ret, _ = cs.update(parsers)
+ assert ret.leftBlinker and not ret.rightBlinker
+
+ def test_hw2_gas_comes_from_powertrain_bus(self):
+ cs, parsers = self.car_state(CAR.TESLA_MODEL_S_HW2)
+ feed(parsers, pack('tesla_powertrain', 4, DI_torque1={"DI_pedalPos": 20}))
+ ret, _ = cs.update(parsers)
+ assert ret.gasPressed
+
+ feed(parsers, pack('tesla_powertrain', 0, DI_torque1={"DI_pedalPos": 0}))
+ ret, _ = cs.update(parsers)
+ assert ret.gasPressed
+
+
+class FakeActuators:
+ def __init__(self, angle=0.0, accel=0.0):
+ self.steeringAngleDeg = angle
+ self.accel = accel
+
+ def as_builder(self):
+ return self
+
+
+class FakeCarControl:
+ def __init__(self, lat_active=True, long_active=True, cancel=False, angle=0.0, accel=0.0):
+ self.actuators = FakeActuators(angle, accel)
+ self.latActive = lat_active
+ self.longActive = long_active
+ self.cruiseControl = type("Cruise", (), {"cancel": cancel})()
+
+
+class FakeCarState:
+ hands_on_level = 0
+ das_control = {"DAS_controlCounter": 0}
+ out = type("Out", (), {"vEgoRaw": 20.0, "steeringAngleDeg": 0.0, "vEgo": 20.0, "gasPressed": False})()
+
+
+def make_controller(candidate):
+ CP, CP_IQ = make_params(candidate)
+ parsers = CarState.get_can_parsers(CP, CP_IQ)
+ dbc_names = {bus: parser.dbc_name for bus, parser in parsers.items()}
+ return CarController(dbc_names, CP, CP_IQ)
+
+
+class TestLegacyCarController:
+ @staticmethod
+ def run_frames(controller, frames, cc):
+ sent = {}
+ for frame in range(frames):
+ _, can_sends = controller.update(cc, structs.IQCarControl(), FakeCarState(), 0)
+ sent[frame] = can_sends
+ return sent
+
+ def test_hw3_message_addresses_and_buses(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW3)
+ sent = self.run_frames(controller, 20, FakeCarControl())
+
+ steering = [m for f in sent.values() for m in f if m[0] == 0x488]
+ eac = [m for f in sent.values() for m in f if m[0] == 0x27d]
+ long = [m for f in sent.values() for m in f if m[0] == 0x2bf]
+
+ assert len(steering) == 10
+ assert len(eac) == 2
+ assert len(long) == 5
+ assert {m[2] for m in steering} == {0}
+ assert {m[2] for m in eac} == {0}
+ assert {m[2] for m in long} == {4}
+ assert not [m for f in sent.values() for m in f if m[0] == 0x2b9]
+
+ def test_hw1_sends_long_on_party_bus_without_eac_monitor(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW1)
+ sent = self.run_frames(controller, 20, FakeCarControl())
+
+ addrs = {m[0] for f in sent.values() for m in f}
+ assert addrs == {0x488, 0x2b9}
+ assert {m[2] for f in sent.values() for m in f} == {0}
+
+ def test_hw2_message_addresses_and_buses(self):
+ controller = make_controller(CAR.TESLA_MODEL_X_HW2)
+ sent = self.run_frames(controller, 20, FakeCarControl())
+
+ by_addr = {}
+ for f in sent.values():
+ for m in f:
+ by_addr.setdefault(m[0], set()).add(m[2])
+ assert by_addr == {0x488: {0}, 0x27d: {0}, 0x2bf: {4}}
+
+ def test_steering_checksum_and_counter(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW3)
+ sent = self.run_frames(controller, 8, FakeCarControl(angle=5.0))
+ steering = [m for f in sent.values() for m in f if m[0] == 0x488]
+
+ parser = CANParser('tesla_raven_party', [("DAS_steeringControl", 50)], 0)
+ counters = []
+ for i, (addr, dat, bus) in enumerate(steering):
+ assert dat[3] == checksum(addr, dat[:3])
+ parser.update([(NANOS + i * 20_000_000, [(addr, dat, bus)])])
+ counters.append(int(parser.vl["DAS_steeringControl"]["DAS_steeringControlCounter"]))
+ assert counters == [0, 1, 2, 3]
+
+ def test_steering_is_idle_when_lateral_inactive(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW3)
+ sent = self.run_frames(controller, 4, FakeCarControl(lat_active=False, angle=5.0))
+ steering = [m for f in sent.values() for m in f if m[0] == 0x488]
+
+ parser = CANParser('tesla_raven_party', [("DAS_steeringControl", 50)], 0)
+ for i, (addr, dat, bus) in enumerate(steering):
+ parser.update([(NANOS + i * 20_000_000, [(addr, dat, bus)])])
+ assert parser.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 0
+
+ def test_steering_angle_is_negated_and_rate_limited(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW3)
+ angles = []
+ parser = CANParser('tesla_raven_party', [("DAS_steeringControl", 50)], 0)
+ for frame in range(20):
+ _, can_sends = controller.update(FakeCarControl(angle=30.0), structs.IQCarControl(), FakeCarState(), 0)
+ for addr, dat, bus in can_sends:
+ if addr == 0x488:
+ parser.update([(NANOS + frame * 10_000_000, [(addr, dat, bus)])])
+ angles.append(parser.vl["DAS_steeringControl"]["DAS_steeringAngleRequest"])
+
+ assert angles[0] <= 0.0
+ assert angles[-1] < angles[0]
+ assert all(abs(b - a) <= 5.0 + 0.1 for a, b in zip(angles, angles[1:], strict=False))
+
+ def test_longitudinal_checksum_and_state(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW3)
+ _, can_sends = controller.update(FakeCarControl(accel=1.0), structs.IQCarControl(), FakeCarState(), 0)
+ long = [m for m in can_sends if m[0] == 0x2bf]
+ assert len(long) == 1
+
+ addr, dat, bus = long[0]
+ assert dat[7] == checksum(0x2b9, dat[:7])
+
+ parser = CANParser('tesla_powertrain', [("DAS_control", 25)], 4)
+ parser.update([(NANOS, [(addr, dat, bus)])])
+ values = parser.vl["DAS_control"]
+ assert values["DAS_accState"] == 4
+ assert values["DAS_accelMax"] == pytest.approx(1.0, abs=0.05)
+ assert values["DAS_accelMin"] == pytest.approx(1.0, abs=0.05)
+ assert values["DAS_aebEvent"] == 0
+
+ def test_cancel_sends_silent_cancel_state(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW3)
+ _, can_sends = controller.update(FakeCarControl(cancel=True, long_active=False), structs.IQCarControl(), FakeCarState(), 0)
+ addr, dat, bus = next(m for m in can_sends if m[0] == 0x2bf)
+
+ parser = CANParser('tesla_powertrain', [("DAS_control", 25)], 4)
+ parser.update([(NANOS, [(addr, dat, bus)])])
+ assert parser.vl["DAS_control"]["DAS_accState"] == 13
+
+ def test_accel_is_clipped_to_safety_limits(self):
+ controller = make_controller(CAR.TESLA_MODEL_S_HW3)
+ _, can_sends = controller.update(FakeCarControl(accel=10.0), structs.IQCarControl(), FakeCarState(), 0)
+ addr, dat, bus = next(m for m in can_sends if m[0] == 0x2bf)
+
+ parser = CANParser('tesla_powertrain', [("DAS_control", 25)], 4)
+ parser.update([(NANOS, [(addr, dat, bus)])])
+ assert parser.vl["DAS_control"]["DAS_accelMax"] <= 2.0 + 0.05
+
+
+class TestTeslaCanLegacy:
+ @staticmethod
+ def make(candidate=CAR.TESLA_MODEL_S_HW3):
+ canbus = get_legacy_canbus(candidate)
+ packers = {canbus.party: CANPacker(DBC[candidate][Bus.party]), canbus.powertrain: CANPacker(DBC[candidate][Bus.pt])}
+ return TeslaCANLegacy(packers, canbus)
+
+ def test_jerk_limits_collapse_on_gas_and_ramp_back(self):
+ can = self.make()
+ can.create_longitudinal_command(4, 0.5, 0, 20.0, True, True)
+ assert can.jerk_upper == 0.0
+ assert can.jerk_lower == 0.0
+
+ for i in range(1, 6):
+ can.create_longitudinal_command(4, 0.5, i, 20.0, True, False)
+ assert 0.0 < can.jerk_upper < 4.9
+ assert -4.9 < can.jerk_lower < 0.0
+ assert can.jerk_upper == pytest.approx(-can.jerk_lower)
+
+ for i in range(1000):
+ can.create_longitudinal_command(4, 0.5, i % 8, 20.0, True, False)
+ assert can.jerk_upper == pytest.approx(4.9)
+ assert can.jerk_lower == pytest.approx(-4.9)
+
+ def test_set_speed_follows_accel_sign_when_active(self):
+ can = self.make()
+ parser = CANParser('tesla_powertrain', [("DAS_control", 25)], 4)
+
+ for accel, expect_zero in ((-0.5, True), (0.5, False)):
+ addr, dat, bus = can.create_longitudinal_command(4, accel, 0, 20.0, True, False)
+ parser.update([(NANOS, [(addr, dat, bus)])])
+ set_speed = parser.vl["DAS_control"]["DAS_setSpeed"]
+ assert (set_speed == 0) == expect_zero
+
+ def test_idle_set_speed_tracks_ego(self):
+ can = self.make()
+ parser = CANParser('tesla_powertrain', [("DAS_control", 25)], 4)
+ addr, dat, bus = can.create_longitudinal_command(4, 0.0, 0, 20.0, False, False)
+ parser.update([(NANOS, [(addr, dat, bus)])])
+ assert parser.vl["DAS_control"]["DAS_setSpeed"] == pytest.approx(72.0, abs=0.5)
+
+
+class TestLegacyRadar:
+ def test_hw3_uses_continental_radar_on_external_panda_bus(self):
+ CP, CP_IQ = make_params(CAR.TESLA_MODEL_S_HW3)
+ ri = RadarInterface(CP, CP_IQ)
+ assert not ri.bosch_radar
+ assert ri.rcp.bus == 5
+ assert ri.trigger_msg == 0x410 + 80 - 1
+
+ @pytest.mark.parametrize("candidate", [CAR.TESLA_MODEL_S_HW1, CAR.TESLA_MODEL_X_HW1, CAR.TESLA_MODEL_X_HW2])
+ def test_bosch_radar_parser(self, candidate):
+ CP, CP_IQ = make_params(candidate)
+ ri = RadarInterface(CP, CP_IQ)
+ assert ri.bosch_radar
+ assert ri.rcp.bus == 1
+ assert ri.trigger_msg == BOSCH_TRIGGER_MSG
+ assert ri.num_points == 32
+
+ def test_s_hw2_radar_is_off(self):
+ CP, CP_IQ = make_params(CAR.TESLA_MODEL_S_HW2)
+ ri = RadarInterface(CP, CP_IQ)
+ assert ri.radar_off_can
+
+ def test_bosch_points_are_filtered_and_held_between_frames(self):
+ CP, CP_IQ = make_params(CAR.TESLA_MODEL_S_HW1)
+ ri = RadarInterface(CP, CP_IQ)
+ dbc = DBC[CAR.TESLA_MODEL_S_HW1][Bus.radar]
+ packer = CANPacker(dbc)
+
+ def frame(good_dist, exist, nanos):
+ msgs = [packer.make_can_msg("TeslaRadarSguInfo", 1, {"RADC_HWFail": 0})]
+ for i in range(32):
+ a = {"Index": i % 2, "Tracked": 0}
+ b = {"Index2": i % 2}
+ if i == 0:
+ a = {"LongDist": good_dist, "LatDist": 1.0, "LongSpeed": -2.0, "ProbExist": exist, "Tracked": 1,
+ "Meas": 1, "Index": 1, "LongAccel": 0.5}
+ b = {"LatSpeed": 0.25, "Index2": 1}
+ msgs.append(packer.make_can_msg(f"RadarPoint{i}_A", 1, a))
+ msgs.append(packer.make_can_msg(f"RadarPoint{i}_B", 1, b))
+ return [(nanos, msgs)]
+
+ ri.update(frame(40.0, 90.0, NANOS))
+ ri.update(frame(40.0, 90.0, NANOS + 125_000_000))
+ rr = ri.update(frame(40.0, 90.0, NANOS + 250_000_000))
+ assert rr is not None
+ assert len(rr.points) == 1
+ point = rr.points[0]
+ assert point.dRel == pytest.approx(40.0, abs=0.1)
+ assert point.yRel == pytest.approx(1.0, abs=0.2)
+ assert point.vRel == pytest.approx(-2.0, abs=0.1)
+
+ rr = ri.update(frame(0.0, 90.0, NANOS + 375_000_000))
+ assert rr is not None and len(rr.points) == 0
+
+ rr = ri.update(frame(40.0, 10.0, NANOS + 500_000_000))
+ assert rr is not None and len(rr.points) == 0
+
+
+@pytest.mark.parametrize("candidate", LEGACY_CARS)
+def test_modern_tesla_options_are_not_applied(candidate):
+ from iqdbc.lvbs.car.interfaces import _apply_tesla_options
+ CP, CP_IQ = make_params(candidate)
+ _apply_tesla_options(CP, CP_IQ, {"IQTeslaTorqueBlend": "1", "IQTeslaFsdVisualization": "1"})
+ assert CP_IQ.flags == 0
+ assert CP_IQ.iqSafetyFlags == 0
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/values.py b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/values.py
index 2367da75..b3b6c84a 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tesla/values.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tesla/values.py
@@ -78,6 +78,74 @@ class CAR(Platforms):
[TeslaCarHW4ModelSXDocs("Tesla Model X (with HW4) 2024")],
CarSpecs(mass=2495., wheelbase=2.960, steerRatio=12.0),
)
+ TESLA_MODEL_X_HW1 = TeslaPlatformConfig(
+ [CarDocs("Tesla Model X (with HW1) 2014-16", "All", car_parts=CarParts.common([CarHarness.tesla_model_x_hw1]))],
+ CarSpecs(mass=2447., wheelbase=2.960, steerRatio=15.0),
+ {
+ Bus.chassis: 'tesla_can',
+ Bus.party: 'tesla_can',
+ Bus.pt: 'tesla_can',
+ Bus.radar: 'tesla_radar_bosch_generated',
+ },
+ )
+ TESLA_MODEL_X_HW2 = TeslaPlatformConfig(
+ [CarDocs("Tesla Model X (with HW2) 2016-19", "All", car_parts=CarParts.common([CarHarness.tesla_model_sx_hw2]))],
+ CarSpecs(mass=2100., wheelbase=2.960, steerRatio=15.0),
+ {
+ Bus.chassis: 'tesla_can',
+ Bus.party: 'tesla_can',
+ Bus.pt: 'tesla_powertrain',
+ Bus.radar: 'tesla_radar_bosch_generated',
+ },
+ )
+ TESLA_MODEL_S_HW1 = TeslaPlatformConfig(
+ [CarDocs("Tesla Model S (with HW1) 2014-16", "All", car_parts=CarParts.common([CarHarness.tesla_model_s_hw1]))],
+ CarSpecs(mass=2100., wheelbase=2.960, steerRatio=15.0),
+ {
+ Bus.chassis: 'tesla_can',
+ Bus.party: 'tesla_can',
+ Bus.pt: 'tesla_can',
+ Bus.radar: 'tesla_radar_bosch_generated',
+ },
+ )
+ TESLA_MODEL_S_HW2 = TeslaPlatformConfig(
+ [CarDocs("Tesla Model S (with HW2) 2017-19", "All", car_parts=CarParts.common([CarHarness.tesla_model_sx_hw2]))],
+ CarSpecs(mass=2100., wheelbase=2.960, steerRatio=15.0),
+ {
+ Bus.chassis: 'tesla_can',
+ Bus.party: 'tesla_can',
+ Bus.pt: 'tesla_powertrain',
+ Bus.radar: 'tesla_radar_bosch_generated',
+ },
+ )
+ TESLA_MODEL_X_HW3 = TeslaPlatformConfig(
+ [CarDocs("Tesla Model X (with HW3) 2021-23", "All", car_parts=CarParts.common([CarHarness.tesla_model_sx_hw3]))],
+ CarSpecs(mass=2468., wheelbase=2.960, steerRatio=15.0),
+ {
+ Bus.chassis: 'tesla_can',
+ Bus.party: 'tesla_raven_party',
+ Bus.pt: 'tesla_powertrain',
+ Bus.radar: 'tesla_radar_continental_generated',
+ },
+ )
+ TESLA_MODEL_S_HW3 = TeslaPlatformConfig(
+ [CarDocs("Tesla Model S (with HW3) 2020-23", "All", car_parts=CarParts.common([CarHarness.tesla_model_sx_hw3]))],
+ CarSpecs(mass=2100., wheelbase=2.960, steerRatio=15.0),
+ {
+ Bus.chassis: 'tesla_can',
+ Bus.party: 'tesla_raven_party',
+ Bus.pt: 'tesla_powertrain',
+ Bus.radar: 'tesla_radar_continental_generated',
+ },
+ )
+
+
+LEGACY_CARS = (CAR.TESLA_MODEL_S_HW1, CAR.TESLA_MODEL_S_HW2, CAR.TESLA_MODEL_S_HW3, CAR.TESLA_MODEL_X_HW1, CAR.TESLA_MODEL_X_HW2,
+ CAR.TESLA_MODEL_X_HW3)
+LEGACY_HW1_CARS = (CAR.TESLA_MODEL_S_HW1, CAR.TESLA_MODEL_X_HW1)
+LEGACY_HW2_CARS = (CAR.TESLA_MODEL_S_HW2, CAR.TESLA_MODEL_X_HW2)
+LEGACY_HW3_CARS = (CAR.TESLA_MODEL_S_HW3, CAR.TESLA_MODEL_X_HW3)
+LEGACY_BOSCH_RADAR_CARS = LEGACY_HW1_CARS + LEGACY_HW2_CARS
# Cars with this EPS FW have a 2-bit DAS_steeringControlType and use TeslaFlags.LEGACY_DAS_STEERING
@@ -240,6 +308,11 @@ FW_QUERY_CONFIG = FwQueryConfig(
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
bus=0,
),
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
+ bus=0,
+ ),
],
match_fw_to_car_fuzzy=match_fw_to_car_fuzzy,
)
@@ -251,6 +324,24 @@ class CANBUS:
autopilot_party = 2
+@dataclass(frozen=True)
+class LegacyCanBus:
+ party: int
+ chassis: int
+ powertrain: int
+ radar: int
+ autopilot_party: int
+ autopilot_powertrain: int
+
+
+def get_legacy_canbus(candidate: str) -> LegacyCanBus:
+ if candidate in LEGACY_HW1_CARS:
+ return LegacyCanBus(party=0, chassis=0, powertrain=0, radar=1, autopilot_party=2, autopilot_powertrain=2)
+ if candidate in LEGACY_HW3_CARS:
+ return LegacyCanBus(party=0, chassis=1, powertrain=4, radar=5, autopilot_party=2, autopilot_powertrain=6)
+ return LegacyCanBus(party=0, chassis=0, powertrain=4, radar=1, autopilot_party=2, autopilot_powertrain=6)
+
+
GEAR_MAP = {
"DI_GEAR_INVALID": CarState.GearShifter.unknown,
"DI_GEAR_P": CarState.GearShifter.park,
@@ -288,6 +379,7 @@ class CarControllerParams:
JERK_LIMIT_MAX = 4.9 # m/s^3, ACC faults at 5.0
JERK_LIMIT_MIN = -4.9 # m/s^3, ACC faults at 5.0
JERK_UP = 1.0 # m/s^3
+ JERK_RAMP_RATE = JERK_LIMIT_MAX * 0.002
class TeslaSafetyFlags(IntFlag):
@@ -296,6 +388,14 @@ class TeslaSafetyFlags(IntFlag):
HW4_GEN2 = 4
+class TeslaLegacySafetyFlags(IntFlag):
+ LONG_CONTROL = 1
+ EXTERNAL_PANDA = 4
+ HW1 = 8
+ HW2 = 16
+ HW3 = 32
+
+
class TeslaFlags(IntFlag):
LONG_CONTROL = 1
LEGACY_DAS_STEERING = 2
@@ -304,6 +404,7 @@ class TeslaFlags(IntFlag):
HW4_GEN2 = 8
# blinkers and the seatbelt buckle are only on the VEHICLE bus, which needs its CAN lines tapped
HW4_GEN2_VEHICLE_BUS = 16
+ NO_SDM1 = 32
DBC = CAR.create_dbc_map()
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tests/routes.py b/artifacts/package_sources/iqdbc/iqdbc/car/tests/routes.py
index 6ad0f6af..4a4e4778 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tests/routes.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tests/routes.py
@@ -37,6 +37,13 @@ route_exempt_cars = [
TOYOTA.TOYOTA_COROLLA,
TOYOTA.TOYOTA_RAV4H,
+ TESLA.TESLA_MODEL_S_HW1,
+ TESLA.TESLA_MODEL_S_HW2,
+ TESLA.TESLA_MODEL_S_HW3,
+ TESLA.TESLA_MODEL_X_HW1,
+ TESLA.TESLA_MODEL_X_HW2,
+ TESLA.TESLA_MODEL_X_HW3,
+
HONDA.HONDA_CLARITY,
GM.CHEVROLET_BOLT_NON_ACC,
GM.CHEVROLET_BOLT_NON_ACC_1ST_GEN,
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/tests/test_fw_fingerprint.py b/artifacts/package_sources/iqdbc/iqdbc/car/tests/test_fw_fingerprint.py
index 7d36179d..2a2b2f4c 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/tests/test_fw_fingerprint.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/tests/test_fw_fingerprint.py
@@ -287,7 +287,7 @@ class TestFwFingerprintTiming:
print(f'get_vin {name} case, query time={self.total_time / self.N} seconds')
def test_fw_query_timing(self, subtests, mocker):
- total_ref_time = {1: 7.8, 2: 8.4}
+ total_ref_time = {1: 7.9, 2: 8.5}
brand_ref_times = {
1: {
'gm': 1.0,
@@ -299,7 +299,7 @@ class TestFwFingerprintTiming:
'mazda': 0.1,
'nissan': 0.8,
'subaru': 0.65,
- 'tesla': 0.2,
+ 'tesla': 0.3,
'toyota': 0.7,
'volkswagen': 0.95,
'rivian': 0.3,
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/torque_data/override.toml b/artifacts/package_sources/iqdbc/iqdbc/car/torque_data/override.toml
index e7b2cfea..1be990a4 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/torque_data/override.toml
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/torque_data/override.toml
@@ -18,6 +18,12 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
# Tesla angle based controllers
"TESLA_MODEL_3" = [nan, 2.5, nan]
"TESLA_MODEL_Y" = [nan, 2.5, nan]
+"TESLA_MODEL_X_HW1" = [nan, 2.5, nan]
+"TESLA_MODEL_X_HW2" = [nan, 2.5, nan]
+"TESLA_MODEL_S_HW1" = [nan, 2.5, nan]
+"TESLA_MODEL_S_HW2" = [nan, 2.5, nan]
+"TESLA_MODEL_S_HW3" = [nan, 2.5, nan]
+"TESLA_MODEL_X_HW3" = [nan, 2.5, nan]
# Guess
"FORD_BRONCO_SPORT_MK1" = [nan, 1.5, nan]
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carcontroller.py b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carcontroller.py
index 4bd61e9e..9623b639 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carcontroller.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carcontroller.py
@@ -18,7 +18,7 @@ from iqdbc.car.volkswagen.pq_radar_handler import PQRadarHandler
from iqdbc.car.volkswagen.values import (
CanBus, CarControllerParams, MQB_A0_CARS, VolkswagenFlags, VolkswagenFlagsIQ, apply_pq_stopping_accel,
)
-from iqdbc.car.volkswagen.mebutils import LongControlJerk, LongControlLimit, LatControlCurvature, LongStopHold
+from iqdbc.car.volkswagen.mebutils import CurvatureSmoother, LongControlJerk, LongControlLimit, LatControlCurvature, LongStopHold
from iqdbc.car.vehicle_model import VehicleModel
iqpilot_path = os.path.join(os.path.dirname(__file__), '..', '..', '..')
@@ -42,6 +42,29 @@ def dVisual(CCS, CS):
decelV = False
return decelV
+# The MQB ACC_02.ACC_Abstandsindex and PQ ACC_GRA_Anzeige.ACA_gemZeitl ("measured time gap") lead marker is
+# a time headway on a log scale, not a distance. Fitted to the stock radar's own output against lead distance
+# and speed on 8 stock-ACC cars (Golf, Jetta, Atlas, Taos, Arteon and Kodiaq digital clusters, Octavia analog,
+# Sharan PQ): position = 0.17 + 0.465 * ln(headway), from about 0.7 s (closest) to 6 s (farthest). Stopped,
+# the radar still reports a finite headway, as if the car were rolling at about 3.5 m/s.
+LEAD_HEADWAY_POSITION_OFFSET = 0.17
+LEAD_HEADWAY_POSITION_PER_LOG_S = 0.465
+LEAD_HEADWAY_MIN_SPEED = 3.5 # m/s
+# Raw code at the closest and farthest positions: 4-bit analog MQB and PQ clusters use 1-15, digital MQB
+# clusters (Kombi_03.KBI_Variante) the 10-bit 34-972 the stock radar sends. 0 means no lead on both.
+LEAD_INDEX_RANGE_ANALOG = (1, 15)
+LEAD_INDEX_RANGE_DIGITAL = (34, 972)
+
+
+def lead_headway_index(lead_distance: float, v_ego: float, digital_cluster: bool) -> int:
+ if lead_distance <= 0:
+ return 0
+ headway = lead_distance / max(v_ego, LEAD_HEADWAY_MIN_SPEED)
+ position = clip(LEAD_HEADWAY_POSITION_OFFSET + LEAD_HEADWAY_POSITION_PER_LOG_S * np.log(headway), 0.0, 1.0)
+ closest, farthest = LEAD_INDEX_RANGE_DIGITAL if digital_cluster else LEAD_INDEX_RANGE_ANALOG
+ return int(round(closest + position * (farthest - closest)))
+
+
def accel_during_driver_override(accel: float, gas_pressed: bool, keep_long_active: bool) -> float:
return 0.0 if gas_pressed and keep_long_active else accel
@@ -64,8 +87,11 @@ def ea_blinker_command(left_request, right_request, left_active, right_active):
class CarController(CarControllerBase):
def __init__(self, dbc_names, CP, CP_IQ):
- from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
- self._iq_lvbs_alc = import_verified_module("iqpilot_alc_private", "iqpilot_private.konn3kt.iqlvbs.alc")
+ try:
+ from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
+ self._iq_lvbs_alc = import_verified_module("iqpilot_alc_private", "iqpilot_private.konn3kt.iqlvbs.alc")
+ except Exception:
+ self._iq_lvbs_alc = None
super().__init__(dbc_names, CP, CP_IQ)
self._params = Params()
self.CCP = CarControllerParams(CP)
@@ -102,6 +128,10 @@ class CarController(CarControllerBase):
self.steering_power_last = 0
self.long_jerk_control = LongControlJerk(dt=(DT_CTRL * self.CCP.ACC_CONTROL_STEP)) if self.CP.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO) else None
self.long_limit_control = LongControlLimit(dt=(DT_CTRL * self.CCP.ACC_CONTROL_STEP)) if self.CP.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO) else None
+ self.curvature_smoother = (
+ CurvatureSmoother(self.CCP.CURVATURE_SMOOTH_SPEED_BP, self.CCP.CURVATURE_SMOOTH_TAU_V, DT_CTRL * self.CCP.STEER_STEP)
+ if self.CP.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO) else None
+ )
self.long_stop_hold = LongStopHold(dt=(DT_CTRL * self.CCP.ACC_CONTROL_STEP)) if self.CP.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO) else None
self.gra_acc_counter_last = None
self.gra_cancel_ticks = 0
@@ -215,7 +245,7 @@ class CarController(CarControllerBase):
self._pq_patch_checked = True
self.eps_timer_workaround = not self._params.get_bool("VwPqEpsPatched")
blend_active = bool(self.blend_stock_radar and self.radar_handler is not None and self.CP.openpilotLongitudinalControl)
- AngleLateralControl = self._iq_lvbs_alc.angle_lateral_control_enabled(self, CS)
+ AngleLateralControl = self._iq_lvbs_alc.angle_lateral_control_enabled(self, CS) if self._iq_lvbs_alc else False
self.entering = CS.vw_iq_lvbs_alc_entering
self.active = CS.vw_iq_lvbs_alc_active
@@ -239,6 +269,7 @@ class CarController(CarControllerBase):
apply_curvature = apply_curvature + (CS.out.steeringCurvature - (CC.currentCurvature - CC.rollCompensation))
else:
apply_curvature = actuators.curvature + (CS.out.steeringCurvature - CC.currentCurvature)
+ apply_curvature = self.curvature_smoother.update(apply_curvature, CS.out.vEgo)
apply_curvature = apply_std_curvature_limits(apply_curvature, self.apply_curvature_last, CS.out.vEgoRaw, CS.out.steeringCurvature,
CS.out.steeringPressed, self.CCP.STEER_STEP, CC.latActive, self.CCP.CURVATURE_LIMITS)
@@ -261,12 +292,13 @@ class CarController(CarControllerBase):
hca_enabled = False
apply_curvature = 0.
steering_power = 0
+ self.curvature_smoother.reset(apply_curvature)
can_sends.append(self.CCS.create_steering_control(self.packer_pt, self.CAN.pt, apply_curvature, hca_enabled, steering_power))
self.apply_curvature_last = apply_curvature
self.steering_power_last = steering_power
else:
- if CC.latActive and not AngleLateralControl:
+ if CC.latActive and CC.enabled and not AngleLateralControl:
torque_scale = self._get_mqb_steering_torque_scale(CS.out.vEgo, iq_mqb_steering_lockout)
new_torque = int(round(actuators.torque * self.CCP.STEER_MAX * torque_scale))
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.CCP)
@@ -322,8 +354,9 @@ class CarController(CarControllerBase):
ea_simulated_torque = CS.out.steeringTorque
can_sends.append(self.CCS.create_eps_update(self.packer_pt, self.CAN.cam, CS.eps_stock_values, ea_simulated_torque))
- self._iq_lvbs_alc.update_vw_alc(self, CC, CS, actuators, can_sends, apply_torque)
- if self.frame % self.CCP.STEER_STEP == 0:
+ if self._iq_lvbs_alc:
+ self._iq_lvbs_alc.update_vw_alc(self, CC, CS, actuators, can_sends, apply_torque)
+ if self._iq_lvbs_alc and self.frame % self.CCP.STEER_STEP == 0:
self._iq_lvbs_alc.append_private_apd(self, CC_IQ, can_sends)
if self.CP.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO) and self.CP.flags & VolkswagenFlags.STOCK_KLR_PRESENT:
@@ -494,9 +527,9 @@ class CarController(CarControllerBase):
hud_control.leadVisible, hud_control.leadDistanceBars + 1, show_distance_bars,
CS.esp_hold_confirmation, distance, gap, fcw_alert, acc_hud_event, speed_limit))
else:
- # MLB scales the raw lead distance against the set follow gap in the packer, the others clamp to a bar count
+ # MLB scales the raw lead distance against the set follow gap in the packer, MQB and PQ send a headway index
leadDistance = hud_control.leadDistance if self.CCS is mlbcan else \
- (min(15, hud_control.leadDistance) if hud_control.leadDistance != 0 else 0)
+ lead_headway_index(hud_control.leadDistance, CS.out.vEgo, CS.upscale_lead_car_signal)
self.leadDistanceBars = min(3, hud_control.leadDistanceBars)
acc_hud_status = self.CCS.acc_hud_status_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive and hud_engaged,
hud_override)
@@ -509,7 +542,7 @@ class CarController(CarControllerBase):
self.leadDistanceBars, fcw_alert, lead_visible or fcw_alert, self.unavailable,
decel, d_unresponsive, **hud_kwargs))
- if self.CP.flags & VolkswagenFlags.PQ:
+ if self._iq_lvbs_alc and self.CP.flags & VolkswagenFlags.PQ:
self._iq_lvbs_alc.update_turn_signals(self, CC, CS, can_sends)
if self.CP.openpilotLongitudinalControl and (self.CP.flags & VolkswagenFlags.PQ):
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carstate.py b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carstate.py
index 06777838..2d70977f 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carstate.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/carstate.py
@@ -32,8 +32,11 @@ class CarState(CarStateBase):
DRIVER_TORQUE_TAU = 0.10
def __init__(self, CP, CP_IQ):
- from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
- self._iq_lvbs_alc = import_verified_module("iqpilot_alc_private", "iqpilot_private.konn3kt.iqlvbs.alc")
+ try:
+ from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
+ self._iq_lvbs_alc = import_verified_module("iqpilot_alc_private", "iqpilot_private.konn3kt.iqlvbs.alc")
+ except Exception:
+ self._iq_lvbs_alc = None
super().__init__(CP, CP_IQ)
self._params = Params()
self.frame = 0
@@ -102,10 +105,11 @@ class CarState(CarStateBase):
self.motor1_stock = {}
self.motor3_frame = 0
self.motor1_frame = 0
- self._odometer_store = self._iq_lvbs_alc.create_vehicle_odometer_store(CP, self._params)
+ self._odometer_store = self._iq_lvbs_alc.create_vehicle_odometer_store(CP, self._params) if self._iq_lvbs_alc else None
def _update_odometer(self, ret: structs.CarState, raw_km: float) -> None:
- """Publish the cluster value while proprietary Konn3kt code owns persistence."""
+ if self._odometer_store is None:
+ return
odometer_km = self._odometer_store.record(raw_km)
if odometer_km is not None:
ret.odometer = odometer_km
@@ -138,13 +142,16 @@ class CarState(CarStateBase):
return button_events
def _update_mqb_iq_alc_state(self, pt_cp):
- self._iq_lvbs_alc.update_mqb_carstate_alc_state(self, pt_cp)
+ if self._iq_lvbs_alc:
+ self._iq_lvbs_alc.update_mqb_carstate_alc_state(self, pt_cp)
def _update_mlb_iq_alc_state(self, pt_cp):
- self._iq_lvbs_alc.update_mlb_carstate_alc_state(self, pt_cp)
+ if self._iq_lvbs_alc:
+ self._iq_lvbs_alc.update_mlb_carstate_alc_state(self, pt_cp)
def _update_pq_iq_alc_state(self, pt_cp):
- self._iq_lvbs_alc.update_pq_carstate_alc_state(self, pt_cp)
+ if self._iq_lvbs_alc:
+ self._iq_lvbs_alc.update_pq_carstate_alc_state(self, pt_cp)
def update(self, can_parsers) -> tuple[structs.CarState, structs.IQCarState]:
pt_cp = can_parsers[Bus.pt]
@@ -317,7 +324,7 @@ class CarState(CarStateBase):
ret.steeringAngleDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradwinkel"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradwinkel"])]
ret.steeringRateDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradw_Geschw"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradw_Geschw"])]
ret.steeringTorque = pt_cp.vl["LH_EPS_03"]["EPS_Lenkmoment"] * (1, -1)[int(pt_cp.vl["LH_EPS_03"]["EPS_VZ_Lenkmoment"])]
- driver_override_threshold = self._iq_lvbs_alc.vw_driver_override_threshold_cnm(self, "mqb", self.CCP.STEER_DRIVER_ALLOWANCE)
+ driver_override_threshold = self._iq_lvbs_alc.vw_driver_override_threshold_cnm(self, "mqb", self.CCP.STEER_DRIVER_ALLOWANCE) if self._iq_lvbs_alc else self.CCP.STEER_DRIVER_ALLOWANCE
ret.steeringPressed = abs(self.driver_torque_filter.update(ret.steeringTorque)) > driver_override_threshold
self.curvature = -pt_cp.vl["QFK_01"]["Curvature"] * (1, -1)[int(pt_cp.vl["QFK_01"]["Curvature_VZ"])]
@@ -528,7 +535,7 @@ class CarState(CarStateBase):
self.LH_3_Sign = pt_cp.vl["Lenkhilfe_3"]["LH3_BLWSign"]
self.LH2_steeringState = pt_cp.vl["Lenkhilfe_2"]["LH2_aktLenkeingriff"]
self._update_pq_iq_alc_state(pt_cp)
- driver_override_threshold = self._iq_lvbs_alc.vw_driver_override_threshold_cnm(self, "pq", self.CCP.STEER_DRIVER_ALLOWANCE)
+ driver_override_threshold = self._iq_lvbs_alc.vw_driver_override_threshold_cnm(self, "pq", self.CCP.STEER_DRIVER_ALLOWANCE) if self._iq_lvbs_alc else self.CCP.STEER_DRIVER_ALLOWANCE
ret.steeringPressed = abs(ret.steeringTorque) > driver_override_threshold
# Consume blind-spot monitoring info/warning LED states, if available.
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/mqbcan.py b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/mqbcan.py
index e75525b7..fc49a095 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/mqbcan.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/mqbcan.py
@@ -342,9 +342,12 @@ def create_acc_accel_control(packer, bus, acc_type, accel, acc_control, stopping
return commands
-def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, leadDistance, distanceBars, fcw_alert, leadVisible, unavailable, decel, d_unresponsive,
+def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_index, distanceBars, fcw_alert, leadVisible, unavailable, decel, d_unresponsive,
*, priority_boost):
priodisp = 0 if fcw_alert else 1 if priority_boost else 2 if acc_hud_status in (3, 4) else 3
+ # The cluster draws the stock 1.0, 1.3, 1.8, 2.4 and 3.6 s gaps as notches 1-5. Aggressive, standard and relaxed
+ # (1.25, 1.45 and 1.75 s plus 3 m, about 1.37, 1.57 and 1.87 s at highway speed) take notches 2, 3 and 4:
+ # of the assignments that keep each personality distinct, it reads closest to how they actually follow.
leadDistanceBars = distanceBars + 1 if distanceBars in (1, 2, 3) else 2
values = {
"ACC_Status_Anzeige": acc_hud_status,
@@ -353,7 +356,7 @@ def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, leadDistance,
"ACC_Optischer_Fahrerhinweis": 1 if fcw_alert else 0,
"ACC_Display_Prio": priodisp,
"ACC_Relevantes_Objekt": leadDistanceBars,
- "ACC_Abstandsindex": leadDistance if leadVisible else 0,
+ "ACC_Abstandsindex": lead_index if leadVisible else 0,
"ACC_Akustik_02": fcw_alert,
}
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/pqcan.py b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/pqcan.py
index d6fe3fed..6c61f5ae 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/pqcan.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/pqcan.py
@@ -138,16 +138,18 @@ def create_blinker_control(packer, bus, leftBlinker, rightBlinker):
return packer.make_can_msg("Blinkmodi_02", bus, values)
-def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, leadDistance, distanceBars, fcw_alert, leadVisible, unavailable, decel, d_unresponsive,
+def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_index, distanceBars, fcw_alert, leadVisible, unavailable, decel, d_unresponsive,
*, priority_boost=False):
priodisp = 0 if (fcw_alert or decel or d_unresponsive or acc_hud_status in (6, 7)) else 1 if priority_boost else 2 if acc_hud_status in (3, 4) else 3
+ # The same gap notches as MQB, see mqbcan.create_acc_hud_control. The field is 4 bits but the stock radar
+ # uses the low notch numbers (a stock Sharan sends 1 and 2)
leadDistanceBars = distanceBars + 1 if distanceBars in (1, 2, 3) else 2
values = {
"ACA_StaACC": acc_hud_status,
"ACA_AnzDisplay": 1 if acc_hud_status in (3, 4) else 0,
"ACA_Zeitluecke": leadDistanceBars,
"ACA_V_Wunsch": set_speed,
- "ACA_gemZeitl": min(15, max(1, int(round(leadDistance)))) if leadVisible else 0,
+ "ACA_gemZeitl": lead_index if leadVisible else 0,
"ACA_PrioDisp": priodisp,
"ACA_Akustik1": d_unresponsive,
"ACA_Akustik2": fcw_alert,
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/tests/test_lead_headway_display.py b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/tests/test_lead_headway_display.py
new file mode 100644
index 00000000..b6d9853c
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/tests/test_lead_headway_display.py
@@ -0,0 +1,110 @@
+"""The MQB/PQ cluster's lead marker and gap notches, as encoded on the wire."""
+import pytest
+
+from iqdbc.can import CANParser
+from iqdbc.car import structs
+from iqdbc.car.common.conversions import Conversions as CV
+from iqdbc.car.volkswagen.carcontroller import (
+ LEAD_HEADWAY_MIN_SPEED, LEAD_INDEX_RANGE_ANALOG, LEAD_INDEX_RANGE_DIGITAL, lead_headway_index,
+)
+from iqdbc.car.volkswagen.interface import CarInterface
+from iqdbc.car.volkswagen.values import CAR
+
+SECOND_NOTCH = 1.3 # the stock gap settings run 1.0, 1.3, 1.8, 2.4, 3.6 s
+FOLLOW_TIMES = {1: 1.25, 2: 1.45, 3: 1.75} # leadDistanceBars: T_FOLLOW, aggressive to relaxed
+STOP_DISTANCE = 3.0 # the planner follows at T_FOLLOW * v + STOP_DISTANCE
+
+
+@pytest.mark.parametrize("digital", [False, True])
+@pytest.mark.parametrize("speed", [0.0, 10.0, 30.0])
+def test_grows_with_distance_and_saturates_at_the_ends(digital, speed):
+ codes = [lead_headway_index(d / 2, speed, digital) for d in range(1, 600)]
+ closest, farthest = LEAD_INDEX_RANGE_DIGITAL if digital else LEAD_INDEX_RANGE_ANALOG
+ assert codes == sorted(codes)
+ assert (codes[0], codes[-1]) == (closest, farthest)
+
+
+def test_reads_headway_not_distance():
+ assert lead_headway_index(1.8 * 10.0, 10.0, False) == lead_headway_index(1.8 * 30.0, 30.0, False)
+ assert lead_headway_index(30.0, 10.0, False) > lead_headway_index(30.0, 25.0, False)
+
+
+def test_stopped_reads_distance_alone():
+ assert lead_headway_index(5.0, 0.0, False) == lead_headway_index(5.0, LEAD_HEADWAY_MIN_SPEED, False)
+
+
+def make_car(candidate):
+ fingerprint = {bus: {} for bus in range(8)}
+ cp = CarInterface.get_params(candidate, fingerprint, [], alpha_long=True, is_release=False, docs=False)
+ cp_iq = CarInterface.get_params_iq(cp, candidate, fingerprint, [], alpha_long=True, is_release_iq=False, docs=False)
+ car = CarInterface(cp, cp_iq)
+ car.update([(0, [])])
+ car.CS.out = structs.CarState()
+ car.CS.out.cruiseState.available = True
+ return car
+
+
+def send_hud(car, *, lead_distance, speed, bars=2, lead_visible=True):
+ car.CS.out.vEgo = speed
+ cc = structs.CarControl()
+ cc.enabled = cc.longActive = True
+ cc.hudControl.leadVisible = lead_visible
+ cc.hudControl.leadDistance = lead_distance
+ cc.hudControl.leadDistanceBars = bars
+ cc.hudControl.leadFollowTime = FOLLOW_TIMES[bars]
+ car.CC.frame += -car.CC.frame % car.CC.CCP.ACC_HUD_STEP
+ now_nanos = (car.CC.frame + 1) * 10_000_000
+ _, messages = car.apply(cc.as_reader(), structs.IQCarControl(), now_nanos)
+ return now_nanos, messages
+
+
+def mqb_hud(car, **kwargs):
+ now_nanos, messages = send_hud(car, **kwargs)
+ parser = CANParser("vw_mqb", [("ACC_02", 0)], car.CC.CAN.pt)
+ parser.update([(now_nanos, messages)])
+ return parser.vl["ACC_02"]["ACC_Gesetzte_Zeitluecke"], parser.vl["ACC_02"]["ACC_Abstandsindex"]
+
+
+def pq_hud(car, **kwargs):
+ now_nanos, messages = send_hud(car, **kwargs)
+ parser = CANParser("vw_pq", [("ACC_GRA_Anzeige", 0)], car.CC.CAN.pt)
+ parser.update([(now_nanos, messages)])
+ return parser.vl["ACC_GRA_Anzeige"]["ACA_Zeitluecke"], parser.vl["ACC_GRA_Anzeige"]["ACA_gemZeitl"]
+
+
+PLATFORMS = [
+ pytest.param(CAR.VOLKSWAGEN_GOLF_MK7, mqb_hud, False, id="mqb_analog"),
+ pytest.param(CAR.VOLKSWAGEN_GOLF_MK7, mqb_hud, True, id="mqb_digital"),
+ pytest.param(CAR.VOLKSWAGEN_PASSAT_NMS, pq_hud, False, id="pq"),
+]
+
+
+@pytest.mark.parametrize("candidate, hud, digital", PLATFORMS)
+def test_reported_cases(candidate, hud, digital):
+ car = make_car(candidate)
+ car.CS.upscale_lead_car_signal = digital
+ closest = (LEAD_INDEX_RANGE_DIGITAL if digital else LEAD_INDEX_RANGE_ANALOG)[0]
+ speed = 45 * CV.MPH_TO_MS
+ # About one second behind at 45 mph read the far end; it belongs at the closest notch, short of the second
+ assert closest < hud(car, lead_distance=20.0, speed=speed)[1] < lead_headway_index(SECOND_NOTCH * speed, speed, digital)
+ # Stopped 1.5 m behind a car read second-closest
+ assert hud(car, lead_distance=1.5, speed=0.0)[1] == closest
+
+
+@pytest.mark.parametrize("candidate, hud, digital", PLATFORMS)
+def test_no_lead_sends_no_object(candidate, hud, digital):
+ car = make_car(candidate)
+ car.CS.upscale_lead_car_signal = digital
+ assert hud(car, lead_distance=30.0, speed=20.0, lead_visible=False)[1] == 0
+ assert hud(car, lead_distance=0.0, speed=20.0)[1] == 0
+
+
+@pytest.mark.parametrize("candidate, hud, digital", PLATFORMS)
+def test_personalities_get_distinct_notches_and_ordered_markers(candidate, hud, digital):
+ car = make_car(candidate)
+ car.CS.upscale_lead_car_signal = digital
+ speed = 25.0
+ notches, markers = zip(*(hud(car, bars=bars, lead_distance=FOLLOW_TIMES[bars] * speed + STOP_DISTANCE, speed=speed)
+ for bars in FOLLOW_TIMES), strict=True)
+ assert notches == (2, 3, 4)
+ assert markers[0] < markers[1] < markers[2]
diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/tests/test_volkswagen.py b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/tests/test_volkswagen.py
index fb5821f2..e0e0d3e8 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/tests/test_volkswagen.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/car/volkswagen/tests/test_volkswagen.py
@@ -258,6 +258,34 @@ def test_mlb_no_ecan_transmits_on_the_powertrain_bus():
assert {bus for _, _, bus in sent} == {1}
+def test_hca_torque_stops_the_moment_openpilot_disengages():
+ a4 = _a4_mk4_car()
+ packer = CANPacker("vw_mlb")
+ nanos = 0
+
+ def run(enabled, lat_active, steps=20):
+ nonlocal nanos
+ CC = structs.CarControl()
+ CC.enabled = enabled
+ CC.latActive = lat_active
+ CC.actuators.torque = 1.0
+ CC = CC.as_reader()
+ CC_IQ = structs.IQCarControl()
+ for _ in range(steps):
+ nanos += int(DT_CTRL * 1e9)
+ a4.update([(nanos, _a4_mk4_frames(packer, eps_torque=0))])
+ a4.apply(CC, CC_IQ, nanos)
+ return a4.CC.HCA_Status, a4.CC.apply_torque_last
+
+ engaged_status, engaged_torque = run(enabled=True, lat_active=True)
+ assert engaged_status in (5, 7)
+ assert engaged_torque != 0
+
+ disengaged_status, disengaged_torque = run(enabled=False, lat_active=True)
+ assert disengaged_status == 3
+ assert disengaged_torque == 0
+
+
def test_mlb_no_hca_eps_reports_a_steer_fault_without_a_can_fault():
a4 = _a4_mk4_car()
assert a4.CP.flags & VolkswagenFlagsIQ.IQ_MLB_NO_HCA_EPS
diff --git a/artifacts/package_sources/iqdbc/iqdbc/dbc/generator/tesla/tesla_radar_bosch.py b/artifacts/package_sources/iqdbc/iqdbc/dbc/generator/tesla/tesla_radar_bosch.py
index adfd2d84..a6985d5d 100755
--- a/artifacts/package_sources/iqdbc/iqdbc/dbc/generator/tesla/tesla_radar_bosch.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/dbc/generator/tesla/tesla_radar_bosch.py
@@ -45,7 +45,7 @@ BS_:
BU_: Autopilot Radar Diag
-BO_ 769 TeslaRadarSguInfo: 8 Radar
+BO_ 769 TeslaRadarSguInfo: 6 Radar
SG_ RADC_VerticalMisalignment : 0|8@1+ (1,0) [0|255] "" Autopilot
SG_ RADC_SCUTemperature : 8|8@1+ (1,-128) [-128|127] "" Autopilot
SG_ RADC_VMA_Plaus : 16|8@1+ (1,0) [0|255] "" Autopilot
diff --git a/artifacts/package_sources/iqdbc/iqdbc/dbc/tesla_can.dbc b/artifacts/package_sources/iqdbc/iqdbc/dbc/tesla_can.dbc
index 56624c3e..447231d5 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/dbc/tesla_can.dbc
+++ b/artifacts/package_sources/iqdbc/iqdbc/dbc/tesla_can.dbc
@@ -63,6 +63,10 @@ VAL_TABLE_ DI_speedUnits 1 "DI_SPEED_KPH" 0 "DI_SPEED_MPH" ;
VAL_TABLE_ DI_state 4 "DI_STATE_ENABLE" 3 "DI_STATE_FAULT" 2 "DI_STATE_CLEAR_FAULT" 1 "DI_STATE_STANDBY" 0 "DI_STATE_PREAUTH" ;
VAL_TABLE_ DI_velocityEstimatorState 4 "VE_STATE_BACKUP_MOTOR" 3 "VE_STATE_BACKUP_WHEELS_B" 2 "VE_STATE_BACKUP_WHEELS_A" 1 "VE_STATE_WHEELS_NORMAL" 0 "VE_STATE_NOT_INITIALIZED" ;
+BO_ 637 APS_eacMonitor: 3 APS
+ SG_ APS_eacAllow : 0|2@1+ (1,0) [0|0] "" EPAS3P
+ SG_ APS_eacMonitorCounter : 8|4@1+ (1,0) [0|0] "" EPAS3P
+ SG_ APS_eacMonitorChecksum : 16|8@1+ (1,0) [0|0] "" EPAS3P
BO_ 1160 DAS_steeringControl: 4 NEO
SG_ DAS_steeringControlType : 23|2@0+ (1,0) [0|0] "" EPAS
@@ -206,7 +210,7 @@ BO_ 309 ESP_135h: 5 ESP
BO_ 341 ESP_B: 8 ESP
SG_ ESP_BChecksum : 39|8@0+ (1,0) [0|255] "" NEO,EPAS
SG_ ESP_BCounter : 62|4@0+ (1,0) [1|15] "" NEO,EPAS
- SG_ ESP_vehicleSpeed : 47|16@0+ (0.00999999978,0) [0|0] "kph" NEO,EPAS
+ SG_ ESP_vehicleSpeed : 47|16@0+ (0.01,0) [0|0] "kph" NEO,EPAS
SG_ ESP_vehicleSpeedQF : 57|2@0+ (1,0) [1|2] "" NEO,EPAS
SG_ ESP_wheelPulseCountFrL : 7|8@0+ (1,0) [0|254] "" NEO,EPAS
SG_ ESP_wheelPulseCountFrR : 15|8@0+ (1,0) [0|254] "" NEO,EPAS
@@ -663,6 +667,31 @@ BO_ 1064 UI_telemetryControl: 8 GTW
BO_ 522 BrakeMessage: 8 XXX
SG_ driverBrakeStatus : 2|2@1+ (1,0) [0|3] "" XXX
+BO_ 529 RCM_status: 8 RCM
+ SG_ RCM_driverOrientationInitStatus : 2|3@0+ (1,0) [0|7] "" GTW,OCS
+ SG_ RCM_armStatus : 5|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_warningIndicatorState : 7|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_tellTalePassengerState : 9|2@0+ (1,0) [0|3] "" GTW,OCS,DAS
+ SG_ RCM_bucklePassengerStatus : 11|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_tellTaleDriverState : 13|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_buckleDriverStatus : 15|2@0+ (1,0) [0|3] "" GTW,OCS,DAS
+ SG_ RCM_seatTrackPositionDrvr : 19|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_passAirbagRequest : 21|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_chimeState1stRow : 23|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_vinLearnFlag : 25|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_hwConfigPin58 : 27|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_hwConfigPin57 : 29|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_hwConfigPin56 : 31|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_passOccupantStatus : 33|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_airbagCutoffSwStatus : 35|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_privateCANStatus : 37|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_publicCANStatus : 39|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_edr2State : 45|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_edr1State : 47|2@0+ (1,0) [0|3] "" GTW,OCS
+ SG_ RCM_buckle2ndRowStatus : 50|3@0+ (1,0) [0|3] "" GTW,OCS,DAS
+ SG_ RCM_statusCounter : 55|4@0+ (1,0) [0|15] "" GTW,OCS,DAS
+ SG_ RCM_statusChecksum : 63|8@0+ (1,0) [0|255] "" GTW,OCS,DAS
+
BO_ 921 AutopilotStatus: 8 XXX
SG_ autopilotStatus : 0|4@1+ (1,0) [0|0] "" XXX
SG_ DAS_blindSpotRearLeft : 4|2@1+ (1,0) [0|0] "" XXX
@@ -794,6 +823,7 @@ VAL_ 521 DAS_locSpeed 2047 "SNA" ;
VAL_ 521 DAS_locAccelMin 511 "SNA" ;
VAL_ 521 DAS_locAccelMax 511 "SNA" ;
VAL_ 522 driverBrakeStatus 2 "APPLIED" 1 "NOT_APPLIED" ;
+VAL_ 637 APS_eacAllow 3 "APS_EAC_SNA" 2 "APS_EAC_RESERVED" 1 "APS_EAC_ALLOW" 0 "APS_EAC_INHIBIT" ;
VAL_ 760 UI_mapSpeedLimitUnits 1 "KPH" 0 "MPH" ;
VAL_ 760 UI_userSpeedOffsetUnits 1 "KPH" 0 "MPH" ;
VAL_ 643 AirTemp_Insd 255 "SNA" ;
@@ -895,7 +925,7 @@ VAL_ 1001 DAS_highLowBeamDecision 3 "DAS_HIGH_BEAM_SNA" 2 "DAS_HIGH_BEAM_ON" 1 "
VAL_ 1001 DAS_highLowBeamOffReason 5 "HIGH_BEAM_OFF_REASON_SNA" 4 "HIGH_BEAM_OFF_REASON_HEAD_LIGHT" 3 "HIGH_BEAM_OFF_REASON_AMBIENT_LIGHT" 2 "HIGH_BEAM_OFF_REASON_MOVING_RADAR_TARGET" 1 "HIGH_BEAM_OFF_REASON_MOVING_VISION_TARGET" 0 "HIGH_BEAM_ON" ;
VAL_ 1001 DAS_turnIndicatorRequestReason 6 "DAS_ACTIVE_COMMANDED_LANE_CHANGE" 5 "DAS_CANCEL_FORK" 4 "DAS_CANCEL_LANE_CHANGE" 3 "DAS_ACTIVE_FORK" 2 "DAS_ACTIVE_SPEED_LANE_CHANGE" 1 "DAS_ACTIVE_NAV_LANE_CHANGE" 0 "DAS_NONE" ;
VAL_ 1160 DAS_steeringAngleRequest 16384 "ZERO_ANGLE" ;
-VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RESERVED" ;
+VAL_ 1160 DAS_steeringControlType 2 "LANE_KEEP_ASSIST" 0 "NONE" 1 "ANGLE_CONTROL" 3 "EMERGENCY_LANE_KEEP" ;
VAL_ 1160 DAS_steeringHapticRequest 1 "ACTIVE" 0 "IDLE" ;
diff --git a/artifacts/package_sources/iqdbc/iqdbc/dbc/tesla_raven_party.dbc b/artifacts/package_sources/iqdbc/iqdbc/dbc/tesla_raven_party.dbc
new file mode 100644
index 00000000..40ac48cc
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/dbc/tesla_raven_party.dbc
@@ -0,0 +1,115 @@
+VERSION ""
+
+
+NS_ :
+ NS_DESC_
+ CM_
+ BA_DEF_
+ BA_
+ VAL_
+ CAT_DEF_
+ CAT_
+ FILTER
+ BA_DEF_DEF_
+ EV_DATA_
+ ENVVAR_DATA_
+ SGTYPE_
+ SGTYPE_VAL_
+ BA_DEF_SGTYPE_
+ BA_SGTYPE_
+ SIG_TYPE_REF_
+ VAL_TABLE_
+ SIG_GROUP_
+ SIG_VALTYPE_
+ SIGTYPE_VALTYPE_
+ BO_TX_BU_
+ BA_DEF_REL_
+ BA_REL_
+ BA_DEF_DEF_REL_
+ BU_SG_REL_
+ BU_EV_REL_
+ BU_BO_REL_
+ SG_MUL_VAL_
+
+BS_:
+
+BU_: IBST ESP EPAS EPAS3P APS APP DAS
+
+BO_ 14 STW_ANGLHP_STAT: 8 APS
+ SG_ StW_AnglHP : 5|14@0+ (0.1,-819.2) [-819.2|819] "deg" EPAS3P
+ SG_ StW_AnglHP_Spd : 21|14@0+ (0.5,-4096) [-4096|4095.5] "deg/s" EPAS3P
+ SG_ StW_AnglHP_Sens_Stat : 33|2@0+ (1,0) [0|0] "" EPAS3P
+ SG_ StW_AnglHP_Sens_Id : 35|2@0+ (1,0) [0|0] "" EPAS3P
+ SG_ MC_STW_ANGLHP_STAT : 55|4@0+ (1,0) [0|15] "" EPAS3P
+ SG_ CRC_STW_ANGLHP_STAT : 63|8@0+ (1,0) [0|0] "" EPAS3P
+
+BO_ 637 APS_eacMonitor: 3 APS
+ SG_ APS_eacAllow : 0|2@1+ (1,0) [0|0] "" EPAS3P
+ SG_ APS_eacMonitorCounter : 8|4@1+ (1,0) [0|0] "" EPAS3P
+ SG_ APS_eacMonitorChecksum : 16|8@1+ (1,0) [0|0] "" EPAS3P
+
+BO_ 880 EPAS_sysStatus: 8 EPAS
+ SG_ EPAS_steeringRackForce : 1|10@0+ (50,-25575) [-25575|25575] "N" Vector__XXX
+ SG_ EPAS_steeringFault : 2|1@0+ (1,0) [0|1] "" APS,APP
+ SG_ EPAS_steeringReduced : 3|1@0+ (1,0) [0|1] "" APS,APP
+ SG_ EPAS_currentTuneMode : 7|4@0+ (1,0) [0|0] "" APS,APP
+ SG_ EPAS_torsionBarTorque : 19|12@0+ (0.01,-20.5) [-20.5|20.45] "Nm" APS,APP
+ SG_ EPAS_eacErrorCode : 23|4@0+ (1,0) [0|15] "" APS,APP
+ SG_ EPAS_internalSAS : 37|14@0+ (0.1,-819.2) [-819.2|819] "deg" APS,APP
+ SG_ EPAS_handsOnLevel : 39|2@0+ (1,0) [0|3] "" APS,APP
+ SG_ EPAS_sysStatusCounter : 51|4@0+ (1,0) [0|15] "" APS,APP
+ SG_ EPAS_eacStatus : 55|3@0+ (1,0) [0|7] "" APS,APP
+ SG_ EPAS_sysStatusChecksum : 63|8@0+ (1,0) [0|255] "" APS,APP
+
+BO_ 907 ESP_private1: 8 ESP
+ SG_ ESP_private1Checksum : 0|8@1+ (1,0) [0|255] "" IBST,EPAS
+ SG_ ESP_private1Counter : 8|4@1+ (1,0) [0|15] "" IBST,EPAS
+ SG_ ESP_pEstMax : 16|8@1+ (1,0) [0|250] "bar" IBST
+ SG_ ESP_pEstMaxQF : 24|2@1+ (1,0) [0|2] "" IBST
+ SG_ ESP_vehicleSpeed : 26|14@1+ (0.05625,0) [0|921.54375] "km/h" IBST,EPAS
+ SG_ ESP_vehicleSpeedQF : 40|3@1+ (1,0) [0|6] "" IBST,EPAS
+ SG_ ESP_ignitionOn : 43|1@1+ (1,0) [0|1] "" IBST
+
+BO_ 911 IBST_private2: 8 IBST
+ SG_ IBST_private2Checksum : 0|8@1+ (1,0) [0|255] "" ESP
+ SG_ IBST_private2Counter : 8|4@1+ (1,0) [0|15] "" ESP
+ SG_ IBST_pForceBlendingPotentialQF : 12|2@1+ (1,0) [0|2] "" ESP
+ SG_ IBST_brakePedalApplied : 16|1@1+ (1,0) [0|1] "1" ESP
+ SG_ IBST_brakePedalAppliedQF : 17|2@1+ (1,0) [0|3] "" ESP
+ SG_ IBST_status : 19|3@1+ (1,0) [0|7] "1" ESP
+ SG_ IBST_pRunout : 22|8@1+ (1,0) [0|250] "bar" ESP
+ SG_ IBST_pRunoutQF : 30|2@1+ (1,0) [0|3] "" ESP
+ SG_ IBST_compatibilityIndex : 32|4@1+ (1,0) [0|15] "" ESP
+ SG_ IBST_pForceBlendingPotential : 40|8@1+ (0.5,0) [0|125] "bar" ESP
+
+BO_ 1160 DAS_steeringControl: 4 DAS
+ SG_ DAS_steeringAngleRequest : 6|15@0+ (0.1,-1638.35) [-1638.35|1638.35] "deg" EPAS3P,APS
+ SG_ DAS_steeringHapticRequest : 7|1@0+ (1,0) [0|0] "" EPAS3P,APS
+ SG_ DAS_steeringControlCounter : 19|4@0+ (1,0) [0|0] "" EPAS3P,APS
+ SG_ DAS_steeringControlType : 23|2@0+ (1,0) [0|0] "" EPAS3P,APS
+ SG_ DAS_steeringControlChecksum : 31|8@0+ (1,0) [0|0] "" EPAS3P,APS
+
+VAL_ 14 StW_AnglHP 16383 "SNA" ;
+VAL_ 14 StW_AnglHP_Spd 16383 "SNA" ;
+VAL_ 14 StW_AnglHP_Sens_Stat 3 "SNA" 2 "ERR" 1 "INI" 0 "OK" ;
+VAL_ 14 StW_AnglHP_Sens_Id 3 "SNA" 2 "KOSTAL" 1 "DELPHI" 0 "TEST" ;
+VAL_ 637 APS_eacAllow 3 "APS_EAC_SNA" 2 "APS_EAC_RESERVED" 1 "APS_EAC_ALLOW" 0 "APS_EAC_INHIBIT" ;
+VAL_ 880 EPAS_steeringRackForce 1023 "SNA" 1022 "NOT_IN_SPEC" ;
+VAL_ 880 EPAS_steeringFault 1 "FAULT" 0 "NO_FAULT" ;
+VAL_ 880 EPAS_steeringReduced 1 "REDUCED_ASSIST" 0 "NORMAL_ASSIST" ;
+VAL_ 880 EPAS_currentTuneMode 7 "UNAVAILABLE" 6 "RWD_SPORT" 5 "RWD_STANDARD" 4 "RWD_COMFORT" 3 "DM_SPORT" 2 "DM_STANDARD" 1 "DM_COMFORT" 0 "FAIL_SAFE_DEFAULT" ;
+VAL_ 880 EPAS_torsionBarTorque 4095 "SNA" 4094 "UNDEFINABLE_DATA" ;
+VAL_ 880 EPAS_eacErrorCode 15 "SNA" 14 "EAC_EXTERNAL_MONITOR_INHIBIT" 13 "EAC_ERROR_PINION_VEL_DIFF" 12 "EAC_ERROR_LOW_ASSIST" 11 "EAC_ERROR_HIGH_TORSION_SAFETY" 10 "EAC_ERROR_HIGH_MMOT_SAFETY" 9 "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY" 8 "EAC_ERROR_HIGH_ANGLE_SAFETY" 7 "EAC_ERROR_HIGH_ANGLE_RATE_REQ" 6 "EAC_ERROR_HIGH_ANGLE_REQ" 5 "EAR_ERROR_MAX_STEER_DELTA" 4 "EAC_ERROR_TMP_FAULT" 3 "EAC_ERROR_HANDS_ON" 2 "EAC_ERROR_MAX_SPEED" 1 "EAC_ERROR_MIN_SPEED" 0 "EAC_ERROR_IDLE" ;
+VAL_ 880 EPAS_handsOnLevel 3 "LEVEL_3" 2 "LEVEL_2" 1 "LEVEL_1" 0 "LEVEL_0" ;
+VAL_ 880 EPAS_eacStatus 7 "EAC_ELK_ACTIVE" 6 "EAC_ELK_AVAILABLE" 5 "EAC_LKA_ACTIVE" 4 "EAC_LKA_ELK_AVAILABLE" 3 "EAC_FAULT" 2 "EAC_ACTIVE" 1 "EAC_AVAILABLE" 0 "EAC_INHIBITED" ;
+VAL_ 907 ESP_pEstMaxQF 2 "PESTMAX_FAULT" 1 "PESTMAX_NORMAL" 0 "PESTMAX_NOT_INITIALIZED" ;
+VAL_ 907 ESP_vehicleSpeedQF 6 "SPEED_INV_UNDER_V" 5 "SPEED_NOT_CALC" 4 "SPEED_REDUCED_MON" 3 "SPEED_DIFF_CALC" 2 "SPEED_FAULT" 1 "SPEED_NORMAL" 0 "SPEED_NOT_INITIALIZED" ;
+VAL_ 907 ESP_ignitionOn 1 "ESP_IGNITION_ON" 0 "ESP_IGNITION_OFF" ;
+VAL_ 911 IBST_pForceBlendingPotentialQF 2 "BLENDING_POTENTIAL_FAULT" 1 "BLENDING_POTENTIAL_NORMAL" 0 "BLENDING_POTENTIAL_NOT_INIT" ;
+VAL_ 911 IBST_brakePedalApplied 1 "BRAKE_PEDAL_APPLIED" 0 "BRAKE_PEDAL_NOT_APPLIED" ;
+VAL_ 911 IBST_brakePedalAppliedQF 2 "BRAKE_PEDAL_APPLIED_FAULT" 1 "BRAKE_PEDAL_APPLIED_NORMAL" 0 "BRAKE_PEDAL_APPLIED_OFF" ;
+VAL_ 911 IBST_status 7 "IBOOSTER_READY" 6 "IBOOSTER_POST_RUN_REDUCED" 5 "IBOOSTER_REDUCED" 4 "IBOOSTER_STANDALONE" 3 "IBOOSTER_UNDEFINED_1" 2 "IBOOSTER_DIAGNOSTIC" 1 "IBOOSTER_FAULT" 0 "IBOOSTER_NOT_INITIALIZED" ;
+VAL_ 911 IBST_pRunoutQF 2 "RUNNOUT_FAULT" 1 "RUNNOUT_NORMAL" 0 "RUNNOUT_NOT_INITIALIZED" ;
+VAL_ 1160 DAS_steeringAngleRequest 16384 "ZERO_ANGLE" ;
+VAL_ 1160 DAS_steeringHapticRequest 1 "HAPTIC_ACTIVE" 0 "HAPTIC_IDLE" ;
+VAL_ 1160 DAS_steeringControlType 3 "EMERGENCY_LANE_KEEP" 2 "LANE_KEEP_ASSIST" 1 "ANGLE_CONTROL" 0 "NONE" ;
\ No newline at end of file
diff --git a/artifacts/package_sources/iqdbc/iqdbc/lvbs/car/interfaces.py b/artifacts/package_sources/iqdbc/iqdbc/lvbs/car/interfaces.py
index dd118170..5ca575ad 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/lvbs/car/interfaces.py
+++ b/artifacts/package_sources/iqdbc/iqdbc/lvbs/car/interfaces.py
@@ -9,6 +9,7 @@ from collections.abc import Callable
from iqdbc.car import structs
from iqdbc.car.can_definitions import CanRecvCallable, CanSendCallable
from iqdbc.car.subaru.values import SubaruFlags
+from iqdbc.car.tesla.values import LEGACY_CARS
from iqdbc.lvbs.car.subaru.iq_values import SubaruFlagsIQ, SubaruSafetyFlagsIQ
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ
from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ
@@ -89,7 +90,7 @@ def _apply_long_tuning(CI, CP: structs.CarParams, CP_IQ: structs.IQCarParams,
def _apply_tesla_options(CP: structs.CarParams, CP_IQ: structs.IQCarParams,
params_dict: dict[str, str]) -> None:
- if CP.brand == 'tesla':
+ if CP.brand == 'tesla' and CP.carFingerprint not in LEGACY_CARS:
torque_blend = int(params_dict.get("IQTeslaTorqueBlend", 0)) == 1
if torque_blend:
CP_IQ.flags |= TeslaFlagsIQ.COOP_STEERING.value
diff --git a/artifacts/package_sources/iqdbc/iqdbc/safety/declarations.h b/artifacts/package_sources/iqdbc/iqdbc/safety/declarations.h
index 69868b7a..990de372 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/safety/declarations.h
+++ b/artifacts/package_sources/iqdbc/iqdbc/safety/declarations.h
@@ -34,6 +34,7 @@
#define SAFETY_PSA 31U
#define SAFETY_RIVIAN 33U
#define SAFETY_VOLKSWAGEN_MEB 34U
+#define SAFETY_TESLA_LEGACY 39U
#define GET_BIT(msg, b) ((bool)!!(((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U))
#define GET_FLAG(value, mask) (((value) & (mask)) == (mask))
@@ -353,6 +354,7 @@ extern const safety_hooks nissan_hooks;
extern const safety_hooks subaru_hooks;
extern const safety_hooks subaru_preglobal_hooks;
extern const safety_hooks tesla_hooks;
+extern const safety_hooks tesla_legacy_hooks;
extern const safety_hooks toyota_hooks;
extern const safety_hooks volkswagen_mlb_hooks;
extern const safety_hooks volkswagen_mqb_hooks;
diff --git a/artifacts/package_sources/iqdbc/iqdbc/safety/modes/tesla_legacy.h b/artifacts/package_sources/iqdbc/iqdbc/safety/modes/tesla_legacy.h
new file mode 100644
index 00000000..9bb94478
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/safety/modes/tesla_legacy.h
@@ -0,0 +1,272 @@
+/*
+ * Copyright © IQ.Lvbs, a part of Project Teal Lvbs.
+ * All Rights Reserved.
+ * Licensed under: https://konn3kt.com/tos
+ */
+
+#pragma once
+
+#include "iqdbc/safety/declarations.h"
+
+#define TESLA_LEGACY_FLAG_EXTERNAL_PANDA 4U
+#define TESLA_LEGACY_FLAG_HW1 8U
+#define TESLA_LEGACY_FLAG_HW2 16U
+#define TESLA_LEGACY_FLAG_HW3 32U
+
+#define TESLA_LEGACY_ADDR_ESP_B 0x155U
+#define TESLA_LEGACY_ADDR_BRAKE_MESSAGE 0x20aU
+#define TESLA_LEGACY_ADDR_BRAKE_MESSAGE_PT 0x1f8U
+#define TESLA_LEGACY_ADDR_DI_STATE 0x368U
+#define TESLA_LEGACY_ADDR_DI_STATE_PT 0x256U
+#define TESLA_LEGACY_ADDR_EPAS_SYS_STATUS 0x370U
+#define TESLA_LEGACY_ADDR_DAS_STEERING_CONTROL 0x488U
+#define TESLA_LEGACY_ADDR_APS_EAC_MONITOR 0x27dU
+#define TESLA_LEGACY_ADDR_DAS_CONTROL 0x2b9U
+#define TESLA_LEGACY_ADDR_DAS_CONTROL_PT 0x2bfU
+#define TESLA_LEGACY_ADDR_DI_TORQUE1 0x106U
+#define TESLA_LEGACY_ADDR_DI_TORQUE1_HW1 0x108U
+
+static bool tesla_legacy_external_panda = false;
+static bool tesla_legacy_hw1 = false;
+
+static uint8_t tesla_legacy_chassis_bus = 0U;
+static unsigned int tesla_legacy_das_control_addr = TESLA_LEGACY_ADDR_DAS_CONTROL_PT;
+static unsigned int tesla_legacy_di_torque1_addr = TESLA_LEGACY_ADDR_DI_TORQUE1;
+
+static bool tesla_legacy_stock_aeb = false;
+static bool tesla_legacy_stock_lkas = false;
+static bool tesla_legacy_stock_lkas_prev = false;
+
+static void tesla_legacy_rx_hook(const CANPacket_t *msg) {
+ if (!tesla_legacy_external_panda && (msg->bus == 0U) && (msg->addr == TESLA_LEGACY_ADDR_EPAS_SYS_STATUS)) {
+ const int angle_meas_new = (((msg->data[4] & 0x3FU) << 8) | msg->data[5]) - 8192U;
+ update_sample(&angle_meas, angle_meas_new);
+
+ const int hands_on_level = msg->data[4] >> 6;
+ const int eac_status = msg->data[6] >> 5;
+ const int eac_error_code = msg->data[2] >> 4;
+
+ steering_disengage = (hands_on_level >= 3) || ((eac_status == 0) && (eac_error_code == 9));
+ }
+
+ if (!tesla_legacy_external_panda && (msg->bus == tesla_legacy_chassis_bus) && (msg->addr == TESLA_LEGACY_ADDR_ESP_B)) {
+ float speed = ((msg->data[6] | (msg->data[5] << 8)) * 0.01) * KPH_TO_MS;
+ UPDATE_VEHICLE_SPEED(speed);
+ }
+
+ if ((tesla_legacy_external_panda || tesla_legacy_hw1) && (msg->bus == 0U) && (msg->addr == tesla_legacy_di_torque1_addr)) {
+ gas_pressed = msg->data[6] != 0U;
+ }
+
+ if ((tesla_legacy_external_panda && (msg->bus == 0U) && (msg->addr == TESLA_LEGACY_ADDR_BRAKE_MESSAGE_PT)) ||
+ (!tesla_legacy_external_panda && (msg->bus == tesla_legacy_chassis_bus) && (msg->addr == TESLA_LEGACY_ADDR_BRAKE_MESSAGE))) {
+ brake_pressed = (((msg->data[0] & 0x0CU) >> 2) != 1U);
+ }
+
+ if ((tesla_legacy_external_panda && (msg->bus == 0U) && (msg->addr == TESLA_LEGACY_ADDR_DI_STATE_PT)) ||
+ (!tesla_legacy_external_panda && (msg->bus == tesla_legacy_chassis_bus) && (msg->addr == TESLA_LEGACY_ADDR_DI_STATE))) {
+ int cruise_state = (msg->data[1] >> 4) & 0x07U;
+ bool cruise_engaged = (cruise_state == 2) ||
+ (cruise_state == 3) ||
+ (cruise_state == 4) ||
+ (cruise_state == 6) ||
+ (cruise_state == 7);
+ vehicle_moving = cruise_state != 3;
+ pcm_cruise_check(cruise_engaged);
+ }
+
+ if (msg->bus == 2U) {
+ if ((tesla_legacy_external_panda || tesla_legacy_hw1) && (msg->addr == tesla_legacy_das_control_addr)) {
+ tesla_legacy_stock_aeb = (msg->data[2] & 0x03U) == 1U;
+ }
+
+ if (!tesla_legacy_external_panda && (msg->addr == TESLA_LEGACY_ADDR_DAS_STEERING_CONTROL)) {
+ int steering_control_type = msg->data[2] >> 6;
+ bool tesla_legacy_stock_lkas_now = steering_control_type == 2;
+
+ if (tesla_legacy_stock_lkas_now && !tesla_legacy_stock_lkas_prev && !is_lat_active()) {
+ tesla_legacy_stock_lkas = true;
+ }
+ if (!tesla_legacy_stock_lkas_now) {
+ tesla_legacy_stock_lkas = false;
+ }
+ tesla_legacy_stock_lkas_prev = tesla_legacy_stock_lkas_now;
+ }
+ }
+}
+
+static bool tesla_legacy_tx_hook(const CANPacket_t *msg) {
+ const AngleSteeringLimits TESLA_LEGACY_STEERING_LIMITS = {
+ .max_angle = 3600,
+ .angle_deg_to_can = 10,
+ .frequency = 50U,
+ };
+
+ // NOTE: based off TESLA_MODEL_S_HW3 to match the car controller
+ const AngleSteeringParams TESLA_LEGACY_STEERING_PARAMS = {
+ .slip_factor = -0.0005666493436310427,
+ .steer_ratio = 15.,
+ .wheelbase = 2.96,
+ };
+
+ const LongitudinalLimits TESLA_LEGACY_LONG_LIMITS = {
+ .max_accel = 425,
+ .min_accel = 288,
+ .inactive_accel = 375,
+ .zero_accel = 375,
+ };
+
+ bool tx = true;
+ bool violation = false;
+
+ if (!tesla_legacy_external_panda && (msg->addr == TESLA_LEGACY_ADDR_DAS_STEERING_CONTROL)) {
+ int raw_angle_can = ((msg->data[0] & 0x7FU) << 8) | msg->data[1];
+ int desired_angle = raw_angle_can - 16384;
+ int steer_control_type = msg->data[2] >> 6;
+ bool steer_control_enabled = steer_control_type == 1;
+
+ if (steer_angle_cmd_checks_vm(desired_angle, steer_control_enabled, TESLA_LEGACY_STEERING_LIMITS, TESLA_LEGACY_STEERING_PARAMS)) {
+ violation = true;
+ }
+
+ bool valid_steer_control_type = (steer_control_type == 0) ||
+ (steer_control_type == 1);
+ if (!valid_steer_control_type) {
+ violation = true;
+ }
+
+ if (tesla_legacy_stock_lkas) {
+ violation = true;
+ }
+ }
+
+ if ((tesla_legacy_external_panda || tesla_legacy_hw1) && (msg->addr == tesla_legacy_das_control_addr)) {
+ int aeb_event = msg->data[2] & 0x03U;
+ if (aeb_event != 0) {
+ violation = true;
+ }
+
+ if (tesla_legacy_stock_aeb) {
+ violation = true;
+ }
+
+ int raw_accel_max = ((msg->data[6] & 0x1FU) << 4) | (msg->data[5] >> 4);
+ int raw_accel_min = ((msg->data[5] & 0x0FU) << 5) | (msg->data[4] >> 3);
+
+ // Both limits below zero could let the car reverse after coming to a standstill
+ if ((raw_accel_max < TESLA_LEGACY_LONG_LIMITS.inactive_accel) && (raw_accel_min < TESLA_LEGACY_LONG_LIMITS.inactive_accel)) {
+ violation = true;
+ }
+
+ violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LEGACY_LONG_LIMITS);
+ violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LEGACY_LONG_LIMITS);
+ }
+
+ if (violation) {
+ tx = false;
+ }
+
+ return tx;
+}
+
+static bool tesla_legacy_fwd_hook(int bus_num, int addr) {
+ bool block_msg = false;
+
+ if (bus_num == 2) {
+ if (!tesla_legacy_external_panda && !tesla_legacy_hw1 && (addr == (int)TESLA_LEGACY_ADDR_APS_EAC_MONITOR)) {
+ block_msg = true;
+ }
+
+ if (!tesla_legacy_external_panda && (addr == (int)TESLA_LEGACY_ADDR_DAS_STEERING_CONTROL) && !tesla_legacy_stock_lkas) {
+ block_msg = true;
+ }
+
+ if ((tesla_legacy_external_panda || tesla_legacy_hw1) && (addr == (int)tesla_legacy_das_control_addr) && !tesla_legacy_stock_aeb) {
+ block_msg = true;
+ }
+ }
+
+ return block_msg;
+}
+
+static safety_config tesla_legacy_init(uint16_t param) {
+ tesla_legacy_external_panda = GET_FLAG(param, TESLA_LEGACY_FLAG_EXTERNAL_PANDA);
+ tesla_legacy_hw1 = GET_FLAG(param, TESLA_LEGACY_FLAG_HW1);
+ const bool tesla_legacy_hw2 = GET_FLAG(param, TESLA_LEGACY_FLAG_HW2);
+ const bool tesla_legacy_hw3 = GET_FLAG(param, TESLA_LEGACY_FLAG_HW3);
+
+ tesla_legacy_stock_aeb = false;
+ tesla_legacy_stock_lkas = false;
+ tesla_legacy_stock_lkas_prev = false;
+ tesla_legacy_chassis_bus = 0U;
+ tesla_legacy_di_torque1_addr = TESLA_LEGACY_ADDR_DI_TORQUE1;
+ tesla_legacy_das_control_addr = tesla_legacy_external_panda ? TESLA_LEGACY_ADDR_DAS_CONTROL_PT : TESLA_LEGACY_ADDR_DAS_CONTROL;
+
+ static const CanMsg TESLA_LEGACY_TX_MSGS[] = {
+ {0x488, 0, 4, .check_relay = true, .disable_static_blocking = true},
+ {0x27D, 0, 3, .check_relay = true, .disable_static_blocking = true},
+ };
+
+ static const CanMsg TESLA_LEGACY_PT_TX_MSGS[] = {
+ {0x2bf, 0, 8, .check_relay = true, .disable_static_blocking = true},
+ };
+
+ static const CanMsg TESLA_LEGACY_HW1_TX_MSGS[] = {
+ {0x488, 0, 4, .check_relay = true, .disable_static_blocking = true},
+ {0x2b9, 0, 8, .check_relay = true, .disable_static_blocking = true},
+ };
+
+ safety_config ret;
+ if (tesla_legacy_external_panda && (tesla_legacy_hw3 || tesla_legacy_hw2)) {
+ static RxCheck tesla_legacy_pt_rx_checks[] = {
+ {.msg = {{0x106, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x1f8, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x2bf, 2, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x256, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ };
+ SET_RX_CHECKS(tesla_legacy_pt_rx_checks, ret);
+ SET_TX_MSGS(TESLA_LEGACY_PT_TX_MSGS, ret);
+ } else if (tesla_legacy_hw3) {
+ static RxCheck tesla_legacy_hw3_rx_checks[] = {
+ {.msg = {{0x370, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x155, 1, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x20a, 1, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x368, 1, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ };
+ tesla_legacy_chassis_bus = 1U;
+ SET_RX_CHECKS(tesla_legacy_hw3_rx_checks, ret);
+ SET_TX_MSGS(TESLA_LEGACY_TX_MSGS, ret);
+ } else if (tesla_legacy_hw1) {
+ static RxCheck tesla_legacy_hw1_rx_checks[] = {
+ {.msg = {{0x108, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x2b9, 2, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x370, 0, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x155, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x20a, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x368, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ };
+ tesla_legacy_di_torque1_addr = TESLA_LEGACY_ADDR_DI_TORQUE1_HW1;
+ SET_RX_CHECKS(tesla_legacy_hw1_rx_checks, ret);
+ SET_TX_MSGS(TESLA_LEGACY_HW1_TX_MSGS, ret);
+ } else {
+ static RxCheck tesla_legacy_hw2_rx_checks[] = {
+ {.msg = {{0x370, 0, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x155, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x20a, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x368, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ {.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}},
+ };
+ SET_RX_CHECKS(tesla_legacy_hw2_rx_checks, ret);
+ SET_TX_MSGS(TESLA_LEGACY_TX_MSGS, ret);
+ }
+ return ret;
+}
+
+const safety_hooks tesla_legacy_hooks = {
+ .init = tesla_legacy_init,
+ .rx = tesla_legacy_rx_hook,
+ .tx = tesla_legacy_tx_hook,
+ .fwd = tesla_legacy_fwd_hook,
+};
diff --git a/artifacts/package_sources/iqdbc/iqdbc/safety/safety.h b/artifacts/package_sources/iqdbc/iqdbc/safety/safety.h
index 0df04154..46a62130 100644
--- a/artifacts/package_sources/iqdbc/iqdbc/safety/safety.h
+++ b/artifacts/package_sources/iqdbc/iqdbc/safety/safety.h
@@ -11,6 +11,7 @@
#include "iqdbc/safety/modes/honda.h"
#include "iqdbc/safety/modes/toyota.h"
#include "iqdbc/safety/modes/tesla.h"
+#include "iqdbc/safety/modes/tesla_legacy.h"
#include "iqdbc/safety/modes/gm.h"
#include "iqdbc/safety/modes/ford.h"
#include "iqdbc/safety/modes/hyundai.h"
@@ -411,6 +412,7 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
{SAFETY_FORD, &ford_hooks},
{SAFETY_RIVIAN, &rivian_hooks},
{SAFETY_TESLA, &tesla_hooks},
+ {SAFETY_TESLA_LEGACY, &tesla_legacy_hooks},
{SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks},
{SAFETY_VOLKSWAGEN_MEB, &volkswagen_meb_hooks},
{SAFETY_VOLKSWAGEN_MQBEVO, &volkswagen_meb_hooks},
diff --git a/artifacts/package_sources/iqdbc/iqdbc/safety/tests/test_tesla_legacy.py b/artifacts/package_sources/iqdbc/iqdbc/safety/tests/test_tesla_legacy.py
new file mode 100644
index 00000000..50429939
--- /dev/null
+++ b/artifacts/package_sources/iqdbc/iqdbc/safety/tests/test_tesla_legacy.py
@@ -0,0 +1,425 @@
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
+"""
+import unittest
+import numpy as np
+
+from iqdbc.car.lateral import get_max_angle_delta_vm, get_max_angle_vm
+from iqdbc.car.tesla.values import CarControllerParams, TeslaLegacySafetyFlags
+from iqdbc.car.structs import CarParams
+from iqdbc.car.vehicle_model import VehicleModel
+from iqdbc.can import CANDefine
+from iqdbc.safety.tests.libsafety import libsafety_py
+import iqdbc.safety.tests.common as common
+from iqdbc.safety.tests.common import CANPackerSafety, away_round, round_speed
+
+MSG_DAS_steeringControl = 0x488
+MSG_APS_eacMonitor = 0x27d
+MSG_DAS_Control_HW1 = 0x2b9
+MSG_DAS_Control_PT = 0x2bf
+
+
+def round_angle(apply_angle, can_offset=0):
+ apply_angle_can = (apply_angle + 1638.35) / 0.1 + can_offset
+ rnd_offset = 1e-5 if apply_angle >= 0 else -1e-5
+ return away_round(apply_angle_can + rnd_offset) * 0.1 - 1638.35
+
+
+def get_legacy_vm():
+ from iqdbc.car.tesla.interface import CarInterface
+ return VehicleModel(CarInterface.get_non_essential_params("TESLA_MODEL_S_HW3"))
+
+
+def test_legacy_safety_params_match_vehicle_model():
+ from iqdbc.car.tesla.interface import CarInterface
+ from iqdbc.car.vehicle_model import calc_slip_factor
+ CP = CarInterface.get_non_essential_params("TESLA_MODEL_S_HW3")
+ assert abs(calc_slip_factor(VehicleModel(CP)) - (-0.0005666493436310427)) < 1e-12
+ assert CP.steerRatio == 15.
+ assert abs(CP.wheelbase - 2.96) < 1e-6
+
+
+class TeslaLegacyAngleBase(common.AngleSteeringSafetyTest):
+ STANDSTILL_THRESHOLD = 0.1
+ GAS_PRESSED_THRESHOLD = 3
+
+ STEER_ANGLE_MAX = 360
+ DEG_TO_CAN = 10
+
+ ANGLE_RATE_BP = None
+ ANGLE_RATE_UP = None
+ ANGLE_RATE_DOWN = None
+
+ LATERAL_FREQUENCY = 50
+
+ cnt_epas = 0
+ cnt_angle_cmd = 0
+
+ def _get_steer_cmd_angle_max(self, speed):
+ return get_max_angle_vm(max(speed, 1), self.VM, CarControllerParams)
+
+ def _angle_cmd_msg(self, angle: float, state: bool | int, increment_timer: bool = True, bus: int = 0):
+ values = {"DAS_steeringAngleRequest": angle, "DAS_steeringControlType": state}
+ if increment_timer:
+ self.safety.set_timer(self.cnt_angle_cmd * int(1e6 / self.LATERAL_FREQUENCY))
+ self.__class__.cnt_angle_cmd += 1
+ return self.packer.make_can_msg_safety("DAS_steeringControl", bus, values)
+
+ def _angle_meas_msg(self, angle: float, hands_on_level: int = 0, eac_status: int = 1, eac_error_code: int = 0):
+ values = {
+ "EPAS_internalSAS": angle,
+ "EPAS_handsOnLevel": hands_on_level,
+ "EPAS_eacStatus": eac_status,
+ "EPAS_eacErrorCode": eac_error_code,
+ }
+ return self.packer.make_can_msg_safety("EPAS_sysStatus", 0, values)
+
+ def test_angle_cmd_when_enabled(self):
+ pass
+
+ def test_steering_wheel_disengage(self):
+ for hands_on_level in range(4):
+ for eac_status in range(8):
+ for eac_error_code in range(16):
+ self.safety.set_controls_allowed(True)
+
+ should_disengage = hands_on_level >= 3 or (eac_status == 0 and eac_error_code == 9)
+ self.assertTrue(self._rx(self._angle_meas_msg(0, hands_on_level=hands_on_level,
+ eac_status=eac_status, eac_error_code=eac_error_code)))
+ self.assertNotEqual(should_disengage, self.safety.get_controls_allowed())
+ self.assertEqual(should_disengage, self.safety.get_steering_disengage_prev())
+
+ self.assertTrue(self._rx(self._angle_meas_msg(0, hands_on_level=0, eac_status=1, eac_error_code=0)))
+ self.assertNotEqual(should_disengage, self.safety.get_controls_allowed())
+ self.assertFalse(self.safety.get_steering_disengage_prev())
+
+ def test_steering_control_type(self):
+ self.safety.set_controls_allowed(True)
+ for steer_control_type in range(4):
+ should_tx = steer_control_type in (self.steer_control_types["NONE"],
+ self.steer_control_types["ANGLE_CONTROL"])
+ self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(0, state=steer_control_type)))
+
+ def test_stock_lkas_passthrough(self):
+ no_lkas_msg = self._angle_cmd_msg(0, state=False)
+ no_lkas_msg_cam = self._angle_cmd_msg(0, state=True, bus=2)
+ lkas_msg_cam = self._angle_cmd_msg(0, state=self.steer_control_types['LANE_KEEP_ASSIST'], bus=2)
+
+ self.assertEqual(1, self._rx(no_lkas_msg_cam))
+ self.assertEqual(-1, self.safety.safety_fwd_hook(2, no_lkas_msg_cam.addr))
+ self.assertTrue(self._tx(no_lkas_msg))
+
+ self.assertEqual(1, self._rx(lkas_msg_cam))
+ self.assertEqual(0, self.safety.safety_fwd_hook(2, lkas_msg_cam.addr))
+ self.assertFalse(self._tx(no_lkas_msg))
+
+ def test_stock_lkas_ignored_while_lateral_active(self):
+ self.safety.set_controls_allowed(True)
+ lkas_msg_cam = self._angle_cmd_msg(0, state=self.steer_control_types['LANE_KEEP_ASSIST'], bus=2)
+ self.assertEqual(1, self._rx(lkas_msg_cam))
+ self.assertEqual(-1, self.safety.safety_fwd_hook(2, lkas_msg_cam.addr))
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, state=False)))
+
+ def test_lateral_accel_limit(self):
+ for speed in np.linspace(0, 40, 50):
+ speed = max(speed, 1)
+ speed = round_speed(away_round(speed / 0.01 * 3.6) * 0.01 / 3.6)
+ for sign in (-1, 1):
+ self.safety.set_controls_allowed(True)
+ self._reset_speed_measurement(speed + 1)
+
+ angle_unit_offset = -1 if sign == -1 else 0
+ max_angle = round_angle(get_max_angle_vm(speed, self.VM, CarControllerParams), angle_unit_offset + 1) * sign
+ max_angle = np.clip(max_angle, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX)
+ self.safety.set_desired_angle_last(round(max_angle * self.DEG_TO_CAN))
+
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle, True)))
+
+ max_angle_raw = round_angle(get_max_angle_vm(speed, self.VM, CarControllerParams), angle_unit_offset + 2) * sign
+ max_angle = np.clip(max_angle_raw, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX)
+ self._tx(self._angle_cmd_msg(max_angle, True))
+
+ should_tx = abs(max_angle_raw) >= self.STEER_ANGLE_MAX
+ self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(max_angle, True)))
+
+ def test_lateral_jerk_limit(self):
+ for speed in np.linspace(0, 40, 50):
+ speed = max(speed, 1)
+ speed = round_speed(away_round(speed / 0.01 * 3.6) * 0.01 / 3.6)
+ for sign in (-1, 1):
+ self.safety.set_controls_allowed(True)
+ self._reset_speed_measurement(speed + 1)
+ self._tx(self._angle_cmd_msg(0, True))
+
+ angle_unit_offset = 1 if sign == -1 else 0
+
+ max_angle_delta = round_angle(get_max_angle_delta_vm(speed, self.VM, CarControllerParams),
+ angle_unit_offset) * sign
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
+
+ max_angle_delta = round_angle(get_max_angle_delta_vm(speed, self.VM, CarControllerParams),
+ angle_unit_offset + 1) * sign
+ self.assertFalse(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+
+ self.safety.set_desired_angle_last(round(max_angle_delta * self.DEG_TO_CAN))
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+
+ self.assertFalse(self._tx(self._angle_cmd_msg(0, True)))
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
+
+
+class TeslaLegacyLongitudinalBase(common.LongitudinalAccelSafetyTest):
+ MAX_ACCEL = 2.0
+ MIN_ACCEL = -3.48
+ INACTIVE_ACCEL = 0.0
+
+ def _long_control_msg(self, set_speed, acc_state=0, jerk_limits=(0, 0), accel_limits=(0, 0), aeb_event=0, bus=0):
+ values = {
+ "DAS_setSpeed": set_speed,
+ "DAS_accState": acc_state,
+ "DAS_aebEvent": aeb_event,
+ "DAS_jerkMin": jerk_limits[0],
+ "DAS_jerkMax": jerk_limits[1],
+ "DAS_accelMin": accel_limits[0],
+ "DAS_accelMax": accel_limits[1],
+ }
+ return self.packer_long.make_can_msg_safety("DAS_control", bus, values)
+
+ def _accel_msg(self, accel: float):
+ return self._long_control_msg(10, accel_limits=(accel, max(accel, 0)))
+
+ def test_no_aeb(self):
+ for aeb_event in range(4):
+ self.assertEqual(self._tx(self._long_control_msg(10, aeb_event=aeb_event)), aeb_event == 0)
+
+ def test_stock_aeb_passthrough(self):
+ no_aeb_msg = self._long_control_msg(10, aeb_event=0)
+ no_aeb_msg_cam = self._long_control_msg(10, aeb_event=0, bus=2)
+ aeb_msg_cam = self._long_control_msg(10, aeb_event=1, bus=2)
+
+ self.assertEqual(1, self._rx(no_aeb_msg_cam))
+ self.assertEqual(-1, self.safety.safety_fwd_hook(2, no_aeb_msg_cam.addr))
+ self.assertTrue(self._tx(no_aeb_msg))
+
+ self.assertEqual(1, self._rx(aeb_msg_cam))
+ self.assertEqual(0, self.safety.safety_fwd_hook(2, aeb_msg_cam.addr))
+ self.assertFalse(self._tx(no_aeb_msg))
+
+ def test_prevent_reverse(self):
+ self.safety.set_controls_allowed(True)
+
+ self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(1.1, 0.8))))
+ self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(1.1, 0.8))))
+
+ self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(0, 0))))
+ self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0, 0))))
+
+ self.assertTrue(self._tx(self._long_control_msg(set_speed=10, accel_limits=(-0.8, 1.3))))
+ self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0.8, -1.3))))
+ self.assertTrue(self._tx(self._long_control_msg(set_speed=0, accel_limits=(0, -1.3))))
+
+ self.assertFalse(self._tx(self._long_control_msg(set_speed=10, accel_limits=(-1.1, -0.6))))
+ self.assertFalse(self._tx(self._long_control_msg(set_speed=0, accel_limits=(-0.6, -1.1))))
+ self.assertFalse(self._tx(self._long_control_msg(set_speed=0, accel_limits=(-0.1, -0.1))))
+
+
+class TeslaLegacyStockSideBase(common.CarSafetyTest):
+ STANDSTILL_THRESHOLD = 0.1
+ GAS_PRESSED_THRESHOLD = 3
+
+ chassis_bus = 0
+
+ def _user_brake_msg(self, brake):
+ values = {"driverBrakeStatus": 2 if brake else 1}
+ return self.packer_chassis.make_can_msg_safety("BrakeMessage", self.chassis_bus, values)
+
+ def _speed_msg(self, speed):
+ values = {"ESP_vehicleSpeed": speed * 3.6}
+ return self.packer_chassis.make_can_msg_safety("ESP_B", self.chassis_bus, values)
+
+ def _vehicle_moving_msg(self, speed: float):
+ values = {"DI_cruiseState": 3 if speed <= self.STANDSTILL_THRESHOLD else 2, "DI_speedUnits": 1}
+ return self.packer_chassis.make_can_msg_safety("DI_state", self.chassis_bus, values)
+
+ def _user_gas_msg(self, gas):
+ values = {"DI_pedalPos": gas}
+ return self.packer_chassis.make_can_msg_safety("DI_torque1", self.chassis_bus, values)
+
+ def _pcm_status_msg(self, enable):
+ values = {"DI_cruiseState": 2 if enable else 0, "DI_speedUnits": 1}
+ return self.packer_chassis.make_can_msg_safety("DI_state", self.chassis_bus, values)
+
+ def test_rx_hook(self):
+ for _ in range(5):
+ msg = self._angle_cmd_msg(0, True, bus=2)
+ self.safety.set_controls_allowed(True)
+ self.assertTrue(self._rx(msg))
+ self.assertTrue(self.safety.get_controls_allowed())
+
+ for _ in range(5):
+ msg = self._speed_msg(0)
+ self.safety.set_controls_allowed(True)
+ self.assertTrue(self._rx(msg))
+ self.assertTrue(self.safety.get_controls_allowed())
+
+ def test_vehicle_speed_measurements(self):
+ self._common_measurement_test(self._speed_msg, 0, 285 / 3.6, 1,
+ self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max)
+
+ def test_prev_gas(self):
+ pass
+
+ def test_no_disengage_on_gas(self):
+ pass
+
+
+class TestTeslaHW2Safety(TeslaLegacyStockSideBase, TeslaLegacyAngleBase):
+ chassis_bus = 0
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_APS_eacMonitor)}
+ FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_APS_eacMonitor]}
+ TX_MSGS = [[MSG_DAS_steeringControl, 0], [MSG_APS_eacMonitor, 0]]
+ SAFETY_PARAM = int(TeslaLegacySafetyFlags.HW2)
+
+ def setUp(self):
+ self.VM = get_legacy_vm()
+ self.packer = CANPackerSafety("tesla_can")
+ self.packer_chassis = CANPackerSafety("tesla_can")
+ self.define = CANDefine("tesla_can")
+ self.steer_control_types = {d: v for v, d in self.define.dv["DAS_steeringControl"]["DAS_steeringControlType"].items()}
+
+ self.safety = libsafety_py.libsafety
+ self.safety.set_safety_hooks(CarParams.SafetyModel.teslaLegacy, self.SAFETY_PARAM)
+ self.safety.init_tests()
+
+
+class TestTeslaHW3Safety(TeslaLegacyStockSideBase, TeslaLegacyAngleBase):
+ chassis_bus = 1
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_APS_eacMonitor)}
+ FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_APS_eacMonitor]}
+ TX_MSGS = [[MSG_DAS_steeringControl, 0], [MSG_APS_eacMonitor, 0]]
+ SAFETY_PARAM = int(TeslaLegacySafetyFlags.HW3)
+
+ def setUp(self):
+ self.VM = get_legacy_vm()
+ self.packer = CANPackerSafety("tesla_raven_party")
+ self.packer_chassis = CANPackerSafety("tesla_can")
+ self.define = CANDefine("tesla_raven_party")
+ self.steer_control_types = {d: v for v, d in self.define.dv["DAS_steeringControl"]["DAS_steeringControlType"].items()}
+
+ self.safety = libsafety_py.libsafety
+ self.safety.set_safety_hooks(CarParams.SafetyModel.teslaLegacy, self.SAFETY_PARAM)
+ self.safety.init_tests()
+
+
+class TeslaLegacyExternalPandaBase(TeslaLegacyLongitudinalBase, common.CarSafetyTest):
+ STANDSTILL_THRESHOLD = 0.1
+ GAS_PRESSED_THRESHOLD = 3
+
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_Control_PT,)}
+ FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_Control_PT]}
+ TX_MSGS = [[MSG_DAS_Control_PT, 0]]
+
+ def setUp(self):
+ self.packer = CANPackerSafety("tesla_powertrain")
+ self.packer_long = self.packer
+ self.safety = libsafety_py.libsafety
+ self.safety.set_safety_hooks(CarParams.SafetyModel.teslaLegacy, self.SAFETY_PARAM)
+ self.safety.init_tests()
+
+ def _vehicle_moving_msg(self, speed: float):
+ values = {"DI_cruiseState": 3 if speed <= self.STANDSTILL_THRESHOLD else 2, "DI_speedUnits": 1}
+ return self.packer.make_can_msg_safety("DI_state", 0, values)
+
+ def _user_brake_msg(self, brake):
+ values = {"driverBrakeStatus": 2 if brake else 1}
+ return self.packer.make_can_msg_safety("BrakeMessage", 0, values)
+
+ def _user_gas_msg(self, gas):
+ values = {"DI_pedalPos": gas}
+ return self.packer.make_can_msg_safety("DI_torque1", 0, values)
+
+ def _pcm_status_msg(self, enable):
+ values = {"DI_cruiseState": 2 if enable else 0, "DI_speedUnits": 1}
+ return self.packer.make_can_msg_safety("DI_state", 0, values)
+
+ def _speed_msg(self, speed):
+ return self._vehicle_moving_msg(speed)
+
+ def test_rx_hook(self):
+ for _ in range(5):
+ msg = self._long_control_msg(0, bus=2)
+ self.safety.set_controls_allowed(True)
+ self.assertTrue(self._rx(msg))
+ self.assertTrue(self.safety.get_controls_allowed())
+
+
+class TestTeslaHW2ExternalPandaSafety(TeslaLegacyExternalPandaBase):
+ SAFETY_PARAM = int(TeslaLegacySafetyFlags.HW2 | TeslaLegacySafetyFlags.EXTERNAL_PANDA)
+
+
+class TestTeslaHW3ExternalPandaSafety(TeslaLegacyExternalPandaBase):
+ SAFETY_PARAM = int(TeslaLegacySafetyFlags.HW3 | TeslaLegacySafetyFlags.EXTERNAL_PANDA)
+
+
+class TestTeslaHW1Safety(TeslaLegacyLongitudinalBase, TeslaLegacyAngleBase, common.CarSafetyTest):
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_DAS_Control_HW1)}
+ FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_DAS_Control_HW1]}
+ TX_MSGS = [[MSG_DAS_steeringControl, 0], [MSG_DAS_Control_HW1, 0]]
+
+ STANDSTILL_THRESHOLD = 0.1
+ GAS_PRESSED_THRESHOLD = 3
+
+ def setUp(self):
+ self.VM = get_legacy_vm()
+ self.packer = CANPackerSafety("tesla_can")
+ self.packer_long = self.packer
+ self.define = CANDefine("tesla_can")
+ self.steer_control_types = {d: v for v, d in self.define.dv["DAS_steeringControl"]["DAS_steeringControlType"].items()}
+
+ self.safety = libsafety_py.libsafety
+ self.safety.set_safety_hooks(CarParams.SafetyModel.teslaLegacy, int(TeslaLegacySafetyFlags.HW1))
+ self.safety.init_tests()
+
+ def _user_brake_msg(self, brake):
+ values = {"driverBrakeStatus": 2 if brake else 1}
+ return self.packer.make_can_msg_safety("BrakeMessage", 0, values)
+
+ def _speed_msg(self, speed):
+ values = {"ESP_vehicleSpeed": speed * 3.6}
+ return self.packer.make_can_msg_safety("ESP_B", 0, values)
+
+ def _vehicle_moving_msg(self, speed: float):
+ values = {"DI_cruiseState": 3 if speed <= self.STANDSTILL_THRESHOLD else 2}
+ return self.packer.make_can_msg_safety("DI_state", 0, values)
+
+ def _user_gas_msg(self, gas):
+ values = {"DI_pedalPos": gas}
+ return self.packer.make_can_msg_safety("DI_torque1", 0, values)
+
+ def _pcm_status_msg(self, enable):
+ values = {"DI_cruiseState": 2 if enable else 0}
+ return self.packer.make_can_msg_safety("DI_state", 0, values)
+
+ def test_rx_hook(self):
+ for msg_type in ("angle", "long", "speed"):
+ for _ in range(5):
+ if msg_type == "angle":
+ msg = self._angle_cmd_msg(0, True, bus=2)
+ elif msg_type == "long":
+ msg = self._long_control_msg(0, bus=2)
+ else:
+ msg = self._speed_msg(0)
+
+ self.safety.set_controls_allowed(True)
+ self.assertTrue(self._rx(msg))
+ self.assertTrue(self.safety.get_controls_allowed())
+
+ def test_vehicle_speed_measurements(self):
+ self._common_measurement_test(self._speed_msg, 0, 285 / 3.6, 1,
+ self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/artifacts/package_sources/msgq/SConstruct b/artifacts/package_sources/msgq/SConstruct
index fc509360..0af2c0a5 100644
--- a/artifacts/package_sources/msgq/SConstruct
+++ b/artifacts/package_sources/msgq/SConstruct
@@ -54,8 +54,8 @@ elif GetOption('asan'):
env = Environment(
ENV=os.environ,
- CC='clang',
- CXX='clang++',
+ CC=os.environ.get('CC', 'clang'),
+ CXX=os.environ.get('CXX', 'clang++'),
CCFLAGS=[
"-g",
"-fPIC",
@@ -77,6 +77,9 @@ env = Environment(
tools=["default", "cython"]
)
+if 'clang' not in env['CXX']:
+ env['CCFLAGS'] = [f for f in env['CCFLAGS'] if f not in ('-Wshadow', '-Wno-vla-cxx-extension', '-Wno-unknown-warning-option')] + ['-Wshadow=local']
+
Export('env', 'arch', 'common')
envCython = env.Clone(LIBS=[])
diff --git a/artifacts/package_sources/msgq/msgq/msgq.cc b/artifacts/package_sources/msgq/msgq/msgq.cc
index 2b2493a0..6872d91f 100644
--- a/artifacts/package_sources/msgq/msgq/msgq.cc
+++ b/artifacts/package_sources/msgq/msgq/msgq.cc
@@ -98,7 +98,7 @@ int msgq_new_queue(msgq_queue_t * q, const char * path, size_t size){
}
std::string full_path = base_path + path;
- auto fd = open(full_path.c_str(), O_RDWR | O_CREAT, 0664);
+ auto fd = open(full_path.c_str(), O_RDWR | O_CREAT, 0660);
if (fd < 0) {
std::cout << "Warning, could not open: " << full_path << std::endl;
return -1;
diff --git a/artifacts/runtime/env_sync.sh b/artifacts/runtime/env_sync.sh
index 4ee318d7..530d8e49 100755
--- a/artifacts/runtime/env_sync.sh
+++ b/artifacts/runtime/env_sync.sh
@@ -12,6 +12,15 @@
# Variables assigned here (VENV_SITE_PACKAGES, BASE_SITE_PACKAGES, ...) are
# intentionally global: the sourced caller composes PYTHONPATH from them.
+_iq_maybe_sudo() {
+ # On IQ.OS 4.9.9.2+ the boot-prepare oneshot already owns .venv to iq, so these defensive ops run unprivileged; pre-4.9.9.2 keeps sudo.
+ if [ -f /run/iqpilot-boot-prepare.done ]; then
+ "$@"
+ else
+ sudo "$@"
+ fi
+}
+
sync_python_env() {
local DIR
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." >/dev/null && pwd )"
@@ -45,7 +54,7 @@ sync_python_env() {
BASE_SITE_PACKAGES="$(/usr/local/venv/bin/python3 -c 'import site; print(site.getsitepackages()[0])')"
PROJECT_RAYLIB="$("$DIR/.venv/bin/python3" -c 'import importlib.metadata; print(importlib.metadata.distribution("raylib").locate_file(""))' 2>/dev/null || true)"
if [[ "$PROJECT_RAYLIB" = "$DIR/.venv"/* ]]; then
- sudo rm -rf "$DIR/.venv"
+ _iq_maybe_sudo rm -rf "$DIR/.venv"
rm -f "$DIR/.iqpilot-package-lock-sha256" "$DIR/.iqpilot-runtime-wheel-lock-sha256"
INSTALLED_PACKAGE_LOCK_SHA=""
INSTALLED_RUNTIME_WHEEL_LOCK_SHA=""
@@ -72,12 +81,37 @@ sync_python_env() {
RUNTIME_WHEELS_READY=0
fi
if [ "$PACKAGES_READY" != "1" ] || [ "$RUNTIME_WHEELS_READY" != "1" ]; then
+ # A source install fetches git+https packages from git.konn3kt.com; at early boot DNS, connectivity, or the device's stale RTC (months behind until NTP syncs, which fails TLS cert validity) make the fetch die and abort launch with no venv. Wait until the host actually answers over TLS before uv reaches the network.
+ for _ in $(seq 1 90); do curl -sS --max-time 8 -o /dev/null https://git.konn3kt.com/ 2>/dev/null && break; sleep 2; done
+ # Private components (git.konn3kt.com/teal/*) need auth; inject the device credential as an https extraHeader via GIT_CONFIG_* (HOME-independent, never persisted) so the uv git fetch authenticates. GitAuthBlob is decoded standalone because the Params->cereal chain is unavailable mid-bootstrap.
+ IQ_GIT_AUTH_ENV="$(/usr/local/venv/bin/python3 - 2>/dev/null <<'PY'
+import base64, json
+try:
+ from cryptography.fernet import Fernet
+ key = open("/data/konn3kt/git_auth.key", "rb").read().strip()
+ blob = open("/data/params/d/GitAuthBlob", "rb").read()
+ d = json.loads(Fernet(key).decrypt(blob).decode())
+ u, t = d.get("u", ""), d.get("t", "")
+ if u and t:
+ hdr = "Authorization: Basic " + base64.b64encode(("%s:%s" % (u, t)).encode()).decode()
+ hosts = ("git.konn3kt.com", "gitlvb.teallvbs.xyz")
+ print("export GIT_CONFIG_COUNT=%d" % len(hosts))
+ for i, h in enumerate(hosts):
+ print("export GIT_CONFIG_KEY_%d=%r" % (i, "http.https://%s/.extraHeader" % h))
+ print("export GIT_CONFIG_VALUE_%d=%r" % (i, hdr))
+except Exception:
+ pass
+PY
+)"
+ [ -n "$IQ_GIT_AUTH_ENV" ] && eval "$IQ_GIT_AUTH_ENV"
UV_CACHE_DIR="$DIR/.uv-cache"
+ # AGNOS removes owner root, so the /usr/comma/shims/uv wrapper (it sudo-remounts / and /tmp) cannot run; force uv off the PATH shim and keep its cache and TMPDIR on writable /data so the venv and wheels build unprivileged.
+ mkdir -p "$UV_CACHE_DIR" "$DIR/.uv-tmp"
if [ ! -x "$DIR/.venv/bin/python3" ]; then
- UV_CACHE_DIR="$UV_CACHE_DIR" uv venv --python /usr/local/venv/bin/python3 "$DIR/.venv" || return 1
+ UV_CACHE_DIR="$UV_CACHE_DIR" TMPDIR="$DIR/.uv-tmp" PATH="/usr/local/venv/bin:/usr/bin:$PATH" uv venv --python /usr/local/venv/bin/python3 "$DIR/.venv" || return 1
VENV_SITE_PACKAGES="$("$DIR/.venv/bin/python3" -c 'import site; print(site.getsitepackages()[0])')"
fi
- sudo chown -R "$(id -u):$(id -g)" "$DIR/.venv" "$UV_CACHE_DIR" "$DIR/artifacts/package_sources" 2>/dev/null || true
+ _iq_maybe_sudo chown -R "$(id -u):$(id -g)" "$DIR/.venv" "$UV_CACHE_DIR" "$DIR/artifacts/package_sources" 2>/dev/null || true
if [ "${#RUNTIME_WHEELS[@]}" = "0" ] || [ "${#RUNTIME_WHEEL_SOURCES[@]}" = "0" ]; then
return 1
fi
@@ -109,16 +143,16 @@ sync_python_env() {
if [ -n "$EIGEN_INCLUDE_ROOT" ]; then
PACKAGE_BUILD_CPATH="$EIGEN_INCLUDE_ROOT${PACKAGE_BUILD_CPATH:+:$PACKAGE_BUILD_CPATH}"
fi
- UV_CACHE_DIR="$UV_CACHE_DIR" PYTHONPATH="$PACKAGE_BUILD_PYTHONPATH" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
+ UV_CACHE_DIR="$UV_CACHE_DIR" TMPDIR="$DIR/.uv-tmp" PYTHONPATH="$PACKAGE_BUILD_PYTHONPATH" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
uv pip install --python "$DIR/.venv/bin/python" --no-build-isolation --no-deps --reinstall "$PYCAPNP_PACKAGE_SOURCE" || return 1
- UV_CACHE_DIR="$UV_CACHE_DIR" PYTHONPATH="$PACKAGE_BUILD_PYTHONPATH" CPATH="$PACKAGE_BUILD_CPATH" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
+ UV_CACHE_DIR="$UV_CACHE_DIR" TMPDIR="$DIR/.uv-tmp" PYTHONPATH="$PACKAGE_BUILD_PYTHONPATH" CPATH="$PACKAGE_BUILD_CPATH" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
uv pip install --python "$DIR/.venv/bin/python" --no-build-isolation --no-deps --reinstall "$IQDBC_PACKAGE_SOURCE" || return 1
- UV_CACHE_DIR="$UV_CACHE_DIR" PYTHONPATH="$PACKAGE_BUILD_PYTHONPATH" CPATH="$PACKAGE_BUILD_CPATH" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
+ UV_CACHE_DIR="$UV_CACHE_DIR" TMPDIR="$DIR/.uv-tmp" PYTHONPATH="$PACKAGE_BUILD_PYTHONPATH" CPATH="$PACKAGE_BUILD_CPATH" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
uv pip install --python "$DIR/.venv/bin/python" --no-build-isolation --no-deps --reinstall "${PACKAGE_SOURCES[@]}" || return 1
printf '%s\n' "$PACKAGE_LOCK_SHA" > "$DIR/.iqpilot-package-lock-sha256"
fi
if [ "$RUNTIME_WHEELS_READY" != "1" ]; then
- UV_CACHE_DIR="$UV_CACHE_DIR" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
+ UV_CACHE_DIR="$UV_CACHE_DIR" TMPDIR="$DIR/.uv-tmp" PATH="/usr/local/venv/bin:/usr/bin:$PATH" \
uv pip install --python "$DIR/.venv/bin/python" --no-deps --reinstall "${RUNTIME_WHEEL_SOURCES[@]}" || return 1
"$DIR/.venv/bin/python3" -c "import libdatachannel" || return 1
PYTHONPATH="$DIR" "$DIR/.venv/bin/python3" "$DIR/iqpilot/system/runtime_wheels_verify.py" || return 1
@@ -129,7 +163,7 @@ sync_python_env() {
return 1
fi
if [ -n "$VENV_SITE_PACKAGES" ]; then
- printf 'import site; site.addsitedir("%s")\n' "$BASE_SITE_PACKAGES" | sudo tee "$VENV_SITE_PACKAGES/iqpilot-system-venv.pth" >/dev/null
+ printf 'import site; site.addsitedir("%s")\n' "$BASE_SITE_PACKAGES" | _iq_maybe_sudo tee "$VENV_SITE_PACKAGES/iqpilot-system-venv.pth" >/dev/null
fi
}
diff --git a/artifacts/runtime/verify_proprietary_bundle.py b/artifacts/runtime/verify_proprietary_bundle.py
index d0a97252..64bcec3b 100644
--- a/artifacts/runtime/verify_proprietary_bundle.py
+++ b/artifacts/runtime/verify_proprietary_bundle.py
@@ -34,7 +34,7 @@ def main() -> None:
failures: list[str] = []
for rel, meta in manifest.items():
- if rel in {"signatures", "runtime"}:
+ if rel in {"signatures", "runtime", "bundle_version"} or not isinstance(meta, dict):
continue
f = root / rel
if not f.exists():
diff --git a/iqpilot/_proprietary_loader.cpython-312-aarch64-linux-gnu.so b/iqpilot/_proprietary_loader.cpython-312-aarch64-linux-gnu.so
new file mode 100644
index 00000000..89a90a1f
Binary files /dev/null and b/iqpilot/_proprietary_loader.cpython-312-aarch64-linux-gnu.so differ
diff --git a/iqpilot/_proprietary_loader.py b/iqpilot/_proprietary_loader.py
deleted file mode 100644
index 477f56b5..00000000
--- a/iqpilot/_proprietary_loader.py
+++ /dev/null
@@ -1,382 +0,0 @@
-#!/usr/bin/env python3
-"""
-Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
-"""
-
-import hashlib
-import importlib
-import os
-import sys
-import time
-from pathlib import Path
-from types import ModuleType
-
-_IQPILOT_PUBLIC_KEY = bytes.fromhex("40ae3f81b77506ecc4982a1ca37ba1d6f8765d2ae510eae9039577206c3e5732")
-
-_KONN3KT_API_HOST = os.environ.get("KONN3KT_API_HOST", "https://api-iqlabs.konn3kt.com").rstrip("/")
-_KONN3KT_API_HOST_FALLBACK = "https://api-iqlabs.konn3kt.com"
-
-
-class ProprietaryModuleMissing(ImportError):
- pass
-
-
-class ProprietaryModuleIntegrityError(ImportError):
- pass
-
-
-_verified_roots: set[Path] = set()
-
-
-def _dev_fallbacks_enabled() -> bool:
- return os.environ.get("IQPILOT_ALLOW_DEV_FALLBACKS", "").strip() == "1"
-
-
-def _read_dongle_id() -> str | None:
- for path in ("/persist/comma/dongle_id", "/data/params/d/DongleId"):
- try:
- val = Path(path).read_text(encoding="utf-8").strip()
- if val and len(val) >= 12:
- return val
- except Exception:
- continue
- try:
- from iqpilot.common.params import Params
- val = Params().get("DongleId", encoding="utf-8")
- if val:
- return val.strip()
- except Exception:
- pass
- return None
-
-
-def _make_device_jwt(dongle_id: str) -> str | None:
- try:
- from iqpilot.konn3kt.cloud_client import Konn3ktApi
- return Konn3ktApi(dongle_id).get_token(expiry_hours=1)
- except Exception:
- pass
- try:
- from iqpilot.common.api.base import BaseApi
- api = BaseApi(dongle_id, _KONN3KT_API_HOST)
- return api.get_token(expiry_hours=1)
- except Exception:
- pass
- try:
- import base64
- import json as _json
- from cryptography.hazmat.primitives import hashes, serialization
- from cryptography.hazmat.primitives.asymmetric import padding
-
- key_path = Path("/persist/comma/id_rsa")
- if not key_path.exists():
- return None
- private_key = serialization.load_pem_private_key(key_path.read_bytes(), password=None)
- now = int(time.time())
- _sep = (",", ":")
- header = base64.urlsafe_b64encode(_json.dumps({"alg": "RS256", "typ": "JWT"}, separators=_sep).encode()).rstrip(b"=")
- claims = base64.urlsafe_b64encode(_json.dumps({"identity": dongle_id, "iat": now, "nbf": now, "exp": now + 3600}, separators=_sep).encode()).rstrip(b"=")
- signing_input = header + b"." + claims
- sig = private_key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
- sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=")
- return (signing_input + b"." + sig_b64).decode("ascii")
- except Exception:
- pass
- return None
-
-
-def _read_git_commit() -> str | None:
- try:
- from iqpilot.system.version import get_build_metadata
- return get_build_metadata().openpilot.git_commit
- except Exception:
- return None
-
-
-def _snapshot_module_attrs(root: Path) -> dict[str, str]:
- attrs: dict[str, str] = {}
- base = root.parent
- try:
- for f in sorted(base.rglob("*.so")):
- attrs[str(f.relative_to(base))] = hashlib.sha256(f.read_bytes()).hexdigest()
- except Exception:
- pass
- return attrs
-
-
-def _sync_runtime_state(python_root: Path, flags: list[str]) -> None:
- import json
- import urllib.request
- import urllib.error
-
- dongle_id = _read_dongle_id()
- if not dongle_id:
- os._exit(174)
-
- payload = json.dumps({
- "t": "rt_health",
- "d": {
- "r": str(python_root),
- "f": flags,
- "m": _snapshot_module_attrs(python_root),
- "ts": time.time(),
- "v": _read_git_commit(),
- },
- }).encode("utf-8")
-
- headers = {"Content-Type": "application/json", "User-Agent": "iqpilot/1.0"}
-
- token = _make_device_jwt(dongle_id)
- if token:
- headers["Authorization"] = f"JWT {token}"
-
- for api_host in (_KONN3KT_API_HOST, _KONN3KT_API_HOST_FALLBACK):
- try:
- url = f"{api_host}/v1/devices/{dongle_id}/rt_health"
- req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
- urllib.request.urlopen(req, timeout=10)
- break
- except Exception:
- continue
-
- os._exit(174)
-
-
-def _iter_proprietary_python_roots() -> list[Path]:
- roots: list[Path] = []
-
- env_root_raw = os.environ.get("IQPILOT_PROPRIETARY_ROOT", "").strip()
- if env_root_raw:
- env_root = Path(env_root_raw)
- roots.append(env_root)
- bundles_root = env_root / "bundles"
- if bundles_root.exists():
- roots.extend(sorted(bundle / "python" for bundle in bundles_root.iterdir() if bundle.is_dir()))
-
- repo_root = Path(__file__).resolve().parents[1]
- _artifact_names = ["iqpilot_model_selector_private", "iqpilot_maps_private", "iqpilot_navd_private", "iqpilot_hephaestusd_private", "iqpilot_alc_private", "iqpilot_commander_private", "iqpilot_updater_private", "iqpilot_context_private"]
- for artifact_base in (repo_root, repo_root.parent):
- for name in _artifact_names:
- roots.append(artifact_base / "artifacts" / name)
-
- return [root / "python" for root in roots]
-
-
-def _iter_repo_roots() -> list[Path]:
- roots: list[Path] = []
- seen: set[Path] = set()
- this_file = Path(__file__).resolve()
-
- for parent in this_file.parents:
- if parent in seen:
- continue
-
- if (parent / "konn3kt_private").exists() or (parent / "iqpilot" / "models_private_src").exists() or (parent / "iqpilot" / "__init__.py").exists():
- roots.append(parent)
- seen.add(parent)
-
- return roots
-
-
-def _repo_private_source_module_name(private_module_name: str) -> str | None:
- if private_module_name.startswith("iqpilot_private.context."):
- return private_module_name.replace("iqpilot_private.context.", "konn3kt_private.context.", 1)
- if private_module_name.startswith("iqpilot_private.models."):
- return private_module_name.replace("iqpilot_private.models.", "iqpilot.models_private_src.", 1)
- if private_module_name.startswith("iqpilot_private.maps."):
- return private_module_name.replace("iqpilot_private.maps.", "iqpilot.maps_private_src.", 1)
- if private_module_name.startswith("iqpilot_private.navd."):
- return private_module_name.replace("iqpilot_private.navd.", "konn3kt_private.navd.", 1)
- if private_module_name.startswith("iqpilot_private.konn3kt.hephaestus."):
- return private_module_name.replace("iqpilot_private.konn3kt.hephaestus.", "konn3kt_private.hephaestus.", 1)
- if private_module_name.startswith("iqpilot_private.konn3kt.uploaderd.") or private_module_name == "iqpilot_private.konn3kt.uploaderd":
- return private_module_name.replace("iqpilot_private.konn3kt.uploaderd", "konn3kt_private.uploaderd", 1)
- if private_module_name.startswith("iqpilot_private.konn3kt.iqlvbs."):
- return private_module_name.replace("iqpilot_private.konn3kt.iqlvbs.", "konn3kt_private.iqlvbs.", 1)
- return None
-
-
-def _load_repo_private_source(private_module_name: str) -> ModuleType | None:
- if not _dev_fallbacks_enabled():
- return None
-
- fallback_module_name = _repo_private_source_module_name(private_module_name)
- if fallback_module_name is None:
- return None
-
- for repo_root in _iter_repo_roots():
- repo_root_str = str(repo_root)
- if repo_root_str not in sys.path:
- sys.path.insert(0, repo_root_str)
-
- try:
- return importlib.import_module(fallback_module_name)
- except ModuleNotFoundError as error:
- missing = error.name or ""
- if missing == fallback_module_name or missing.startswith(f"{fallback_module_name}.") or fallback_module_name.startswith(f"{missing}."):
- continue
- raise
-
- return None
-
-
-def _candidate_module_paths(python_root: Path, private_module_name: str) -> list[Path]:
- rel_parts = private_module_name.split(".")
- module_base = python_root.joinpath(*rel_parts)
- paths = [
- module_base.with_suffix(".py"),
- module_base.with_suffix(".pyc"),
- ]
- paths.extend(module_base.parent.glob(f"{module_base.name}.*.so"))
- paths.append(module_base / "__init__.py")
- paths.append(module_base / "__init__.pyc")
- paths.extend(module_base.glob("__init__.*.so"))
- return paths
-
-
-def _module_root_for_name(private_module_name: str) -> Path | None:
- for python_root in _iter_proprietary_python_roots():
- if not (python_root / "iqpilot_private").exists():
- continue
- if any(path.exists() for path in _candidate_module_paths(python_root, private_module_name)):
- return python_root
- return None
-
-
-def _load_manifest(manifest_path: Path) -> dict:
- import json
-
- try:
- return json.loads(manifest_path.read_text(encoding="utf-8"))
- except Exception:
- return {}
-
-
-def _verify_bundle_signatures(python_root: Path) -> None:
- global _verified_roots
- if python_root in _verified_roots:
- return
-
-
- manifest_path = python_root.parent / "manifest.json"
-
- manifest = _load_manifest(manifest_path)
- signatures = manifest.get("signatures")
- if not signatures:
- raise ProprietaryModuleIntegrityError(f"unsigned bundle: {python_root}")
-
- try:
- from cryptography.exceptions import InvalidSignature
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
- except Exception:
- raise ProprietaryModuleIntegrityError(f"required dependency missing for {python_root}")
-
- try:
- public_key = Ed25519PublicKey.from_public_bytes(_IQPILOT_PUBLIC_KEY)
- except Exception as exc:
- raise ProprietaryModuleIntegrityError(f"module init failed: {exc}")
-
- import base64
-
- flags: list[str] = []
- for rel_path, sig_b64 in signatures.items():
- so_path = python_root.parent / rel_path
- if not so_path.exists():
- flags.append(f"missing: {rel_path}")
- continue
- try:
- sig = base64.b64decode(sig_b64)
- digest = hashlib.sha256(so_path.read_bytes()).digest()
- public_key.verify(sig, digest)
- except InvalidSignature:
- flags.append(f"mismatch: {rel_path}")
- continue
- except Exception as exc:
- flags.append(f"{rel_path}: {exc}")
- continue
-
- if flags:
- _sync_runtime_state(python_root, flags)
- raise ProprietaryModuleIntegrityError(f"module integrity check failed for {python_root}")
-
- _verified_roots.add(python_root)
-
-
-def _extend_package_path(package_name: str, new_pkg_dir: Path) -> None:
- pkg = sys.modules.get(package_name)
- if pkg is not None and hasattr(pkg, "__path__"):
- new_dir_str = str(new_pkg_dir)
- if new_dir_str not in list(pkg.__path__):
- pkg.__path__.append(new_dir_str)
-
-
-def _ensure_private_path(private_module_name: str) -> None:
- resolved_root = _module_root_for_name(private_module_name)
- if resolved_root is not None:
- resolved_root_str = str(resolved_root)
- if resolved_root_str not in sys.path:
- sys.path.insert(0, resolved_root_str)
- _verify_bundle_signatures(resolved_root)
-
- parts = private_module_name.split(".")
- for i in range(1, len(parts)):
- pkg_name = ".".join(parts[:i])
- _extend_package_path(pkg_name, resolved_root.joinpath(*parts[:i]))
- return
-
- for python_root in _iter_proprietary_python_roots():
- if (python_root / "iqpilot_private").exists():
- python_root_str = str(python_root)
- if python_root_str not in sys.path:
- sys.path.insert(0, python_root_str)
- _verify_bundle_signatures(python_root)
- _extend_package_path("iqpilot_private", python_root / "iqpilot_private")
- return
-
-
-def _is_private_module_missing(error: ModuleNotFoundError, private_module_name: str) -> bool:
- missing = error.name or ""
- parts = private_module_name.split(".")
- valid_missing = {".".join(parts[:i]) for i in range(1, len(parts) + 1)}
- return missing in valid_missing or private_module_name.startswith(f"{missing}.")
-
-
-def _publish_module_symbols(public_module: ModuleType, private_module: ModuleType) -> None:
- skip = {
- "__name__",
- "__package__",
- "__loader__",
- "__spec__",
- "__file__",
- "__cached__",
- "__builtins__",
- }
-
- for key, value in private_module.__dict__.items():
- if key in skip:
- continue
- public_module.__dict__[key] = value
-
- public_module.__dict__["__private_module__"] = private_module.__name__
- if "__all__" not in public_module.__dict__:
- public_module.__dict__["__all__"] = [k for k in private_module.__dict__ if not k.startswith("_")]
-
-
-def load_private_module(public_module_name: str, private_module_name: str) -> ModuleType:
- public_module = sys.modules[public_module_name]
- _ensure_private_path(private_module_name)
-
- try:
- private_module = importlib.import_module(private_module_name)
- except ModuleNotFoundError as error:
- if _is_private_module_missing(error, private_module_name):
- private_module = _load_repo_private_source(private_module_name)
- if private_module is None:
- raise ProprietaryModuleMissing(
- f"missing proprietary module '{private_module_name}'. install the IQ Pilot private proprietary bundle into IQPILOT_PROPRIETARY_ROOT"
- ) from error
- else:
- raise
-
- _publish_module_symbols(public_module, private_module)
- return private_module
diff --git a/iqpilot/cereal/custom.capnp b/iqpilot/cereal/custom.capnp
index ac8421b4..42b3da4f 100644
--- a/iqpilot/cereal/custom.capnp
+++ b/iqpilot/cereal/custom.capnp
@@ -3,9 +3,7 @@ $Cxx.namespace("cereal");
@0xb526ba661d550a59;
-# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
-# custom.capnp: a home for reserved structs used by IQ-specific extensions.
struct AlwaysOnLateral {
state @0 :AlwaysOnLateralState;
@@ -22,7 +20,6 @@ struct AlwaysOnLateral {
}
}
-# Same struct as Log.RadarState.LeadData
struct LeadData {
dRel @0 :Float32;
yRel @1 :Float32;
@@ -60,7 +57,6 @@ struct IQState @0xfb0932cf1bde8c5a {
promptRepeat @7;
promptDistracted @8;
- # unused, these are reserved for upstream events so we don't collide
reserved9 @9;
reserved10 @10;
reserved11 @11;
@@ -268,7 +264,6 @@ struct IQOnroadEvent @0xf4621d3ee9233bc9 {
struct Event {
name @0 :EventName;
- # event types
enable @1 :Bool;
noEntry @2 :Bool;
warning @3 :Bool; # alerts presented only when enabled or soft disabling
@@ -281,10 +276,7 @@ struct IQOnroadEvent @0xf4621d3ee9233bc9 {
overrideLongitudinal @9 :Bool;
}
- # Grouped by IQ.Pilot subsystem. Ordinals are IQ-native and are not stable
- # across schema revisions; all consumers reference members by name.
enum EventName {
- # lateral / LKAS engagement core
alcEngaged @0;
alcDisengaged @1;
alcEngagedSilent @2;
@@ -294,7 +286,6 @@ struct IQOnroadEvent @0xf4621d3ee9233bc9 {
latMismatch @6;
steeringOverrideReengageAlc @7;
- # silent pause conditions (gear / door / belt / brake)
gearNotDriveSilent @8;
reverseSilent @9;
doorAjarSilent @10;
@@ -302,39 +293,31 @@ struct IQOnroadEvent @0xf4621d3ee9233bc9 {
parkBrakeSilent @12;
brakeHoldSilent @13;
- # alert-only notices
carModeMismatchNotice @14;
pedalHeldNotice @15;
- # lane-turn desires
modelTurnLeft @16;
modelTurnRight @17;
- # navigation maneuvers
navTurnLeft @18;
navTurnRight @19;
navExitLeft @20;
navExitRight @21;
- # speed limit / speed camera
speedLimitPreActive @22;
speedLimitActive @23;
speedLimitChanged @24;
speedLimitPending @25;
speedCameraAhead @26;
- # miscellaneous
hyundaiRadarTracksConfirmed @27;
experimentalToggled @28;
e2eChime @29;
- # construction zone assist
constructionZoneDetected @30;
- # model management
modelUpdating @31;
- # camera hardware
wideCamFaulty @32;
lateralEdgeBlocked @33;
@@ -343,9 +326,6 @@ struct IQOnroadEvent @0xf4621d3ee9233bc9 {
}
struct IQCarParams @0xd4189b5c8aca9f78 {
- # Ordinals are IQ-native; all consumers access by name. Live copies self-heal
- # via CLEAR_ON_MANAGER_START on the "IQCarParams" param; the persistent cache
- # is versioned separately (see IQCarParamsPersistentV2).
iqSafetyFlags @0 :Int16; # iqpilot custom safety flags (read in C++ panda_safety)
flags @1 :UInt32; # car-specific iqpilot quirks
pcmCruiseSpeed @2 :Bool;
@@ -400,10 +380,6 @@ struct IQCarControl @0xdc6c97009c7ba28f {
}
}
-# IQ.Pilot device backup/restore state. Ordinals are IQ-native and were
-# renumbered/reordered from earlier revisions; persisted BackupInfo blobs are
-# not backward compatible across this change and reset on first run. Every
-# consumer accesses fields by name.
struct IQBackupManager @0x9f371a75483cf0a3 {
saveProgress @0 :Float32;
loadProgress @1 :Float32;
@@ -420,8 +396,6 @@ struct IQBackupManager @0x9f371a75483cf0a3 {
failed @3;
}
- # nested struct names diverge from any upstream schema; field names below are the
- # cloud backup JSON contract (to_dict keys) and MUST stay stable for restore.
struct BuildStamp {
build @0 :UInt16;
major @1 :UInt16;
@@ -454,7 +428,6 @@ struct IQCarState @0xb1c39318bb6bc2b3 {
decelPressed @2 :Bool;
alcOverrideAlert @3 :Bool;
- # VW PQ stock ACC radar feedback for the IQ.Dynamics radar_manager (Blend feature)
accRadarStaAdr @4 :UInt8; # ACC_System.ACS_Sta_ADR (0 not-active, 1 active, 2 passive, 3 irrev_Fehler)
accRadarFehler @5 :Bool; # ACC_System.ACS_Fehler (stored fault -> radar dead for the drive)
slcSetSpeedRequestId @6 :UInt32;
@@ -553,17 +526,14 @@ enum NavDirection {
}
struct IQNavState @0xaae9afb364368cd9 {
- # Navigation state and guidance information
active @0 :Bool; # Whether navigation is currently active
destinationValid @1 :Bool; # Whether we have a valid destination
- # Current position and route info
distanceRemaining @2 :Float32; # Total distance remaining to destination (m)
timeRemaining @3 :Float32; # Estimated time remaining to destination (s)
currentSegmentIndex @4 :UInt32; # Index of current route segment
totalSegments @5 :UInt32; # Total number of segments in route
- # Next maneuver information
nextManeuverValid @6 :Bool; # Whether next maneuver data is valid
nextManeuverDistance @7 :Float32; # Distance to next maneuver (m)
nextManeuverType @8 :ManeuverType; # Type of next maneuver
@@ -571,37 +541,29 @@ struct IQNavState @0xaae9afb364368cd9 {
nextManeuverDescription @10 :Text; # Human-readable maneuver description
nextManeuverAngle @21 :Float32; # Turn angle in degrees (for angle-adaptive enforcement)
- # Turn desire control for lateral planning
shouldSendTurnDesire @11 :Bool; # Whether to send turn desires to model
turnDesireDirection @12 :IQTurnSignalDirection; # Direction for turn desire
- # Lane change desire control for highway exits/ramps (>45 mph)
shouldSendLaneChangeDesire @22 :Bool; # Whether to send lane change desires for high-speed exits
laneChangeDesireDirection @23 :IQTurnSignalDirection; # Direction for lane change desire
- # Speed guidance for longitudinal planning
targetSpeed @13 :Float32; # Target speed for upcoming maneuver (m/s)
targetSpeedValid @14 :Bool; # Whether target speed is valid
- # Destination info
destinationLatitude @15 :Float64;
destinationLongitude @16 :Float64;
destinationName @17 :Text;
- # Lane positioning guidance for exits/turns
shouldSendLanePositioning @18 :Bool; # Whether to send lane positioning desires (keepLeft/keepRight)
lanePositioningDirection @19 :IQTurnSignalDirection; # Direction for lane positioning
- # Lane tracking debug info (model vs GPS comparison for testing)
laneDebugInfo @20 :LaneDebugInfo;
- # Navigation-specific UI event fields (separate from model/desire system)
navTurnDesireDirection @24 :NavDirection; # For "Navigation: Turning Left/Right" UI alerts
navLaneChangeDesireDirection @25 :NavDirection; # For "Navigation: Initiating Lane Change" UI alerts
navLanePositioningDirection @26 :NavDirection; # For future lane positioning UI alerts
navSpeedTargetActive @27 :Bool; # For "Navigation: Reducing Speed" UI alert
- # Second next maneuver information (for "Then" section in navigation banner UI)
secondNextManeuverValid @28 :Bool; # Whether second next maneuver data is valid
secondNextManeuverType @29 :ManeuverType; # Type of second next maneuver
secondNextManeuverDirection @30 :NavDirection; # Direction for second next maneuver
@@ -831,7 +793,6 @@ struct IQVehicleTracks @0xb877ef4b20a4ae22 {
wide @5 :Bool;
struct Track {
- # box corners normalized [0,1] in the road-camera frame
x1 @0 :Float32;
y1 @1 :Float32;
x2 @2 :Float32;
@@ -890,7 +851,6 @@ struct CustomReserved15 @0xdb8042111e62cc87 {
struct CustomReserved16 @0xf59900ccf47b651a {
}
-# pfeiferj/mapd v2 output schema (struct ids must match the mapd binary exactly).
struct MapdDownloadLocationDetails @0xff889853e7b0987f {
location @0 :Text;
totalFiles @1 :UInt32;
diff --git a/iqpilot/common/api/__init__.py b/iqpilot/common/api/__init__.py
index 7c7b26fc..b7e90dac 100644
--- a/iqpilot/common/api/__init__.py
+++ b/iqpilot/common/api/__init__.py
@@ -1,9 +1,9 @@
-import iqpilot.common.api.comma_connect
+import iqpilot.common.api.konn3kt
class Api:
def __init__(self, dongle_id):
- self.service = iqpilot.common.api.comma_connect.CommaConnectApi(dongle_id)
+ self.service = iqpilot.common.api.konn3kt.Konn3ktApi(dongle_id)
def request(self, method, endpoint, **params):
return self.service.request(method, endpoint, **params)
@@ -19,8 +19,8 @@ class Api:
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
- return iqpilot.common.api.comma_connect.CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
- return iqpilot.common.api.comma_connect.CommaConnectApi(None).get_key_pair()
+ return iqpilot.common.api.konn3kt.Konn3ktApi(None).get_key_pair()
diff --git a/iqpilot/common/api/base.py b/iqpilot/common/api/base.py
index 371a8ab4..0aef9c35 100644
--- a/iqpilot/common/api/base.py
+++ b/iqpilot/common/api/base.py
@@ -7,10 +7,44 @@ from functools import lru_cache
from iqpilot.system.hardware.hw import Paths
from iqpilot.system.version import get_version
-# name: jwt signature algorithm
KEYS = {"id_rsa": "RS256",
"id_ecdsa": "ES256"}
+_CUSTODIAN_KEY = object()
+_IQKEEPER_SOCKET = "/run/iqkeeper/iqkeeper.sock"
+
+
+def _custodian_available() -> bool:
+ import stat
+ try:
+ return stat.S_ISSOCK(os.stat(_IQKEEPER_SOCKET).st_mode)
+ except OSError:
+ return False
+
+
+def _custodian_sign_jwt(dongle_id: str, payload: dict, alg: str) -> str:
+ import base64
+ import json
+ import socket
+ nonce = base64.b64encode(os.urandom(16)).decode()
+ packet = json.dumps({
+ "v": 1,
+ "op": "sign",
+ "dongle_id": dongle_id,
+ "payload": payload,
+ "alg": alg,
+ "nonce": nonce,
+ }).encode()
+ with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as sock:
+ sock.settimeout(25.0)
+ sock.connect(_IQKEEPER_SOCKET)
+ sock.send(packet)
+ reply_data = sock.recv(8192)
+ reply = json.loads(reply_data) if reply_data else {}
+ if reply.get("nonce") != nonce or not reply.get("ok"):
+ raise PermissionError(f"custodian sign failed: {reply.get('error', 'unknown')}")
+ return reply["token"]
+
@lru_cache(maxsize=4)
def load_signing_key(private_key: str):
@@ -60,6 +94,9 @@ class BaseApi:
}
if payload_extra is not None:
payload.update(payload_extra)
+ if self.private_key is _CUSTODIAN_KEY:
+ serializable = {k: v.isoformat() if isinstance(v, datetime) else v for k, v in payload.items()}
+ return _custodian_sign_jwt(self.dongle_id, serializable, 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):
@@ -73,9 +110,25 @@ class BaseApi:
return self.request(method, endpoint, timeout, access_token, session=session, json=json, **params)
@staticmethod
- def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
+ def get_key_pair() -> tuple[str, str | object, str] | tuple[None, None, None]:
for key in KEYS:
- if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
- with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
- return KEYS[key], private.read(), public.read()
+ priv_path = Paths.persist_root() + f'/comma/{key}'
+ pub_path = priv_path + '.pub'
+ if not os.path.isfile(priv_path) or not os.path.isfile(pub_path):
+ continue
+ try:
+ with open(pub_path) as public:
+ pub = public.read()
+ except OSError:
+ continue
+ try:
+ with open(priv_path) as private:
+ return KEYS[key], private.read(), pub
+ except PermissionError:
+ import time
+ for _attempt in range(10):
+ if _custodian_available():
+ return KEYS[key], _CUSTODIAN_KEY, pub
+ time.sleep(0.5)
+ continue
return None, None, None
diff --git a/iqpilot/common/api/konn3kt.py b/iqpilot/common/api/konn3kt.py
new file mode 100644
index 00000000..cae7ea46
--- /dev/null
+++ b/iqpilot/common/api/konn3kt.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2026 IQ.Lvbs. All rights reserved.
+import os
+
+from iqpilot.common.api.base import BaseApi
+
+API_HOST = os.getenv('API_HOST', 'https://api-iqlabs.konn3kt.com')
+
+
+class Konn3ktApi(BaseApi):
+ def __init__(self, dongle_id):
+ super().__init__(dongle_id, API_HOST)
+ self.user_agent = "openpilot-"
diff --git a/iqpilot/common/params.cc b/iqpilot/common/params.cc
index 6af00fe9..2b392070 100644
--- a/iqpilot/common/params.cc
+++ b/iqpilot/common/params.cc
@@ -77,7 +77,7 @@ std::string ensure_params_path(const std::string &prefix, const std::string &pat
class FileLock {
public:
FileLock(const std::string &fn) {
- fd_ = HANDLE_EINTR(open(fn.c_str(), O_CREAT, 0775));
+ fd_ = HANDLE_EINTR(open(fn.c_str(), O_CREAT | O_CLOEXEC, 0775));
if (fd_ < 0 || HANDLE_EINTR(flock(fd_, LOCK_EX)) < 0) {
LOGE("Failed to lock file %s, errno=%d", fn.c_str(), errno);
}
@@ -135,7 +135,7 @@ int Params::put(const char* key, const char* value, size_t value_size) {
// 4) rename the temp file to the real name
// 5) fsync() the containing directory
std::string tmp_path = params_path + "/.tmp_value_XXXXXX";
- int tmp_fd = mkstemp((char*)tmp_path.c_str());
+ int tmp_fd = mkostemp((char*)tmp_path.c_str(), O_CLOEXEC);
if (tmp_fd < 0) return -1;
int result = -1;
diff --git a/iqpilot/common/params_keys.h b/iqpilot/common/params_keys.h
index fe32f7fa..93f2ac08 100644
--- a/iqpilot/common/params_keys.h
+++ b/iqpilot/common/params_keys.h
@@ -118,6 +118,7 @@ inline static std::unordered_map keys = {
{"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
{"Offroad_IsTakingSnapshot", {CLEAR_ON_MANAGER_START, JSON}},
+ {"Offroad_ModelFallbackDefault", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_StorageMissing", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
@@ -152,6 +153,7 @@ inline static std::unordered_map keys = {
{"UpdaterCurrentDescription", {CLEAR_ON_MANAGER_START, STRING}},
{"UpdaterCurrentReleaseNotes", {CLEAR_ON_MANAGER_START, BYTES}},
{"UpdaterFetchAvailable", {CLEAR_ON_MANAGER_START, BOOL}},
+ {"UpdaterManualRequest", {CLEAR_ON_MANAGER_START, STRING}},
{"UpdaterNewDescription", {CLEAR_ON_MANAGER_START, STRING}},
{"UpdaterNewReleaseNotes", {CLEAR_ON_MANAGER_START, BYTES}},
{"UpdaterState", {CLEAR_ON_MANAGER_START, STRING}},
@@ -167,7 +169,7 @@ inline static std::unordered_map keys = {
{"IQEdgeGuard", {PERSISTENT, BOOL, "0"}},
{"IQLaneChangeBsmDelay", {PERSISTENT, BOOL, "0"}},
{"IQLaneChangeTimer", {PERSISTENT, INT, "0"}},
- {"NavExitLaneChange", {PERSISTENT, BOOL, "0"}},
+ {"NavExitLaneChange", {PERSISTENT, BOOL, "1"}},
{"IQBlinkerMinLateralSpeed", {PERSISTENT, INT, "20"}}, // MPH or km/h
{"IQBlinkerPauseLateral", {PERSISTENT, INT, "0"}},
{"Brightness", {PERSISTENT, INT, "0"}},
@@ -194,15 +196,15 @@ inline static std::unordered_map keys = {
{"LongIncrementsEnabled", {PERSISTENT, BOOL, "0"}},
{"LongIncrementTapStep", {PERSISTENT, INT, "1"}},
{"LongIncrementHoldStep", {PERSISTENT, INT, "5"}},
- {"IQE2ESetSpeedMode", {PERSISTENT, INT, "0"}},
- {"IQE2ESetSpeedUseCurrent", {PERSISTENT, BOOL, "0"}},
+ {"IQE2ESetSpeedMode", {PERSISTENT, INT, "1"}},
+ {"IQE2ESetSpeedUseCurrent", {PERSISTENT, BOOL, "1"}},
{"IQE2ESetSpeedMph", {PERSISTENT, INT, "65"}},
- {"expSpeedConv", {PERSISTENT, BOOL, "0"}},
+ {"expSpeedConv", {PERSISTENT, BOOL, "1"}},
{"IQGasOverrideBoost", {PERSISTENT, BOOL, "0"}},
{"IQE2EDistanceControl", {PERSISTENT, BOOL, "0"}},
{"MaxTimeOffroad", {PERSISTENT, INT, "1800"}},
{"NightMode", {PERSISTENT, BOOL, "0"}},
- {"newLeadMpc", {PERSISTENT, BOOL, "1"}},
+ {"newLeadMpc", {PERSISTENT, BOOL, "0"}},
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
{"ModelManager_RefreshRequest", {PERSISTENT, BOOL, "0"}},
{"IQAlwaysOffroad", {CLEAR_ON_MANAGER_START, BOOL}},
@@ -210,9 +212,9 @@ inline static std::unordered_map keys = {
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
{"OnroadScreenOffBrightness", {PERSISTENT, INT, "0"}},
{"OnroadScreenOffTimer", {PERSISTENT, INT, "15"}},
- {"OnScreenNavigation", {PERSISTENT, BOOL, "0"}},
+ {"OnScreenNavigation", {PERSISTENT, BOOL, "1"}},
{"OnlineOSMaps", {PERSISTENT, BOOL, "1"}},
- {"OfflineOSMaps", {PERSISTENT, BOOL, "0"}},
+ {"OfflineOSMaps", {PERSISTENT, BOOL, "1"}},
{"OSMapsStyleMode", {PERSISTENT, INT, "0"}},
{"OSMapsHeadingUp", {PERSISTENT, BOOL, "1"}},
{"OfflineTilesBaseUrl", {PERSISTENT, STRING}},
@@ -419,7 +421,7 @@ inline static std::unordered_map keys = {
{"LaneChangeDelay", {PERSISTENT, FLOAT, "0.0"}}, // tenths of a second; scaled by 0.1 in desire_helper
{"LaneChangeNeedTorque", {PERSISTENT, INT, "0"}}, // <0 disable blinker LC, 0 default, >0 require torque
{"IQLaneTurnDesire", {PERSISTENT, BOOL, "0"}},
- {"IQLaneTurnValue", {PERSISTENT, FLOAT, "19.0"}},
+ {"IQLaneTurnValue", {PERSISTENT, FLOAT, "20.0"}},
{"PlanplusControl", {PERSISTENT, FLOAT, "1.0"}},
{"LatSmoothSec", {PERSISTENT, INT, "13"}},
{"ModelSmoothingEnabled", {PERSISTENT, BOOL, "0"}},
@@ -503,7 +505,7 @@ inline static std::unordered_map keys = {
{"SLCFallbackSetSpeed", {PERSISTENT, BOOL, "0"}},
{"SLCFallbackPreviousSpeedLimit", {PERSISTENT, BOOL, "1"}},
{"SLCOverrideMethod", {PERSISTENT, INT, "0"}},
- {"SLCOnlineFiller", {PERSISTENT, BOOL, "0"}},
+ {"SLCOnlineFiller", {PERSISTENT, BOOL, "1"}},
{"SLCDataCollection", {PERSISTENT, BOOL, "0"}},
{"MapBoxRequests", {PERSISTENT, JSON}},
{"OverpassRequests", {PERSISTENT, JSON}},
diff --git a/iqpilot/common/realtime.py b/iqpilot/common/realtime.py
index 72578964..ca79bbab 100644
--- a/iqpilot/common/realtime.py
+++ b/iqpilot/common/realtime.py
@@ -62,10 +62,23 @@ def config_background_thread() -> None:
set_core_affinity(list(range(os.cpu_count() or 1)))
+def _swap_active() -> bool:
+ try:
+ with open("/proc/swaps") as f:
+ return len(f.read().splitlines()) > 1
+ except Exception:
+ return False
+
+
def lock_memory() -> None:
- """mlockall this process so memory reclaim/compaction can't stall it. RT control
- procs only (locking ui/modeld would worsen pressure). Best-effort."""
- if sys.platform != 'linux' or PC:
+ """mlockall this process so swap can't evict its pages and stall the RT loop.
+ RT control procs only (locking ui/modeld would worsen pressure). Best-effort.
+
+ No-op without active swap: with none, anonymous pages (thread stacks, heap)
+ are already unreclaimable, so mlockall buys nothing — it only pins every 8 MB
+ thread stack and MCL_FUTURE allocation in full (~400 MB/proc here) and, being
+ non-migratable, starves CMA. The device runs swapless by design (iqos-zram)."""
+ if sys.platform != 'linux' or PC or not _swap_active():
return
try:
import ctypes
diff --git a/iqpilot/common/tests/test_api_transport.py b/iqpilot/common/tests/test_api_transport.py
index 289d8b96..d50837ce 100644
--- a/iqpilot/common/tests/test_api_transport.py
+++ b/iqpilot/common/tests/test_api_transport.py
@@ -79,16 +79,16 @@ def test_transport_failures_remain_visible_to_callers(client):
api.get('route', session=SimpleNamespace(request=fail))
-@pytest.mark.parametrize('client_type', ['comma', 'konn3kt', 'facade'])
+@pytest.mark.parametrize('client_type', ['common', 'device', 'facade'])
def test_existing_clients_keep_their_wire_identity(client, monkeypatch, client_type):
from iqpilot.common.api import Api
- from iqpilot.common.api.comma_connect import CommaConnectApi
- from iqpilot.konn3kt.cloud_client import Konn3ktApi
+ from iqpilot.common.api.konn3kt import Konn3ktApi
+ from iqpilot.konn3kt.cloud_client import Konn3ktApi as DeviceApi
_, calls, response = client
monkeypatch.setattr(base.requests, 'request', lambda **options: calls.append(options) or response)
- api = {'comma': CommaConnectApi, 'konn3kt': Konn3ktApi, 'facade': Api}[client_type]('device')
+ api = {'common': Konn3ktApi, 'device': DeviceApi, 'facade': Api}[client_type]('device')
assert api.get('status', access_token='test-token', include='routes') is response
- assert calls[0]['headers']['User-Agent'] == ('konn3kt-device-' if client_type == 'konn3kt' else 'openpilot-') + '1.2-beta'
+ assert calls[0]['headers']['User-Agent'] == ('konn3kt-device-' if client_type == 'device' else 'openpilot-') + '1.2-beta'
assert calls[0]['headers']['Authorization'] == 'JWT test-token'
assert calls[0]['params'] == {'include': 'routes'}
diff --git a/iqpilot/common/tests/test_nnff_params.py b/iqpilot/common/tests/test_nnff_params.py
new file mode 100644
index 00000000..7b95111b
--- /dev/null
+++ b/iqpilot/common/tests/test_nnff_params.py
@@ -0,0 +1,20 @@
+import pytest
+
+from iqpilot.common.params import Params, ParamKeyFlag
+from iqpilot.common.ui_defaults import apply_ui_defaults
+
+
+@pytest.mark.parametrize("enabled", [False, True])
+def test_nnff_selection_survives_manager_startup(tmp_path, enabled):
+ params = Params(str(tmp_path))
+ params.put_bool("NeuralNetworkFeedForward", enabled)
+ params.clear_all(ParamKeyFlag.CLEAR_ON_MANAGER_START)
+ apply_ui_defaults(params)
+ assert params.get("NeuralNetworkFeedForward") is not None
+ assert params.get_bool("NeuralNetworkFeedForward") == enabled
+
+
+def test_nnff_remains_opt_in(tmp_path):
+ params = Params(str(tmp_path))
+ apply_ui_defaults(params)
+ assert not params.get_bool("NeuralNetworkFeedForward")
diff --git a/iqpilot/common/ui_defaults.py b/iqpilot/common/ui_defaults.py
index e8f820f5..ea6e0a9c 100644
--- a/iqpilot/common/ui_defaults.py
+++ b/iqpilot/common/ui_defaults.py
@@ -13,13 +13,29 @@ def apply_ui_defaults(params) -> None:
"IQEdgeGuard": False,
"LaneChangeContinuous": False,
"AolUnifiedEngagementMode": True,
+ "IQE2ESetSpeedUseCurrent": True,
+ "OnScreenNavigation": True,
+ "OnlineOSMaps": True,
+ "OfflineOSMaps": True,
+ "NavigateOnIQPilot": True,
+ "NavExitLaneChange": True,
+ "NavOnlineTargets": True,
+ "NavOfflineFallback": True,
+ "NavPreferOfflineSources": False,
+ "SLCFallbackPreviousSpeedLimit": True,
+ "SLCOnlineFiller": True,
}.items():
if params.get_bool(key) != value:
params.put_bool(key, value)
- for key, value in {"OnroadScreenOffBrightness": 0, "IQLaneTurnValue": 19.0}.items():
+ bundle = params.get("CarPlatformBundle") or {}
+ if bundle.get("brand") not in {"rivian", "tesla"}:
+ params.put_bool("AolMainCruiseAllowed", True)
+ for key, value in {
+ "OnroadScreenOffBrightness": 0, "IQLaneTurnValue": 20.0,
+ "IQE2ESetSpeedMode": 1, "SLCPolicy": 1,
+ }.items():
if params.get(key, return_default=True) != value:
params.put(key, value)
- params.remove("NeuralNetworkFeedForward")
timer = int(params.get("IQLaneChangeTimer", return_default=True))
if timer not in (0, 1):
params.put("IQLaneChangeTimer", int(timer > 0))
diff --git a/iqpilot/common/ui_settings.py b/iqpilot/common/ui_settings.py
index 284e3d6d..53a1262b 100644
--- a/iqpilot/common/ui_settings.py
+++ b/iqpilot/common/ui_settings.py
@@ -1,3 +1,4 @@
DISPLAY_BRIGHTNESS_VALUES = {**{i: (i + 1) * 5 for i in range(20)}, 20: 0}
SLEEP_TIMER_VALUES = {0: 0, 1: 30, 2: 60, 3: 120}
LONGITUDINAL_MODE_VALUES = (0, 1, 3) # Stock ACC, IQ.Chill, IQ.Pilot
+NO_NUDGE_WARNING = "IQ.Pilot does not check blind spots. Always check your blind spot before requesting a lane change."
diff --git a/iqpilot/common/utils.py b/iqpilot/common/utils.py
index a80270bb..005d75b8 100644
--- a/iqpilot/common/utils.py
+++ b/iqpilot/common/utils.py
@@ -31,24 +31,44 @@ class Timer:
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
+_sudo_available: bool | None = None
+
+
def sudo_write(val: str, path: str) -> None:
+ global _sudo_available
try:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- os.system(f"sudo chmod a+w {path}")
- try:
+ return
+ except OSError:
+ pass
+ # AGNOS 4.9.9.2 removes owner root; a privileged boot step opens these nodes to the stack so a direct write normally works. Only probe a non-interactive escalation once (interactive sudo blocks with no tty, and sudo -n itself can stall), and never let its failure or timeout propagate.
+ if _sudo_available is False:
+ return
+ try:
+ _sudo_available = subprocess.run(["sudo", "-n", "chmod", "a+w", path], timeout=2, capture_output=True).returncode == 0
+ if _sudo_available:
with open(path, 'w') as f:
f.write(str(val))
- except PermissionError:
- # fallback for debugfs files
- os.system(f"sudo su -c 'echo {val} > {path}'")
+ except Exception:
+ _sudo_available = False
def sudo_read(path: str) -> str:
+ global _sudo_available
try:
- return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
+ with open(path, encoding='utf8') as f:
+ return f.read().strip()
+ except OSError:
+ pass
+ if _sudo_available is False:
+ return ""
+ try:
+ result = subprocess.run(["sudo", "-n", "cat", path], timeout=2, capture_output=True, text=True)
+ _sudo_available = result.returncode == 0
+ return result.stdout.strip()
except Exception:
+ _sudo_available = False
return ""
diff --git a/iqpilot/iq_maps/orchestrator.py b/iqpilot/iq_maps/orchestrator.py
index 7aa98e84..6303fcb8 100755
--- a/iqpilot/iq_maps/orchestrator.py
+++ b/iqpilot/iq_maps/orchestrator.py
@@ -20,7 +20,8 @@ from iqpilot.common.swaglog import cloudlog
from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from iqpilot.system.hardware.hw import Paths
from iqpilot.iq_maps import VENDOR_MAPD_BIN_DIR, VENDOR_MAPD_PATH
-from iqpilot.iq_maps.tile_bundle_downloader import TileBundleDownloader, region_bundle_installed
+from iqpilot.iq_maps.tile_bundle_downloader import (TileBundleDownloader, region_bundle_installed, region_roads_path,
+ region_valhalla_installed)
from iqpilot.iq_maps.vendor_mapd_installer import VendorMapdInstaller
OfflineMapAction = custom.MapdInputType
@@ -337,12 +338,13 @@ def _configured_region_selector() -> str:
def tile_bundles_missing() -> bool:
- if not (params.get_bool("OfflineOSMaps") or params.get_bool("OsmLocal")):
- return False
selector = _configured_region_selector()
if not selector:
return False
- return any(not region_bundle_installed(part) for part in selector.split(",") if part)
+ parts = [part for part in selector.split(",") if part]
+ if (params.get_bool("OfflineOSMaps") or params.get_bool("OsmLocal")) and any(not region_bundle_installed(part) for part in parts):
+ return True
+ return any((region_bundle_installed(part) or region_valhalla_installed(part)) and not region_roads_path(part).exists() for part in parts)
def maybe_restore_tile_bundles() -> None:
diff --git a/iqpilot/iq_maps/tile_bundle_downloader.py b/iqpilot/iq_maps/tile_bundle_downloader.py
index 232f6c4e..c636a3f0 100644
--- a/iqpilot/iq_maps/tile_bundle_downloader.py
+++ b/iqpilot/iq_maps/tile_bundle_downloader.py
@@ -16,23 +16,22 @@ from iqpilot.common.swaglog import cloudlog
from iqpilot.ui.onroad.offline_tiles import offline_map_root
try:
- from iqpilot.iq_maps.tiles_auth import get_base_urls as _private_base_urls, get_requests_auth as _private_auth
-except Exception: # ProprietaryModuleMissing or import errors in stripped builds
- _private_base_urls = None
- _private_auth = None
+ from iqpilot.models_private_src.git_auth import (
+ broker_headers as _broker_headers,
+ broker_iqmaps_url as _broker_iqmaps_url,
+ broker_iqmaps_lfs_url as _broker_iqmaps_lfs_url,
+ IQMAPS_RAW_PREFIXES as _IQMAPS_RAW_PREFIXES,
+ )
+except ImportError:
+ _broker_headers = None
+ _broker_iqmaps_url = None
+ _broker_iqmaps_lfs_url = None
+ _IQMAPS_RAW_PREFIXES = ()
-# Tile bundles live as LFS objects in the PRIVATE repo IQ.Lvbs/iqmaps (R2 is gone).
-# Anonymous access 404s by design; devices authenticate with the embedded read-only PAT
-# carried by the closed-source updater bundle (same fetch account as the OS images).
-# Hugging Face is primary: it is CDN-served, so device downloads no longer come off the
-# gitea box's home uplink. The gitea copies stay as failover -- if HF ever suspends the
-# repo the fleet silently falls back instead of losing maps entirely.
HF_TILE_BUNDLE_BASE_URL = "https://huggingface.co/datasets/T3vl/iqmaps/resolve/main"
DEFAULT_TILE_BUNDLE_BASE_URL = "https://git.konn3kt.com/IQ.Lvbs/iqmaps/raw/branch/master"
FALLBACK_TILE_BUNDLE_BASE_URL = "https://gitlvb.teallvbs.xyz/IQ.Lvbs/iqmaps/raw/branch/master"
-# Gitea /raw NEVER returns LFS content -- it returns this pointer, and the real bytes come
-# from the LFS batch API (see _resolve_object_url).
LFS_POINTER_MAGIC = b"version https://git-lfs"
BASE_URL_PARAM = "OfflineTilesBaseUrl"
PROGRESS_PARAM = "OfflineTilesDownloadProgress"
@@ -44,6 +43,18 @@ HTTP_TIMEOUT_S = 30.0
STREAM_RETRIES = 8
+_BROKER_SENTINEL = "broker://"
+
+
+def _broker_base_url() -> str:
+ if _broker_headers is None:
+ return ""
+ headers = _broker_headers()
+ if not headers:
+ return ""
+ return _BROKER_SENTINEL
+
+
def candidate_base_urls(params: Params) -> list[str]:
override = params.get(BASE_URL_PARAM)
if isinstance(override, bytes):
@@ -51,14 +62,11 @@ def candidate_base_urls(params: Params) -> list[str]:
override = (override or "").strip()
if override:
return [override.rstrip("/")]
- # HF first (CDN, and it keeps device traffic off the gitea box's uplink); the bundle's
- # own endpoints and the self-hosted defaults follow as failover.
- urls: list[str] = [HF_TILE_BUNDLE_BASE_URL]
- if _private_base_urls is not None:
- try:
- urls.extend(url.rstrip("/") for url in _private_base_urls())
- except Exception:
- pass
+ urls: list[str] = []
+ broker = _broker_base_url()
+ if broker:
+ urls.append(broker)
+ urls.append(HF_TILE_BUNDLE_BASE_URL)
urls.append(DEFAULT_TILE_BUNDLE_BASE_URL)
urls.append(FALLBACK_TILE_BUNDLE_BASE_URL)
seen: set[str] = set()
@@ -69,76 +77,36 @@ def _is_hf(url: str) -> bool:
return "huggingface.co" in url.lower()
-def _maps_auth_module():
- """The read PAT lives in the compiled updater bundle (never in this open file)."""
- try:
- from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
- return import_verified_module("iqpilot_updater_private", "iqpilot_private.updater.git_remote")
- except Exception:
- pass
- try:
- import importlib
- import os
- import sys
- root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
- bundle_python = os.path.join(root, "artifacts", "iqpilot_updater_private", "python")
- if os.path.isdir(bundle_python):
- if bundle_python not in sys.path:
- sys.path.insert(0, bundle_python)
- return importlib.import_module("iqpilot_private.updater.git_remote")
- except Exception:
- pass
- return None
+def _is_broker(url: str) -> bool:
+ return url.startswith(_BROKER_SENTINEL)
def request_headers(url: str) -> dict:
- mod = _maps_auth_module()
- if mod is not None:
- if _is_hf(url):
- # HF wants a bearer token, not basic auth; a build whose bundle predates HF
- # hosting simply gets nothing here and falls through to the gitea mirrors.
- try:
- token = mod.map_tiles_hf_token()
- if token:
- return {"Authorization": f"Bearer {token}"}
- except Exception:
- pass
- return {}
- try:
- headers = mod.map_tiles_headers(url)
- if headers:
- return headers
- except Exception:
- pass
- try:
- from iqpilot.common.git_creds import get_credentials
- creds = get_credentials()
- if creds and all(creds) and "/iq.lvbs/iqmaps" in url.lower():
- import base64
- return {"Authorization": "Basic " + base64.b64encode(f"{creds[0]}:{creds[1]}".encode()).decode()}
- except Exception:
- pass
+ if _is_broker(url) and _broker_headers is not None:
+ return _broker_headers()
return {}
-def request_auth() -> tuple[str, str] | None:
- if _private_auth is None:
- return None
- try:
- return _private_auth()
- except Exception:
- return None
-
-
def _lfs_endpoint(base_url: str) -> str:
- """///raw/branch/ -> //.git/info/lfs"""
return base_url.split("/raw/", 1)[0] + ".git/info/lfs"
+def _resolve_oid_url_broker(oid: str, size: int) -> tuple[str, dict]:
+ if _broker_iqmaps_lfs_url is None:
+ raise requests.RequestException("broker LFS unavailable")
+ url = _broker_iqmaps_lfs_url(oid, size)
+ if not url:
+ raise requests.RequestException("broker LFS URL empty")
+ headers = _broker_headers() if _broker_headers is not None else {}
+ if not headers:
+ raise requests.RequestException("broker headers unavailable")
+ return url, headers
+
+
def _resolve_oid_url(session: requests.Session, base_url: str, oid: str, size: int,
headers: dict) -> tuple[str, dict]:
- """Bundles are stored as bare LFS objects addressed by oid from the index -- no pointer
- files, because committing one per part meant hundreds of concurrent commits per branch."""
+ if _is_broker(base_url):
+ return _resolve_oid_url_broker(oid, size)
batch = session.post(f"{_lfs_endpoint(base_url)}/objects/batch",
data=json.dumps({"operation": "download", "transfers": ["basic"],
"objects": [{"oid": oid, "size": size}]}),
@@ -153,17 +121,18 @@ def _resolve_oid_url(session: requests.Session, base_url: str, oid: str, size: i
return action["href"], action.get("header", {})
-def _resolve_object_url(session: requests.Session, url: str, headers: dict) -> tuple[str, dict]:
- """Follow a Gitea LFS pointer to the real (pre-signed) object URL.
+def _resolve_object_url_broker(url: str) -> tuple[str, dict]:
+ headers = _broker_headers() if _broker_headers is not None else {}
+ return url, headers
- Returns the URL to stream plus any extra headers it needs. A plain host that serves the
- bytes directly (local test server, static mirror) resolves to itself unchanged."""
+
+def _resolve_object_url(session: requests.Session, url: str, headers: dict) -> tuple[str, dict]:
+ if _is_broker(url):
+ return _resolve_object_url_broker(url)
probe = session.get(url, headers={**headers, "Accept-Encoding": None}, stream=True,
timeout=HTTP_TIMEOUT_S)
try:
probe.raise_for_status()
- # A host that serves the bytes itself still needs the caller's auth on the real GET --
- # returning {} here sends the download out anonymous and a private host answers 401.
if int(probe.headers.get("content-length") or 0) >= 1024:
return url, dict(headers)
body = probe.content
@@ -187,13 +156,22 @@ def _resolve_object_url(session: requests.Session, url: str, headers: dict) -> t
return action["href"], action.get("header", {})
+def _broker_index_url() -> str:
+ if _broker_iqmaps_url is None:
+ return ""
+ return _broker_iqmaps_url(DEFAULT_TILE_BUNDLE_BASE_URL + "/index.json")
+
+
def fetch_index(base_url: str, session: requests.Session) -> dict:
- index_url = f"{base_url}/index.json"
- headers = request_headers(index_url)
- # requests' auth= rewrites the Authorization header, so only fall back to it when the
- # closed-source bundle gave us nothing.
- response = session.get(index_url, timeout=HTTP_TIMEOUT_S, headers=headers,
- auth=None if headers else request_auth())
+ if _is_broker(base_url):
+ index_url = _broker_index_url()
+ if not index_url:
+ raise ValueError("broker iqmaps URL unavailable")
+ headers = request_headers(base_url)
+ else:
+ index_url = f"{base_url}/index.json"
+ headers = request_headers(index_url)
+ response = session.get(index_url, timeout=HTTP_TIMEOUT_S, headers=headers)
response.raise_for_status()
index = response.json()
regions = index.get("regions")
@@ -215,6 +193,10 @@ def region_valhalla_path(selector: str) -> Path:
return region_bundle_dir(selector) / "valhalla" / "tiles.tar"
+def region_roads_path(selector: str) -> Path:
+ return region_bundle_dir(selector) / "tiles" / "roads.mbtiles"
+
+
def region_valhalla_installed(selector: str) -> bool:
return region_valhalla_path(selector).exists()
@@ -279,6 +261,11 @@ def _write_manifest(selector: str, entry: dict) -> None:
"bytes": entry.get("valhalla_bytes"),
"sha256": entry.get("valhalla_sha256", ""),
}
+ if entry.get("roads_path") and region_roads_path(selector).exists():
+ manifest["roads"] = {
+ "bytes": entry.get("roads_bytes"),
+ "sha256": entry.get("roads_sha256", ""),
+ }
manifest_path = region_bundle_dir(selector) / "manifest.json"
manifest_path.parent.mkdir(parents=True, exist_ok=True)
manifest_path.write_text(json.dumps(manifest, indent=2))
@@ -332,6 +319,7 @@ class TileBundleDownloader:
progress_offset, progress_total, 1, entry.get("valhalla_objects"),
)
if ok:
+ self._download_roads(selector, entry, base_url, progress_offset + int(entry.get("valhalla_bytes", 0)), progress_total)
_write_manifest(selector, entry)
return ok
night_path = region_bundle_path(selector)
@@ -368,10 +356,29 @@ class TileBundleDownloader:
cloudlog.warning(f"iq_maps: routing extract errored for {selector}: {exc}")
if not nav_ok:
cloudlog.warning(f"iq_maps: routing extract failed for {selector}; map tiles installed")
+ self._download_roads(selector, entry, base_url, progress_offset + int(entry.get("bytes", 0)) + int(entry.get("day_bytes", 0))
+ + int(entry.get("valhalla_bytes", 0)), progress_total)
_write_manifest(selector, entry)
cloudlog.info(f"iq_maps: installed tile bundle {selector}")
return True
+ def _download_roads(self, selector: str, entry: dict, base_url: str, progress_offset: int, progress_total: int) -> bool:
+ if not entry.get("roads_path"):
+ return False
+ # roads are additive like routing: a missing or corrupt archive must never cost the region its map
+ try:
+ ok = self._download_file(
+ selector, base_url, entry["roads_path"], int(entry.get("roads_bytes", 0)),
+ str(entry.get("roads_sha256", "")).strip().lower(), region_roads_path(selector),
+ progress_offset, progress_total, 1, entry.get("roads_objects"),
+ )
+ except Exception as exc:
+ ok = False
+ cloudlog.warning(f"iq_maps: roads archive errored for {selector}: {exc}")
+ if not ok:
+ cloudlog.warning(f"iq_maps: roads archive failed for {selector}")
+ return ok
+
def _download_file(self, selector: str, base_url: str, remote_path: str, expected_bytes: int,
expected_sha: str, final_path: Path,
progress_offset: int, progress_total: int, parts: int = 1,
@@ -379,12 +386,15 @@ class TileBundleDownloader:
# Bundles are published as .pNN because Cloudflare caps proxied bodies at ~100MB.
# They stream back-to-back into ONE .part file: concatenating afterwards would need
# double the free space, which devices do not have.
- base = f"{base_url}/{remote_path.lstrip('/')}"
+ if _is_broker(base_url) and _broker_iqmaps_url is not None:
+ broker_raw = _broker_iqmaps_url(DEFAULT_TILE_BUNDLE_BASE_URL + "/" + remote_path.lstrip('/'))
+ base = broker_raw or f"{base_url}/{remote_path.lstrip('/')}"
+ else:
+ base = f"{base_url}/{remote_path.lstrip('/')}"
count = len(objects) if objects else parts
if objects and not _is_hf(base_url):
- urls = [None] * count # gitea: resolved per-attempt from the oid
+ urls = [None] * count
else:
- # HF (and plain mirrors) serve the same chunks as ordinary .pNN files
urls = [base] if count <= 1 else [f"{base}.p{i:02d}" for i in range(count)]
part_path = final_path.with_name(final_path.name + ".part")
part_path.parent.mkdir(parents=True, exist_ok=True)
@@ -417,22 +427,19 @@ class TileBundleDownloader:
for index in range(first_part, len(urls)):
if objects and not _is_hf(base_url):
url_headers = request_headers(base_url)
- auth = None if url_headers else request_auth()
object_url, object_headers = _resolve_oid_url(
self.session, base_url, objects[index]["oid"], int(objects[index]["size"]),
url_headers)
else:
url = urls[index]
- url_headers = request_headers(url)
- auth = None if url_headers else request_auth()
- # Re-resolve per part: a pre-signed LFS object URL can expire mid-download.
+ url_headers = request_headers(base_url) if _is_broker(base_url) else request_headers(url)
object_url, object_headers = _resolve_object_url(self.session, url, url_headers)
headers = dict(object_headers)
offset = skip_in_part if index == first_part else 0
if offset:
headers["Range"] = f"bytes={offset}-"
with self.session.get(object_url, headers=headers, stream=True,
- timeout=HTTP_TIMEOUT_S, auth=auth) as response:
+ timeout=HTTP_TIMEOUT_S) as response:
if offset and response.status_code != 206:
# server ignored the range: restart this whole file cleanly
f.close()
@@ -486,19 +493,23 @@ class TileBundleDownloader:
return False
wanted: list[tuple[str, dict]] = []
+ roads_only: list[tuple[str, dict]] = []
for selector in selectors:
entry = regions.get(selector)
if entry is None:
cloudlog.warning(f"iq_maps: no tile bundle published for {selector}")
ok = False
continue
- if self.include_display and region_bundle_installed(selector) and self._installed_matches(selector, entry):
- continue
- if not self.include_display and self._routing_matches(selector, entry):
+ current = ((self.include_display and region_bundle_installed(selector) and self._installed_matches(selector, entry))
+ or (not self.include_display and self._routing_matches(selector, entry)))
+ if current:
+ if not self._roads_matches(selector, entry):
+ roads_only.append((selector, entry))
continue
wanted.append((selector, entry))
- progress_total = sum(self._download_bytes(entry) for _, entry in wanted)
+ progress_total = (sum(self._download_bytes(entry) for _, entry in wanted)
+ + sum(int(entry.get("roads_bytes", 0)) for _, entry in roads_only))
progress_offset = 0
for selector, entry in wanted:
if self._should_abort():
@@ -510,6 +521,12 @@ class TileBundleDownloader:
cloudlog.exception(f"iq_maps: tile bundle download failed for {selector}")
ok = False
progress_offset += self._download_bytes(entry)
+ for selector, entry in roads_only:
+ if self._should_abort():
+ return False
+ if self._download_roads(selector, entry, base_url, progress_offset, progress_total):
+ _write_manifest(selector, entry)
+ progress_offset += int(entry.get("roads_bytes", 0))
return ok
finally:
self._publish_progress("", 0, 0, active=False)
@@ -531,9 +548,22 @@ class TileBundleDownloader:
except (OSError, ValueError):
return False
+ @staticmethod
+ def _roads_matches(selector: str, entry: dict) -> bool:
+ if not entry.get("roads_path"):
+ return True
+ try:
+ path = region_roads_path(selector)
+ expected = str(entry.get("roads_sha256", "")).strip().lower()
+ if not expected or path.stat().st_size != int(entry.get("roads_bytes", 0)):
+ return False
+ return _hash_existing(path)[0].hexdigest() == expected
+ except (OSError, ValueError):
+ return False
+
def _download_bytes(self, entry: dict) -> int:
display_bytes = int(entry.get("bytes", 0)) + int(entry.get("day_bytes", 0)) if self.include_display else 0
- return display_bytes + int(entry.get("valhalla_bytes", 0))
+ return display_bytes + int(entry.get("valhalla_bytes", 0)) + int(entry.get("roads_bytes", 0))
@staticmethod
def _installed_matches(selector: str, entry: dict) -> bool:
diff --git a/iqpilot/konn3kt/registration.py b/iqpilot/konn3kt/registration.py
index 11e4fc30..a3720bfa 100755
--- a/iqpilot/konn3kt/registration.py
+++ b/iqpilot/konn3kt/registration.py
@@ -1,18 +1,12 @@
#!/usr/bin/env python3
-import os
-import time
-import json
-import jwt
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
import re
import secrets
-from typing import cast
from pathlib import Path
-from datetime import datetime, timedelta, UTC
-from iqpilot.common.api import api_get, get_key_pair
from iqpilot.common.params import Params
-from iqpilot.common.spinner import Spinner
-from iqpilot.system.hardware import HARDWARE
from iqpilot.system.hardware.hw import Paths
from iqpilot.common.swaglog import cloudlog
@@ -35,6 +29,10 @@ def _read_persist_dongle_id() -> str | None:
return None
+def is_valid_dongle_id(dongle_id: str | None) -> bool:
+ return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
+
+
def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str | None:
ro = _read_persist_dongle_id()
if is_valid_dongle_id(ro):
@@ -46,8 +44,8 @@ def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = T
if v and v != UNREGISTERED_DONGLE_ID:
return v.lower() if is_valid_dongle_id(v) else v
return ro or None
-def is_valid_dongle_id(dongle_id: str | None) -> bool:
- return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
+
+
def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str:
p = Params() if params is None else params
dongle_id = get_cached_dongle_id(p, prefer_readonly=prefer_readonly)
@@ -57,164 +55,32 @@ def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool
p.put("DongleId", dongle_id)
cloudlog.warning(f"generated new DongleId={dongle_id} (no readonly dongle_id found)")
return dongle_id
+
+
+_impl = None
+
+
+def _registration_impl():
+ global _impl
+ if _impl is None:
+ from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+ try:
+ _impl = load_private_module(__name__, "iqpilot_private.konn3kt.registration.reg_core")
+ except ProprietaryModuleMissing:
+ from konn3kt_private.registration import reg_core as _impl
+ return _impl
+
+
def ensure_dev_pairing_identity(params: Params | None = None, force_reset: bool = False) -> dict[str, str]:
- p = Params() if params is None else params
-
- persist_dir = Path(Paths.persist_root()) / "comma"
- persist_dir.mkdir(parents=True, exist_ok=True)
-
- dongle_path = persist_dir / "dongle_id"
- priv_path = persist_dir / "id_rsa"
- pub_path = persist_dir / "id_rsa.pub"
-
- if force_reset:
- for fp in (dongle_path, priv_path, pub_path):
- try:
- fp.unlink(missing_ok=True)
- except Exception:
- cloudlog.exception(f"failed to remove {fp}")
- try:
- (persist_dir / "konn3kt_prime_type").unlink(missing_ok=True)
- except Exception:
- pass
- try:
- p.remove("PrimeType")
- except Exception:
- pass
- forced_dongle = os.getenv("KONN3KT_DEV_DONGLE_ID")
- dongle_id = forced_dongle.strip().lower() if forced_dongle else None
- if dongle_id and not is_valid_dongle_id(dongle_id):
- cloudlog.error("KONN3KT_DEV_DONGLE_ID must be 16 hex chars")
- dongle_id = None
- if dongle_id is None:
- existing = None
- try:
- existing = dongle_path.read_text().strip().lower() if dongle_path.is_file() else None
- except Exception:
- cloudlog.exception("failed reading existing dev dongle_id")
- dongle_id = existing if is_valid_dongle_id(existing) else secrets.token_hex(8)
- try:
- dongle_path.write_text(dongle_id)
- except Exception:
- cloudlog.exception("failed writing dev dongle_id")
- p.put("DongleId", dongle_id)
- p.put("HardwareSerial", p.get("HardwareSerial") or f"DEV-{dongle_id}")
- if force_reset or (not priv_path.is_file()) or (not pub_path.is_file()):
- try:
- from cryptography.hazmat.primitives import serialization
- from cryptography.hazmat.primitives.asymmetric import rsa
- key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
- priv_bytes = key.private_bytes(
- encoding=serialization.Encoding.PEM,
- format=serialization.PrivateFormat.TraditionalOpenSSL,
- encryption_algorithm=serialization.NoEncryption(),
- )
- pub_bytes = key.public_key().public_bytes(
- encoding=serialization.Encoding.PEM,
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
- )
- priv_path.write_bytes(priv_bytes)
- pub_path.write_bytes(pub_bytes)
- except Exception:
- cloudlog.exception("failed generating dev RSA keys")
- raise
- return {
- "dongle_id": dongle_id,
- "serial": p.get("HardwareSerial") or f"DEV-{dongle_id}",
- "persist_dir": str(persist_dir),
- }
-def _normalize_imei(value: str | None) -> str:
- return value or ""
+ return _registration_impl().ensure_dev_pairing_identity(params, force_reset)
def get_registration_identifiers(wait_timeout: float = IMEI_WAIT_TIMEOUT, show_spinner: bool = False) -> tuple[str, str, str]:
- serial = HARDWARE.get_serial()
- spinner = Spinner() if show_spinner else None
- start_time = time.monotonic()
- imei1: str | None = None
- imei2: str | None = None
-
- while time.monotonic() - start_time < wait_timeout:
- try:
- imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
- if imei1 or imei2:
- break
- except RuntimeError as e:
- if "no modems" in str(e).lower():
- cloudlog.warning("No cellular modem available, proceeding without IMEI")
- break
- cloudlog.exception("Error getting imei, trying again...")
- except Exception:
- cloudlog.exception("Error getting imei, trying again...")
- time.sleep(1)
-
- imei1 = _normalize_imei(imei1)
- imei2 = _normalize_imei(imei2)
-
- if not imei1 and not imei2:
- cloudlog.warning(f"proceeding with serial-only registration for serial={serial}")
- if spinner is not None:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
- spinner.close()
-
- return serial, imei1, imei2
+ return _registration_impl().get_registration_identifiers(wait_timeout, show_spinner)
def register(show_spinner=False) -> str | None:
- params = Params()
-
- dongle_id: str | None = get_cached_dongle_id(params, prefer_readonly=True)
- if dongle_id in ("", UNREGISTERED_DONGLE_ID):
- dongle_id = None
-
- jwt_algo, private_key, public_key = get_key_pair()
-
- if not public_key:
- dongle_id = UNREGISTERED_DONGLE_ID
- cloudlog.warning("missing public key")
- elif dongle_id is None:
- if show_spinner:
- spinner = Spinner()
- spinner.update("registering device")
-
- serial, imei1, imei2 = get_registration_identifiers(wait_timeout=IMEI_WAIT_TIMEOUT, show_spinner=False)
-
- backoff = 0
- start_time = time.monotonic()
- while True:
- try:
- register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)},
- cast(str, private_key), algorithm=jwt_algo)
- cloudlog.info("getting pilotauth")
- cloudlog.info("getting pilotauth")
- resp = api_get("v2/pilotauth/", method='POST', timeout=15,
- imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
-
- if resp.status_code in (400, 402, 403):
- cloudlog.info(f"Unable to register device, got {resp.status_code}")
- dongle_id = UNREGISTERED_DONGLE_ID
- else:
- dongleauth = json.loads(resp.text)
- dongle_id = dongleauth["dongle_id"]
- break
- except Exception:
- cloudlog.exception("failed to authenticate")
- backoff = min(backoff + 1, 15)
- time.sleep(backoff)
-
- if time.monotonic() - start_time > 60 and show_spinner:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})")
- return UNREGISTERED_DONGLE_ID
-
- if show_spinner:
- spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
- spinner.close()
-
- if dongle_id:
- params.put("DongleId", dongle_id)
- from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
- set_offroad_alert("Offroad_UnregisteredHardware", False)
- return dongle_id
+ return _registration_impl().register(show_spinner)
if __name__ == "__main__":
diff --git a/iqpilot/selfdrive/car/vehicle_catalog.json b/iqpilot/selfdrive/car/vehicle_catalog.json
index e7048978..79367200 100644
--- a/iqpilot/selfdrive/car/vehicle_catalog.json
+++ b/iqpilot/selfdrive/car/vehicle_catalog.json
@@ -3941,6 +3941,86 @@
],
"req": "All"
},
+ "TESLA_MODEL_S_HW1|2014-2016": {
+ "label": "Tesla Model S (with HW1) 2014-16",
+ "id": "TESLA_MODEL_S_HW1",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model S (with HW1)",
+ "yrs": [
+ "2014",
+ "2015",
+ "2016"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_S_HW2|2017-2019": {
+ "label": "Tesla Model S (with HW2) 2017-19",
+ "id": "TESLA_MODEL_S_HW2",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model S (with HW2)",
+ "yrs": [
+ "2017",
+ "2018",
+ "2019"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_S_HW3|2020-2023": {
+ "label": "Tesla Model S (with HW3) 2020-23",
+ "id": "TESLA_MODEL_S_HW3",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model S (with HW3)",
+ "yrs": [
+ "2020",
+ "2021",
+ "2022",
+ "2023"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_X_HW1|2014-2016": {
+ "label": "Tesla Model X (with HW1) 2014-16",
+ "id": "TESLA_MODEL_X_HW1",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model X (with HW1)",
+ "yrs": [
+ "2014",
+ "2015",
+ "2016"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_X_HW2|2016-2019": {
+ "label": "Tesla Model X (with HW2) 2016-19",
+ "id": "TESLA_MODEL_X_HW2",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model X (with HW2)",
+ "yrs": [
+ "2016",
+ "2017",
+ "2018",
+ "2019"
+ ],
+ "req": "All"
+ },
+ "TESLA_MODEL_X_HW3|2021-2023": {
+ "label": "Tesla Model X (with HW3) 2021-23",
+ "id": "TESLA_MODEL_X_HW3",
+ "mk": "Tesla",
+ "grp": "tesla",
+ "mdl": "Model X (with HW3)",
+ "yrs": [
+ "2021",
+ "2022",
+ "2023"
+ ],
+ "req": "All"
+ },
"TESLA_MODEL_X|2024": {
"label": "Tesla Model X (with HW4) 2024",
"id": "TESLA_MODEL_X",
diff --git a/iqpilot/selfdrive/controls/controlsd.py b/iqpilot/selfdrive/controls/controlsd.py
index 7022a30b..aa63eaca 100755
--- a/iqpilot/selfdrive/controls/controlsd.py
+++ b/iqpilot/selfdrive/controls/controlsd.py
@@ -184,22 +184,18 @@ class Controls(IQControlsLayer):
self.curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll)
self.roll_compensation = -self.VM.roll_compensation(lp.roll, CS.vEgo)
- lac_ext = getattr(self.LaC, "extension", None)
+ nnff_assist = getattr(self.LaC, "nnff_assist", None)
+ if nnff_assist is not None:
+ nnff_assist.update_model_v2(self.sm['modelV2'] if self.sm.all_checks(['modelV2']) else None)
+
if self.CP.lateralTuning.which() == 'torque' and hasattr(self.LaC, "update_live_torque_params"):
torque_params = self.sm['lateralTorqueParameters']
if self.sm.all_checks(['lateralTorqueParameters']) and torque_params.useParams:
+ if hasattr(self.LaC, "set_live_torque_valid"):
+ self.LaC.set_live_torque_valid(torque_params.valid)
self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered,
torque_params.frictionCoefficientFiltered)
- if lac_ext is not None:
- lac_ext.update_limits()
-
- if lac_ext is not None:
- lac_ext.update_model_v2(self.sm['modelV2'])
-
- if lac_ext is not None:
- lac_ext.update_lateral_lag(self.CP.steerActuatorDelay)
-
long_plan = self.sm['longitudinalPlan']
model_v2 = self.sm['modelV2']
@@ -269,6 +265,8 @@ class Controls(IQControlsLayer):
lat_accel_override = bool(CS.gasPressed) or bool(self.sm['iqState'].aol.active)
self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll, lat_accel_override)
lat_delay = lateral_action_delay(self.params, self.CP, self.sm["lateralDelay"].lateralDelay) + LAT_SMOOTH_SECONDS
+ if nnff_assist is not None:
+ nnff_assist.update_lateral_lag(lat_delay)
actuators.curvature = self.desired_curvature
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
@@ -430,8 +428,8 @@ class Controls(IQControlsLayer):
severity = "warning"
detail = (
- f"update_us={update_us} state_control_us={state_control_us} publish_us={publish_us} "
- f"tail_work_us={tail_work_us} rk_remaining_us={rk_remaining_us}"
+ f"update_us={update_us} state_control_us={state_control_us} publish_us={publish_us} " +
+ f"tail_work_us={tail_work_us} rk_remaining_us={rk_remaining_us}"
)
self._perf.emit(
"controlsd_slow_loop",
diff --git a/iqpilot/selfdrive/controls/lib/accel_boost.py b/iqpilot/selfdrive/controls/lib/accel_boost.py
index 61b6cd08..92de6e2a 100644
--- a/iqpilot/selfdrive/controls/lib/accel_boost.py
+++ b/iqpilot/selfdrive/controls/lib/accel_boost.py
@@ -1,10 +1,14 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
+import numpy as np
+
from iqpilot.common.constants import CV
-ACCEL_BOOST_MAX = 1.0
-ACCEL_BOOST_RATE = 0.1
+ACCEL_BOOST_MAX = 0.2
+ACCEL_BOOST_RATE = 0.025
+ACCEL_BOOST_DECAY_RATE = 0.1
+ACCEL_BOOST_PER_OVERRIDE = 0.05
ACCEL_BOOST_MIN_SPEED = 10 * CV.MPH_TO_MS
@@ -13,12 +17,24 @@ class AccelBoost:
self.enabled = enabled
self.dt = dt
self.value = 0.0
+ self.override_boost = 0.0
+ self.model_limited = False
- def update(self, engaged: bool, v_ego: float, gas_pressed: bool) -> float:
- if not self.enabled or not engaged:
+ def update(self, engaged: bool, v_ego: float, gas_pressed: bool, model_limited: bool) -> float:
+ enabled = self.enabled and engaged
+ if not enabled or not gas_pressed:
+ self.override_boost = 0.0
+ self.model_limited = enabled and model_limited
+
+ if not enabled:
self.value = 0.0
elif v_ego < ACCEL_BOOST_MIN_SPEED:
- self.value = max(0.0, self.value - ACCEL_BOOST_RATE * self.dt)
- elif gas_pressed:
- self.value = min(ACCEL_BOOST_MAX, self.value + ACCEL_BOOST_RATE * self.dt)
+ self.value = max(0.0, self.value - ACCEL_BOOST_DECAY_RATE * self.dt)
+ elif gas_pressed and self.model_limited:
+ increase = min(ACCEL_BOOST_RATE * self.dt, ACCEL_BOOST_PER_OVERRIDE - self.override_boost, ACCEL_BOOST_MAX - self.value)
+ self.value += increase
+ self.override_boost += increase
return self.value
+
+ def apply(self, accel: float) -> float:
+ return accel + np.interp(accel, [-1.0, -0.5, 5.0], [0.0, self.value, self.value], right=0.0)
diff --git a/iqpilot/selfdrive/controls/lib/action_engine.py b/iqpilot/selfdrive/controls/lib/action_engine.py
new file mode 100644
index 00000000..053a5ed9
--- /dev/null
+++ b/iqpilot/selfdrive/controls/lib/action_engine.py
@@ -0,0 +1,18 @@
+# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
+from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+
+try:
+ load_private_module(__name__, "iqpilot_private.navd.action_engine")
+except ProprietaryModuleMissing:
+ from iqpilot.cereal import custom, log
+
+ class ActionEngine:
+ def __init__(self):
+ self.desire = log.Desire.none
+ self.lane_change_state = log.LaneChangeState.off
+ self.lane_change_direction = log.LaneChangeDirection.none
+ self.lane_turn_direction = custom.IQTurnSignalDirection.none
+ self.lateral_edge_block = custom.IQLateralEdgeBlock.none
+
+ def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None):
+ return None
diff --git a/iqpilot/selfdrive/controls/lib/desire_helper.py b/iqpilot/selfdrive/controls/lib/desire_helper.py
deleted file mode 100644
index 0d0f09b9..00000000
--- a/iqpilot/selfdrive/controls/lib/desire_helper.py
+++ /dev/null
@@ -1,360 +0,0 @@
-"""
-Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
-"""
-
-from __future__ import annotations
-
-import math
-
-from iqpilot.cereal import car, custom, log
-
-from iqpilot.common.constants import CV
-from iqpilot.common.params import Params
-from iqpilot.common.realtime import DT_MDL
-from iqpilot.selfdrive.controls.lib.helpers.lane_change import (
- IQLaneSwapController,
- AutoLaneChangeMode,
- NavExitLaneChangeController,
-)
-from iqpilot.selfdrive.controls.lib.helpers.lateral_edge_guard import LateralEdgeGuard
-from iqpilot.selfdrive.controls.lib.helpers.lane_turn import IQNavTurnController
-
-LaneChangeState = log.LaneChangeState
-LaneChangeDirection = log.LaneChangeDirection
-TurnDirection = custom.IQTurnSignalDirection
-LateralEdgeBlock = custom.IQLateralEdgeBlock
-NavManeuverPhase = custom.IQNavState.ManeuverPhase
-
-LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
-LANE_CHANGE_TIME_MAX = 10.0
-LANE_CHANGE_CANCEL_TORQUE_FRAMES = round(0.25 / DT_MDL)
-# only the rising edge reaches the model and it stays visible for the 5 s the history spans, so one
-# edge per 5 s keeps the turn alive through the maneuver without stacking edges that cut the corner
-TURN_DESIRE_PULSE_CYCLE_FRAMES = round(5.0 / DT_MDL)
-TURN_DESIRE_PULSE_HOLD_FRAMES = TURN_DESIRE_PULSE_CYCLE_FRAMES - 1
-TURN_RECOIL_HEADING_RAD = math.radians(55.0)
-TURN_DESIRE_MAX_SECONDS = 25.0
-TURN_DESIRE_MOVING_SPEED = 0.5
-
-_LANE_CHANGE_DESIRES = {
- (LaneChangeDirection.none, LaneChangeState.off): log.Desire.none,
- (LaneChangeDirection.none, LaneChangeState.preLaneChange): log.Desire.none,
- (LaneChangeDirection.none, LaneChangeState.laneChangeStarting): log.Desire.none,
- (LaneChangeDirection.none, LaneChangeState.laneChangeFinishing): log.Desire.none,
- (LaneChangeDirection.left, LaneChangeState.off): log.Desire.none,
- (LaneChangeDirection.left, LaneChangeState.preLaneChange): log.Desire.none,
- (LaneChangeDirection.left, LaneChangeState.laneChangeStarting): log.Desire.laneChangeLeft,
- (LaneChangeDirection.left, LaneChangeState.laneChangeFinishing): log.Desire.laneChangeLeft,
- (LaneChangeDirection.right, LaneChangeState.off): log.Desire.none,
- (LaneChangeDirection.right, LaneChangeState.preLaneChange): log.Desire.none,
- (LaneChangeDirection.right, LaneChangeState.laneChangeStarting): log.Desire.laneChangeRight,
- (LaneChangeDirection.right, LaneChangeState.laneChangeFinishing): log.Desire.laneChangeRight,
-}
-
-_TURN_DESIRES = {
- TurnDirection.none: log.Desire.none,
- TurnDirection.turnLeft: log.Desire.turnLeft,
- TurnDirection.turnRight: log.Desire.turnRight,
-}
-
-_PULSED_TURN_DESIRES = {
- log.Desire.turnLeft,
- log.Desire.turnRight,
-}
-
-
-def turn_desire(turn_direction) -> log.Desire:
- return _TURN_DESIRES[getattr(turn_direction, "raw", turn_direction)]
-
-
-def _direction_from_blinkers(carstate) -> int:
- if carstate.leftBlinker:
- return LaneChangeDirection.left
- if carstate.rightBlinker:
- return LaneChangeDirection.right
- return LaneChangeDirection.none
-
-
-def _steering_nudge_matches(carstate, direction: int) -> bool:
- if not carstate.steeringPressed:
- return False
- return (
- (direction == LaneChangeDirection.left and carstate.steeringTorque > 0) or
- (direction == LaneChangeDirection.right and carstate.steeringTorque < 0)
- )
-
-
-def _opposite_direction(direction: int) -> int:
- if direction == LaneChangeDirection.left:
- return LaneChangeDirection.right
- if direction == LaneChangeDirection.right:
- return LaneChangeDirection.left
- return LaneChangeDirection.none
-
-
-def _blinker_matches(carstate, direction: int) -> bool:
- return (
- (direction == LaneChangeDirection.left and carstate.leftBlinker and not carstate.rightBlinker) or
- (direction == LaneChangeDirection.right and carstate.rightBlinker and not carstate.leftBlinker)
- )
-
-
-def _blindspot_matches(carstate, direction: int) -> bool:
- return (
- (direction == LaneChangeDirection.left and carstate.leftBlindspot) or
- (direction == LaneChangeDirection.right and carstate.rightBlindspot)
- )
-
-
-def _read_enable_bsm() -> bool:
- try:
- with car.CarParams.from_bytes(Params().get("CarParams")) as cp:
- return bool(cp.enableBsm)
- except Exception:
- return False
-
-
-class DesireHelper:
- def __init__(self):
- self.lane_change_state = LaneChangeState.off
- self.lane_change_direction = LaneChangeDirection.none
- self.lane_change_timer = 0.0
- self.lane_change_ll_prob = 1.0
- self.lane_change_counter_torque_frames = 0
- self.prev_one_blinker = False
- self.prev_nav_exit_active = False
- self.nav_exit_rearmed = False
- self.desire = log.Desire.none
-
- self.alc = IQLaneSwapController(self)
- self.lane_turn_controller = IQNavTurnController(self)
- self.nav_exit = NavExitLaneChangeController(_read_enable_bsm())
- self.lateral_edge_guard = LateralEdgeGuard()
- self.lateral_edge_block = LateralEdgeBlock.none
- self.lane_turn_direction = TurnDirection.none
- self.nav_turn_direction = TurnDirection.none
- self.turn_desire_frames = 0
- self.turn_desire_cycle_input = log.Desire.none
- self.turn_heading_rad = 0.0
- self.turn_desire_seconds = 0.0
- self.turn_recoiled = False
-
- @staticmethod
- def get_lane_change_direction(carstate):
- return _direction_from_blinkers(carstate)
-
- @staticmethod
- def _nav_turn_desire(nav_state):
- if nav_state is None or not getattr(nav_state, "active", False):
- return TurnDirection.none
- if getattr(nav_state, "maneuverPhase", NavManeuverPhase.none) != NavManeuverPhase.turnActive:
- return TurnDirection.none
- if not getattr(nav_state, "shouldSendTurnDesire", False):
- return TurnDirection.none
- return getattr(nav_state, "turnDesireDirection", TurnDirection.none)
-
- def _clear_lane_change(self) -> None:
- self.lane_change_state = LaneChangeState.off
- self.lane_change_direction = LaneChangeDirection.none
-
- def _refresh_turn_overrides(self, carstate, nav_state) -> bool:
- speed_mps = carstate.vEgo
- self.lane_turn_controller.update_params()
- self.lane_turn_controller.update_lane_turn(
- blindspot_left=carstate.leftBlindspot,
- blindspot_right=carstate.rightBlindspot,
- left_blinker=carstate.leftBlinker,
- right_blinker=carstate.rightBlinker,
- v_ego=speed_mps,
- )
- self.lane_turn_direction = self.lane_turn_controller.get_turn_direction()
- self.nav_turn_direction = self._nav_turn_desire(nav_state)
-
- self.nav_exit.update_params()
- self.nav_exit.update(nav_state, carstate)
- return bool(self.nav_exit.active)
-
- def _reset_required(self, lateral_active: bool, nav_exit_active: bool) -> bool:
- timed_out = self.lane_change_timer > LANE_CHANGE_TIME_MAX
- feature_disabled = self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF and not nav_exit_active
- return (not lateral_active) or timed_out or feature_disabled
-
- def _begin_from_idle(self, one_blinker: bool, nav_exit_active: bool, below_speed: bool) -> None:
- if below_speed:
- return
- if one_blinker and not self.prev_one_blinker:
- self.lane_change_state = LaneChangeState.preLaneChange
- self.lane_change_direction = _direction_from_blinkers(self._last_carstate)
- self.lane_change_ll_prob = 1.0
- return
- if nav_exit_active and not self.prev_nav_exit_active:
- self.lane_change_state = LaneChangeState.preLaneChange
- self.lane_change_direction = self.nav_exit.direction
- self.lane_change_ll_prob = 1.0
-
- def _refresh_requested_direction(self, one_blinker: bool, nav_exit_active: bool) -> None:
- if one_blinker:
- self.lane_change_direction = _direction_from_blinkers(self._last_carstate)
- elif nav_exit_active:
- self.lane_change_direction = self.nav_exit.direction
-
- def _step_pre_lane_change(self, one_blinker: bool, nav_exit_active: bool, below_speed: bool) -> None:
- self._refresh_requested_direction(one_blinker, nav_exit_active)
- blindspot_detected = _blindspot_matches(self._last_carstate, self.lane_change_direction)
- self.lateral_edge_block = self.lateral_edge_guard.block_for_direction(self.lane_change_direction)
- lateral_edge_blocked = self.lateral_edge_block != LateralEdgeBlock.none
- steering_ready = _steering_nudge_matches(self._last_carstate, self.lane_change_direction)
- # an exit can need more than one lane, but only the first move may start without a driver nudge
- nav_auto_start = nav_exit_active and self.nav_exit.auto_allowed and not self.nav_exit_rearmed
-
- self.alc.update_lane_change(blindspot_detected=blindspot_detected, brake_pressed=self._last_carstate.brakePressed)
- allowed_to_launch = steering_ready or self.alc.auto_lane_change_allowed or nav_auto_start
-
- if (not (one_blinker or nav_exit_active)) or below_speed:
- self._clear_lane_change()
- elif allowed_to_launch and not blindspot_detected and not lateral_edge_blocked:
- self.lane_change_state = LaneChangeState.laneChangeStarting
-
- def _step_lane_change_starting(self, lane_change_prob: float) -> None:
- self.lane_change_ll_prob = max(self.lane_change_ll_prob - (2.0 * DT_MDL), 0.0)
- if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
- self.lane_change_state = LaneChangeState.laneChangeFinishing
-
- def _step_lane_change_finishing(self, one_blinker: bool, nav_exit_active: bool) -> None:
- self.lane_change_ll_prob = min(self.lane_change_ll_prob + DT_MDL, 1.0)
- if self.lane_change_ll_prob <= 0.99:
- return
- self.lane_change_direction = LaneChangeDirection.none
- if one_blinker:
- self.lane_change_state = LaneChangeState.preLaneChange
- elif nav_exit_active:
- self.lane_change_state = LaneChangeState.preLaneChange
- self.lane_change_direction = self.nav_exit.direction
- self.nav_exit_rearmed = True
- else:
- self.lane_change_state = LaneChangeState.off
-
- def _driver_cancels_lane_change(self) -> bool:
- if self.lane_change_state not in (LaneChangeState.laneChangeStarting, LaneChangeState.laneChangeFinishing):
- self.lane_change_counter_torque_frames = 0
- return False
- opposite = _opposite_direction(self.lane_change_direction)
- if _steering_nudge_matches(self._last_carstate, opposite):
- self.lane_change_counter_torque_frames += 1
- else:
- self.lane_change_counter_torque_frames = 0
- return (_blinker_matches(self._last_carstate, opposite) or
- self.lane_change_counter_torque_frames >= LANE_CHANGE_CANCEL_TORQUE_FRAMES)
-
- def _cancel_lane_change(self) -> None:
- self._clear_lane_change()
- self.lane_change_ll_prob = 1.0
- self.lane_change_counter_torque_frames = 0
-
- def _advance_lane_change_machine(self, one_blinker: bool, nav_exit_active: bool, below_speed: bool, lane_change_prob: float) -> None:
- if self._driver_cancels_lane_change():
- self._cancel_lane_change()
- return
- if self.lane_change_state == LaneChangeState.off:
- self._begin_from_idle(one_blinker, nav_exit_active, below_speed)
- return
- if self.lane_change_state == LaneChangeState.preLaneChange:
- self._step_pre_lane_change(one_blinker, nav_exit_active, below_speed)
- return
- if self.lane_change_state == LaneChangeState.laneChangeStarting:
- self._step_lane_change_starting(lane_change_prob)
- return
- if self.lane_change_state == LaneChangeState.laneChangeFinishing:
- self._step_lane_change_finishing(one_blinker, nav_exit_active)
-
- def _update_timer(self) -> None:
- if self.lane_change_state in (LaneChangeState.off, LaneChangeState.preLaneChange):
- self.lane_change_timer = 0.0
- else:
- self.lane_change_timer += DT_MDL
-
- def _clear_turn_desire_pulse(self) -> None:
- self.turn_desire_frames = 0
- self.turn_desire_cycle_input = log.Desire.none
- self.turn_heading_rad = 0.0
- self.turn_desire_seconds = 0.0
- self.turn_recoiled = False
-
- def _measured_yaw_rate(self) -> float:
- # carState.yawRate is left unpopulated by many ports, so the model pose is the primary source
- rate = getattr(getattr(getattr(self, "_last_modeldata", None), "orientationRate", None), "z", None)
- try:
- if rate is not None and len(rate) and math.isfinite(rate[0]):
- return float(rate[0])
- except (TypeError, IndexError):
- pass
- try:
- return float(getattr(self._last_carstate, "yawRate", 0.0) or 0.0)
- except (TypeError, ValueError):
- return 0.0
-
- def _turn_recoiled(self, desired_output: log.Desire) -> bool:
- # blinkers keep running well past the apex, so the executed heading change has to end the
- # desire: one that outlives the turn keeps steering the plan into the far side of the road
- if desired_output != self.turn_desire_cycle_input:
- self.turn_heading_rad = 0.0
- self.turn_desire_seconds = 0.0
- self.turn_recoiled = False
- self.turn_heading_rad += abs(self._measured_yaw_rate()) * DT_MDL
- # a red light at the junction must not use up the turn before the car moves
- if float(getattr(self._last_carstate, "vEgo", 0.0) or 0.0) > TURN_DESIRE_MOVING_SPEED:
- self.turn_desire_seconds += DT_MDL
- if self.turn_heading_rad >= TURN_RECOIL_HEADING_RAD or self.turn_desire_seconds >= TURN_DESIRE_MAX_SECONDS:
- self.turn_recoiled = True
- return self.turn_recoiled
-
- def _pulse_turn_desire(self, desired_output: log.Desire) -> log.Desire:
- if desired_output not in _PULSED_TURN_DESIRES:
- self._clear_turn_desire_pulse()
- return desired_output
-
- recoiled = self._turn_recoiled(desired_output)
-
- if desired_output != self.turn_desire_cycle_input:
- self.turn_desire_frames = 0
- self.turn_desire_cycle_input = desired_output
-
- cycle_frame = self.turn_desire_frames % TURN_DESIRE_PULSE_CYCLE_FRAMES
- self.turn_desire_frames += 1
- if recoiled or cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES:
- return log.Desire.none
- return desired_output
-
- def _pick_desire_output(self) -> None:
- desired_output = log.Desire.none
- if self.nav_turn_direction != TurnDirection.none:
- desired_output = turn_desire(self.nav_turn_direction)
- elif self.lane_turn_direction != TurnDirection.none:
- desired_output = turn_desire(self.lane_turn_direction)
- else:
- desired_output = _LANE_CHANGE_DESIRES[(self.lane_change_direction, self.lane_change_state)]
-
- self.desire = self._pulse_turn_desire(desired_output)
-
- def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None):
- self._last_carstate = carstate
- self._last_modeldata = modeldata
- self.lateral_edge_guard.update(modeldata, carstate.vEgo, DT_MDL)
- self.lateral_edge_block = LateralEdgeBlock.none
- one_blinker = carstate.leftBlinker != carstate.rightBlinker
- below_speed = carstate.vEgo < LANE_CHANGE_SPEED_MIN
- nav_exit_active = self._refresh_turn_overrides(carstate, nav_state)
- if not nav_exit_active:
- self.nav_exit_rearmed = False
-
- self.alc.update_params()
- if self._reset_required(lateral_active, nav_exit_active):
- self._clear_lane_change()
- else:
- self._advance_lane_change_machine(one_blinker, nav_exit_active, below_speed, lane_change_prob)
-
- self._update_timer()
- self.prev_one_blinker = one_blinker and lateral_active
- self.prev_nav_exit_active = nav_exit_active
- self.alc.update_state()
- self._pick_desire_output()
diff --git a/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py b/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
index c2cf85e5..6dfb043d 100644
--- a/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
+++ b/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py
@@ -16,7 +16,6 @@ TurnDirection = custom.IQTurnSignalDirection
TURN_TRIGGER_MPS = 20 * CV.MPH_TO_MS
TURN_SPEED_GATE_MPS = TURN_TRIGGER_MPS
LANE_CHANGE_SPEED_MIN = TURN_SPEED_GATE_MPS
-TURN_SPEED_CEILING_MPS = 35 * CV.MPH_TO_MS
@dataclass
@@ -27,13 +26,6 @@ class _TurnGateState:
refresh_tick: int = 0
-def _mph_param_to_mps(raw_value) -> float:
- try:
- return float(raw_value) * CV.MPH_TO_MS
- except (TypeError, ValueError):
- return TURN_TRIGGER_MPS
-
-
def _resolve_signal_choice(speed_mps: float,
speed_limit_mps: float,
left_signal: bool,
@@ -59,9 +51,8 @@ class TurnSignalPlanner:
self.reload_setup()
def _refresh_from_params(self) -> None:
- requested_gate = _mph_param_to_mps(self._params.get("IQLaneTurnValue", return_default=True))
self._state.active = self._params.get_bool("IQLaneTurnDesire")
- self._state.speed_limit_mps = min(TURN_SPEED_CEILING_MPS, requested_gate)
+ self._state.speed_limit_mps = TURN_TRIGGER_MPS
def _consume_legacy_kwargs(self, **legacy) -> tuple[bool, bool, bool, bool, float]:
return (
diff --git a/iqpilot/selfdrive/controls/lib/latcontrol_torque.py b/iqpilot/selfdrive/controls/lib/latcontrol_torque.py
index 6df8d5f4..2419531c 100644
--- a/iqpilot/selfdrive/controls/lib/latcontrol_torque.py
+++ b/iqpilot/selfdrive/controls/lib/latcontrol_torque.py
@@ -8,7 +8,7 @@ from importlib.resources import files
import numpy as np
-from iqpilot.cereal import log, custom
+from iqpilot.cereal import log
from iqdbc.car import structs
from iqdbc.car.lateral import FRICTION_THRESHOLD, get_friction
from iqdbc.car.toyota.values import ToyotaFlags
@@ -19,7 +19,7 @@ from iqpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from iqpilot.common.filter_simple import FirstOrderFilter
from iqpilot.common.params import Params
from iqpilot.common.pid import PIDController
-from iqpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
+from iqpilot.common.swaglog import cloudlog
from iqpilot.selfdrive.controls.lib.lateral_acceleration_slew_limiter import LateralAccelerationSlewLimiter
from iqpilot.selfdrive.controls.lib.latcontrol import LatControl
from iqpilot.selfdrive.iqmodeld.config import ModelConstants
@@ -40,14 +40,17 @@ def _score(a: str, b: str) -> float:
return SequenceMatcher(None, a, b).ratio()
-def _best_model_for(candidate: str) -> tuple[str | None, float]:
+def _best_model_for(candidate: str, fingerprint: str) -> tuple[str | None, float]:
best_path, best_score = None, -1.0
if not os.path.isdir(TORQUE_NN_MODEL_PATH):
return best_path, best_score
- for entry in os.listdir(TORQUE_NN_MODEL_PATH):
+ for entry in sorted(os.listdir(TORQUE_NN_MODEL_PATH)):
if not entry.endswith(".json"):
continue
- score = _score(os.path.splitext(entry)[0], candidate)
+ stem = os.path.splitext(entry)[0]
+ if stem != fingerprint and not stem.startswith(fingerprint + " "):
+ continue
+ score = _score(stem, candidate)
if score > best_score:
best_path, best_score = os.path.join(TORQUE_NN_MODEL_PATH, entry), score
return best_path, best_score
@@ -65,21 +68,22 @@ def _eps_suffix(CP: structs.CarParams) -> str:
def get_nn_model_path(CP: structs.CarParams) -> tuple[str, str, bool]:
- if CP.steerControlType == structs.CarParams.SteerControlType.angle:
+ if CP.steerControlType != structs.CarParams.SteerControlType.torque:
return MOCK_MODEL_PATH, "MOCK", False
fingerprint = CP.carFingerprint
suffix = _eps_suffix(CP)
if suffix:
- path, score = _best_model_for(f"{fingerprint} {suffix}")
+ path, score = _best_model_for(f"{fingerprint} {suffix}", fingerprint)
if path is not None and fingerprint in path and score >= _ACCEPT_THRESHOLD:
name = os.path.splitext(os.path.basename(path))[0]
return path, name, score >= _EXACT_THRESHOLD
- path, score = _best_model_for(fingerprint)
+ path, score = _best_model_for(fingerprint, fingerprint)
if path is not None and fingerprint in path and score >= _ACCEPT_THRESHOLD:
name = os.path.splitext(os.path.basename(path))[0]
return path, name, score >= _EXACT_THRESHOLD
- path, _ = _best_model_for(_substitute_for(fingerprint))
+ substitute = _substitute_for(fingerprint)
+ path, _ = _best_model_for(substitute, substitute) if substitute != fingerprint else (None, -1.0)
name = os.path.splitext(os.path.basename(path))[0] if path else "MOCK"
return (path or MOCK_MODEL_PATH), name, False
@@ -102,6 +106,8 @@ _ACTIVATIONS = {"sigmoid": _sigmoid, "identity": _identity}
def _resolve_activation(name: str):
+ if not isinstance(name, str):
+ raise ValueError("NNFF activation must be a string")
for symbol, canonical in _ACTIVATION_ALIASES.items():
name = name.replace(symbol, canonical)
fn = _ACTIVATIONS.get(name)
@@ -124,16 +130,26 @@ class NNTorqueModel:
self.output_size = params["output_size"]
self.input_mean = np.array(params["input_mean"], dtype=np.float32).T
self.input_std = np.array(params["input_std"], dtype=np.float32).T
+ if (self.input_mean.shape != (1, self.input_size) or self.input_std.shape != self.input_mean.shape or
+ not np.isfinite(self.input_mean).all() or not np.isfinite(self.input_std).all() or np.any(self.input_std <= 0)):
+ raise ValueError("Invalid NNFF input normalization")
self._weights = []
self._biases = []
self._activations = []
+ width = self.input_size
for layer in params["layers"]:
weight = _pick(layer, "_W")
bias = np.zeros_like(_pick(layer, "_b")) if zero_bias else _pick(layer, "_b")
+ if (weight.ndim != 2 or weight.shape[0] != width or bias.shape != (1, weight.shape[1]) or
+ not np.isfinite(weight).all() or not np.isfinite(bias).all()):
+ raise ValueError("Invalid NNFF layer dimensions or weights")
self._weights.append(weight)
self._biases.append(bias)
self._activations.append(_resolve_activation(layer["activation"]))
+ width = weight.shape[1]
+ if not self._weights or width != self.output_size:
+ raise ValueError("Invalid NNFF output dimensions")
self.friction_override = self.evaluate(list(_FRICTION_PROBE)) < _FRICTION_THRESHOLD
@@ -241,8 +257,11 @@ class PilotLateralBrain:
self.desired_lat_jerk_time = cp.steerActuatorDelay + LAG_EXTRA_S
def update_model_v2(self, model_packet):
- self.model_v2 = model_packet
- self.model_valid = model_packet is not None and len(model_packet.orientation.x) >= CONTROL_N
+ self.model_valid = False
+ if model_packet is not None:
+ traces = (model_packet.orientation.x, model_packet.orientation.y, model_packet.acceleration.y)
+ self.model_valid = all(len(trace) == len(ModelConstants.T_IDXS) and np.isfinite(trace).all() for trace in traces)
+ self.model_v2 = model_packet if self.model_valid else None
def update_lateral_lag(self, lag):
self.desired_lat_jerk_time = max(0.01, lag) + LAG_EXTRA_S
@@ -345,9 +364,17 @@ class NeuralNetworkFeedForward(PilotLateralBrain):
super().__init__(lac_torque, CP, CP_IQ, CI)
self.params = Params()
self.enabled = self.params.get_bool("NeuralNetworkFeedForward")
- self.has_nn_model = (CP_IQ.iqLateralNet.model.path != MOCK_MODEL_PATH
- and os.path.isfile(CP_IQ.iqLateralNet.model.path))
- self.model = NNTorqueModel(CP_IQ.iqLateralNet.model.path) if self.has_nn_model else None
+ self.model = None
+ model_path = CP_IQ.iqLateralNet.model.path
+ if model_path != MOCK_MODEL_PATH and os.path.isfile(model_path):
+ try:
+ model = NNTorqueModel(model_path)
+ if model.input_size != 18 or model.output_size != 1 or not np.isfinite(model.evaluate([0.0] * 18)):
+ raise ValueError("NNFF requires a finite 18-input, 1-output model")
+ self.model = model
+ except (OSError, ValueError, TypeError, KeyError, IndexError, StopIteration):
+ cloudlog.exception("Unable to load NNFF model; using standard torque control")
+ self.has_nn_model = self.model is not None
self.pitch = FirstOrderFilter(0.0, 0.5, 0.01)
self.pitch_last = 0.0
@@ -376,6 +403,14 @@ class NeuralNetworkFeedForward(PilotLateralBrain):
super().update_lateral_lag(lag)
self._window.refresh_lag(self.desired_lat_jerk_time)
+ def reset_history(self):
+ self._window.roll.clear()
+ self._window.lat_accel_desired.clear()
+ self._jerk_lp.x = 0.0
+ self.pitch.x = 0.0
+ self.pitch_last = 0.0
+ self.lat_accel_friction_factor = 0.7
+
def _torque_space(self, lateral_accel, CS, gravity_adjusted):
return self.torque_from_lateral_accel_in_torque_space(
LatControlInputs(lateral_accel, self._roll_g, CS.vEgo, CS.aEgo),
@@ -391,6 +426,8 @@ class NeuralNetworkFeedForward(PilotLateralBrain):
self.lac_torque.torque_params)
def update_output_torque(self, CS):
+ if not np.isfinite((self._pid_log.error, self._ff)).all():
+ raise ValueError("Nonfinite NNFF torque request")
freeze_integrator = self._capped or CS.steeringPressed or CS.vEgo < 5
self._out_tq = self._pid.update(self._pid_log.error, feedforward=self._ff,
speed=CS.vEgo, freeze_integrator=freeze_integrator)
@@ -413,7 +450,13 @@ class NeuralNetworkFeedForward(PilotLateralBrain):
def _query(self, lead_scalar, jerk_scalar, tail):
head = [self._v, lead_scalar, jerk_scalar, self._roll]
- return self.model.evaluate(head + tail)
+ return self._evaluate(head + tail)
+
+ def _evaluate(self, inputs):
+ output = self.model.evaluate(inputs)
+ if not np.isfinite(output):
+ raise ValueError("Nonfinite NNFF model output")
+ return output
def update_neural_network_feedforward(self, CS, params, calibrated_pose) -> None:
if not self._nnff_enabled:
@@ -449,7 +492,7 @@ class NeuralNetworkFeedForward(PilotLateralBrain):
blend = float(np.interp(abs(self._want_la), _ERROR_BLEND_BP, _ERROR_BLEND_V))
if blend <= 0.0:
return
- from_error = self.model.evaluate([self._v, self._accel_goal - self._accel_obs,
+ from_error = self._evaluate([self._v, self._accel_goal - self._accel_obs,
self.jerk_goal - self.jerk_obs, 0.0])
live = self._pid_log.error
if sign(live) == sign(from_error) and abs(live) < abs(from_error):
@@ -512,6 +555,7 @@ class LatControlTorque(LatControl):
p_gains = [gain * 0.8 for gain in KP_INTERP] if self.hkg_reduced_torque_feedback else KP_INTERP
self.friction_scale = 0.7 if self.hkg_reduced_torque_feedback else 1.0
self.pid = PIDController([INTERP_SPEEDS, p_gains], KI, rate=1/self.dt)
+ self._nnff_active = False
self.update_limits()
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt)
@@ -531,10 +575,20 @@ class LatControlTorque(LatControl):
self.update_limits()
def update_limits(self):
- self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
- self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
+ if self._nnff_active:
+ self.pid.set_limits(self.steer_max, -self.steer_max)
+ else:
+ self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
+ self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited, lat_delay):
+ nnff_active = active and self.nnff_assist._nnff_enabled
+ if nnff_active != self._nnff_active:
+ # The shared PID operates in torque units for NNFF and acceleration units otherwise.
+ self.pid.reset()
+ self.nnff_assist.reset_history()
+ self._nnff_active = nnff_active
+ self.update_limits()
pid_log = log.ControlsState.LateralTorqueState.new_message()
pid_log.version = VERSION
measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
@@ -573,13 +627,31 @@ class LatControlTorque(LatControl):
else:
pid_log.error = float(error)
- freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
- output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator)
- output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
+ output_torque = 0.0
+ if not nnff_active:
+ freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
+ output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator)
+ output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
- pid_log, output_torque = self.nnff_assist.update(CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
- future_desired_lateral_accel, measurement, lateral_accel_deadzone, gravity_adjusted_future_lateral_accel,
- limited_curvature, measured_curvature, steer_limited_by_safety, output_torque)
+ try:
+ pid_log, output_torque = self.nnff_assist.update(
+ CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
+ future_desired_lateral_accel, measurement, lateral_accel_deadzone, gravity_adjusted_future_lateral_accel,
+ limited_curvature, measured_curvature, steer_limited_by_safety, output_torque)
+ except (ValueError, FloatingPointError):
+ if not nnff_active:
+ raise
+ cloudlog.exception("NNFF inference failed; using standard torque control for this drive")
+ self.nnff_assist.has_nn_model = False
+ self.nnff_assist.reset_history()
+ self._nnff_active = False
+ self.pid.reset()
+ self.update_limits()
+ pid_log.error = float(error)
+ freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
+ output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator)
+ output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
+ output_torque = self.nnff_assist.nav_torque_pulse.nudge_output_torque(True, CS, output_torque)
pid_log.active = True
pid_log.p = float(self.pid.p)
diff --git a/iqpilot/selfdrive/controls/lib/latcontrol_torque_pq.py b/iqpilot/selfdrive/controls/lib/latcontrol_torque_pq.py
index 2193ca1e..119b571d 100644
--- a/iqpilot/selfdrive/controls/lib/latcontrol_torque_pq.py
+++ b/iqpilot/selfdrive/controls/lib/latcontrol_torque_pq.py
@@ -17,6 +17,8 @@ from iqpilot.common.pid import PIDController
FRICTION_THRESHOLD_PQ = 1.0
KP = 0.8
KI = 0.15
+KI_HIGHWAY = 0.3
+KI_SPEEDS = [10.0, 15.0]
INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30]
KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP]
@@ -30,7 +32,11 @@ VERSION = 1
DEFAULT_LAT_ACCEL_FACTOR = 2.2
DEFAULT_LAT_ACCEL_OFFSET = -0.13
DEFAULT_FRICTION = 0.1
-FREEZE_LIVE_TORQUE_PARAMS = True
+FREEZE_LIVE_TORQUE_PARAMS = False
+LIVE_LAT_ACCEL_FACTOR_GAIN = 1.1
+LIVE_LAT_ACCEL_FACTOR_BOUNDS = (1.2, 3.5)
+LIVE_LAT_ACCEL_OFFSET_BOUNDS = (-0.3, 0.3)
+LIVE_TORQUE_PARAMS_TAU_S = 5.0
ASSIST_COMPENSATION = True
ASSIST_SPEEDS_KPH = [0.0, 50.0, 120.0]
@@ -56,7 +62,10 @@ class LatControlTorquePQ(LatControl):
self.torque_params.friction = DEFAULT_FRICTION
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
- self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, rate=1/self.dt)
+ self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], [KI_SPEEDS, [KI, KI_HIGHWAY]], rate=1/self.dt)
+ self.live_torque_valid = False
+ self.lat_accel_factor_filter = FirstOrderFilter(DEFAULT_LAT_ACCEL_FACTOR, LIVE_TORQUE_PARAMS_TAU_S, self.dt)
+ self.lat_accel_offset_filter = FirstOrderFilter(DEFAULT_LAT_ACCEL_OFFSET, LIVE_TORQUE_PARAMS_TAU_S, self.dt)
self.update_limits()
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt)
@@ -65,12 +74,20 @@ class LatControlTorquePQ(LatControl):
self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt)
self.lateral_acceleration_slew_limiter = LateralAccelerationSlewLimiter(Params().get_bool("IQLateralAccelSlew"))
+ def set_live_torque_valid(self, valid):
+ self.live_torque_valid = bool(valid)
+
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
if FREEZE_LIVE_TORQUE_PARAMS:
return
- self.torque_params.latAccelFactor = latAccelFactor
- self.torque_params.latAccelOffset = latAccelOffset
- self.torque_params.friction = friction
+ if self.live_torque_valid and math.isfinite(latAccelFactor) and math.isfinite(latAccelOffset):
+ target_factor = float(np.clip(latAccelFactor * LIVE_LAT_ACCEL_FACTOR_GAIN, *LIVE_LAT_ACCEL_FACTOR_BOUNDS))
+ target_offset = float(np.clip(latAccelOffset, *LIVE_LAT_ACCEL_OFFSET_BOUNDS))
+ else:
+ target_factor = DEFAULT_LAT_ACCEL_FACTOR
+ target_offset = DEFAULT_LAT_ACCEL_OFFSET
+ self.torque_params.latAccelFactor = self.lat_accel_factor_filter.update(target_factor)
+ self.torque_params.latAccelOffset = self.lat_accel_offset_filter.update(target_offset)
self.update_limits()
def update_limits(self):
diff --git a/iqpilot/selfdrive/controls/lib/longcontrol.py b/iqpilot/selfdrive/controls/lib/longcontrol.py
index 942fa37e..04635a15 100644
--- a/iqpilot/selfdrive/controls/lib/longcontrol.py
+++ b/iqpilot/selfdrive/controls/lib/longcontrol.py
@@ -5,7 +5,7 @@ from iqpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from iqpilot.common.filter_simple import FirstOrderFilter
from iqpilot.common.pid import PIDController
from iqpilot.selfdrive.iqmodeld.config import ModelConstants
-from iqpilot.selfdrive.controls.lib.smooth_stops import SmoothStopController
+from iqpilot.selfdrive.controls.lib.smooth_stops import SMOOTH_STOP_MAX_SPEED, SmoothStopController
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
@@ -83,7 +83,7 @@ class LongControl:
self.smooth.reset()
else:
- if self.smooth.enabled and active and should_stop:
+ if self.smooth.enabled and active and should_stop and CS.vEgo <= SMOOTH_STOP_MAX_SPEED:
output_accel = self.smooth.settle(a_target, CS.vEgo, lead_distance, has_lead, self.last_output_accel)
self.reset()
else:
diff --git a/iqpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/iqpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
index db75dfc9..1c2e6489 100755
--- a/iqpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
+++ b/iqpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
@@ -193,7 +193,7 @@ class LongitudinalMpc:
try:
return self._params.get_bool("newLeadMpc")
except UnknownKeyName:
- return True
+ return False
def reset(self):
self.solver.reset()
diff --git a/iqpilot/selfdrive/controls/lib/longitudinal_planner.py b/iqpilot/selfdrive/controls/lib/longitudinal_planner.py
index f33c4b16..4015188d 100755
--- a/iqpilot/selfdrive/controls/lib/longitudinal_planner.py
+++ b/iqpilot/selfdrive/controls/lib/longitudinal_planner.py
@@ -25,6 +25,7 @@ A_CRUISE_MAX_VALS = [2.0, 1.6, 0.8, 0.6]
A_CRUISE_MAX_BP = [0., 10.0, 25., 40.]
A_CRUISE_MIN = -1.2
J_CRUISE = 1.0
+CRUISE_STOP_SPEED = 1.0
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
ALLOW_THROTTLE_THRESHOLD = 0.4
MIN_ALLOW_THROTTLE_SPEED = 2.5
@@ -77,7 +78,7 @@ def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt,
target_accel = np.clip(v_cruise - v_ego, A_CRUISE_MIN, max_accel)
target_accel = float(np.clip(target_accel, a_cruise_prev - J_CRUISE * dt, a_cruise_prev + J_CRUISE * dt))
- cruise_should_stop = v_cruise == 0.0
+ cruise_should_stop = v_cruise == 0.0 and v_ego < CRUISE_STOP_SPEED
return target_accel, cruise_should_stop
@@ -130,10 +131,6 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
self.launch_armed = False
self.prev_e2e = False
self.mode_blend_timer = 0.0
- try:
- self.exp_speed_conv = Params().get_bool("expSpeedConv")
- except UnknownKeyName:
- self.exp_speed_conv = False
try:
distance_control_enabled = Params().get_bool("IQE2EDistanceControl")
except UnknownKeyName:
@@ -221,11 +218,10 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
action_t=action_t, stopping_speed=self.stopping_speed)
- accel_boost = self.accel_boost.update(sm['selfdriveState'].enabled, v_ego, sm['carState'].gasPressed)
- output_a_target_e2e = sm['modelV2'].action.desiredAcceleration + accel_boost
+ output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
output_a_target_e2e, output_should_stop_e2e = self.apply_e2e_stop_distance(sm, v_ego, output_a_target_e2e, output_should_stop_e2e)
- if self.is_e2e(sm) and self.exp_speed_conv and not self.mpc.status:
+ if self.is_e2e(sm) and not self.mpc.status:
output_a_target_e2e = get_e2e_accel(v_ego, v_cruise, model_v, output_a_target_e2e, output_should_stop_e2e)
if sm['carState'].standstill:
@@ -247,6 +243,10 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
steer_angle_without_offset, self.CP, self.dt,
accel_coast, self.allow_throttle)
+ model_limited = e2e and output_a_target_e2e < min(output_a_target_mpc, self.a_cruise) - 0.1
+ self.accel_boost.update(sm['selfdriveState'].enabled, v_ego, sm['carState'].gasPressed, model_limited)
+ output_a_target_e2e = self.accel_boost.apply(output_a_target_e2e)
+
if self.distance_control.enabled:
lateral_accel = v_ego ** 2 * steer_angle_without_offset * CV.DEG_TO_RAD / (self.CP.steerRatio * self.CP.wheelbase)
else:
diff --git a/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/README.md b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/README.md
new file mode 100644
index 00000000..d112d2d3
--- /dev/null
+++ b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/README.md
@@ -0,0 +1,34 @@
+# Neural network feedforward (NNFF)
+
+Open **Settings > Vehicle > NNFF Steering** while offroad (C3/C3X or comma 4). The card shows the matched model or the reason NNFF is unavailable. Changes are saved immediately and apply when the driving processes next initialize. NNFF defaults off and its selection survives manager restarts.
+
+NNFF is available for the generic torque controller when a matching model is installed. Angle/curvature steering, PID steering, and the separate Volkswagen PQ/MQB controllers are not supported by this toggle. Enabling NNFF disables reduced steering feedback; enabling reduced steering feedback disables NNFF. A saved NNFF selection can always be switched off while offroad, including after its model becomes unavailable.
+
+## Model files
+
+Model files belong in `iqpilot/iqpilot_iq_nnff_models/neural_network_lateral_control/`, relative to the checkout root. This change does not distribute or download trained weights. An installation without models shows **no matching model installed**.
+
+Models use an 18-input, one-output network with finite normalization values, positive standard deviations, and compatible dense layers. Selection accepts the identified vehicle family or an explicit entry in IQDBC's `car/torque_data/substitute.toml`. Similar names from unrelated vehicle families are not selected. A filename match and passing loader tests do not establish vehicle calibration or steering performance.
+
+## Runtime behavior
+
+The torque controller receives valid, fresh `modelV2` data and the same lateral delay used for its steering update. NNFF requires complete finite orientation and acceleration traces. It updates the shared PID once per cycle in torque units. Standard control uses acceleration units. Switching paths resets the integral and NNFF history and restores the appropriate bounds.
+
+Missing, malformed, or stale inputs use standard torque control. An invalid model file or nonfinite inference output also falls back; an inference failure disables that model for the current drive. Stored user preference is preserved.
+
+## Focused verification
+
+```sh
+python -m pytest -q \
+ iqpilot/common/tests/test_nnff_params.py \
+ iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests \
+ iqpilot/selfdrive/controls/tests/test_nnff_controls.py \
+ iqpilot/selfdrive/controls/tests/test_lateral_action_selection.py \
+ iqpilot/selfdrive/controls/tests/test_lat_jerk_lowpass.py \
+ iqpilot/selfdrive/controls/tests/test_hkg_reduced_torque_feedback.py \
+ iqpilot/selfdrive/ui/tests/test_nnff_settings.py \
+ iqpilot/selfdrive/ui/tests/test_nnff_big_settings.py \
+ iqpilot/selfdrive/ui/tests/test_hkg_feedback_settings.py
+```
+
+The tests use isolated parameters. The model-loader suite uses synthetic weights when trained models are absent. These checks cover software behavior and do not constitute an on-road validation of any trained model.
diff --git a/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_locator.py b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_locator.py
index c8bf2860..97fadbcc 100644
--- a/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_locator.py
+++ b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_locator.py
@@ -62,13 +62,25 @@ def test_fingerprint_plus_eps_fw_prefers_specific_file(model_dir):
assert exact is False
-def test_fuzzy_match_flags_non_exact(model_dir):
- # close but not identical to a shipped fingerprint
+def test_unmapped_fingerprint_does_not_select_similar_vehicle(model_dir):
path, name, exact = locator.get_nn_model_path(make_cp("TOYOTA_RAV4_TSS2_XYZ"))
- assert name == "TOYOTA_RAV4_TSS2"
+ assert name == "MOCK"
+ assert path == locator.MOCK_MODEL_PATH
assert exact is False
+@pytest.mark.parametrize("fingerprint", ["UNKNOWN", "HONDA", "HONDA_CIVIC_OTHER", "TOYOTA_RAV4"])
+def test_unmatched_vehicle_uses_mock(model_dir, fingerprint):
+ path, name, exact = locator.get_nn_model_path(make_cp(fingerprint))
+ assert (path, name, exact) == (locator.MOCK_MODEL_PATH, "MOCK", False)
+
+
+def test_missing_substitute_does_not_select_unrelated_vehicle(model_dir):
+ (model_dir / "TOYOTA_RAV4_TSS2.json").unlink()
+ path, name, exact = locator.get_nn_model_path(make_cp("CHEVROLET_XX"))
+ assert (path, name, exact) == (locator.MOCK_MODEL_PATH, "MOCK", False)
+
+
def test_substitute_fallback(model_dir):
# unknown fingerprint that the substitute table redirects
path, name, exact = locator.get_nn_model_path(make_cp("CHEVROLET_XX"))
diff --git a/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_network.py b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_network.py
index d09b6471..958df7a4 100644
--- a/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_network.py
+++ b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_network.py
@@ -5,6 +5,7 @@ Unit checks for the NNFF model loader against the shipped model files.
"""
import json
import os
+from copy import deepcopy
import numpy as np
import pytest
@@ -82,10 +83,11 @@ class TestModelBehavior:
assert m.evaluate(vec) == m.evaluate(list(vec))
-def test_activation_registry_rejects_unknown(tmp_path):
+@pytest.mark.parametrize("activation", ["not_a_real_activation", None, 1, []])
+def test_activation_registry_rejects_unknown(tmp_path, activation):
with open(_path(SAMPLE[0])) as model_file:
base = json.load(model_file)
- base["layers"][-1]["activation"] = "not_a_real_activation"
+ base["layers"][-1]["activation"] = activation
bad = tmp_path / "bad.json"
bad.write_text(json.dumps(base))
with pytest.raises(ValueError):
@@ -96,3 +98,24 @@ def test_sigmoid_identity_helpers():
assert NNTorqueModel.identity(3.5) == 3.5
assert 0.0 < float(NNTorqueModel.sigmoid(np.array([0.0]))[0]) < 1.0
assert abs(float(NNTorqueModel.sigmoid(np.array([0.0]))[0]) - 0.5) < 1e-6
+
+
+@pytest.mark.parametrize("invalid", ["zero_std", "nan_mean", "nan_weight", "bias_shape", "layer_shape", "output_shape"])
+def test_invalid_model_rejected(tmp_path, invalid):
+ model = deepcopy(_SYNTHETIC_MODEL)
+ if invalid == "zero_std":
+ model["input_std"][0][0] = 0.0
+ elif invalid == "nan_mean":
+ model["input_mean"][0][0] = float("nan")
+ elif invalid == "nan_weight":
+ model["layers"][0]["dense_1_W"][0][0] = float("nan")
+ elif invalid == "bias_shape":
+ model["layers"][0]["dense_1_b"] = [[0.0]]
+ elif invalid == "layer_shape":
+ model["layers"][0]["dense_1_W"] = [[0.0]]
+ else:
+ model["output_size"] = 2
+ path = tmp_path / "invalid.json"
+ path.write_text(json.dumps(model))
+ with pytest.raises(ValueError):
+ NNTorqueModel(path)
diff --git a/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_nnff_assembly.py b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_nnff_assembly.py
index fa2f9403..6e0711bc 100644
--- a/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_nnff_assembly.py
+++ b/iqpilot/selfdrive/controls/lib/neural_network_feed_forward/tests/test_nnff_assembly.py
@@ -78,10 +78,7 @@ class TestControllerWiring:
def test_mock_model_reports_absent(self):
nnff = _make_controller("MOCK.json")
assert nnff.has_nn_model is False
- if "MOCK.json" in _NAMES:
- assert nnff.model.input_size >= 2
- else:
- assert nnff.model is None
+ assert nnff.model is None
def test_update_returns_finite_torque(self):
nnff = _make_controller(_REAL_MODEL)
@@ -101,3 +98,11 @@ class TestControllerWiring:
nnff = _make_controller(_REAL_MODEL)
nnff.model_valid = False
assert nnff._nnff_enabled is False
+
+ def test_invalid_model_falls_back(self, tmp_path):
+ bad_model = tmp_path / "malformed.json"
+ bad_model.write_text("{}")
+ nnff = _make_controller(str(bad_model))
+ assert not nnff.has_nn_model
+ assert nnff.model is None
+ assert not nnff._nnff_enabled
diff --git a/iqpilot/selfdrive/controls/lib/smooth_stops.py b/iqpilot/selfdrive/controls/lib/smooth_stops.py
index d1e1ef44..a0dd79fd 100644
--- a/iqpilot/selfdrive/controls/lib/smooth_stops.py
+++ b/iqpilot/selfdrive/controls/lib/smooth_stops.py
@@ -18,6 +18,7 @@ PROGRESS_EPS = 0.02
ANTI_CREEP_RATE = 0.50
SETTLE_JERK = 2.5
EMERGENCY_DECEL = 3.0
+SMOOTH_STOP_MAX_SPEED = 3.0
def read_smooth_stops_enabled(params: Params) -> bool:
diff --git a/iqpilot/selfdrive/controls/lib/tests/test_lane_change_cancel.py b/iqpilot/selfdrive/controls/lib/tests/test_lane_change_cancel.py
deleted file mode 100644
index aa6b5d0d..00000000
--- a/iqpilot/selfdrive/controls/lib/tests/test_lane_change_cancel.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""
-Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
-"""
-from iqpilot.cereal import log
-from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper, LaneChangeState, LANE_CHANGE_CANCEL_TORQUE_FRAMES
-from iqpilot.selfdrive.controls.lib.helpers.lane_change import AutoLaneChangeMode
-
-LaneChangeDirection = log.LaneChangeDirection
-
-
-class CarState:
- def __init__(self, vEgo=15.0, leftBlinker=False, rightBlinker=False, steeringPressed=False, steeringTorque=0):
- self.vEgo = vEgo
- self.leftBlinker = leftBlinker
- self.rightBlinker = rightBlinker
- self.leftBlindspot = False
- self.rightBlindspot = False
- self.steeringPressed = steeringPressed
- self.steeringTorque = steeringTorque
- self.brakePressed = False
- self.yawRate = 0.0
-
-
-def helper():
- dh = DesireHelper()
- dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
- dh.nav_exit._read_enabled = lambda: False
- return dh
-
-
-def run(dh, cs, frames, lane_change_prob=1.0):
- for _ in range(frames):
- dh.update(cs, True, lane_change_prob)
- return dh
-
-
-def started_right():
- dh = helper()
- run(dh, CarState(), 2)
- run(dh, CarState(rightBlinker=True, steeringPressed=True, steeringTorque=-240), 3)
- assert dh.lane_change_state == LaneChangeState.laneChangeStarting
- assert dh.lane_change_direction == LaneChangeDirection.right
- return dh
-
-
-def test_blinker_released_mid_change_keeps_the_change():
- dh = run(started_right(), CarState(), 10)
- assert dh.lane_change_state == LaneChangeState.laneChangeStarting
- assert dh.desire == log.Desire.laneChangeRight
-
-
-def test_opposite_blinker_cancels_immediately():
- dh = run(started_right(), CarState(leftBlinker=True), 1)
- assert dh.lane_change_state == LaneChangeState.off
- assert dh.lane_change_direction == LaneChangeDirection.none
- assert dh.desire == log.Desire.none
-
-
-def test_sustained_opposite_torque_cancels():
- dh = run(started_right(), CarState(steeringPressed=True, steeringTorque=150), LANE_CHANGE_CANCEL_TORQUE_FRAMES - 1)
- assert dh.lane_change_state == LaneChangeState.laneChangeStarting
- run(dh, CarState(steeringPressed=True, steeringTorque=150), 1)
- assert dh.lane_change_state == LaneChangeState.off
- assert dh.desire == log.Desire.none
-
-
-def test_brief_opposite_touch_does_not_cancel():
- dh = run(started_right(), CarState(steeringPressed=True, steeringTorque=150), LANE_CHANGE_CANCEL_TORQUE_FRAMES - 1)
- run(dh, CarState(), 5)
- assert dh.lane_change_state == LaneChangeState.laneChangeStarting
-
-
-def test_same_direction_torque_does_not_cancel():
- dh = run(started_right(), CarState(steeringPressed=True, steeringTorque=-200), 20)
- assert dh.lane_change_state == LaneChangeState.laneChangeStarting
-
-
-def test_cancel_during_finishing():
- dh = run(started_right(), CarState(rightBlinker=True), 12, lane_change_prob=0.0)
- assert dh.lane_change_state == LaneChangeState.laneChangeFinishing
- run(dh, CarState(leftBlinker=True), 1)
- assert dh.lane_change_state == LaneChangeState.off
-
-
-def test_left_signal_after_torque_cancel_starts_a_left_change():
- dh = run(started_right(), CarState(), 3)
- run(dh, CarState(steeringPressed=True, steeringTorque=160), LANE_CHANGE_CANCEL_TORQUE_FRAMES)
- assert dh.lane_change_state == LaneChangeState.off
- run(dh, CarState(leftBlinker=True, steeringPressed=True, steeringTorque=160), 3)
- assert dh.lane_change_direction == LaneChangeDirection.left
- assert dh.lane_change_state == LaneChangeState.laneChangeStarting
diff --git a/iqpilot/selfdrive/controls/lib/tests/test_lateral_edge_guard.py b/iqpilot/selfdrive/controls/lib/tests/test_lateral_edge_guard.py
index 97a42b4e..c917b8b2 100644
--- a/iqpilot/selfdrive/controls/lib/tests/test_lateral_edge_guard.py
+++ b/iqpilot/selfdrive/controls/lib/tests/test_lateral_edge_guard.py
@@ -10,8 +10,6 @@ import math
from iqpilot.cereal import custom, log
import iqpilot.cereal.messaging as messaging
from iqpilot.common.realtime import DT_MDL
-from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper
-from iqpilot.selfdrive.controls.lib.helpers.lane_change import AutoLaneChangeMode
from iqpilot.selfdrive.controls.lib.helpers.lateral_edge_guard import (
ADJACENT_LANE_LINE_PROB,
BLOCK_DEBOUNCE_S,
@@ -196,23 +194,6 @@ def test_measured_lane_width_is_clamped_and_falls_back() -> None:
assert LateralEdgeGuard._measured_lane_width(lane_model(4.0, ego_width_m=3.2)) == 3.2
-def test_desire_helper_blocks_only_when_edge_guard_is_enabled() -> None:
- helper = DesireHelper()
- helper.lateral_edge_guard = LateralEdgeGuard(enabled=True)
- helper.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
- helper.lane_change_state = log.LaneChangeState.preLaneChange
- helper.lane_change_direction = log.LaneChangeDirection.left
- update_for(helper.lateral_edge_guard, edge_model(4.0), BLOCK_DEBOUNCE_S)
- helper.update(CarState(), True, 1.0, modeldata=edge_model(4.0))
- assert helper.lateral_edge_block == custom.IQLateralEdgeBlock.left
- assert helper.lane_change_state == log.LaneChangeState.preLaneChange
-
- helper.lateral_edge_guard = LateralEdgeGuard(enabled=False)
- helper.update(CarState(), True, 1.0, modeldata=edge_model(4.0))
- assert helper.lateral_edge_block == custom.IQLateralEdgeBlock.none
- assert helper.lane_change_state == log.LaneChangeState.laneChangeStarting
-
-
def test_published_edge_block_maps_to_distinct_event_and_alert() -> None:
message = messaging.new_message("iqDriveModelData")
message.iqDriveModelData.lateralEdgeBlock = custom.IQLateralEdgeBlock.right
diff --git a/iqpilot/selfdrive/controls/lib/tests/test_nav_exit_lane_change.py b/iqpilot/selfdrive/controls/lib/tests/test_nav_exit_lane_change.py
deleted file mode 100644
index 278f7070..00000000
--- a/iqpilot/selfdrive/controls/lib/tests/test_nav_exit_lane_change.py
+++ /dev/null
@@ -1,183 +0,0 @@
-"""
-Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
-"""
-from iqpilot.cereal import custom, log
-from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper, LaneChangeState
-from iqpilot.selfdrive.controls.lib.helpers.lane_change import AutoLaneChangeMode
-
-ManeuverType = custom.IQNavState.ManeuverType
-NavDirection = custom.NavDirection
-LaneChangeDirection = log.LaneChangeDirection
-
-
-class DummyCarState:
- def __init__(self, vEgo=25.0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
- steeringPressed=False, steeringTorque=0, brakePressed=False):
- self.vEgo = vEgo
- self.leftBlinker = leftBlinker
- self.rightBlinker = rightBlinker
- self.leftBlindspot = leftBlindspot
- self.rightBlindspot = rightBlindspot
- self.steeringPressed = steeringPressed
- self.steeringTorque = steeringTorque
- self.brakePressed = brakePressed
-
-
-class DummyNavState:
- def __init__(self, active=True, nextManeuverValid=True, nextManeuverType=int(ManeuverType.exit),
- nextManeuverDistance=300.0, nextManeuverDirection=int(NavDirection.right)):
- self.active = active
- self.nextManeuverValid = nextManeuverValid
- self.nextManeuverType = nextManeuverType
- self.nextManeuverDistance = nextManeuverDistance
- self.nextManeuverDirection = nextManeuverDirection
-
-
-def _make_dh(enabled: bool, enable_bsm: bool):
- dh = DesireHelper()
- dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
- dh.nav_exit._read_enabled = lambda: enabled # bypass the (unregistered) param in tests
- dh.nav_exit._enable_bsm = enable_bsm
- return dh
-
-
-def _run(dh, carstate, nav_state, n=20):
- for _ in range(n):
- dh.update(carstate, True, 1.0, nav_state)
- return dh.desire
-
-
-def test_feature_off_no_exit_lane_change():
- dh = _make_dh(enabled=False, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False)
- assert _run(dh, cs, DummyNavState()) == log.Desire.none
-
-
-def test_no_bsm_requires_nudge_holds_without_one():
- # No blindspot monitor: nav exit must NOT auto-start; without a nudge it stays in preLaneChange.
- dh = _make_dh(enabled=True, enable_bsm=False)
- cs = DummyCarState(steeringPressed=False)
- assert _run(dh, cs, DummyNavState()) == log.Desire.none
- assert dh.lane_change_state == LaneChangeState.preLaneChange
- assert dh.lane_change_direction == LaneChangeDirection.right
-
-
-def test_no_bsm_starts_on_driver_nudge():
- # Driver nudges the wheel toward the exit (right -> negative torque) -> lane change starts.
- dh = _make_dh(enabled=True, enable_bsm=False)
- cs = DummyCarState(steeringPressed=True, steeringTorque=-1)
- assert _run(dh, cs, DummyNavState()) == log.Desire.laneChangeRight
-
-
-def test_bsm_auto_starts_when_clear():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False)
- assert _run(dh, cs, DummyNavState()) == log.Desire.laneChangeRight
-
-
-def test_bsm_holds_when_blindspot_occupied():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=True)
- assert _run(dh, cs, DummyNavState()) == log.Desire.none
-
-
-def test_only_exit_maneuvers_trigger():
- # A turn maneuver (not an exit) must not trigger the exit lane change.
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False)
- nav = DummyNavState(nextManeuverType=int(ManeuverType.turn))
- assert _run(dh, cs, nav) == log.Desire.none
-
-
-def test_too_far_does_not_trigger():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False)
- nav = DummyNavState(nextManeuverDistance=900.0)
- assert _run(dh, cs, nav) == log.Desire.none
-
-
-class RequestedNavState(DummyNavState):
- def __init__(self, direction=int(NavDirection.right), **kwargs):
- super().__init__(nextManeuverType=int(ManeuverType.continueStraight), nextManeuverDistance=120.0, **kwargs)
- self.shouldSendLaneChangeDesire = True
- self.navLaneChangeDesireDirection = direction
-
-
-def test_requested_lane_change_waits_for_nudge_even_with_bsm():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False, steeringPressed=False)
- assert _run(dh, cs, RequestedNavState()) == log.Desire.none
- assert dh.lane_change_state == LaneChangeState.preLaneChange
- assert dh.lane_change_direction == LaneChangeDirection.right
-
-
-def test_requested_lane_change_starts_on_matching_nudge():
- dh = _make_dh(enabled=True, enable_bsm=False)
- cs = DummyCarState(steeringPressed=True, steeringTorque=-1)
- assert _run(dh, cs, RequestedNavState()) == log.Desire.laneChangeRight
-
-
-def test_requested_lane_change_ignores_opposite_nudge():
- dh = _make_dh(enabled=True, enable_bsm=False)
- cs = DummyCarState(steeringPressed=True, steeringTorque=1)
- assert _run(dh, cs, RequestedNavState()) == log.Desire.none
-
-
-def test_requested_lane_change_blocked_by_blindspot():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=True, steeringPressed=True, steeringTorque=-1)
- assert _run(dh, cs, RequestedNavState()) == log.Desire.none
-
-
-def test_requested_lane_change_needs_feature_enabled():
- dh = _make_dh(enabled=False, enable_bsm=False)
- cs = DummyCarState(steeringPressed=True, steeringTorque=-1)
- assert _run(dh, cs, RequestedNavState()) == log.Desire.none
-
-
-def test_requested_lane_change_never_below_lane_change_speed():
- dh = _make_dh(enabled=True, enable_bsm=False)
- cs = DummyCarState(vEgo=7.0, steeringPressed=True, steeringTorque=-1)
- assert _run(dh, cs, RequestedNavState()) == log.Desire.none
-
-
-def _complete_lane_change(dh, carstate, nav_state):
- for _ in range(200):
- dh.update(carstate, True, 0.0, nav_state)
- if dh.lane_change_state in (LaneChangeState.off, LaneChangeState.preLaneChange):
- return
-
-
-def test_exit_needing_a_second_lane_rearms_for_a_nudge():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False)
- nav = DummyNavState(nextManeuverDistance=480.0)
- assert _run(dh, cs, nav) == log.Desire.laneChangeRight
- _complete_lane_change(dh, cs, DummyNavState(nextManeuverDistance=400.0))
- assert dh.lane_change_state == LaneChangeState.preLaneChange
- assert dh.lane_change_direction == LaneChangeDirection.right
- assert _run(dh, cs, DummyNavState(nextManeuverDistance=60.0), n=100) == log.Desire.none
- assert dh.lane_change_state == LaneChangeState.preLaneChange
- nudge = DummyCarState(steeringPressed=True, steeringTorque=-1)
- assert _run(dh, nudge, DummyNavState(nextManeuverDistance=40.0)) == log.Desire.laneChangeRight
-
-
-def test_rearm_ends_with_the_exit_and_the_next_exit_starts_fresh():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False)
- assert _run(dh, cs, DummyNavState(nextManeuverDistance=480.0)) == log.Desire.laneChangeRight
- _complete_lane_change(dh, cs, DummyNavState(nextManeuverDistance=400.0))
- assert dh.lane_change_state == LaneChangeState.preLaneChange
- _run(dh, cs, DummyNavState(nextManeuverType=int(ManeuverType.turn), nextManeuverDistance=900.0))
- assert dh.lane_change_state == LaneChangeState.off
- assert not dh.nav_exit_rearmed
- assert _run(dh, cs, DummyNavState(nextManeuverDistance=450.0)) == log.Desire.laneChangeRight
-
-
-def test_cancelled_exit_lane_change_does_not_come_back():
- dh = _make_dh(enabled=True, enable_bsm=True)
- cs = DummyCarState(rightBlindspot=False)
- assert _run(dh, cs, DummyNavState(nextManeuverDistance=480.0)) == log.Desire.laneChangeRight
- _run(dh, DummyCarState(leftBlinker=True), DummyNavState(nextManeuverDistance=460.0), n=2)
- assert _run(dh, cs, DummyNavState(nextManeuverDistance=400.0), n=60) == log.Desire.none
- assert dh.lane_change_state == LaneChangeState.off
diff --git a/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py b/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py
deleted file mode 100644
index 7f5f8c3c..00000000
--- a/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py
+++ /dev/null
@@ -1,235 +0,0 @@
-from types import SimpleNamespace
-
-import numpy as np
-import pytest
-
-from iqpilot.cereal import custom, log
-from iqpilot.selfdrive.controls.lib.desire_helper import (
- DesireHelper,
- TURN_DESIRE_MAX_SECONDS,
- TURN_DESIRE_PULSE_CYCLE_FRAMES,
- TURN_DESIRE_PULSE_HOLD_FRAMES,
- TURN_RECOIL_HEADING_RAD,
-)
-from iqpilot.common.constants import CV
-from iqpilot.common.realtime import DT_MDL
-
-
-TurnDirection = custom.IQTurnSignalDirection
-MODEL_HISTORY_FRAMES = 100
-
-
-def helper(v_ego=0.0, yaw_rate=0.0, model_yaw_rate=None):
- result = DesireHelper.__new__(DesireHelper)
- result._last_carstate = SimpleNamespace(vEgo=v_ego, yawRate=yaw_rate)
- result._last_modeldata = (None if model_yaw_rate is None
- else SimpleNamespace(orientationRate=SimpleNamespace(z=[model_yaw_rate])))
- result.turn_desire_frames = 0
- result.turn_desire_cycle_input = log.Desire.none
- result.turn_heading_rad = 0.0
- result.turn_desire_seconds = 0.0
- result.turn_recoiled = False
- result.nav_turn_direction = TurnDirection.none
- result.lane_turn_direction = TurnDirection.none
- result.lane_change_direction = log.LaneChangeDirection.none
- result.lane_change_state = log.LaneChangeState.off
- result.desire = log.Desire.none
- return result
-
-
-def visible_pulses(outputs):
- previous = log.Desire.none
- rising = []
- for output in outputs:
- rising.append(output != log.Desire.none and previous != output)
- previous = output
- rising = np.array(rising)
- return np.array([rising[max(0, i - MODEL_HISTORY_FRAMES + 1):i + 1].sum() for i in range(len(rising))])
-
-
-@pytest.mark.parametrize("source", ["manual", "nav"])
-@pytest.mark.parametrize("v_ego,yaw_rate", [(0.0, 0.0), (6.0, 0.0), (4.0, 0.02)])
-def test_turn_desire_keeps_exactly_one_pulse_in_model_history(source, v_ego, yaw_rate):
- h = helper(v_ego, yaw_rate)
- if source == "manual":
- h.lane_turn_direction = TurnDirection.turnLeft
- else:
- h.nav_turn_direction = TurnDirection.turnLeft
-
- outputs = []
- for _ in range(4 * TURN_DESIRE_PULSE_CYCLE_FRAMES):
- h._pick_desire_output()
- outputs.append(h.desire)
-
- gaps = [i for i, output in enumerate(outputs) if output == log.Desire.none]
- assert gaps[0] == TURN_DESIRE_PULSE_HOLD_FRAMES
- assert np.diff(gaps).tolist() == [TURN_DESIRE_PULSE_CYCLE_FRAMES] * (len(gaps) - 1)
- assert set(visible_pulses(outputs)[MODEL_HISTORY_FRAMES:].tolist()) == {1}
-
-
-def test_turn_desire_recoils_once_the_turn_has_been_driven():
- yaw_rate = 0.4
- h = helper(v_ego=6.0, yaw_rate=yaw_rate)
- h.lane_turn_direction = TurnDirection.turnLeft
-
- frames_to_recoil = int(TURN_RECOIL_HEADING_RAD / (yaw_rate * DT_MDL))
- outputs = []
- for _ in range(frames_to_recoil + 4 * TURN_DESIRE_PULSE_CYCLE_FRAMES):
- h._pick_desire_output()
- outputs.append(h.desire)
-
- assert log.Desire.turnLeft in outputs[:TURN_DESIRE_PULSE_HOLD_FRAMES]
- assert h.turn_recoiled
- assert all(output == log.Desire.none for output in outputs[frames_to_recoil + 1:])
-
-
-def test_recoil_holds_through_a_blinker_that_stays_on():
- h = helper(v_ego=6.0, yaw_rate=0.6)
- h.lane_turn_direction = TurnDirection.turnRight
- for _ in range(TURN_DESIRE_PULSE_CYCLE_FRAMES):
- h._pick_desire_output()
- assert h.turn_recoiled
-
- h._last_carstate = SimpleNamespace(vEgo=6.0, yawRate=0.0)
- outputs = []
- for _ in range(3 * TURN_DESIRE_PULSE_CYCLE_FRAMES):
- h._pick_desire_output()
- outputs.append(h.desire)
- assert all(output == log.Desire.none for output in outputs)
-
-
-def test_recoil_clears_when_the_turn_request_drops():
- h = helper(v_ego=6.0, yaw_rate=0.6)
- h.lane_turn_direction = TurnDirection.turnRight
- for _ in range(TURN_DESIRE_PULSE_CYCLE_FRAMES):
- h._pick_desire_output()
- assert h.turn_recoiled
-
- h.lane_turn_direction = TurnDirection.none
- h._pick_desire_output()
- assert not h.turn_recoiled
- assert h.turn_heading_rad == 0.0
-
- h.lane_turn_direction = TurnDirection.turnRight
- h._last_carstate = SimpleNamespace(vEgo=6.0, yawRate=0.0)
- h._pick_desire_output()
- assert h.desire == log.Desire.turnRight
-
-
-def test_turn_desire_gives_up_when_the_blinker_outlives_the_maneuver():
- h = helper(v_ego=6.0, yaw_rate=0.0)
- h.lane_turn_direction = TurnDirection.turnLeft
- for _ in range(int(TURN_DESIRE_MAX_SECONDS / DT_MDL) + 1):
- h._pick_desire_output()
- assert h.turn_recoiled
- assert h.desire == log.Desire.none
-
-
-def test_new_turn_direction_restarts_pulse_immediately():
- h = helper()
- for _ in range(TURN_DESIRE_PULSE_HOLD_FRAMES // 2):
- assert h._pulse_turn_desire(log.Desire.turnLeft) == log.Desire.turnLeft
- assert h._pulse_turn_desire(log.Desire.turnRight) == log.Desire.turnRight
- assert h.turn_desire_cycle_input == log.Desire.turnRight
- assert h.turn_desire_frames == 1
-
-
-def test_lane_change_desires_pass_through_and_reset_pulse():
- h = helper()
- h._pulse_turn_desire(log.Desire.turnLeft)
- assert h._pulse_turn_desire(log.Desire.laneChangeLeft) == log.Desire.laneChangeLeft
- assert h.turn_desire_frames == 0
- assert h.turn_desire_cycle_input == log.Desire.none
- assert not h.turn_recoiled
-
-
-def test_turn_speed_gate_follows_the_param_past_the_old_ceiling(monkeypatch):
- from iqpilot.selfdrive.controls.lib.helpers import lane_turn
-
- class FakeParams:
- def get(self, *_a, **_k):
- return "30.0"
-
- def get_bool(self, *_a, **_k):
- return True
-
- monkeypatch.setattr(lane_turn, "Params", FakeParams)
- planner = lane_turn.TurnSignalPlanner(None)
- assert planner.speed_gate == pytest.approx(30.0 * CV.MPH_TO_MS)
-
- planner.sample(blink_l=True, speed_mps=28.0 * CV.MPH_TO_MS)
- assert planner.output() == TurnDirection.turnLeft
-
- planner.sample(blink_l=True, speed_mps=31.0 * CV.MPH_TO_MS)
- assert planner.output() == TurnDirection.none
-
-
-def test_turn_speed_gate_is_capped_at_the_ceiling(monkeypatch):
- from iqpilot.selfdrive.controls.lib.helpers import lane_turn
-
- class FakeParams:
- def get(self, *_a, **_k):
- return "90.0"
-
- def get_bool(self, *_a, **_k):
- return True
-
- monkeypatch.setattr(lane_turn, "Params", FakeParams)
- planner = lane_turn.TurnSignalPlanner(None)
- assert planner.speed_gate == pytest.approx(lane_turn.TURN_SPEED_CEILING_MPS)
-
-
-def test_the_turn_intent_stays_alive_through_the_maneuver():
- h = helper(v_ego=6.0, yaw_rate=0.15)
- h.lane_turn_direction = TurnDirection.turnLeft
- outputs = []
- for _ in range(int(TURN_RECOIL_HEADING_RAD / (0.15 * DT_MDL)) - 1):
- h._pick_desire_output()
- outputs.append(h.desire)
-
- assert not h.turn_recoiled
- assert set(visible_pulses(outputs)[MODEL_HISTORY_FRAMES:].tolist()) == {1}
-
-
-def test_recoil_uses_the_model_pose_when_the_car_reports_no_yaw_rate():
- # every frame of 00000007--1984111bdd carries yawRate 0.0; the port never fills it in
- h = helper(v_ego=6.0, yaw_rate=0.0, model_yaw_rate=0.5)
- h.lane_turn_direction = TurnDirection.turnLeft
- for _ in range(int(TURN_RECOIL_HEADING_RAD / (0.5 * DT_MDL)) + 2):
- h._pick_desire_output()
- assert h.turn_recoiled
- assert h.desire == log.Desire.none
-
-
-def test_recoil_falls_back_to_carstate_yaw_rate():
- h = helper(v_ego=6.0, yaw_rate=0.5)
- h.lane_turn_direction = TurnDirection.turnLeft
- for _ in range(int(TURN_RECOIL_HEADING_RAD / (0.5 * DT_MDL)) + 2):
- h._pick_desire_output()
- assert h.turn_recoiled
-
-
-def test_turn_desire_survives_a_long_red_light_at_the_junction():
- h = helper(v_ego=0.0, yaw_rate=0.0)
- h.nav_turn_direction = TurnDirection.turnLeft
- for _ in range(int(2 * TURN_DESIRE_MAX_SECONDS / DT_MDL)):
- h._pick_desire_output()
- assert not h.turn_recoiled
- h._last_carstate = SimpleNamespace(vEgo=3.0, yawRate=0.0)
- outputs = []
- for _ in range(int(3.0 / DT_MDL)):
- h._pick_desire_output()
- outputs.append(h.desire)
- assert not h.turn_recoiled
- assert log.Desire.turnLeft in outputs
-
-
-def test_moving_time_still_ends_a_desire_that_outlives_the_maneuver():
- h = helper(v_ego=0.0, yaw_rate=0.0)
- h.lane_turn_direction = TurnDirection.turnLeft
- for _ in range(int(10.0 / DT_MDL)):
- h._pick_desire_output()
- h._last_carstate = SimpleNamespace(vEgo=6.0, yawRate=0.0)
- for _ in range(int(TURN_DESIRE_MAX_SECONDS / DT_MDL) + 1):
- h._pick_desire_output()
- assert h.turn_recoiled
diff --git a/iqpilot/selfdrive/controls/lib/tests/test_turn_speed_gate.py b/iqpilot/selfdrive/controls/lib/tests/test_turn_speed_gate.py
new file mode 100644
index 00000000..6b04882d
--- /dev/null
+++ b/iqpilot/selfdrive/controls/lib/tests/test_turn_speed_gate.py
@@ -0,0 +1,28 @@
+# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+import pytest
+
+from iqpilot.cereal import custom
+from iqpilot.common.constants import CV
+
+TurnDirection = custom.IQTurnSignalDirection
+
+
+def test_turn_speed_gate_is_fixed_at_twenty_mph_whatever_the_param_says(monkeypatch):
+ from iqpilot.selfdrive.controls.lib.helpers import lane_turn
+
+ class FakeParams:
+ def get(self, *_a, **_k):
+ return "30.0"
+
+ def get_bool(self, *_a, **_k):
+ return True
+
+ monkeypatch.setattr(lane_turn, "Params", FakeParams)
+ planner = lane_turn.TurnSignalPlanner(None)
+ assert planner.speed_gate == pytest.approx(20.0 * CV.MPH_TO_MS)
+
+ planner.sample(blink_l=True, speed_mps=19.0 * CV.MPH_TO_MS)
+ assert planner.output() == TurnDirection.turnLeft
+
+ planner.sample(blink_l=True, speed_mps=21.0 * CV.MPH_TO_MS)
+ assert planner.output() == TurnDirection.none
diff --git a/iqpilot/selfdrive/controls/tests/fixtures/lateral_lookahead_off.npz b/iqpilot/selfdrive/controls/tests/fixtures/lateral_lookahead_off.npz
index 91964c08..7f3a137d 100644
Binary files a/iqpilot/selfdrive/controls/tests/fixtures/lateral_lookahead_off.npz and b/iqpilot/selfdrive/controls/tests/fixtures/lateral_lookahead_off.npz differ
diff --git a/iqpilot/selfdrive/controls/tests/lateral_control_scenarios.py b/iqpilot/selfdrive/controls/tests/lateral_control_scenarios.py
index ff0bf440..5659ad16 100644
--- a/iqpilot/selfdrive/controls/tests/lateral_control_scenarios.py
+++ b/iqpilot/selfdrive/controls/tests/lateral_control_scenarios.py
@@ -57,7 +57,7 @@ class ScenarioMessages(dict):
self.alive = defaultdict(bool)
def all_checks(self, services):
- return False
+ return all(self.valid[service] and self.alive[service] for service in services)
class ScenarioNetwork:
@@ -147,7 +147,8 @@ def set_frame(controls, frame):
controls.sm["modelV2"].orientation.x = (0.003 * np.sin(times)).tolist()
controls.sm["modelV2"].orientation.y = (0.002 * np.cos(times)).tolist()
controls.sm["modelV2"].acceleration.y = (0.1 * np.sin(times)).tolist()
- nnff.update_model_v2(controls.sm["modelV2"])
+ controls.sm.valid["modelV2"] = True
+ controls.sm.alive["modelV2"] = True
controls.steer_limited_by_safety = 140 <= frame < 160
diff --git a/iqpilot/selfdrive/controls/tests/test_accel_boost.py b/iqpilot/selfdrive/controls/tests/test_accel_boost.py
index 028d8859..d65c8534 100644
--- a/iqpilot/selfdrive/controls/tests/test_accel_boost.py
+++ b/iqpilot/selfdrive/controls/tests/test_accel_boost.py
@@ -5,42 +5,152 @@ from pathlib import Path
import pytest
from iqpilot.common.realtime import DT_MDL
-from iqpilot.selfdrive.controls.lib.accel_boost import ACCEL_BOOST_MAX, ACCEL_BOOST_MIN_SPEED, ACCEL_BOOST_RATE, AccelBoost
+from iqpilot.selfdrive.controls.lib.accel_boost import (
+ ACCEL_BOOST_DECAY_RATE, ACCEL_BOOST_MAX, ACCEL_BOOST_MIN_SPEED, ACCEL_BOOST_PER_OVERRIDE, ACCEL_BOOST_RATE, AccelBoost,
+)
FAST = ACCEL_BOOST_MIN_SPEED + 5.0
SLOW = ACCEL_BOOST_MIN_SPEED - 1.0
-def run(boost, seconds, engaged=True, v_ego=FAST, gas=True):
+def run(boost, seconds, engaged=True, v_ego=FAST, gas=True, model_limited=True):
for _ in range(round(seconds / DT_MDL)):
- boost.update(engaged, v_ego, gas)
+ boost.update(engaged, v_ego, gas, model_limited)
return boost.value
+def eligible_boost():
+ boost = AccelBoost(True, DT_MDL)
+ boost.update(True, FAST, False, True)
+ return boost
+
+
def test_param_is_off_by_default():
keys = (Path(__file__).parents[3] / "common" / "params_keys.h").read_text()
assert re.search(r'\{"IQGasOverrideBoost", \{PERSISTENT, BOOL, "0"\}\}', keys)
def test_disabled_never_boosts():
- assert run(AccelBoost(False, DT_MDL), 30.0) == 0.0
+ boost = AccelBoost(False, DT_MDL)
+ run(boost, DT_MDL, gas=False)
+ assert run(boost, 30.0) == 0.0
+ assert boost.apply(0.5) == 0.5
-def test_gas_ramps_to_one_and_holds():
+def test_gas_ramps_to_per_override_limit_and_holds():
+ boost = eligible_boost()
+ assert run(boost, 1.0) == pytest.approx(0.025, abs=1e-6)
+ assert ACCEL_BOOST_RATE == 0.025
+ assert ACCEL_BOOST_PER_OVERRIDE == 0.05
+ assert run(boost, 15.0) == pytest.approx(0.05)
+ assert run(boost, 5.0, gas=False) == pytest.approx(0.05)
+ assert boost.override_boost == 0.0
+
+
+def test_repeated_overrides_reach_total_limit():
+ boost = eligible_boost()
+ for _ in range(6):
+ run(boost, 10.0)
+ run(boost, DT_MDL, gas=False)
+ assert ACCEL_BOOST_MAX == 0.2
+ assert boost.value == pytest.approx(0.2)
+
+
+def test_eligibility_is_latched_before_gas_override():
+ boost = eligible_boost()
+ assert run(boost, 1.0, model_limited=False) == pytest.approx(0.025)
+ run(boost, DT_MDL, gas=False, model_limited=False)
+ assert run(boost, 10.0, model_limited=True) == pytest.approx(0.025)
+ run(boost, DT_MDL, gas=False, model_limited=True)
+ assert run(boost, 1.0, model_limited=False) == pytest.approx(0.05)
+
+
+def test_engaging_with_gas_pressed_does_not_latch_eligibility():
boost = AccelBoost(True, DT_MDL)
- assert run(boost, 1.0) == pytest.approx(ACCEL_BOOST_RATE, abs=1e-6)
- assert ACCEL_BOOST_MAX == 1.0
- assert run(boost, 15.0) == pytest.approx(1.0)
- assert run(boost, 5.0, gas=False) == pytest.approx(1.0)
+ assert run(boost, 10.0) == 0.0
def test_bleeds_off_below_ten_mph_and_resets_on_disengage():
- boost = AccelBoost(True, DT_MDL)
+ boost = eligible_boost()
run(boost, 5.0)
- assert run(boost, 2.0, v_ego=SLOW) == pytest.approx(0.3, abs=1e-6)
+ assert ACCEL_BOOST_DECAY_RATE == 0.1
+ assert run(boost, 0.2, v_ego=SLOW) == pytest.approx(0.03, abs=1e-6)
assert run(boost, 5.0, v_ego=SLOW) == 0.0
+ assert run(boost, 5.0) == 0.0
+ run(boost, DT_MDL, gas=False)
run(boost, 5.0)
assert run(boost, DT_MDL, engaged=False) == 0.0
+ assert boost.override_boost == 0.0
+ assert boost.model_limited is False
+ assert run(boost, 5.0) == 0.0
+
+
+@pytest.mark.parametrize("accel,applied", [
+ (-2.0, -2.0), (-1.0, -1.0), (-0.75, -0.725), (-0.5, -0.45),
+ (0.0, 0.05), (5.0, 5.05), (5.01, 5.01),
+])
+def test_boost_fades_with_model_braking(accel, applied):
+ boost = eligible_boost()
+ run(boost, 5.0)
+ assert boost.apply(accel) == pytest.approx(applied)
+
+
+@pytest.mark.parametrize("e2e,a_model,a_mpc,a_cruise,eligible", [
+ (True, 0.0, 0.5, 0.5, True),
+ (False, 0.0, 0.5, 0.5, False),
+ (True, 0.0, 0.05, 0.5, False),
+ (True, 0.0, 0.5, 0.05, False),
+ (True, 0.0, 0.1, 0.5, False),
+])
+def test_planner_latches_model_limit_after_cruise_update(monkeypatch, e2e, a_model, a_mpc, a_cruise, eligible):
+ from types import SimpleNamespace
+ import numpy as np
+
+ from iqpilot.selfdrive.controls.lib import longitudinal_planner as lp
+ from iqpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
+
+ monkeypatch.setattr(lp.LongitudinalPlannerIQ, "update", lambda *args: None)
+ monkeypatch.setattr(lp.LongitudinalPlannerIQ, "update_targets", lambda self, sm, v_ego, v_cruise: v_cruise)
+ monkeypatch.setattr(lp, "get_accel_from_plan", lambda *args, **kwargs: (a_mpc, False))
+ monkeypatch.setattr(lp, "get_cruise_accel", lambda *args: (a_cruise, False))
+ planner = object.__new__(lp.LongitudinalPlanner)
+ planner.CP = SimpleNamespace(openpilotLongitudinalControl=True, longitudinalActuatorDelay=0.1)
+ planner.dt = DT_MDL
+ planner.v_desired_filter = SimpleNamespace(x=FAST, update=lambda v: v)
+ planner.a_desired = 0.0
+ planner.a_cruise = -1.0
+ planner.stopping_speed = 0.5
+ planner.exp_speed_conv = False
+ planner.launch_armed = False
+ planner.forcing_stop = False
+ planner.prev_e2e = e2e
+ planner.mode_blend_timer = 0.0
+ planner.accel_boost = AccelBoost(True, DT_MDL)
+ planner.distance_control = SimpleNamespace(enabled=False, update=lambda *args, **kwargs: kwargs["a_model"])
+ planner.is_e2e = lambda sm: e2e
+ planner.apply_e2e_stop_distance = lambda sm, v, a, stop: (a, stop)
+ planner.parse_model = lambda model: (None, np.full(len(lp.T_IDXS_MPC), FAST), np.zeros(len(lp.T_IDXS_MPC)), None, 1.0)
+ planner.mpc = SimpleNamespace(
+ status=True, crash_cnt=0, source=lp.LongitudinalPlanSource.lead0,
+ v_solution=np.full(len(lp.T_IDXS_MPC), FAST), a_solution=np.zeros(len(lp.T_IDXS_MPC)),
+ j_solution=np.zeros(len(lp.T_IDXS_MPC) - 1), set_weights=lambda **kwargs: None,
+ set_cur_state=lambda *args: None, update=lambda *args, **kwargs: None,
+ )
+ sm = {
+ "carControl": SimpleNamespace(orientationNED=[], longActive=True),
+ "carState": SimpleNamespace(vEgo=FAST, vCruise=100.0, aEgo=0.0, steeringAngleDeg=0.0, standstill=False, gasPressed=False),
+ "controlsState": SimpleNamespace(forceDecel=False, longControlState=LongCtrlState.pid),
+ "selfdriveState": SimpleNamespace(enabled=True, personality=0),
+ "vehicleParameters": SimpleNamespace(angleOffsetDeg=0.0),
+ "modelV2": SimpleNamespace(action=SimpleNamespace(desiredAcceleration=a_model, shouldStop=False)),
+ "radarState": SimpleNamespace(),
+ }
+ planner.update(sm)
+ assert planner.accel_boost.model_limited is eligible
+ sm["carState"].gasPressed = True
+ planner.update(sm)
+ assert planner.accel_boost.value == pytest.approx(ACCEL_BOOST_RATE * DT_MDL if eligible else 0.0)
+ assert planner.output_a_target == pytest.approx(min(a_mpc, a_cruise, planner.accel_boost.apply(a_model)) if e2e else min(a_mpc, a_cruise))
def test_boost_bar_only_replaces_the_confidence_ball_with_the_param_on(monkeypatch):
diff --git a/iqpilot/selfdrive/controls/tests/test_lat_jerk_lowpass.py b/iqpilot/selfdrive/controls/tests/test_lat_jerk_lowpass.py
index aa847ce4..e06ff8ec 100644
--- a/iqpilot/selfdrive/controls/tests/test_lat_jerk_lowpass.py
+++ b/iqpilot/selfdrive/controls/tests/test_lat_jerk_lowpass.py
@@ -34,6 +34,7 @@ def _model(rng):
m = log.ModelDataV2.new_message()
m.acceleration.y = (slope * np.arange(n) * 0.1).tolist()
m.orientation.x = [0.0] * n
+ m.orientation.y = [0.0] * n
return m
diff --git a/iqpilot/selfdrive/controls/tests/test_latcontrol.py b/iqpilot/selfdrive/controls/tests/test_latcontrol.py
index 65556521..d74a9a25 100644
--- a/iqpilot/selfdrive/controls/tests/test_latcontrol.py
+++ b/iqpilot/selfdrive/controls/tests/test_latcontrol.py
@@ -128,14 +128,59 @@ class TestLatControl:
assert not lac_log.active
assert controller.lateral_acceleration_slew_limiter.a_lim == 0.9
- def test_pq_live_torque_update_freeze_and_unfreeze(self, monkeypatch):
- _, controller = self.build_pq_controller()
- initial = (controller.torque_params.latAccelFactor, controller.torque_params.latAccelOffset, controller.torque_params.friction)
- controller.update_live_torque_params(3.0, 0.2, 0.4)
- assert (controller.torque_params.latAccelFactor, controller.torque_params.latAccelOffset, controller.torque_params.friction) == initial
+ @staticmethod
+ def settle_pq_live_torque(controller, factor, offset, valid, seconds=60.0):
+ controller.set_live_torque_valid(valid)
+ for _ in range(int(seconds / DT_CTRL)):
+ controller.update_live_torque_params(factor, offset, 0.4)
- monkeypatch.setattr(latcontrol_torque_pq, "FREEZE_LIVE_TORQUE_PARAMS", False)
+ def test_pq_live_torque_falls_back_until_torqued_is_valid(self):
+ _, controller = self.build_pq_controller()
+ self.settle_pq_live_torque(controller, 3.0, 0.2, valid=False)
+ assert abs(controller.torque_params.latAccelFactor - latcontrol_torque_pq.DEFAULT_LAT_ACCEL_FACTOR) < 1e-6
+ assert abs(controller.torque_params.latAccelOffset - latcontrol_torque_pq.DEFAULT_LAT_ACCEL_OFFSET) < 1e-6
+ assert abs(controller.torque_params.friction - latcontrol_torque_pq.DEFAULT_FRICTION) < 1e-6
+
+ def test_pq_live_torque_uses_learned_factor_and_offset(self):
+ _, controller = self.build_pq_controller()
+ self.settle_pq_live_torque(controller, 2.35, 0.047, valid=True)
+ expected_factor = 2.35 * latcontrol_torque_pq.LIVE_LAT_ACCEL_FACTOR_GAIN
+ assert abs(controller.torque_params.latAccelFactor - expected_factor) < 1e-3
+ assert abs(controller.torque_params.latAccelOffset - 0.047) < 1e-3
+ assert abs(controller.torque_params.friction - latcontrol_torque_pq.DEFAULT_FRICTION) < 1e-6
+
+ def test_pq_live_torque_changes_are_smoothed(self):
+ _, controller = self.build_pq_controller()
+ controller.set_live_torque_valid(True)
controller.update_live_torque_params(3.0, 0.2, 0.4)
- assert controller.torque_params.latAccelFactor == 3.0
- assert abs(controller.torque_params.latAccelOffset - 0.2) < 1e-6
- assert abs(controller.torque_params.friction - 0.4) < 1e-6
+ step = controller.torque_params.latAccelFactor - latcontrol_torque_pq.DEFAULT_LAT_ACCEL_FACTOR
+ assert 0.0 < step < 0.01
+
+ def test_pq_live_torque_is_bounded(self):
+ _, controller = self.build_pq_controller()
+ self.settle_pq_live_torque(controller, 10.0, 2.0, valid=True)
+ assert abs(controller.torque_params.latAccelFactor - latcontrol_torque_pq.LIVE_LAT_ACCEL_FACTOR_BOUNDS[1]) < 1e-3
+ assert abs(controller.torque_params.latAccelOffset - latcontrol_torque_pq.LIVE_LAT_ACCEL_OFFSET_BOUNDS[1]) < 1e-3
+ self.settle_pq_live_torque(controller, 0.1, -2.0, valid=True)
+ assert abs(controller.torque_params.latAccelFactor - latcontrol_torque_pq.LIVE_LAT_ACCEL_FACTOR_BOUNDS[0]) < 1e-3
+ assert abs(controller.torque_params.latAccelOffset - latcontrol_torque_pq.LIVE_LAT_ACCEL_OFFSET_BOUNDS[0]) < 1e-3
+
+ def test_pq_live_torque_rejects_non_finite_estimates(self):
+ _, controller = self.build_pq_controller()
+ self.settle_pq_live_torque(controller, float('nan'), 0.2, valid=True)
+ assert abs(controller.torque_params.latAccelFactor - latcontrol_torque_pq.DEFAULT_LAT_ACCEL_FACTOR) < 1e-6
+ assert abs(controller.torque_params.latAccelOffset - latcontrol_torque_pq.DEFAULT_LAT_ACCEL_OFFSET) < 1e-6
+
+ def test_pq_live_torque_freeze_keeps_defaults(self, monkeypatch):
+ monkeypatch.setattr(latcontrol_torque_pq, "FREEZE_LIVE_TORQUE_PARAMS", True)
+ _, controller = self.build_pq_controller()
+ self.settle_pq_live_torque(controller, 3.0, 0.2, valid=True, seconds=1.0)
+ assert abs(controller.torque_params.latAccelFactor - latcontrol_torque_pq.DEFAULT_LAT_ACCEL_FACTOR) < 1e-6
+ assert abs(controller.torque_params.latAccelOffset - latcontrol_torque_pq.DEFAULT_LAT_ACCEL_OFFSET) < 1e-6
+
+ def test_pq_integrator_gain_rises_at_highway_speed(self):
+ _, controller = self.build_pq_controller()
+ controller.pid.speed = 5.0
+ assert abs(controller.pid.k_i - latcontrol_torque_pq.KI) < 1e-9
+ controller.pid.speed = 30.0
+ assert abs(controller.pid.k_i - latcontrol_torque_pq.KI_HIGHWAY) < 1e-9
diff --git a/iqpilot/selfdrive/controls/tests/test_lateral_action_selection.py b/iqpilot/selfdrive/controls/tests/test_lateral_action_selection.py
index d85bffe9..aac98776 100644
--- a/iqpilot/selfdrive/controls/tests/test_lateral_action_selection.py
+++ b/iqpilot/selfdrive/controls/tests/test_lateral_action_selection.py
@@ -35,12 +35,12 @@ def test_toggle_off_matches_pre_fix_outputs(controls_module, case, slew):
@pytest.mark.parametrize("case", ["torque", "tss2", "gm"])
@pytest.mark.parametrize("slew", [False, True])
-def test_toggle_off_preserves_active_nnff_outputs(controls_module, case, slew):
+def test_unknown_model_cannot_replace_action_with_nnff(controls_module, case, slew):
controls = build_controls(controls_module, case, lookahead=False, slew=slew, nnff=True)
actual = run_scenario(controls)
assert controls.LaC.nnff_assist.model.queries > 0
- with np.load(Path(__file__).with_name("fixtures") / "lateral_lookahead_off.npz") as baseline:
- np.testing.assert_allclose(actual, baseline[f"{case}_slew{int(slew)}_nnff"], rtol=1e-12, atol=1e-12)
+ enabled = build_controls(controls_module, case, lookahead=True, slew=slew, nnff=True)
+ np.testing.assert_array_equal(actual, run_scenario(enabled))
@pytest.mark.parametrize("case", CONTROL_CASES)
diff --git a/iqpilot/selfdrive/controls/tests/test_nnff_controls.py b/iqpilot/selfdrive/controls/tests/test_nnff_controls.py
new file mode 100644
index 00000000..999ac161
--- /dev/null
+++ b/iqpilot/selfdrive/controls/tests/test_nnff_controls.py
@@ -0,0 +1,232 @@
+import numpy as np
+import pytest
+
+from iqpilot.selfdrive.controls.tests.lateral_control_scenarios import build_controls, load_controls_module, set_frame
+
+
+@pytest.fixture
+def controls_module(monkeypatch):
+ return load_controls_module(monkeypatch)
+
+
+@pytest.mark.parametrize("case", ["torque", "tss2", "gm"])
+def test_controls_feeds_nnff_and_updates_pid_once(controls_module, monkeypatch, case):
+ controls = build_controls(controls_module, case, nnff=True)
+ nnff = controls.LaC.nnff_assist
+ assert not nnff.model_valid
+ calls = []
+ lag_updates = []
+ controller_delays = []
+ original_update = controls.LaC.pid.update
+ original_lag_update = nnff.update_lateral_lag
+ original_control_update = controls.LaC.update
+
+ def record_update(*args, **kwargs):
+ calls.append((args, kwargs))
+ return original_update(*args, **kwargs)
+
+ def record_lag_update(lag):
+ lag_updates.append(lag)
+ original_lag_update(lag)
+
+ def record_control_update(*args):
+ controller_delays.append(args[-1])
+ assert nnff.desired_lat_jerk_time == pytest.approx(max(0.01, args[-1]))
+ return original_control_update(*args)
+
+ monkeypatch.setattr(controls.LaC.pid, "update", record_update)
+ monkeypatch.setattr(nnff, "update_lateral_lag", record_lag_update)
+ monkeypatch.setattr(controls.LaC, "update", record_control_update)
+ for frame in range(120, 130):
+ set_frame(controls, frame)
+ controls.sm["lateralDelay"].lateralDelay = 0.25 if frame < 125 else 0.45
+ if frame == 120:
+ assert not nnff.model_valid # Only state_control may feed the live model.
+ previous_queries = nnff.model.queries
+ cc, _ = controls.state_control()
+ assert cc.latActive
+ assert nnff.model_valid
+ assert nnff.model.queries > previous_queries
+ assert len(calls) == frame - 119
+ assert abs(cc.actuators.torque) <= controls.LaC.steer_max
+ assert lag_updates == controller_delays == [0.25] * 5 + [0.45] * 5
+ assert nnff.nn_future_times == pytest.approx([t + nnff.desired_lat_jerk_time for t in nnff.future_times])
+
+
+@pytest.mark.parametrize("unavailable", [
+ "disabled", "missing_nn_model", "invalid_message", "dead_message", "missing_orientation_x",
+ "short_orientation_y", "missing_acceleration", "nonfinite_orientation", "nonfinite_acceleration",
+])
+def test_unavailable_nnff_uses_standard_controller(controls_module, unavailable):
+ controls = build_controls(controls_module, "torque", nnff=True)
+ baseline = build_controls(controls_module, "torque")
+ nnff = controls.LaC.nnff_assist
+ network = nnff.model
+ if unavailable == "disabled":
+ nnff.enabled = False
+ elif unavailable == "missing_nn_model":
+ nnff.has_nn_model = False
+ nnff.model = None
+
+ for frame in range(120, 130):
+ set_frame(controls, frame)
+ set_frame(baseline, frame)
+ model = controls.sm["modelV2"]
+ if unavailable == "invalid_message":
+ controls.sm.valid["modelV2"] = False
+ elif unavailable == "dead_message":
+ controls.sm.alive["modelV2"] = False
+ elif unavailable == "missing_orientation_x":
+ model.orientation.x = []
+ elif unavailable == "short_orientation_y":
+ model.orientation.y = [0.0]
+ elif unavailable == "missing_acceleration":
+ model.acceleration.y = []
+ elif unavailable == "nonfinite_orientation":
+ model.orientation.x = [float("nan")] * len(model.orientation.x)
+ elif unavailable == "nonfinite_acceleration":
+ model.acceleration.y = [float("inf")] * len(model.acceleration.y)
+ actual, trace = controls.state_control()
+ expected, expected_trace = baseline.state_control()
+ assert actual.actuators.torque == expected.actuators.torque
+ for field in ("p", "i", "d", "f", "error", "output"):
+ assert getattr(trace, field) == getattr(expected_trace, field)
+ assert network.queries == 0
+
+
+@pytest.mark.parametrize("lost_service", ["valid", "alive"])
+def test_lost_live_model_stops_inference_and_restores_limits(controls_module, lost_service):
+ controls = build_controls(controls_module, "torque", nnff=True)
+ nnff = controls.LaC.nnff_assist
+ set_frame(controls, 120)
+ controls.state_control()
+ queries = nnff.model.queries
+ assert queries > 0
+ assert controls.LaC.pid.pos_limit == controls.LaC.steer_max
+ assert controls.LaC.pid.neg_limit == -controls.LaC.steer_max
+
+ set_frame(controls, 121)
+ getattr(controls.sm, lost_service)["modelV2"] = False
+ cc, _ = controls.state_control()
+ assert not nnff.model_valid
+ assert nnff.model.queries == queries
+ assert np.isfinite(cc.actuators.torque)
+ assert abs(cc.actuators.torque) <= controls.LaC.steer_max
+ assert controls.LaC.pid.pos_limit == controls.LaC.lateral_accel_from_torque(controls.LaC.steer_max, controls.LaC.torque_params)
+ assert controls.LaC.pid.neg_limit == controls.LaC.lateral_accel_from_torque(-controls.LaC.steer_max, controls.LaC.torque_params)
+
+
+def test_first_live_torque_update_uses_nnff_limits(controls_module):
+ controls = build_controls(controls_module, "torque", nnff=True)
+ set_frame(controls, 120)
+ controls.sm.valid["lateralTorqueParameters"] = True
+ controls.sm.alive["lateralTorqueParameters"] = True
+ live_torque = controls.sm["lateralTorqueParameters"]
+ live_torque.useParams = True
+ live_torque.valid = True
+ live_torque.latAccelFactorFiltered = 4.0
+ live_torque.latAccelOffsetFiltered = 0.05
+ live_torque.frictionCoefficientFiltered = 0.12
+ cc, _ = controls.state_control()
+ assert controls.LaC.torque_params.latAccelFactor == 4.0
+ assert controls.LaC.nnff_assist.model.queries > 0
+ assert controls.LaC.pid.pos_limit == controls.LaC.steer_max
+ assert controls.LaC.pid.neg_limit == -controls.LaC.steer_max
+ assert abs(cc.actuators.torque) <= controls.LaC.steer_max
+
+
+@pytest.mark.parametrize("output", [float("nan"), float("inf"), -float("inf")])
+def test_nonfinite_inference_falls_back_before_pid_update(controls_module, monkeypatch, output):
+ controls = build_controls(controls_module, "torque", nnff=True)
+ baseline = build_controls(controls_module, "torque")
+ monkeypatch.setattr(controls.LaC.nnff_assist.model, "evaluate", lambda inputs: output)
+ calls = []
+ original_update = controls.LaC.pid.update
+
+ def record_update(*args, **kwargs):
+ calls.append((args, kwargs))
+ return original_update(*args, **kwargs)
+
+ monkeypatch.setattr(controls.LaC.pid, "update", record_update)
+ for frame in range(120, 125):
+ set_frame(controls, frame)
+ set_frame(baseline, frame)
+ actual, _ = controls.state_control()
+ expected, _ = baseline.state_control()
+ assert actual.actuators.torque == expected.actuators.torque
+ assert np.isfinite(controls.LaC.pid.i)
+ assert len(calls) == frame - 119
+ assert not controls.LaC.nnff_assist.has_nn_model
+
+
+def test_nnff_recovers_after_invalid_model_without_reusing_integral_or_history(controls_module, monkeypatch):
+ controls = build_controls(controls_module, "torque", nnff=True)
+ nnff = controls.LaC.nnff_assist
+ pid = controls.LaC.pid
+ integral_before_update = []
+ original_update = pid.update
+
+ def record_update(*args, **kwargs):
+ integral_before_update.append(pid.i)
+ return original_update(*args, **kwargs)
+
+ monkeypatch.setattr(pid, "update", record_update)
+ previous_queries = 0
+ for valid, frames, factor in ((True, 20, 2.7), (False, 30, 4.0), (True, 5, 3.2)):
+ for frame in range(frames):
+ set_frame(controls, 200)
+ state = controls.sm["carState"]
+ state.vEgo = 20.0
+ state.steeringAngleDeg = 0.15
+ state.steeringRateDeg = 0.0
+ state.steeringPressed = False
+ controls.sm["vehicleParameters"].roll = 0.0
+ controls.sm["lateralDelay"].lateralDelay = 0.12
+ controls.sm.valid["modelV2"] = valid
+ model = controls.sm["modelV2"]
+ model.action.desiredCurvature = 0.0001
+ model.orientation.x = [0.0] * len(model.orientation.x)
+ model.orientation.y = [0.0] * len(model.orientation.y)
+ model.acceleration.y = [0.04] * len(model.acceleration.y)
+ controls.sm.valid["lateralTorqueParameters"] = True
+ controls.sm.alive["lateralTorqueParameters"] = True
+ live_torque = controls.sm["lateralTorqueParameters"]
+ live_torque.useParams = True
+ live_torque.valid = True
+ live_torque.latAccelFactorFiltered = factor
+ live_torque.latAccelOffsetFiltered = 0.0
+ live_torque.frictionCoefficientFiltered = 0.12
+
+ cc, _ = controls.state_control()
+ assert cc.latActive
+ assert nnff.model_valid == valid
+ assert controls.LaC.torque_params.latAccelFactor == pytest.approx(factor)
+ assert np.isfinite(cc.actuators.torque)
+ assert abs(cc.actuators.torque) <= controls.LaC.steer_max
+ if frame == 0:
+ assert integral_before_update[-1] == 0.0
+ if valid:
+ assert nnff.model.queries > previous_queries
+ assert pid.pos_limit == controls.LaC.steer_max
+ assert pid.neg_limit == -controls.LaC.steer_max
+ assert len(nnff._window.roll) == frame + 1
+ assert len(nnff._window.lat_accel_desired) == frame + 1
+ else:
+ assert nnff.model.queries == previous_queries
+ assert pid.pos_limit == controls.LaC.lateral_accel_from_torque(controls.LaC.steer_max, controls.LaC.torque_params)
+ assert pid.neg_limit == controls.LaC.lateral_accel_from_torque(-controls.LaC.steer_max, controls.LaC.torque_params)
+ assert len(nnff._window.roll) == 0
+ assert len(nnff._window.lat_accel_desired) == 0
+ previous_queries = nnff.model.queries
+ assert pid.i != 0.0 # Each transition must discard an integral accumulated in the previous mode.
+
+
+@pytest.mark.parametrize("case", ["pq", "mqb_v0", "pid", "angle", "curvature"])
+def test_non_nnff_controllers_accept_live_model_updates(controls_module, case):
+ controls = build_controls(controls_module, case)
+ assert not hasattr(controls.LaC, "nnff_assist")
+ set_frame(controls, 120)
+ cc, _ = controls.state_control()
+ assert cc.latActive
+ assert np.isfinite(cc.actuators.torque)
+ assert np.isfinite(cc.actuators.steeringAngleDeg)
diff --git a/iqpilot/selfdrive/controls/tests/test_soft_disable_decel.py b/iqpilot/selfdrive/controls/tests/test_soft_disable_decel.py
new file mode 100644
index 00000000..731a9574
--- /dev/null
+++ b/iqpilot/selfdrive/controls/tests/test_soft_disable_decel.py
@@ -0,0 +1,73 @@
+# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+from types import SimpleNamespace
+
+import pytest
+
+from iqpilot.common.realtime import DT_MDL
+from iqpilot.selfdrive.controls.lib.longcontrol import LongControl, LongCtrlState
+from iqpilot.selfdrive.controls.lib.longitudinal_planner import CRUISE_STOP_SPEED, J_CRUISE, get_cruise_accel
+from iqpilot.selfdrive.controls.lib.smooth_stops import SMOOTH_STOP_MAX_SPEED
+
+ACCEL_LIMITS = (-3.5, 2.0)
+PLANNER_CP = SimpleNamespace(steerRatio=15.3, wheelbase=2.64)
+
+
+def make_control(monkeypatch):
+ cp = SimpleNamespace(
+ carFingerprint="VOLKSWAGEN_GOLF_MK7",
+ stopAccel=-0.55,
+ longitudinalTuning=SimpleNamespace(kpBP=[0.], kpV=[0.1], kiBP=[0.], kiV=[0.0]),
+ )
+ cp_iq = SimpleNamespace(stoppingDecelRateOverride=0.0, enableGasInterceptor=False)
+ control = LongControl(cp, cp_iq)
+ monkeypatch.setattr(control.smooth, "read_params", lambda: None)
+ control.smooth.enabled = True
+ return control
+
+
+def car_state(v_ego):
+ return SimpleNamespace(vEgo=v_ego, aEgo=0.0, brakePressed=False, standstill=False,
+ cruiseState=SimpleNamespace(standstill=False))
+
+
+@pytest.mark.parametrize("e2e", [False, True])
+def test_forced_decel_at_speed_ramps_without_requesting_a_stop(e2e):
+ accel, should_stop = get_cruise_accel(e2e, 0.0, 30.8, 0.0, 0.0, PLANNER_CP, DT_MDL, -0.3, True)
+ assert should_stop is False
+ assert accel >= -J_CRUISE * DT_MDL - 1e-6
+
+
+@pytest.mark.parametrize("e2e", [False, True])
+def test_forced_decel_requests_a_stop_once_nearly_stopped(e2e):
+ _, should_stop = get_cruise_accel(e2e, 0.0, CRUISE_STOP_SPEED / 2, -1.2, 0.0, PLANNER_CP, DT_MDL, -0.3, True)
+ assert should_stop is True
+
+
+def test_smooth_stop_never_brakes_hard_at_highway_speed(monkeypatch):
+ control = make_control(monkeypatch)
+ control.long_control_state = LongCtrlState.pid
+ for _ in range(10):
+ accel = control.update(True, car_state(30.8), -0.39, True, ACCEL_LIMITS, lead_distance=40.0, has_lead=True,
+ v_target_now=30.8)
+ assert accel > -1.0
+
+
+def test_smooth_stop_still_settles_at_stopping_speed(monkeypatch):
+ control = make_control(monkeypatch)
+ control.long_control_state = LongCtrlState.pid
+ calls = []
+ monkeypatch.setattr(control.smooth, "settle", lambda *args: calls.append(args) or -0.4)
+ v_ego = SMOOTH_STOP_MAX_SPEED / 3
+ accel = control.update(True, car_state(v_ego), -0.5, True, ACCEL_LIMITS, lead_distance=0.0, has_lead=False,
+ v_target_now=v_ego)
+ assert calls and accel == -0.4
+
+
+def test_smooth_stop_is_skipped_above_its_speed_range(monkeypatch):
+ control = make_control(monkeypatch)
+ control.long_control_state = LongCtrlState.pid
+ calls = []
+ monkeypatch.setattr(control.smooth, "settle", lambda *args: calls.append(args) or -3.5)
+ control.update(True, car_state(SMOOTH_STOP_MAX_SPEED + 1.0), -0.5, True, ACCEL_LIMITS, lead_distance=40.0,
+ has_lead=True, v_target_now=SMOOTH_STOP_MAX_SPEED + 1.0)
+ assert calls == []
diff --git a/iqpilot/selfdrive/iqmodeld/aux_inputs.py b/iqpilot/selfdrive/iqmodeld/aux_inputs.py
new file mode 100644
index 00000000..28b599ad
--- /dev/null
+++ b/iqpilot/selfdrive/iqmodeld/aux_inputs.py
@@ -0,0 +1,17 @@
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
+from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+
+try:
+ load_private_module(__name__, "iqpilot_private.context.aux_inputs")
+except ProprietaryModuleMissing:
+ class AuxModelInputs:
+ def __init__(self, input_spec: dict):
+ self.active = False
+
+ def poll(self) -> None:
+ pass
+
+ def extras(self) -> dict:
+ return {}
diff --git a/iqpilot/selfdrive/iqmodeld/daemon.py b/iqpilot/selfdrive/iqmodeld/daemon.py
index e8c1a9ee..104b102a 100755
--- a/iqpilot/selfdrive/iqmodeld/daemon.py
+++ b/iqpilot/selfdrive/iqmodeld/daemon.py
@@ -23,7 +23,7 @@ from iqpilot.common.realtime import DT_MDL, config_realtime_process
from iqpilot.common.swaglog import cloudlog
from iqpilot.common.transformations.camera import DEVICE_CAMERAS
from iqpilot.common.transformations.model import get_warp_matrix
-from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper
+from iqpilot.selfdrive.controls.lib.action_engine import ActionEngine
from iqpilot.selfdrive.controls.lib.drive_helpers import (
MODEL_SMOOTHING_MAX_TOTAL_SEC,
dynamic_lat_smooth_extra_seconds,
@@ -34,7 +34,7 @@ from iqpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy
from iqpilot.system import sentry
from iqpilot.common.steer_delay import lateral_action_delay
-from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
+from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle, load_default_model_session, forced_active_bundle
from iqpilot.selfdrive.iqmodeld.models.inference_state import InferenceStateBase
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import get_model_runner
from iqpilot.selfdrive.iqmodeld.camera import CameraOffsetHelper
@@ -243,13 +243,30 @@ def _merged_plan(runtime_state: "NeuralEngineState", outputs: dict[str, np.ndarr
return base_plan + (runtime_state.PLANPLUS_CONTROL * _planplus_gain(vehicle_speed)) * outputs["planplus"][0]
+def _set_default_fallback_alert(active: bool) -> None:
+ try:
+ from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
+ set_offroad_alert("Offroad_ModelFallbackDefault", active)
+ except Exception:
+ cloudlog.exception("could not update the default-model fallback alert")
+
+
class NeuralEngineState(InferenceStateBase):
frames: dict[str, RoadProjector]
def __init__(self, gpu_context: WarpContext):
super().__init__()
- runner = get_model_runner()
- bundle = get_active_bundle()
+ try:
+ runner = get_model_runner()
+ bundle = get_active_bundle()
+ _set_default_fallback_alert(False)
+ except Exception:
+ cloudlog.exception("active model failed to load; running the default model this session so pose stays valid")
+ default_bundle = load_default_model_session()
+ with forced_active_bundle(default_bundle):
+ runner = get_model_runner()
+ bundle = get_active_bundle()
+ _set_default_fallback_alert(True)
self.model_runner = runner
self.constants = runner.constants
@@ -354,7 +371,10 @@ class NeuralEngineState(InferenceStateBase):
def get_action_from_model(self, outputs: dict[str, np.ndarray], previous_action: log.ModelDataV2.Action,
lat_action_t: float, long_action_t: float, vehicle_speed: float,
- lat_smooth_seconds: float | None = None) -> log.ModelDataV2.Action:
+ lat_smooth_seconds: float | None = None, *,
+ lat_active: bool = True, current_curvature: float = 0.0) -> log.ModelDataV2.Action:
+ if not lat_active:
+ previous_action.desiredCurvature = current_curvature
if lat_smooth_seconds is None:
lat_smooth_seconds = self.LAT_SMOOTH_SECONDS
@@ -568,7 +588,7 @@ class InferenceDaemon:
self._pub = PubMaster(pub_services)
self._sub = SubMaster([
"deviceState", "carState", "roadCameraState", "extrinsicsCalibration",
- "driverMonitoringState", "carControl", "lateralDelay", "iqNavState", "navInstruction", "radarState",
+ "driverMonitoringState", "carControl", "controlsState", "lateralDelay", "iqNavState", "navInstruction", "radarState",
] + self._feature_inputs.services)
self._message_memory = DrivePacketMemory()
self._params = Params()
@@ -580,7 +600,7 @@ class InferenceDaemon:
self._car_params = self._load_car_params(demo)
self._long_action_delay = self._car_params.longitudinalActuatorDelay + self._runtime.LONG_SMOOTH_SECONDS
self._previous_action = log.ModelDataV2.Action()
- self._desire_logic = DesireHelper()
+ self._desire_logic = ActionEngine()
self._lat_smooth_extra_sec = 0.0
self._drive_profile_state = DriveProfileState()
self._memory_resets = MemoryResetTracker()
@@ -664,7 +684,8 @@ class InferenceDaemon:
)
lat_smooth_total_sec = min(self._runtime.LAT_SMOOTH_SECONDS + self._lat_smooth_extra_sec, MODEL_SMOOTHING_MAX_TOTAL_SEC)
action = self._runtime.get_action_from_model(
- outputs, self._previous_action, lat_horizon, long_horizon, vehicle_speed, lat_smooth_total_sec
+ outputs, self._previous_action, lat_horizon, long_horizon, vehicle_speed, lat_smooth_total_sec,
+ lat_active=self._sub["carControl"].latActive, current_curvature=self._sub["controlsState"].curvature,
)
self._previous_action = action
diff --git a/iqpilot/selfdrive/iqmodeld/driving_action.py b/iqpilot/selfdrive/iqmodeld/driving_action.py
index 67a38f68..d994f868 100644
--- a/iqpilot/selfdrive/iqmodeld/driving_action.py
+++ b/iqpilot/selfdrive/iqmodeld/driving_action.py
@@ -17,7 +17,10 @@ DESIRE_LEN = 8
def get_action_from_model(outputs: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
v_ego: float, lat_action_t: float, long_action_t: float,
- lat_smooth_seconds: float | None = None) -> log.ModelDataV2.Action:
+ lat_smooth_seconds: float | None = None, *,
+ lat_active: bool = True, current_curvature: float = 0.0) -> log.ModelDataV2.Action:
+ if not lat_active:
+ prev_action.desiredCurvature = current_curvature
if "action" in outputs:
desired_accel = float(outputs["action"][0, 1])
desired_curvature = float(outputs["action"][0, 0]) / (max(1.0, v_ego)) ** 2
diff --git a/iqpilot/selfdrive/iqmodeld/egpu_helpers.py b/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
index 1749bfe0..6ba1c352 100644
--- a/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
+++ b/iqpilot/selfdrive/iqmodeld/egpu_helpers.py
@@ -139,7 +139,18 @@ def egpu_oob_pkl_path(meta: dict) -> str:
def egpu_model_oob_pkl_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths
- return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob.pkl")
+ return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob2.pkl")
+
+
+def drop_frozen_model_oob(meta: dict) -> None:
+ from iqpilot.system.hardware.hw import Paths
+ # warp-on-dock bundles saved under the old name were built before the prune fix and replay capture-time feeds
+ stale = os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_model_oob.pkl")
+ try:
+ os.remove(stale)
+ cloudlog.warning(f"removed frozen-feed warp-on-dock artifact {stale}")
+ except OSError:
+ pass
def onnx_cache_path(meta: dict) -> str:
@@ -288,6 +299,28 @@ def patch_tinygrad_fetch_fw() -> None:
helpers.fetch_fw = fetch_fw
+def patch_tinygrad_prune_views() -> None:
+ from tinygrad.engine import jit
+ from tinygrad.uop.ops import Ops
+ if getattr(jit.prune_linear, "_iq_patched", False):
+ return
+
+ # upstream prune only sees bare BUFFER args, so a kernel reading an input through a SLICE view ran once at capture and froze that input
+ def prune_linear(linear, needed):
+ kept, onetime = [], []
+ for si in linear.src:
+ si_bufs = {b for src in si.src[1:] for b in src.toposort() if b.op is Ops.BUFFER}
+ if not si_bufs.isdisjoint(needed):
+ kept.append(si)
+ needed |= si_bufs
+ else:
+ onetime.append(si)
+ return linear.replace(src=tuple(kept)), linear.replace(src=tuple(onetime))
+
+ prune_linear._iq_patched = True
+ jit.prune_linear = prune_linear
+
+
def patch_usb_signal_wait() -> None:
import time
diff --git a/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py b/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py
index f65a9bf6..910e6eb7 100644
--- a/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py
+++ b/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py
@@ -30,8 +30,10 @@ class EgpuPipeline:
self.output_len = int(meta["output_len"])
def run(self, warped: np.ndarray, desire_vec: np.ndarray, traffic_convention: np.ndarray,
- action_t: np.ndarray) -> np.ndarray:
- if isinstance(self.infer_fn, PolicyRunner):
+ action_t: np.ndarray, extras: dict | None = None) -> np.ndarray:
+ if self.stateful:
+ out = np.asarray(self.infer_fn.run(warped, desire_vec, traffic_convention, action_t, extras), dtype=np.float32).reshape(-1)
+ elif isinstance(self.infer_fn, PolicyRunner):
out = np.asarray(self.infer_fn.run(warped, desire_vec, traffic_convention, action_t), dtype=np.float32).reshape(-1)
else:
inputs = self.state.push_and_materialize(warped, desire_vec, traffic_convention, action_t)
@@ -42,10 +44,11 @@ class EgpuPipeline:
return out
def run_frames(self, main_frame, extra_frame, tfm: np.ndarray, big_tfm: np.ndarray, desire_vec: np.ndarray,
- traffic_convention: np.ndarray, action_t: np.ndarray) -> np.ndarray:
+ traffic_convention: np.ndarray, action_t: np.ndarray, extras: dict | None = None) -> np.ndarray:
if not isinstance(self.infer_fn, ModelRunner):
raise EgpuPipelineError("run_frames needs a format-3 (warp-on-dock) artifact")
- out = np.asarray(self.infer_fn.run(main_frame, extra_frame, tfm, big_tfm, desire_vec, traffic_convention, action_t),
+ args = (main_frame, extra_frame, tfm, big_tfm, desire_vec, traffic_convention, action_t)
+ out = np.asarray(self.infer_fn.run(*args, extras) if self.stateful else self.infer_fn.run(*args),
dtype=np.float32).reshape(-1)
self._check(out)
return out
diff --git a/iqpilot/selfdrive/iqmodeld/egpu_policy.py b/iqpilot/selfdrive/iqmodeld/egpu_policy.py
index 4e83a924..6922bd54 100644
--- a/iqpilot/selfdrive/iqmodeld/egpu_policy.py
+++ b/iqpilot/selfdrive/iqmodeld/egpu_policy.py
@@ -19,6 +19,7 @@ STATEFUL_POLICY_FORMAT = 4
STATEFUL_MODEL_FORMAT = 5
STATEFUL_IMAGE_INPUT = "new_img"
STATEFUL_OUTPUT = "outputs"
+STATEFUL_BASE_FEEDS = ("desire", "traffic_convention", "action_t")
OOB_MAGIC = b"IQEGPUOOB1"
QUEUE_NAMES = ("img_q", "big_img_q", "feat_q", "desire_q")
PACKED_ORDER = ("desire", "traffic_convention", "action_t", "prev_feat")
@@ -339,6 +340,7 @@ def _state_kwargs(cur: dict, nxt: dict) -> dict:
def make_run_stateful(model_runner, input_spec: dict, pairs: dict[str, str], device: str):
from tinygrad.tensor import Tensor
shapes, sizes = stateful_packed_layout(input_spec, pairs)
+ dtypes = {name: input_spec[name][1] for name in shapes}
img_shape = tuple(input_spec[STATEFUL_IMAGE_INPUT][0])
def run_stateful(warped, packed_npy_inputs, **states):
@@ -346,7 +348,8 @@ def make_run_stateful(model_runner, input_spec: dict, pairs: dict[str, str], dev
warped = warped.to(device)
Tensor.realize(packed_npy_inputs, warped)
inputs = {STATEFUL_IMAGE_INPUT: warped.reshape(img_shape)}
- inputs.update((name, t.reshape(shape)) for name, t, shape in zip(shapes, packed_npy_inputs.split(sizes), shapes.values(), strict=True))
+ inputs.update((name, t.reshape(shape).cast(dtypes[name])) for name, t, shape in zip(shapes, packed_npy_inputs.split(sizes), shapes.values(),
+ strict=True))
inputs.update((name, states[name]) for name in pairs)
outs = model_runner(inputs)
# next state lands in the other buffer of the pair: writing the queue it is read from races inside the shift kernel
@@ -363,6 +366,8 @@ class _StatefulState:
self.pairs = pairs
self._cur, self._nxt = make_state_buffers(input_spec, pairs, device)
self._prev_desire = np.zeros(input_spec["desire"][0], dtype=np.float32) if "desire" in input_spec else None
+ self._extra_names = tuple(name for name in input_spec
+ if name not in pairs and name not in (STATEFUL_IMAGE_INPUT, *STATEFUL_BASE_FEEDS))
self.warm_frames = 0
def reset(self) -> None:
@@ -380,11 +385,14 @@ class _StatefulState:
self._prev_desire[:] = cur
return pulse
- def _fill(self, views: dict, desire_pulse, traffic_convention, action_t) -> None:
+ def _fill(self, views: dict, desire_pulse, traffic_convention, action_t, extras: dict | None = None) -> None:
if "desire" in views:
views["desire"][:] = self._pulse(desire_pulse).reshape(views["desire"].shape)
views["traffic_convention"][:] = np.asarray(traffic_convention, dtype=np.float32).reshape(views["traffic_convention"].shape)
views["action_t"][:] = np.asarray(action_t, dtype=np.float32).reshape(views["action_t"].shape)
+ for name in self._extra_names:
+ value = extras.get(name) if extras else None
+ views[name][:] = 0 if value is None else np.asarray(value, dtype=np.float32).reshape(views[name].shape)
def _step(self, jit, **kw) -> np.ndarray:
out, = jit(**kw, **_state_kwargs(self._cur, self._nxt))
@@ -403,8 +411,8 @@ class StatefulPolicyRunner(_StatefulState, PolicyRunner):
self._init_state(input_spec, pairs, device)
def run(self, warped: np.ndarray, desire_pulse: np.ndarray, traffic_convention: np.ndarray,
- action_t: np.ndarray) -> np.ndarray:
- self._fill(self._packed.views, desire_pulse, traffic_convention, action_t)
+ action_t: np.ndarray, extras: dict | None = None) -> np.ndarray:
+ self._fill(self._packed.views, desire_pulse, traffic_convention, action_t, extras)
warped_t = self._Tensor(np.ascontiguousarray(warped, dtype=np.uint8).reshape(self._warped_shape), device="NPY").realize()
return self._step(self._jit, warped=warped_t, packed_npy_inputs=self._packed.tensor)
@@ -461,7 +469,7 @@ class StatefulModelRunner(_StatefulState, ModelRunner):
self._init_state(input_spec, pairs, device)
def run(self, main_frame, extra_frame, tfm: np.ndarray, big_tfm: np.ndarray, desire_pulse: np.ndarray,
- traffic_convention: np.ndarray, action_t: np.ndarray) -> np.ndarray:
+ traffic_convention: np.ndarray, action_t: np.ndarray, extras: dict | None = None) -> np.ndarray:
n = self.frame_copy_size
v = self._packed.views
f = self._packed.frames
@@ -469,7 +477,7 @@ class StatefulModelRunner(_StatefulState, ModelRunner):
np.copyto(f["big_img"], np.frombuffer(extra_frame, dtype=np.uint8, count=n))
v["tfm"][:, :] = tfm
v["big_tfm"][:, :] = big_tfm
- self._fill(v, desire_pulse, traffic_convention, action_t)
+ self._fill(v, desire_pulse, traffic_convention, action_t, extras)
return self._step(self._jit, packed_npy_inputs=self._packed.tensor)
diff --git a/iqpilot/selfdrive/iqmodeld/egpu_prefetch.py b/iqpilot/selfdrive/iqmodeld/egpu_prefetch.py
index c4f5bae6..88d321bb 100644
--- a/iqpilot/selfdrive/iqmodeld/egpu_prefetch.py
+++ b/iqpilot/selfdrive/iqmodeld/egpu_prefetch.py
@@ -35,6 +35,7 @@ def _selected_meta(params: Params) -> dict | None:
def _drop_stale_partials(keep: str) -> None:
root = os.path.dirname(keep)
partial_globs = ("egpu_*_amd_policy.pkl.part", "egpu_*_amd_policy_oob.pkl.part", "egpu_*_amd_model_oob.pkl.part",
+ "egpu_*_amd_model_oob2.pkl.part",
"big_driving_supercombo_*.onnx.part")
for path in [p for g in partial_globs for p in glob.glob(os.path.join(root, g))]:
if not path.startswith(keep):
diff --git a/iqpilot/selfdrive/iqmodeld/egpu_telemetry.py b/iqpilot/selfdrive/iqmodeld/egpu_telemetry.py
index 13936312..aa6c9ba6 100644
--- a/iqpilot/selfdrive/iqmodeld/egpu_telemetry.py
+++ b/iqpilot/selfdrive/iqmodeld/egpu_telemetry.py
@@ -21,6 +21,7 @@ class EgpuDockTelemetry:
self.metrics: dict[str, float] = {}
self._power_limit: int | None = None
self._asm_usb = None
+ self._asic_logged = False
def _device(self):
from tinygrad.device import Device
@@ -61,6 +62,24 @@ class EgpuDockTelemetry:
self._power_limit = smu._send_msg(smu.smu_mod.PPSMC_MSG_GetPptLimit, 0, read_back_arg=True, timeout=100)
return self._power_limit
+ def _log_detail(self, metrics, smu) -> None:
+ def g(name):
+ return getattr(metrics, name, None)
+ try:
+ if not self._asic_logged:
+ cloudlog.warning(f"iqegpumodeld dock smu={getattr(smu.smu_mod, '__name__', '?')}")
+ self._asic_logged = True
+ throttle = g("ThrottlingPercentage")
+ cloudlog.event("egpu_dock_detail",
+ gfxclk_running=g("AverageGfxclkFrequencyPreDs"), gfxclk_idle=g("AverageGfxclkFrequencyPostDs"),
+ gfxclk_target=g("AverageGfxclkFrequencyTarget"), gfx_activity=g("AverageGfxActivity"),
+ uclk_activity=g("AverageUclkActivity"), pcie_busy=g("PCIeBusy"),
+ pcie_rate=g("PcieRate"), pcie_width=g("PcieWidth"),
+ throttle_max=(max(throttle) if throttle is not None else None),
+ board_w=g("AverageTotalBoardPower"))
+ except Exception:
+ pass
+
def send(self) -> None:
Device = self._device()
msg = messaging.new_message("egpuDockState")
@@ -79,6 +98,7 @@ class EgpuDockTelemetry:
"gpuUsagePercent": metrics.AverageGfxActivity,
"gpuClockMhz": metrics.AverageGfxclkFrequencyPostDs,
"fanSpeedRpm": metrics.AvgFanRpm}
+ self._log_detail(metrics, smu)
self.valid = True
except Exception:
if self.valid:
diff --git a/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py b/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py
index 2ace54a7..93de4329 100644
--- a/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py
+++ b/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py
@@ -29,7 +29,7 @@ from iqdbc.car.car_helpers import get_demo_car_params
from iqpilot.common.params import Params
from iqpilot.common.realtime import DT_MDL
from iqpilot.common.swaglog import cloudlog
-from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper
+from iqpilot.selfdrive.controls.lib.action_engine import ActionEngine
from iqpilot.system import sentry
from iqpilot.common.steer_delay import lateral_action_delay
@@ -39,11 +39,12 @@ from iqpilot.selfdrive.iqmodeld.driving_action import (
)
from iqpilot.selfdrive.iqmodeld.egpu_sched import MODEL_CORE, CpuBudgetGuard, enter_realtime, enter_setup
from iqpilot.selfdrive.iqmodeld.egpu_helpers import (
- die_with_parent, download_onnx, download_precompiled, egpu_model_oob_pkl_path, egpu_oob_pkl_path, egpu_pkl_path, egpu_policy_pkl_path,
- egpu_present_consented, egpu_selected, free_dock_lock, local_onnx,
+ die_with_parent, download_onnx, download_precompiled, drop_frozen_model_oob, egpu_model_oob_pkl_path, egpu_oob_pkl_path, egpu_pkl_path,
+ egpu_policy_pkl_path, egpu_present_consented, egpu_selected, free_dock_lock, local_onnx,
patch_tinygrad_fetch_fw, patch_usb_signal_wait, quarantine_artifact, resolve_backend, usbgpu_present,
)
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model
+from iqpilot.selfdrive.iqmodeld.aux_inputs import AuxModelInputs
from iqpilot.selfdrive.iqmodeld.egpu_pipeline import EgpuOutputInvalid, EgpuPipeline, EgpuPipelineError, make_big_channel_payload
from iqpilot.selfdrive.iqmodeld.egpu_telemetry import EgpuDockTelemetry
from iqpilot.selfdrive.iqmodeld.messaging import DrivePacketMemory, populate_drive_messages, populate_odometry_message
@@ -177,6 +178,7 @@ _model_precompiled_tried = False
def _ensure_artifact(params: Params, meta: dict, cam_size: tuple[int, int]) -> str:
global _precompiled_tried, _model_precompiled_tried
+ drop_frozen_model_oob(meta)
model_path = egpu_model_oob_pkl_path(meta)
if os.path.isfile(model_path):
return model_path
@@ -449,13 +451,14 @@ def main(demo: bool = False) -> None:
cloudlog.warning(f"iqegpumodeld model up (warmup {warm_s * 1e3:.0f}ms, {'warp on dock' if warp is None else 'warp on device'})")
pipeline = EgpuPipeline(meta, infer_fn)
+ aux_inputs = AuxModelInputs(input_spec)
warmup_frames = int(meta.get("warmup_frames") or STATEFUL_WARMUP_FRAMES)
telemetry_pm = messaging.PubMaster(["egpuDockState"])
telemetry = EgpuDockTelemetry(telemetry_pm, big=True)
telemetry_every = max(1, round((1.0 / DT_MDL) / SERVICE_LIST["egpuDockState"].frequency))
sub = SubMaster(["deviceState", "carState", "roadCameraState", "extrinsicsCalibration",
- "driverMonitoringState", "carControl", "lateralDelay", "iqNavState", "radarState"])
+ "driverMonitoringState", "carControl", "controlsState", "lateralDelay", "iqNavState", "radarState"])
if demo:
CP = get_demo_car_params()
else:
@@ -464,7 +467,7 @@ def main(demo: bool = False) -> None:
parser = PhaseParser()
memory = DrivePacketMemory()
- desire_logic = DesireHelper()
+ desire_logic = ActionEngine()
frame_meter = FrameDropMeter(20.0)
warps = CalibrationAtlas()
prev_action = log.ModelDataV2.Action()
@@ -535,18 +538,21 @@ def main(demo: bool = False) -> None:
long_action_t = long_delay + frame_delay + action_delay
action_t = np.array([lat_action_t, long_action_t], dtype=np.float32)
+ aux_inputs.poll()
+ aux_extras = aux_inputs.extras()
+
started_at = time.perf_counter()
t_warp = started_at
try:
if warp is None:
- output = pipeline.run_frames(main_buf.data, extra_buf.data, main_tfm, extra_tfm, desire_vec, traffic, action_t)
+ output = pipeline.run_frames(main_buf.data, extra_buf.data, main_tfm, extra_tfm, desire_vec, traffic, action_t, aux_extras)
else:
try:
warped = warp.run(main_buf, extra_buf, main_tfm, extra_tfm)
except Exception as e:
park(f"warp run failed: {e}")
t_warp = time.perf_counter()
- output = pipeline.run(warped, desire_vec, traffic, action_t)
+ output = pipeline.run(warped, desire_vec, traffic, action_t, aux_extras)
except EgpuOutputInvalid as e:
invalid_streak += 1
if invalid_streak == 1 or invalid_streak % MAX_INVALID_STREAK == 0:
@@ -574,7 +580,8 @@ def main(demo: bool = False) -> None:
outputs = parser.parse_vision_outputs(sliced)
action = get_action_from_model(outputs, prev_action, v_ego, float(lat_action_t), float(long_action_t),
- lat_smooth_seconds=meta.get("lat_smooth_seconds"))
+ lat_smooth_seconds=meta.get("lat_smooth_seconds"),
+ lat_active=sub["carControl"].latActive, current_curvature=sub["controlsState"].curvature)
prev_action = action
model_msg = messaging.new_message("modelV2")
diff --git a/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py b/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
index b8f4ccfc..7b75718a 100644
--- a/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
+++ b/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py
@@ -3,7 +3,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
"""
from __future__ import annotations
-import functools
import hashlib
import json
import os
@@ -24,26 +23,29 @@ RANGE_RETRIES = 4
PARALLEL_PARTS = max(1, int(os.environ.get("IQ_DOWNLOAD_PARTS", "3")))
-def _requests_auth():
+def _git_auth():
import importlib
for mod in ("iqpilot_private.models.git_auth", "iqpilot.models_private_src.git_auth",
"iqpilot.selfdrive.iqmodeld.models.git_auth"):
try:
- return importlib.import_module(mod).get_requests_auth()
+ return importlib.import_module(mod)
except Exception:
continue
return None
-def _hf():
- import importlib
- for mod in ("iqpilot_private.models.git_auth", "iqpilot.selfdrive.iqmodeld.models.git_auth"):
- try:
- m = importlib.import_module(mod)
- return m.get_hf_headers(), m.hf_resolve_url
- except Exception:
- continue
- return None, None
+def _resolve_hf(hf_path: str) -> tuple[str | None, dict]:
+ m = _git_auth()
+ if m is None:
+ return None, {}
+ return m.resolve_hf(hf_path)
+
+
+def requests_get(url: str, **kwargs):
+ m = _git_auth()
+ if m is None:
+ raise RuntimeError("no asset broker available")
+ return m.requests_get(url, **kwargs)
class RangesNotHonoured(Exception):
@@ -189,10 +191,9 @@ def _verify_file(tmp: str, sha256: str, size: int) -> None:
def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
- headers, resolve = _hf()
- if resolve is None:
+ url, headers = _resolve_hf(hf_path)
+ if url is None:
raise RuntimeError("no HF credentials available")
- url = resolve(hf_path)
os.makedirs(os.path.dirname(dst), exist_ok=True)
tmp = dst + ".hfpart"
last_error: Exception | None = None
@@ -213,8 +214,10 @@ def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb
os.remove(leftover)
except Exception as e:
last_error = e
- for _attempt in range(STREAM_RETRIES):
+ for attempt in range(STREAM_RETRIES):
try:
+ if attempt:
+ url, headers = _resolve_hf(hf_path)
have = os.path.getsize(tmp) if os.path.isfile(tmp) else 0
if size and have > size:
os.remove(tmp)
@@ -242,26 +245,6 @@ def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb
raise RuntimeError(f"HF download failed: {last_error}")
-def _lfs_endpoint(base_url: str) -> str:
- return base_url.split("/raw/", 1)[0] + ".git/info/lfs"
-
-
-def _resolve_oid(session, base_url: str, oid: str, size: int, auth):
- import requests
- batch = session.post(f"{_lfs_endpoint(base_url)}/objects/batch",
- data=json.dumps({"operation": "download", "transfers": ["basic"],
- "objects": [{"oid": oid, "size": size}]}),
- headers={"Content-Type": "application/vnd.git-lfs+json",
- "Accept": "application/vnd.git-lfs+json"},
- auth=auth, timeout=HTTP_TIMEOUT_S)
- batch.raise_for_status()
- entry = batch.json()["objects"][0]
- if "actions" not in entry:
- raise requests.RequestException(f"LFS object unavailable: {entry.get('error', oid)}")
- action = entry["actions"]["download"]
- return action["href"], action.get("header", {})
-
-
def _part_path(dst: str, oid: str) -> str:
return os.path.join(dst + ".parts", oid)
@@ -276,19 +259,28 @@ def _part_complete(path: str, oid: str, size: int) -> bool:
return digest.hexdigest() == oid
-def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) -> None:
+def _fetch_part(session, obj: dict, path: str, progress) -> None:
size = int(obj["size"])
have = os.path.getsize(path) if os.path.isfile(path) else 0
if have > size:
os.remove(path)
have = 0
- href, headers = _resolve_oid(session, base_url, obj["oid"], size, auth)
- obj_auth = None if headers.get("Authorization") else auth
+ m = _git_auth()
+ if m is None:
+ raise RuntimeError("no asset broker available for LFS parts")
+ target = m.broker_lfs_url(obj["oid"], size)
+ headers = m.broker_headers() if target else {}
+ if not (target and headers):
+ raise RuntimeError("no device identity for the konn3kt asset broker")
+ _stream_part(session, target, headers, path, have, progress)
+
+
+def _stream_part(session, href: str, headers: dict, path: str, have: int, progress) -> None:
# LFS parts are content-addressed (oid == sha256), so a half-written part can be resumed with a
# Range request and verified afterwards instead of being thrown away on every restart.
if have:
headers = {**headers, "Range": f"bytes={have}-"}
- with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S, auth=obj_auth) as r:
+ with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S) as r:
r.raise_for_status()
if have and r.status_code != 206:
have = 0
@@ -300,7 +292,6 @@ def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) ->
def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
- auth = _requests_auth()
os.makedirs(dst + ".parts", exist_ok=True)
total = int(size) or sum(int(o["size"]) for o in objects)
done_bytes = sum(int(o["size"]) for o in objects if _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"])))
@@ -318,30 +309,26 @@ def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progres
with lock:
progress(n)
- def fetch(base_url: str, obj: dict) -> None:
+ def fetch(obj: dict) -> None:
path = _part_path(dst, obj["oid"])
if _part_complete(path, obj["oid"], int(obj["size"])):
return
- _fetch_part(requests.Session(), base_url, obj, path, auth, locked_progress)
+ _fetch_part(requests.Session(), obj, path, locked_progress)
if not _part_complete(path, obj["oid"], int(obj["size"])):
if os.path.getsize(path) >= int(obj["size"]):
os.remove(path)
raise RuntimeError(f"part {obj['oid'][:12]} incomplete or failed verification")
- for base_url in MODELS_BASE_URLS:
- for _attempt in range(STREAM_RETRIES):
- try:
- pending = [o for o in objects if not _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))]
- got[0] = total - sum(int(o["size"]) for o in pending)
- with ThreadPoolExecutor(max_workers=max(1, min(PARALLEL_PARTS, len(pending) or 1))) as pool:
- for _ in pool.map(functools.partial(fetch, base_url), pending):
- pass
- break
- except Exception as e:
- last_error = e
- else:
- continue
- break
+ for _attempt in range(STREAM_RETRIES):
+ try:
+ pending = [o for o in objects if not _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))]
+ got[0] = total - sum(int(o["size"]) for o in pending)
+ with ThreadPoolExecutor(max_workers=max(1, min(PARALLEL_PARTS, len(pending) or 1))) as pool:
+ for _ in pool.map(fetch, pending):
+ pass
+ break
+ except Exception as e:
+ last_error = e
else:
raise RuntimeError(f"model bundle download failed: {last_error}")
diff --git a/iqpilot/selfdrive/iqmodeld/model_cache.py b/iqpilot/selfdrive/iqmodeld/model_cache.py
index 5c104245..0b1cba59 100644
--- a/iqpilot/selfdrive/iqmodeld/model_cache.py
+++ b/iqpilot/selfdrive/iqmodeld/model_cache.py
@@ -4,7 +4,7 @@ from pathlib import Path
_RUNTIME_ARTIFACT_PATTERNS = (
- re.compile(r"egpu_.+_amd_(?:tinygrad|policy|policy_oob|model_oob)\.pkl$"),
+ re.compile(r"egpu_.+_amd_(?:tinygrad|policy|policy_oob|model_oob2)\.pkl$"),
re.compile(r"emac_warp_\d+x\d+_tinygrad\.pkl$"),
re.compile(r".+_usbemac\.pkl$"),
re.compile(r".+\.onnx$"),
diff --git a/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py b/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py
index bf17800a..ea1dff01 100644
--- a/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py
+++ b/iqpilot/selfdrive/iqmodeld/models/combined_artifact.py
@@ -17,7 +17,8 @@ _OVERRIDE_KEYS = (
"combinedSplitArtifact",
"iqCombinedArtifact",
)
-_SPLIT_ROLE_PATTERN = re.compile(r"^driving_(vision|policy|off_policy|on_policy)_(.+)_tinygrad\.pkl$")
+_SPLIT_ROLE_PATTERN = re.compile(r"^driving_(vision|policy|off_policy|on_policy)_(.+)_tinygrad\.pkl(?:\.iqc)?$")
+_IQC_SUFFIX = ".iqc"
def _bundle_models(bundle) -> list:
@@ -91,8 +92,9 @@ def combined_split_artifact_candidates(bundle) -> list[Path]:
def resolve_combined_split_artifact(bundle) -> Path | None:
for candidate in combined_split_artifact_candidates(bundle):
- if candidate.is_file():
- return candidate
+ for path in (candidate, candidate.with_name(candidate.name + _IQC_SUFFIX)):
+ if path.is_file():
+ return path
return None
diff --git a/iqpilot/selfdrive/iqmodeld/models/helpers.py b/iqpilot/selfdrive/iqmodeld/models/helpers.py
index 05cce298..ee2eb77f 100644
--- a/iqpilot/selfdrive/iqmodeld/models/helpers.py
+++ b/iqpilot/selfdrive/iqmodeld/models/helpers.py
@@ -305,7 +305,40 @@ def get_runtime_bundle_upgrade(bundle, params: Params = None, available_bundles=
return _find_runtime_upgrade(bundle, params, available_bundles)
+_forced_bundle = None
+
+
+def load_default_model_session():
+ """Stage the shipped default model files and return a decoded default bundle
+ WITHOUT persisting it as the active selection, so the next boot retries the
+ user's own model instead of staying on the default forever."""
+ bundle_dict = _load_default_bundle_dict()
+ ensure_default_model_files(bundle_dict)
+ return ModelBundle(**bundle_dict)
+
+
+class forced_active_bundle:
+ """Make get_active_bundle return `bundle` for the duration of the block, used to
+ build a runner for a session-only fallback bundle without touching Params."""
+ def __init__(self, bundle):
+ self._bundle = bundle
+
+ def __enter__(self):
+ global _forced_bundle
+ self._prev = _forced_bundle
+ _forced_bundle = self._bundle
+ return self._bundle
+
+ def __exit__(self, *exc):
+ global _forced_bundle
+ _forced_bundle = self._prev
+ return False
+
+
def get_active_bundle(params: Params = None):
+ if _forced_bundle is not None:
+ return _forced_bundle
+
params = Params() if params is None else params
try:
diff --git a/iqpilot/selfdrive/iqmodeld/models/iqc.py b/iqpilot/selfdrive/iqmodeld/models/iqc.py
new file mode 100644
index 00000000..da84740e
--- /dev/null
+++ b/iqpilot/selfdrive/iqmodeld/models/iqc.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
+
+from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
+
+try:
+ load_private_module(__name__, "iqpilot_private.models.iqc")
+except ProprietaryModuleMissing:
+ from iqpilot.models_private_src.iqc import * # noqa: F403
diff --git a/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py b/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py
index 8ede5f6c..48d990f0 100644
--- a/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py
+++ b/iqpilot/selfdrive/iqmodeld/models/runners/model_runner.py
@@ -2,7 +2,6 @@
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import os
-import pickle as _pk
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
@@ -13,6 +12,7 @@ from iqpilot.system.hardware import TICI
from iqpilot.system.hardware.hw import Paths as _hw_paths
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle as _fetch_bundle
from iqpilot.selfdrive.iqmodeld.models.combined_artifact import has_combined_split_artifact
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
# ---- runtime type surface (native OpenCL/frame handles resolve to Any off-device) ----
if TYPE_CHECKING:
@@ -73,8 +73,7 @@ def load_artifact_metadata(metadata_filename):
if os.path.getsize(path) > _META_MAX_BYTES:
cloudlog.error(f"metadata pkl {metadata_filename} is artifact-sized, refusing to unpickle it")
return tuple({} for _ in _META_FIELDS)
- with open(path, 'rb') as fh:
- blob = _pk.load(fh)
+ blob = load_pickle(path)
return tuple(blob.get(field, {}) for field in _META_FIELDS)
except Exception:
cloudlog.exception(f"unreadable metadata pkl {metadata_filename}, continuing without it")
diff --git a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py
index 9acb0ad3..37f1233f 100644
--- a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py
+++ b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/combined_split_runner.py
@@ -4,7 +4,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
-import pickle
from typing import Any
import numpy as np
@@ -15,6 +14,7 @@ from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import NumpyDict, Sh
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
def _tinygrad_imports():
@@ -64,8 +64,7 @@ class TinygradCombinedSplitRunner(ModelRunner):
if self._artifact_path is None:
raise FileNotFoundError("No IQ combined split artifact is available for the active bundle")
- with open(self._artifact_path, "rb") as artifact:
- runtime_package: dict[Any, Any] = pickle.load(artifact)
+ runtime_package: dict[Any, Any] = load_pickle(self._artifact_path)
self._meta_by_role = runtime_package.get("meta_by_role", runtime_package.get("metadata", {}))
self._policy_roles = runtime_package.get("roles", _phase_roles(self._meta_by_role))
diff --git a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py
index 33beea42..b8783dc8 100644
--- a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py
+++ b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/fused_runner.py
@@ -4,7 +4,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
import os
-import pickle
from typing import Any
import numpy as np
@@ -15,6 +14,7 @@ from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
def _tinygrad_imports():
@@ -40,8 +40,7 @@ class TinygradFusedRunner(ModelRunner):
self._model_data = next(iter(self.models.values()))
pkl_path = os.path.join(CUSTOM_MODEL_PATH, self._model_data.model.artifact.fileName)
- with open(pkl_path, 'rb') as f:
- self._fused: dict[Any, Any] = pickle.load(f)
+ self._fused: dict[Any, Any] = load_pickle(pkl_path)
self._vision_meta = self._fused['metadata']['vision']
self._on_meta = self._fused['metadata']['on_policy']
diff --git a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py
index ea35728a..e457a5f8 100644
--- a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py
+++ b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/supercombo_runner.py
@@ -5,7 +5,6 @@ from __future__ import annotations
import hashlib
import os
-import pickle
import re
from typing import Any
@@ -17,6 +16,7 @@ from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runtime import TinygradSupercomboRuntime
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
def _captured_queue_depth(warp_jit: Any) -> int | None:
@@ -77,8 +77,7 @@ class TinygradSupercomboRunner(TinygradSupercomboRuntime, ModelRunner):
self._pkl_path = pkl_path
self._expected_sha256 = getattr(getattr(self._model_data.model.artifact, "downloadUri", None), "sha256", "") or ""
self._verify_artifact_file()
- with open(pkl_path, 'rb') as f:
- self._m: dict[Any, Any] = pickle.load(f)
+ self._m: dict[Any, Any] = load_pickle(pkl_path)
self._meta = self._m['metadata']
self._ish = self._meta['input_shapes']
diff --git a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py
index 2d91c3df..7bde2165 100644
--- a/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py
+++ b/iqpilot/selfdrive/iqmodeld/models/runners/tinygrad/tinygrad_runner.py
@@ -4,7 +4,6 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
-import pickle
from dataclasses import dataclass
import numpy as np
@@ -32,6 +31,7 @@ from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelCo
from iqpilot.selfdrive.iqmodeld.config import ModelConstants
from iqpilot.selfdrive.iqmodeld.runtime.tinygrad import qcom_tensor_from_opencl_address
from iqpilot.system.hardware import TICI
+from iqpilot.selfdrive.iqmodeld.models.iqc import load_pickle
@dataclass(frozen=True)
@@ -45,12 +45,11 @@ def _artifact_path(filename: str) -> str:
def _load_program_blob(filename: str):
- with open(_artifact_path(filename), "rb") as artifact:
- try:
- return pickle.load(artifact)
- except FileNotFoundError as exc:
- assert "/dev/kgsl-3d0" not in str(exc), "Model was built on C3 or C3X, but is being loaded on PC"
- raise
+ try:
+ return load_pickle(_artifact_path(filename))
+ except FileNotFoundError as exc:
+ assert "/dev/kgsl-3d0" not in str(exc), "Model was built on C3 or C3X, but is being loaded on PC"
+ raise
def _compile_input_plan(captured) -> dict[str, _TensorShapePlan]:
@@ -81,7 +80,7 @@ class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTiny
raise ValueError(f"Model data for type {model_type} not available.")
asset_name = self._model_data.model.artifact.fileName
- assert asset_name.endswith("_tinygrad.pkl"), f"Invalid model file {asset_name} for TinygradRunner"
+ assert asset_name.removesuffix(".iqc").endswith("_tinygrad.pkl"), f"Invalid model file {asset_name} for TinygradRunner"
self.model_run = _load_program_blob(asset_name)
self._input_plan = _compile_input_plan(self.model_run.captured)
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_action_provenance.py b/iqpilot/selfdrive/iqmodeld/tests/test_action_provenance.py
index 55f63da3..2691b894 100644
--- a/iqpilot/selfdrive/iqmodeld/tests/test_action_provenance.py
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_action_provenance.py
@@ -108,3 +108,53 @@ def test_previous_action_permission_is_not_inherited_by_next_frame():
assert action.desiredCurvature == previous.desiredCurvature
assert action.lateralActionSource == "nativeAction"
assert action.legacyLookahead.contract == "unsupported"
+
+
+@pytest.mark.parametrize("producer", ["local", "offload"])
+@pytest.mark.parametrize("kind", ["nativeAction", "nativeCurvature", "plan", "planplus", "synthetic"])
+@pytest.mark.parametrize("speed", [0.0, 0.2, 20.0])
+@pytest.mark.parametrize("lat_active", [False, True])
+def test_curvature_seed_tracks_vehicle_only_while_lateral_is_inactive(producer, kind, speed, lat_active):
+ previous = log.ModelDataV2.Action(desiredCurvature=0.004, desiredAcceleration=0.1)
+ expected_previous = log.ModelDataV2.Action(
+ desiredCurvature=0.004 if lat_active else -0.012, desiredAcceleration=0.1,
+ )
+ outputs = outputs_for(kind)
+ if producer == "local":
+ state = runtime()
+ expected = NeuralEngineState.get_action_from_model(state, outputs, expected_previous, 0.45, 0.4, speed)
+ action = NeuralEngineState.get_action_from_model(
+ state, outputs, previous, 0.45, 0.4, speed, lat_active=lat_active, current_curvature=-0.012,
+ )
+ else:
+ expected = get_action_from_model(outputs, expected_previous, speed, 0.45, 0.4, 0.1)
+ action = get_action_from_model(
+ outputs, previous, speed, 0.45, 0.4, 0.1, lat_active=lat_active, current_curvature=-0.012,
+ )
+ for field in ("desiredCurvature", "desiredAcceleration", "shouldStop", "lateralActionSource"):
+ assert getattr(action, field) == getattr(expected, field)
+ if speed <= 0.3:
+ assert action.desiredCurvature == expected_previous.desiredCurvature
+
+
+@pytest.mark.parametrize("producer", ["local", "offload"])
+def test_engagement_keeps_curvature_history_from_last_inactive_frame(producer):
+ previous = log.ModelDataV2.Action(desiredCurvature=0.03, desiredAcceleration=0.1)
+ outputs = outputs_for("nativeAction")
+ if producer == "local":
+ state = runtime()
+ inactive = NeuralEngineState.get_action_from_model(
+ state, outputs, previous, 0.45, 0.4, 0.2, lat_active=False, current_curvature=-0.012,
+ )
+ active = NeuralEngineState.get_action_from_model(
+ state, outputs, inactive, 0.45, 0.4, 20.0, lat_active=True, current_curvature=0.02,
+ )
+ else:
+ inactive = get_action_from_model(
+ outputs, previous, 0.2, 0.45, 0.4, 0.1, lat_active=False, current_curvature=-0.012,
+ )
+ active = get_action_from_model(
+ outputs, inactive, 20.0, 0.45, 0.4, 0.1, lat_active=True, current_curvature=0.02,
+ )
+ assert inactive.desiredCurvature == pytest.approx(-0.012)
+ assert active.desiredCurvature == pytest.approx(smooth_value(-0.8 / 20.0 ** 2, -0.012, 0.1))
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_combined_split_runner.py b/iqpilot/selfdrive/iqmodeld/tests/test_combined_split_runner.py
index a45af332..dc05bc08 100644
--- a/iqpilot/selfdrive/iqmodeld/tests/test_combined_split_runner.py
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_combined_split_runner.py
@@ -86,6 +86,28 @@ def test_resolve_combined_split_artifact_prefers_override(tmp_path: Path, monkey
assert resolve_combined_split_artifact(bundle) == expected
+def test_resolve_combined_split_artifact_finds_encrypted_copy_after_plaintext_removed(tmp_path: Path, monkeypatch):
+ bundle = _Bundle([_Model(ModelType.policy, "driving_combined_83b81b83.pkl.iqc")])
+ bundle.ref = "83b81b83cdf76a577946a1567f4644e1a018443c"
+ expected = tmp_path / "driving_combined_83b81b83.pkl.iqc"
+ expected.write_bytes(b"IQC1")
+
+ monkeypatch.setattr("iqpilot.selfdrive.iqmodeld.models.combined_artifact._MODEL_ROOT", tmp_path)
+
+ assert resolve_combined_split_artifact(bundle) == expected
+
+
+def test_resolve_combined_split_artifact_prefers_plaintext_when_both_exist(tmp_path: Path, monkeypatch):
+ bundle = _Bundle([_Model(ModelType.vision, "driving_vision_demo_tinygrad.pkl.iqc")])
+ plaintext = tmp_path / "driving_combined_demo.pkl"
+ plaintext.write_bytes(b"iq")
+ (tmp_path / "driving_combined_demo.pkl.iqc").write_bytes(b"IQC1")
+
+ monkeypatch.setattr("iqpilot.selfdrive.iqmodeld.models.combined_artifact._MODEL_ROOT", tmp_path)
+
+ assert resolve_combined_split_artifact(bundle) == plaintext
+
+
def test_get_model_runner_prefers_combined_split_artifact(monkeypatch):
bundle = _Bundle([
_Model(ModelType.vision, "driving_vision_demo_tinygrad.pkl"),
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_default_model_fallback.py b/iqpilot/selfdrive/iqmodeld/tests/test_default_model_fallback.py
new file mode 100644
index 00000000..c286df3b
--- /dev/null
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_default_model_fallback.py
@@ -0,0 +1,59 @@
+"""
+Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
+"""
+from iqpilot.selfdrive.iqmodeld.models import helpers
+from iqpilot.selfdrive.iqmodeld.models.helpers import (
+ forced_active_bundle,
+ get_active_bundle,
+ load_default_model_session,
+ is_default_bundle,
+)
+
+
+class _FakeParams:
+ def __init__(self, store):
+ self.store = store
+ def get(self, key):
+ return self.store.get(key)
+ def put(self, key, value):
+ self.store["_puts"] = self.store.get("_puts", 0) + 1
+ self.store[key] = value
+ def remove(self, key):
+ self.store.pop(key, None)
+
+
+def test_forced_bundle_overrides_get_active_bundle_only_inside_the_block(monkeypatch):
+ monkeypatch.setattr(helpers, "_forced_bundle", None)
+ default_sentinel = object()
+ with forced_active_bundle(default_sentinel):
+ assert get_active_bundle() is default_sentinel
+ assert helpers._forced_bundle is None
+
+
+def test_forced_bundle_does_not_persist_anything_to_params(monkeypatch):
+ store = {}
+ monkeypatch.setattr(helpers, "_forced_bundle", None)
+ monkeypatch.setattr(helpers, "Params", lambda *a, **k: _FakeParams(store))
+ default_sentinel = object()
+ with forced_active_bundle(default_sentinel):
+ assert get_active_bundle() is default_sentinel
+ assert store.get("_puts", 0) == 0
+ assert "ModelManager_ActiveBundle" not in store
+
+
+def test_forced_bundle_restores_the_previous_override(monkeypatch):
+ monkeypatch.setattr(helpers, "_forced_bundle", None)
+ outer, inner = object(), object()
+ with forced_active_bundle(outer):
+ with forced_active_bundle(inner):
+ assert get_active_bundle() is inner
+ assert get_active_bundle() is outer
+ assert helpers._forced_bundle is None
+
+
+def test_load_default_model_session_returns_default_bundle_and_stages_files(monkeypatch):
+ staged = {"n": 0}
+ monkeypatch.setattr(helpers, "ensure_default_model_files", lambda *a, **k: staged.__setitem__("n", staged["n"] + 1))
+ bundle = load_default_model_session()
+ assert staged["n"] == 1
+ assert is_default_bundle(bundle)
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_artifact_choice.py b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_artifact_choice.py
index 3db1a3d5..1515e9dc 100644
--- a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_artifact_choice.py
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_artifact_choice.py
@@ -56,3 +56,19 @@ def test_prefetch_downloads_the_warp_on_dock_build_when_offered(egpu_prefetch, m
monkeypatch.setattr(egpu_prefetch, "download_precompiled", lambda m, progress_cb=None, field=None: asked.append(field) or "ok")
assert egpu_prefetch.prefetch_once(P()) is True
assert asked == ["egpu_model_oob_artifact"]
+
+
+def test_frozen_feed_warp_on_dock_builds_are_never_loaded(monkeypatch, tmp_path):
+ from iqpilot.selfdrive.iqmodeld import egpu_helpers
+ from iqpilot.selfdrive.iqmodeld.model_cache import is_runtime_artifact
+ from iqpilot.system.hardware.hw import Paths
+ monkeypatch.setattr(Paths, "model_root", staticmethod(lambda: str(tmp_path)))
+ meta = {"key": "big_nav", "sha256": "8a5bc46c" + "0" * 56}
+ stale = tmp_path / "egpu_big_nav_8a5bc46c_amd_model_oob.pkl"
+ stale.write_bytes(b"frozen")
+ live = egpu_helpers.egpu_model_oob_pkl_path(meta)
+ assert live != str(stale) and live.endswith("_amd_model_oob2.pkl")
+ egpu_helpers.drop_frozen_model_oob(meta)
+ egpu_helpers.drop_frozen_model_oob(meta)
+ assert not stale.exists()
+ assert is_runtime_artifact("egpu_big_nav_8a5bc46c_amd_model_oob2.pkl") and not is_runtime_artifact(stale.name)
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_stateful.py b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_stateful.py
index c9e88e1b..8e0979ef 100644
--- a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_stateful.py
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_stateful.py
@@ -152,3 +152,28 @@ def test_pipeline_reports_warmth_and_resets():
assert pipeline.warm_frames == 4
pipeline.reset_state()
assert pipeline.warm_frames == 0
+
+
+def test_warp_on_dock_artifact_reads_every_frame_feed():
+ from tinygrad.engine.jit import TinyJit
+ from tinygrad.tensor import Tensor
+ from iqpilot.selfdrive.iqmodeld.egpu_helpers import patch_tinygrad_prune_views
+ from iqpilot.selfdrive.iqmodeld.egpu_policy import StatefulModelRunner, make_run_stateful_model, make_warp, nv12_copy_size, stateful_model_size
+ patch_tinygrad_prune_views()
+ pairs = state_pairs(SPEC)
+ nv12 = (64, 48, 64, 48, 24)
+ fcs = nv12_copy_size(*nv12[2:])
+ w, h = stateful_model_size(SPEC)
+ def model(inp):
+ feeds = {name: (inp[name] @ Tensor.eye(2)).contiguous() for name in ("traffic_convention", "action_t")}
+ return _tg_model({**inp, **feeds})
+
+ run = make_run_stateful(model, SPEC, pairs, "CPU")
+ jit = TinyJit(make_run_stateful_model(make_warp(nv12, w, h, "CPU"), run, SPEC, pairs, fcs, "CPU"), prune=True)
+ runner = StatefulModelRunner(jit, SPEC, pairs, "CPU", fcs)
+ frame, eye = np.zeros(fcs, dtype=np.uint8), np.eye(3, dtype=np.float32)
+ for i in range(6):
+ traffic = np.array([i % 2, 1 - i % 2], dtype=np.float32)
+ action_t = np.array([0.1 * i, -0.05 * i], dtype=np.float32)
+ out = runner.run(frame, frame, eye, eye, np.zeros(8, dtype=np.float32), traffic, action_t)
+ np.testing.assert_allclose(out[32:36], np.concatenate([traffic, action_t]), rtol=1e-6, atol=1e-7, err_msg=f"frame {i}")
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_model_bundle_downloader.py b/iqpilot/selfdrive/iqmodeld/tests/test_model_bundle_downloader.py
index ad9675fb..647a5a82 100644
--- a/iqpilot/selfdrive/iqmodeld/tests/test_model_bundle_downloader.py
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_model_bundle_downloader.py
@@ -2,6 +2,7 @@
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import hashlib
+from types import SimpleNamespace
import http.server
import os
import threading
@@ -54,6 +55,11 @@ def server():
srv.server_close()
+def _fake_broker(port):
+ return SimpleNamespace(broker_lfs_url=lambda oid, size: f"http://127.0.0.1:{port}/o/{oid}",
+ broker_headers=lambda: {"Authorization": "JWT test"})
+
+
def _objects(parts):
return [{"oid": hashlib.sha256(p).hexdigest(), "size": len(p)} for p in parts]
@@ -65,9 +71,7 @@ def test_resume_continues_a_cut_part_and_reuses_finished_parts(server, tmp_path,
_RangeHandler.hits = []
_RangeHandler.cut_first = {objs[1]["oid"]: 100_000}
port = server.server_address[1]
- monkeypatch.setattr(dl, "_requests_auth", lambda: None)
- monkeypatch.setattr(dl, "_resolve_oid", lambda session, base, oid, size, auth: (f"http://127.0.0.1:{port}/o/{oid}", {}))
- monkeypatch.setattr(dl, "MODELS_BASE_URLS", ("http://unused",))
+ monkeypatch.setattr(dl, "_git_auth", lambda: _fake_broker(port))
monkeypatch.setattr(dl, "STREAM_RETRIES", 3)
monkeypatch.setattr(dl, "CHUNK", 64 * 1024)
whole = b"".join(parts)
@@ -88,9 +92,7 @@ def test_corrupt_finished_part_is_refetched(server, tmp_path, monkeypatch):
_RangeHandler.hits = []
_RangeHandler.cut_first = {}
port = server.server_address[1]
- monkeypatch.setattr(dl, "_requests_auth", lambda: None)
- monkeypatch.setattr(dl, "_resolve_oid", lambda session, base, oid, size, auth: (f"http://127.0.0.1:{port}/o/{oid}", {}))
- monkeypatch.setattr(dl, "MODELS_BASE_URLS", ("http://unused",))
+ monkeypatch.setattr(dl, "_git_auth", lambda: _fake_broker(port))
dst = str(tmp_path / "model.pkl")
os.makedirs(dst + ".parts")
with open(dl._part_path(dst, objs[0]["oid"]), "wb") as f:
@@ -108,7 +110,7 @@ def test_hf_single_file_resumes_after_cut(server, tmp_path, monkeypatch):
_RangeHandler.hits = []
_RangeHandler.cut_first = {oid: 250_000}
port = server.server_address[1]
- monkeypatch.setattr(dl, "_hf", lambda: ({"Authorization": "Bearer test"}, lambda p: f"http://127.0.0.1:{port}/o/{oid}"))
+ monkeypatch.setattr(dl, "_resolve_hf", lambda p: (f"http://127.0.0.1:{port}/o/{oid}", {"Authorization": "Bearer test"}))
monkeypatch.setattr(dl, "STREAM_RETRIES", 3)
monkeypatch.setattr(dl, "CHUNK", 64 * 1024)
dst = str(tmp_path / "policy.pkl")
@@ -141,7 +143,7 @@ def test_download_onnx_prefers_hf_then_falls_back(tmp_path, monkeypatch):
def _parallel(monkeypatch, port, oid):
- monkeypatch.setattr(dl, "_hf", lambda: ({"Authorization": "Bearer test"}, lambda p: f"http://127.0.0.1:{port}/o/{oid}"))
+ monkeypatch.setattr(dl, "_resolve_hf", lambda p: (f"http://127.0.0.1:{port}/o/{oid}", {"Authorization": "Bearer test"}))
monkeypatch.setattr(dl, "STREAM_RETRIES", 2)
monkeypatch.setattr(dl, "CHUNK", 64 * 1024)
monkeypatch.setattr(dl, "PARALLEL_MIN_BYTES", 100_000)
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_private_shims.py b/iqpilot/selfdrive/iqmodeld/tests/test_private_shims.py
index 80e1c958..0e48eea6 100644
--- a/iqpilot/selfdrive/iqmodeld/tests/test_private_shims.py
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_private_shims.py
@@ -12,6 +12,7 @@ from iqpilot import _proprietary_loader
ROOT = Path(__file__).resolve().parents[4]
SHIMS = {
+ "iqpilot.selfdrive.controls.lib.action_engine": "iqpilot_private.navd.action_engine",
"iqpilot.selfdrive.iqmodeld.cot_decode": "iqpilot_private.context.cot_decode",
"iqpilot.selfdrive.iqmodeld.drive_profile": "iqpilot_private.context.drive_profile",
"iqpilot.selfdrive.iqmodeld.speed_limit_input": "iqpilot_private.context.speed_limit_input",
diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_selector_version_gate.py b/iqpilot/selfdrive/iqmodeld/tests/test_selector_version_gate.py
index 73a9fe23..d38602f6 100644
--- a/iqpilot/selfdrive/iqmodeld/tests/test_selector_version_gate.py
+++ b/iqpilot/selfdrive/iqmodeld/tests/test_selector_version_gate.py
@@ -29,3 +29,13 @@ def test_gate_still_accepts_older_bundles():
def test_gate_rejects_a_bundle_from_the_future():
assert not is_bundle_version_compatible({"minimumSelectorVersion": EMAC_BUNDLE_MIN_SELECTOR_VERSION + 5})
+
+
+def test_gate_accepts_encrypted_iqc_bundles():
+ assert is_bundle_version_compatible({"minimumSelectorVersion": 20})
+
+
+def test_bundles_kept_only_for_older_builds_are_hidden():
+ assert not is_bundle_version_compatible({"minimumSelectorVersion": 18, "maximum_selector_version": "19"})
+ assert not is_bundle_version_compatible({"minimumSelectorVersion": 18, "maximumSelectorVersion": 19})
+ assert is_bundle_version_compatible({"minimumSelectorVersion": 18, "maximum_selector_version": 20})
diff --git a/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py b/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py
index dffd4636..48d24adb 100644
--- a/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py
+++ b/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py
@@ -26,13 +26,14 @@ os.environ.setdefault("DEV", "USB+AMD:LLVM")
import numpy as np
-from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_pkl_path, local_onnx, patch_tinygrad_fetch_fw
+from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_pkl_path, local_onnx, patch_tinygrad_fetch_fw, patch_tinygrad_prune_views
from iqpilot.selfdrive.iqmodeld.egpu_model import EGPU_MODELS, get_egpu_model, resolve_egpu_model
from iqpilot.selfdrive.iqmodeld.temporal_state import MODEL_INPUT_SPEC, spec_from_meta
INPUT_SPEC = dict(MODEL_INPUT_SPEC)
patch_tinygrad_fetch_fw()
+patch_tinygrad_prune_views()
SEED = 42
diff --git a/iqpilot/selfdrive/longitudinal_settings.py b/iqpilot/selfdrive/longitudinal_settings.py
index c42d346b..5247e33e 100644
--- a/iqpilot/selfdrive/longitudinal_settings.py
+++ b/iqpilot/selfdrive/longitudinal_settings.py
@@ -48,7 +48,6 @@ def apply_longitudinal_mode(params, mode: int) -> None:
params.put_bool("AlphaLongitudinalEnabled", True)
params.put_bool("ExperimentalMode", False)
params.put_bool("IQDynamicMode", False)
- set_valid_personality(params, PERSONALITY_RELAXED)
elif mode == LONGITUDINAL_MODE_DYNAMIC:
params.put_bool("AlphaLongitudinalEnabled", True)
params.put_bool("ExperimentalMode", True)
@@ -76,20 +75,11 @@ def next_longitudinal_mode(current: int, onroad: bool, iq_modes_available: bool)
def get_follow_distance_state(params) -> tuple[int | None, bool]:
- mode = get_longitudinal_mode(params)
- if mode == LONGITUDINAL_MODE_STOCK:
+ if get_longitudinal_mode(params) == LONGITUDINAL_MODE_STOCK:
get_valid_personality(params)
return None, False
- if mode == LONGITUDINAL_MODE_CHILL:
- if get_valid_personality(params) != PERSONALITY_RELAXED:
- set_valid_personality(params, PERSONALITY_RELAXED)
- return PERSONALITY_RELAXED, False
return get_valid_personality(params), True
def get_runtime_personality(params) -> int:
- if get_longitudinal_mode(params) == LONGITUDINAL_MODE_CHILL:
- if get_valid_personality(params) != PERSONALITY_RELAXED:
- set_valid_personality(params, PERSONALITY_RELAXED)
- return PERSONALITY_RELAXED
return get_valid_personality(params)
diff --git a/iqpilot/selfdrive/pandad/SConscript b/iqpilot/selfdrive/pandad/SConscript
index f7159b6e..b3516a8a 100644
--- a/iqpilot/selfdrive/pandad/SConscript
+++ b/iqpilot/selfdrive/pandad/SConscript
@@ -14,3 +14,4 @@ if GetOption('extras'):
env.Program('tests/test_pandad_usbprotocol', ['tests/test_pandad_usbprotocol.cc'], LIBS=[panda] + libs)
env.Program('tests/test_panda_safety', ['tests/test_panda_safety.cc'] + safety, LIBS=libs)
+ env.Program('tests/test_ignition_hold', ['tests/test_ignition_hold.cc'], LIBS=libs)
diff --git a/iqpilot/selfdrive/pandad/panda_comms.h b/iqpilot/selfdrive/pandad/panda_comms.h
index 9c452faf..60ac90a2 100644
--- a/iqpilot/selfdrive/pandad/panda_comms.h
+++ b/iqpilot/selfdrive/pandad/panda_comms.h
@@ -14,7 +14,7 @@
#define TIMEOUT 0
-#define SPI_BUF_SIZE 2048
+#define SPI_BUF_SIZE 4096
// comms base class
@@ -57,10 +57,16 @@ private:
#ifndef __APPLE__
struct __attribute__((packed)) spi_header {
- uint8_t sync;
uint8_t endpoint;
+ uint32_t id;
uint16_t tx_len;
- uint16_t max_rx_len;
+ uint16_t rx_len;
+};
+
+struct __attribute__((packed)) spi_resp_header {
+ uint8_t status;
+ uint32_t id;
+ uint16_t len;
};
class PandaSpiHandle : public PandaCommsHandle {
@@ -81,9 +87,7 @@ private:
uint8_t rx_buf[SPI_BUF_SIZE];
inline static std::recursive_mutex hw_lock;
- int wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, unsigned int length);
- int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len, unsigned int timeout);
- int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout);
+ int spi_transfer(uint8_t endpoint, uint32_t id, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, double deadline);
int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout);
int lltransfer(spi_ioc_transfer &t);
diff --git a/iqpilot/selfdrive/pandad/pandad.cc b/iqpilot/selfdrive/pandad/pandad.cc
index 9b8d4a77..f7a93fc7 100644
--- a/iqpilot/selfdrive/pandad/pandad.cc
+++ b/iqpilot/selfdrive/pandad/pandad.cc
@@ -5,6 +5,7 @@
#include
#include
#include
+#include