IQ.Pilot Release Commit @ b6534c0

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-27 20:17:33 -05:00
commit 00f07cac48
4706 changed files with 1257146 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

@@ -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,78 @@
#!/usr/bin/env python3
"""
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import os
import threading
from websocket import ABNF, create_connection
import iqpilot.cereal.messaging as messaging
from iqpilot.common.api import Api
from iqpilot.common.params import Params
from iqpilot.common.swaglog import cloudlog
CAN_SERVICES = ["can"]
RECONNECT_MIN = 1.0
RECONNECT_MAX = 10.0
def _api_host() -> str:
host = "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:
ws = create_connection(ws_uri, cookie="jwt=" + token, enable_multithread=True, timeout=30.0)
cloudlog.info("canlived: connected to %s", ws_uri)
try:
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
ws.send_frame(ABNF.create_frame(raw, ABNF.OPCODE_BINARY, 1))
if not got_any:
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
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 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)

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,48 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import base64
import gzip
import json
from iqpilot.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")
_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)

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

@@ -0,0 +1,226 @@
#!/usr/bin/env python3
import os
import time
import json
import jwt
import re
import secrets
from typing import cast
from pathlib import Path
from datetime import datetime, timedelta, UTC
from iqpilot.common.api import api_get, get_key_pair
from iqpilot.common.params import Params
from iqpilot.common.spinner import Spinner
from iqpilot.system.hardware import HARDWARE, PC
from iqpilot.system.hardware.hw import Paths
from iqpilot.common.swaglog import cloudlog
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
_DONGLE_ID_RE = re.compile(r"^[a-fA-F0-9]{16}$")
IMEI_WAIT_TIMEOUT = 15.0
def _read_persist_dongle_id() -> str | None:
p = Path(Paths.persist_root()) / "comma" / "dongle_id"
try:
if not p.is_file():
return None
s = p.read_text().strip()
return s or None
except Exception:
cloudlog.exception("failed to read persist dongle_id")
return None
def get_cached_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str | None:
ro = _read_persist_dongle_id()
if is_valid_dongle_id(ro):
ro = ro.lower()
if prefer_readonly and ro:
return ro
p = Params() if params is None else params
v = p.get("DongleId")
if v and v != UNREGISTERED_DONGLE_ID:
return v.lower() if is_valid_dongle_id(v) else v
return ro or None
def is_valid_dongle_id(dongle_id: str | None) -> bool:
return bool(dongle_id and _DONGLE_ID_RE.fullmatch(dongle_id))
def get_or_create_dongle_id(params: Params | None = None, prefer_readonly: bool = True) -> str:
p = Params() if params is None else params
dongle_id = get_cached_dongle_id(p, prefer_readonly=prefer_readonly)
if dongle_id and dongle_id != UNREGISTERED_DONGLE_ID:
return dongle_id
dongle_id = secrets.token_hex(8)
p.put("DongleId", dongle_id)
cloudlog.warning(f"generated new DongleId={dongle_id} (no readonly dongle_id found)")
return dongle_id
def ensure_dev_pairing_identity(params: Params | None = None, force_reset: bool = False) -> dict[str, str]:
p = Params() if params is None else params
persist_dir = Path(Paths.persist_root()) / "comma"
persist_dir.mkdir(parents=True, exist_ok=True)
dongle_path = persist_dir / "dongle_id"
priv_path = persist_dir / "id_rsa"
pub_path = persist_dir / "id_rsa.pub"
if force_reset:
for fp in (dongle_path, priv_path, pub_path):
try:
fp.unlink(missing_ok=True)
except Exception:
cloudlog.exception(f"failed to remove {fp}")
try:
(persist_dir / "konn3kt_prime_type").unlink(missing_ok=True)
except Exception:
pass
try:
p.remove("PrimeType")
except Exception:
pass
forced_dongle = os.getenv("KONN3KT_DEV_DONGLE_ID")
dongle_id = forced_dongle.strip().lower() if forced_dongle else None
if dongle_id and not is_valid_dongle_id(dongle_id):
cloudlog.error("KONN3KT_DEV_DONGLE_ID must be 16 hex chars")
dongle_id = None
if dongle_id is None:
existing = None
try:
existing = dongle_path.read_text().strip().lower() if dongle_path.is_file() else None
except Exception:
cloudlog.exception("failed reading existing dev dongle_id")
dongle_id = existing if is_valid_dongle_id(existing) else secrets.token_hex(8)
try:
dongle_path.write_text(dongle_id)
except Exception:
cloudlog.exception("failed writing dev dongle_id")
p.put("DongleId", dongle_id)
p.put("HardwareSerial", p.get("HardwareSerial") or f"DEV-{dongle_id}")
if force_reset or (not priv_path.is_file()) or (not pub_path.is_file()):
try:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
priv_bytes = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
pub_bytes = key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
priv_path.write_bytes(priv_bytes)
pub_path.write_bytes(pub_bytes)
except Exception:
cloudlog.exception("failed generating dev RSA keys")
raise
return {
"dongle_id": dongle_id,
"serial": p.get("HardwareSerial") or f"DEV-{dongle_id}",
"persist_dir": str(persist_dir),
}
def is_registered_device() -> bool:
dongle = Params().get("DongleId")
return dongle not in (None, UNREGISTERED_DONGLE_ID)
def _normalize_imei(value: str | None) -> str:
return value or ""
def get_registration_identifiers(wait_timeout: float = IMEI_WAIT_TIMEOUT, show_spinner: bool = False) -> tuple[str, str, str]:
serial = HARDWARE.get_serial()
spinner = Spinner() if show_spinner else None
start_time = time.monotonic()
imei1: str | None = None
imei2: str | None = None
while time.monotonic() - start_time < wait_timeout:
try:
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
if imei1 or imei2:
break
except RuntimeError as e:
if "no modems" in str(e).lower():
cloudlog.warning("No cellular modem available, proceeding without IMEI")
break
cloudlog.exception("Error getting imei, trying again...")
except Exception:
cloudlog.exception("Error getting imei, trying again...")
time.sleep(1)
imei1 = _normalize_imei(imei1)
imei2 = _normalize_imei(imei2)
if not imei1 and not imei2:
cloudlog.warning(f"proceeding with serial-only registration for serial={serial}")
if spinner is not None:
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
spinner.close()
return serial, imei1, imei2
def register(show_spinner=False) -> str | None:
params = Params()
dongle_id: str | None = get_cached_dongle_id(params, prefer_readonly=True)
if dongle_id in ("", UNREGISTERED_DONGLE_ID):
dongle_id = None
jwt_algo, private_key, public_key = get_key_pair()
if not public_key:
dongle_id = UNREGISTERED_DONGLE_ID
cloudlog.warning("missing public key")
elif dongle_id is None:
if show_spinner:
spinner = Spinner()
spinner.update("registering device")
serial, imei1, imei2 = get_registration_identifiers(wait_timeout=IMEI_WAIT_TIMEOUT, show_spinner=False)
backoff = 0
start_time = time.monotonic()
while True:
try:
register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)},
cast(str, private_key), algorithm=jwt_algo)
cloudlog.info("getting pilotauth")
cloudlog.info("getting pilotauth")
resp = api_get("v2/pilotauth/", method='POST', timeout=15,
imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
if resp.status_code in (400, 402, 403):
cloudlog.info(f"Unable to register device, got {resp.status_code}")
dongle_id = UNREGISTERED_DONGLE_ID
else:
dongleauth = json.loads(resp.text)
dongle_id = dongleauth["dongle_id"]
break
except Exception:
cloudlog.exception("failed to authenticate")
backoff = min(backoff + 1, 15)
time.sleep(backoff)
if time.monotonic() - start_time > 60 and show_spinner:
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})")
return UNREGISTERED_DONGLE_ID
if show_spinner:
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
spinner.close()
if dongle_id:
params.put("DongleId", dongle_id)
from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
set_offroad_alert("Offroad_UnregisteredHardware", False)
return dongle_id
if __name__ == "__main__":
print(register())

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()