IQ.Pilot Release Commit @ f2a861c
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
78
iqpilot/konn3kt/canlive/canlived.py
Executable file
78
iqpilot/konn3kt/canlive/canlived.py
Executable 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()
|
||||
@@ -4,7 +4,7 @@ Copyright ©️ IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licens
|
||||
|
||||
|
||||
import os
|
||||
from openpilot.common.api.base import BaseApi
|
||||
from iqpilot.common.api.base import BaseApi
|
||||
API_HOST = os.getenv('KONN3KT_API_HOST', 'https://api-iqlabs.konn3kt.com')
|
||||
|
||||
class Konn3ktApi(BaseApi):
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
"""
|
||||
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
|
||||
from iqpilot.common.params import Params, ParamKeyType
|
||||
|
||||
|
||||
def encode_param(name: str, params=None, use_default: bool = False) -> bytes | None:
|
||||
@@ -26,7 +22,6 @@ def encode_param(name: str, params=None, use_default: bool = False) -> bytes | N
|
||||
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"),
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/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()
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/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()
|
||||
@@ -1,122 +0,0 @@
|
||||
"""
|
||||
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()
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/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")
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/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")
|
||||
@@ -9,12 +9,12 @@ 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
|
||||
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"
|
||||
@@ -166,22 +166,12 @@ def get_registration_identifiers(wait_timeout: float = IMEI_WAIT_TIMEOUT, show_s
|
||||
|
||||
|
||||
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:
|
||||
@@ -219,7 +209,7 @@ def register(show_spinner=False) -> str | None:
|
||||
|
||||
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
|
||||
return UNREGISTERED_DONGLE_ID
|
||||
|
||||
if show_spinner:
|
||||
spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1 or None}, {imei2 or None})")
|
||||
@@ -227,7 +217,7 @@ def register(show_spinner=False) -> str | None:
|
||||
|
||||
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
|
||||
from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
|
||||
set_offroad_alert("Offroad_UnregisteredHardware", False)
|
||||
return dongle_id
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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 == ""
|
||||
@@ -1 +0,0 @@
|
||||
"""Public wrappers for proprietary uploader modules."""
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user