1
0
forked from IQ.Lvbs/IQ.Pilot

IQ.Pilot Prebuilt Release @ ab07000

This commit is contained in:
IQ.Lvbs history cleanup
2026-08-22 23:42:42 -05:00
commit 9f9c9a70cc
3729 changed files with 778697 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env python3
"""
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View File

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
canlived — live CAN bridge to konn3kt.
Streams the device's live CAN bus to the konn3kt server so it can be viewed remotely
in Cabana (via a local ZMQ proxy on the laptop). This is the remote analogue of running
`./cereal/messaging/bridge` locally: instead of re-publishing CAN over a LAN ZMQ socket,
canlived opens its OWN websocket to konn3kt and forwards the raw capnp `Event` frames.
It is deliberately a separate daemon (not part of hephaestusd's control websocket):
* hephaestusd's send path fragments everything as TEXT frames through one queue, which
cannot carry binary capnp and would head-of-line-block the control plane at 100Hz.
* a dedicated socket means CAN traffic and control traffic never contend.
Lifecycle: the manager launches canlived only while the `CanLiveStreaming` param is set.
hephaestusd sets/clears that param via startCanLive/stopCanLive, which the konn3kt server
calls when the first viewer connects / the last viewer disconnects. So canlived runs only
during an active debug session — no idle connections, no battery/data cost otherwise.
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved.
"""
import os
import threading
from websocket import ABNF, create_connection
import cereal.messaging as messaging
from openpilot.common.api import Api
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
# Cabana's live "Device" stream subscribes only to "can", so that's all we forward to
# match the local experience exactly. (sendcan/TX is not shown by the live device view.)
CAN_SERVICES = ["can"]
# Reconnect backoff bounds (seconds).
RECONNECT_MIN = 1.0
RECONNECT_MAX = 10.0
def _api_host() -> str:
# Same host hephaestusd talks to; force the websocket scheme.
host = os.getenv("HEPHAESTUS_HOST") or os.getenv("KONN3KT_API_HOST") or "wss://api-iqlabs.konn3kt.com"
host = host.rstrip("/")
if host.startswith("https://"):
host = "wss://" + host[len("https://"):]
elif host.startswith("http://"):
host = "ws://" + host[len("http://"):]
return host
def _stream_once(dongle_id: str, ws_uri: str, token: str, exit_event: threading.Event) -> None:
"""Open one websocket and pump CAN until it drops or we're asked to exit."""
ws = create_connection(ws_uri, cookie="jwt=" + token, enable_multithread=True, timeout=30.0)
cloudlog.info("canlived: connected to %s", ws_uri)
try:
# Blocking receive with a short timeout so we periodically re-check exit_event and
# the socket stays responsive to shutdown even when the bus is quiet.
socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in CAN_SERVICES]
while not exit_event.is_set():
got_any = False
for sock in socks:
while True:
raw = sock.receive(non_blocking=True)
if raw is None:
break
got_any = True
# Forward the exact capnp Event bytes as a single binary frame. canlived owns
# this socket, so there is no fragmentation/interleaving to worry about.
ws.send_frame(ABNF.create_frame(raw, ABNF.OPCODE_BINARY, 1))
if not got_any:
# Nothing pending across any sub — yield briefly instead of busy-spinning.
exit_event.wait(0.005)
finally:
try:
ws.close()
except Exception:
pass
def main(exit_event: threading.Event | None = None) -> None:
if exit_event is None:
exit_event = threading.Event()
params = Params()
dongle_id = params.get("DongleId", encoding="utf-8")
if not dongle_id:
cloudlog.error("canlived: no DongleId, cannot stream")
return
api = Api(dongle_id)
host = _api_host()
ws_uri = f"{host}/ws/can/{dongle_id}"
backoff = RECONNECT_MIN
while not exit_event.is_set():
try:
token = api.get_token(expiry_hours=1)
_stream_once(dongle_id, ws_uri, token, exit_event)
backoff = RECONNECT_MIN # clean disconnect, reset backoff
except Exception as e:
cloudlog.exception("canlived: stream error: %s", e)
exit_event.wait(backoff)
backoff = min(backoff * 2, RECONNECT_MAX)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,17 @@
"""
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import os
from openpilot.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)

View File

@@ -0,0 +1,3 @@
"""
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View File

@@ -0,0 +1,53 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Serialises openpilot params to/from their on-wire byte form for backup transport.
The byte encoding is unchanged so archives round-trip: BYTES pass through, JSON is
json-encoded, everything else is str()'d; decoding is typed per the param's key.
"""
import base64
import gzip
import json
from openpilot.common.params import Params, ParamKeyType
def encode_param(name: str, params=None, use_default: bool = False) -> bytes | None:
params = params or Params()
raw = params.get_default_value(name) if use_default else params.get(name)
if raw is None:
return None
ktype = params.get_type(name)
if ktype == ParamKeyType.BYTES:
return bytes(raw)
if ktype == ParamKeyType.JSON:
return json.dumps(raw).encode("utf-8")
return str(raw).encode("utf-8")
# text-form decoders keyed by param type; anything unlisted is left as the raw string
_FROM_TEXT = {
ParamKeyType.STRING: lambda s: s,
ParamKeyType.BOOL: lambda s: s.lower() in ("true", "1", "yes"),
ParamKeyType.INT: int,
ParamKeyType.FLOAT: float,
ParamKeyType.TIME: str,
ParamKeyType.JSON: json.loads,
}
def restore_param_from_base64(name: str, b64_data: str, compressed: bool = False) -> None:
params = Params()
ktype = params.get_type(name)
blob = base64.b64decode(b64_data)
if compressed:
blob = gzip.decompress(blob)
if ktype == ParamKeyType.BYTES:
value = blob
else:
value = _FROM_TEXT.get(ktype, lambda s: s)(blob.decode("utf-8"))
params.put(name, value)

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env python3
"""
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import logging
from openpilot.iqpilot._proprietary_loader import load_private_module
_ORIGINAL_LOGGER_LOG = logging.Logger._log
def _ble_transport_logger_shim(self, level, msg, args,
exc_info=None, extra=None, stack_info=False, stacklevel=1, **kwargs):
if kwargs:
suffix = " ".join(f"{key}={value!r}" for key, value in sorted(kwargs.items()))
msg = f"{msg} {suffix}".strip() if msg is not None else suffix
return _ORIGINAL_LOGGER_LOG(
self, level, msg, args,
exc_info=exc_info, extra=extra, stack_info=stack_info, stacklevel=stacklevel,
)
logging.Logger._log = _ble_transport_logger_shim
load_private_module(__name__, "iqpilot_private.konn3kt.hephaestus.ble_transportd")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from openpilot.iqpilot._proprietary_loader import load_private_module
load_private_module(__name__, "iqpilot_private.konn3kt.hephaestus.hephaestusd")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,122 @@
"""
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import importlib
import os
import time
from multiprocessing import Process
HEPHAESTUS_MGR_PID_PARAM = "HephaestusdPid"
def _cloudlog():
try:
from openpilot.common.swaglog import cloudlog
return cloudlog
except Exception:
return None
def _log(level: str, msg: str) -> None:
cl = _cloudlog()
if cl is not None:
try:
getattr(cl, level)(msg)
return
except Exception:
pass
try:
print(f"manage_hephaestusd[{level}]: {msg}", flush=True)
except Exception:
pass
def _lightweight_launcher(proc: str, name: str) -> None:
try:
mod = importlib.import_module(proc)
try:
from setproctitle import setproctitle
setproctitle(proc)
except Exception:
pass
cl = _cloudlog()
if cl is not None:
try:
cl.bind(daemon=name)
except Exception:
pass
mod.main()
except KeyboardInterrupt:
_log("warning", f"child {proc} got SIGINT")
except Exception:
_log("exception", f"child {proc} exception")
raise
def _bind_global_best_effort(dongle_id_param: str) -> None:
try:
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware import HARDWARE
except Exception:
return
try:
dongle_id = Params().get(dongle_id_param)
try:
from openpilot.system.version import get_build_metadata
build_metadata = get_build_metadata()
cloudlog.bind_global(dongle_id=dongle_id,
version=build_metadata.openpilot.version,
origin=build_metadata.openpilot.git_normalized_origin,
branch=build_metadata.channel,
commit=build_metadata.openpilot.git_commit,
dirty=build_metadata.openpilot.is_dirty,
device=HARDWARE.get_device_type())
except Exception:
cloudlog.bind_global(dongle_id=dongle_id, device=HARDWARE.get_device_type())
except Exception:
pass
def _remove_pid_param(pid_param: str) -> None:
try:
from openpilot.common.params import Params
Params().remove(pid_param)
except Exception:
pass
def manage_hephaestusd(dongle_id_param: str, pid_param: str, process_name: str, target: str) -> None:
_bind_global_best_effort(dongle_id_param)
try:
while 1:
_log("info", f"starting {process_name} daemon")
proc = Process(name=process_name, target=_lightweight_launcher, args=(target, process_name))
proc.start()
# Lower priority so BLE stack doesn't compete with OP's Python processes
# on an already heavily loaded system (RT processes like pandad/modeld are unaffected)
if proc.pid is not None:
try:
os.setpriority(os.PRIO_PROCESS, proc.pid, 10)
except OSError:
pass
proc.join()
_log("info", f"{process_name} exited (exitcode={proc.exitcode})")
if proc.exitcode == 174:
time.sleep(30)
else:
time.sleep(5)
except Exception:
_log("exception", f"manage_{process_name}.exception")
finally:
_remove_pid_param(pid_param)
def main():
manage_hephaestusd(dongle_id_param="DongleId", pid_param=HEPHAESTUS_MGR_PID_PARAM, process_name="hephaestusd",
target="iqpilot.konn3kt.hephaestus.hephaestusd")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from openpilot.iqpilot._proprietary_loader import load_private_module
load_private_module(__name__, "iqpilot_private.konn3kt.iqlvbs.alc")

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Public shim for the proprietary IQ.Lvbs git read-auth helper.
The token + git config logic live in the standalone, signed bundle
``iqpilot_private.updater.git_remote`` (artifact iqpilot_updater_private) -- a
dedicated bundle so the read-only PAT can be rotated by rebuilding only that tiny
bundle, never touching ALC. Never in the open tree.
The private module exports:
configure(repo_dir: str) -> None
Install the read-only token as an ephemeral http.<host>.extraHeader on
repo_dir (the only auth method that survives the WAF's 403-to-anonymous).
"""
from openpilot.iqpilot._proprietary_loader import load_private_module
load_private_module(__name__, "iqpilot_private.updater.git_remote")

236
iqpilot/konn3kt/registration.py Executable file
View File

@@ -0,0 +1,236 @@
#!/usr/bin/env python3
import os
import time
import json
import jwt
import re
import secrets
from typing import cast
from pathlib import Path
from datetime import datetime, timedelta, UTC
from openpilot.common.api import api_get, get_key_pair
from openpilot.common.params import Params
from openpilot.common.spinner import Spinner
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
_DONGLE_ID_RE = re.compile(r"^[a-fA-F0-9]{16}$")
IMEI_WAIT_TIMEOUT = 15.0
def _read_persist_dongle_id() -> str | None:
p = Path(Paths.persist_root()) / "comma" / "dongle_id"
try:
if not p.is_file():
return None
s = p.read_text().strip()
return s or None
except Exception:
cloudlog.exception("failed to read persist dongle_id")
return None
def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str | None:
ro = _read_persist_dongle_id()
if is_valid_dongle_id(ro):
ro = ro.lower()
if prefer_readonly and ro:
return ro
p = Params() if params is None else params
v = p.get("DongleId")
if v and v != UNREGISTERED_DONGLE_ID:
return v.lower() if is_valid_dongle_id(v) else v
return ro or None
def is_valid_dongle_id(dongle_id: str | None) -> bool:
return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str:
p = Params() if params is None else params
dongle_id = get_cached_dongle_id(p, prefer_readonly=prefer_readonly)
if dongle_id and dongle_id != UNREGISTERED_DONGLE_ID:
return dongle_id
dongle_id = secrets.token_hex(8)
p.put("DongleId", dongle_id)
cloudlog.warning(f"generated new DongleId={dongle_id} (no readonly dongle_id found)")
return dongle_id
def ensure_dev_pairing_identity(params: Params | None = None, force_reset: bool = False) -> dict[str, str]:
p = Params() if params is None else params
persist_dir = Path(Paths.persist_root()) / "comma"
persist_dir.mkdir(parents=True, exist_ok=True)
dongle_path = persist_dir / "dongle_id"
priv_path = persist_dir / "id_rsa"
pub_path = persist_dir / "id_rsa.pub"
if force_reset:
for fp in (dongle_path, priv_path, pub_path):
try:
fp.unlink(missing_ok=True)
except Exception:
cloudlog.exception(f"failed to remove {fp}")
try:
(persist_dir / "konn3kt_prime_type").unlink(missing_ok=True)
except Exception:
pass
try:
p.remove("PrimeType")
except Exception:
pass
forced_dongle = os.getenv("KONN3KT_DEV_DONGLE_ID")
dongle_id = forced_dongle.strip().lower() if forced_dongle else None
if dongle_id and not is_valid_dongle_id(dongle_id):
cloudlog.error("KONN3KT_DEV_DONGLE_ID must be 16 hex chars")
dongle_id = None
if dongle_id is None:
existing = None
try:
existing = dongle_path.read_text().strip().lower() if dongle_path.is_file() else None
except Exception:
cloudlog.exception("failed reading existing dev dongle_id")
dongle_id = existing if is_valid_dongle_id(existing) else secrets.token_hex(8)
try:
dongle_path.write_text(dongle_id)
except Exception:
cloudlog.exception("failed writing dev dongle_id")
p.put("DongleId", dongle_id)
p.put("HardwareSerial", p.get("HardwareSerial") or f"DEV-{dongle_id}")
if force_reset or (not priv_path.is_file()) or (not pub_path.is_file()):
try:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
priv_bytes = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
pub_bytes = key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
priv_path.write_bytes(priv_bytes)
pub_path.write_bytes(pub_bytes)
except Exception:
cloudlog.exception("failed generating dev RSA keys")
raise
return {
"dongle_id": dongle_id,
"serial": p.get("HardwareSerial") or f"DEV-{dongle_id}",
"persist_dir": str(persist_dir),
}
def is_registered_device() -> bool:
dongle = Params().get("DongleId")
return dongle not in (None, UNREGISTERED_DONGLE_ID)
def _normalize_imei(value: str | None) -> str:
return value or ""
def get_registration_identifiers(wait_timeout: float = IMEI_WAIT_TIMEOUT, show_spinner: bool = False) -> tuple[str, str, str]:
serial = HARDWARE.get_serial()
spinner = Spinner() if show_spinner else None
start_time = time.monotonic()
imei1: str | None = None
imei2: str | None = None
while time.monotonic() - start_time < wait_timeout:
try:
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
if imei1 or imei2:
break
except RuntimeError as e:
if "no modems" in str(e).lower():
cloudlog.warning("No cellular modem available, proceeding without IMEI")
break
cloudlog.exception("Error getting imei, trying again...")
except Exception:
cloudlog.exception("Error getting imei, trying again...")
time.sleep(1)
imei1 = _normalize_imei(imei1)
imei2 = _normalize_imei(imei2)
if not imei1 and not imei2:
cloudlog.warning(f"proceeding with serial-only registration for serial={serial}")
if spinner is not None:
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
spinner.close()
return serial, imei1, imei2
def register(show_spinner=False) -> str | None:
"""
All devices built since March 2024 come with all
info stored in /persist/. This is kept around
only for devices built before then.
With a backend update to take serial number instead
of dongle ID to some endpoints, this can be removed
entirely.
"""
params = Params()
dongle_id: str | None = get_cached_dongle_id(params, prefer_readonly=True)
if dongle_id in ("", UNREGISTERED_DONGLE_ID):
dongle_id = None
# Create registration token, in the future, this key will make JWTs directly
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 # hotfix to prevent an infinite wait for registration
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 openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert # lazy: keeps registration import light for the setup zipapp
set_offroad_alert("Offroad_UnregisteredHardware", False)
return dongle_id
if __name__ == "__main__":
print(register())

View File

@@ -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
"""
def hephaestus_ready(params=None) -> bool:
return True
def hephaestus_ready_shim():
return hephaestus_ready()

View File

@@ -0,0 +1,41 @@
from unittest.mock import patch
from openpilot.iqpilot.konn3kt import registration
def test_get_registration_identifiers_uses_serial_without_imei():
imei_calls = {"count": 0}
def fake_get_imei(slot: int) -> str | None:
imei_calls["count"] += 1
return None
monotonic_values = iter([0.0, 0.0, 1.0, 2.0])
with patch.object(registration.HARDWARE, "get_serial", return_value="lite123"), \
patch.object(registration.HARDWARE, "get_imei", side_effect=fake_get_imei), \
patch.object(registration.time, "monotonic", side_effect=lambda: next(monotonic_values)), \
patch.object(registration.time, "sleep", return_value=None):
serial, imei1, imei2 = registration.get_registration_identifiers(wait_timeout=1.5, show_spinner=False)
assert serial == "lite123"
assert imei1 == ""
assert imei2 == ""
assert imei_calls["count"] >= 2
def test_get_registration_identifiers_returns_first_available_imei():
imeis = [None, "123456789012345"]
monotonic_values = iter([0.0, 0.0, 0.5, 0.5])
def fake_get_imei(slot: int) -> str | None:
return imeis.pop(0) if slot == 0 else None
with patch.object(registration.HARDWARE, "get_serial", return_value="lite123"), \
patch.object(registration.HARDWARE, "get_imei", side_effect=fake_get_imei), \
patch.object(registration.time, "monotonic", side_effect=lambda: next(monotonic_values)), \
patch.object(registration.time, "sleep", return_value=None):
serial, imei1, imei2 = registration.get_registration_identifiers(wait_timeout=2.0, show_spinner=False)
assert serial == "lite123"
assert imei1 == "123456789012345"
assert imei2 == ""

View File

@@ -0,0 +1 @@
"""Public wrappers for proprietary uploader modules."""

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from openpilot.iqpilot._proprietary_loader import load_private_module
load_private_module(__name__, "iqpilot_private.konn3kt.uploaderd.iquploaderd")
if __name__ == "__main__":
main()