IQ.Pilot Release Commit @ 27f668a

This commit is contained in:
IQ.Lvbs CI [bot]
2026-09-03 18:17:35 -05:00
parent 7745f48100
commit 1b2f28290b
69 changed files with 728 additions and 193 deletions

View File

@@ -201,8 +201,8 @@
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": {
"mode": 420, "mode": 420,
"sha256": "0b8f118dc45ad0c3809855800960b47c893ee121ea75d94e8569dcce46d58ce5", "sha256": "07b3ace231d9912f1926ffe7cef0f2a5d9649f75b7ebac2b9f296957d98be166",
"size": 7312 "size": 7681
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": {
"mode": 420, "mode": 420,
@@ -216,8 +216,8 @@
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/messaging.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/messaging.py": {
"mode": 420, "mode": 420,
"sha256": "17d48331eeeaf746ee6982a9c65f28d740a15a4c34c37617115c104894d49c06", "sha256": "1d345eb35d4eea2a0b8c8e8491b4c1a74e073d8340703cdd334ec78992026f72",
"size": 11920 "size": 12033
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/metadata.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/metadata.py": {
"mode": 420, "mode": 420,
@@ -536,27 +536,27 @@
}, },
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "f42a1bab373092b3010ca45b02d7ea7b27cd1e1e574c97f701129b2fad01f833", "sha256": "0fec7e56196064f024cf317a6f23d9f2111f90753c48bb7d616e06e27335157d",
"size": 333888 "size": 333888
}, },
"python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "306f0362e86558d33072f800b736fa71bfaf4ee5e4383c4e743b72b74724d2e3", "sha256": "4d6e7c4bc97ced0bf2def103d2fc8eb98364747aa33e495bc448c68644739750",
"size": 68064 "size": 68064
}, },
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "31339f53f093c3877e13eac90a6647cbca2de92f339c47e5006a85bcd2e3656c", "sha256": "a23b14f9f2c5c7a058c20ef0421bb741fc67c2adff13824c1c808e6831430f91",
"size": 267960 "size": 267960
}, },
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "d5e855d1784a8ba66844639b871d2747ccce264c55359cf0e641ce7e3d66460d", "sha256": "189c7d6d5599f92fc224a086ef55520118130785f482c0667cc808071c84e718",
"size": 203512 "size": 203544
}, },
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "34d4ef1746143ea714f84f3c325e2ddaecec6a19c544fed68d8fc4080578c73f", "sha256": "2cf0bc9d6362f2d4c9691f4f0ef7989a5651cc3153e99e5c2e15d9da1b93e598",
"size": 399040 "size": 399040
}, },
"runtime": { "runtime": {
@@ -568,10 +568,10 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "7BtEAP11moL2lbQW5dYt1hDqJmDV1EuiEoSfJ0Tfislun2kS+/rwPiy7Ws8kjgrKLv+ZlXlWz6G5IBWk6z+aAw==", "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "laKo35uiFJAsuGj7+N5l5IVbXmTQ/jyB01c31OfA7XCT0AG09/cg9ArgDhQv5qr3babesKgUKoPg2vtFnnXyDg==",
"python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "Sv2riVgvm+4PDVTDLTuy1wpf15JObbP8hSAD4iiwF4RUs49GVGhulXkjzO28mLmF6tEuNDNWHRucxmuZrCKNAg==", "python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "Sd6VF2hrGHXoXTe3IpCYoTDczM741xcQ1sKiHGcCgNqkeORvLumWxic20ylmWhWOTHziPB9Y723vFOGcKZzDAQ==",
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "d4kaBPAEwe55BrQEVHWmxcVlEoQPsMVLOAYgpZVd6uiBnwWSUxl1X5nmqdKtxWSk8TFqU2xZkhfw4OOgG9JmDw==", "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "O9O/B4IDlCutovmEPDnLxdKAFsURjXS+pyaK0OvmoOlOQW1fQS+ZahxaQ0dkwASDzEMWUxUFr0j9PrlihUVpCA==",
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "hvBkDKSMKjpxmRzhhGoRZT5WuOWLY+g3p2NIMlA7b6qYYpVaApoZNuquOerywEF1zJnwPiwpsg5QtQVe/5DfAA==", "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "77vKNawQIdv95LPwTFn79N2RXaz6dQUcRtgtnH+R7T2TN2LBSKhKvtzu2sKkKyDG7038qrJfhE9BiJYtho56BA==",
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "d3JAlH7P/X87II6iobeQ51melHy1vxdlXn0Nzo2DG65rcdVLM2B66YETPpwlX6Qv9NxhUlDcl/364CR+V+eyDQ==" "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "Fhxo++ZiJmWGBghe/dH/ytHe+T67v/HAA6k4js7Bd+C07dA/zMlgTI45QlBx2fZN2qC602Z4Stijy+/EQziuCg=="
} }
} }

View File

@@ -1 +1 @@
KylnzVsK5XC3Jq1G7bBbKa5lv+oJSlfBpBsNpWMEEUgHDyOEj7+5EeeHDOgy8Lt4LEFgEeIV2WEJW0GJVpxUCQ== nBzDAB5lnwH7Ynehfwf48y7ACh39HIRNiff55kmMtNG466wcxBrkMcnEJz/rDf7ehRcZmyD08CVW5QCfMsHBAA==

View File

@@ -71,6 +71,11 @@ def egpu_oob_pkl_path(meta: dict) -> str:
return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_policy_oob.pkl") return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_policy_oob.pkl")
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")
def onnx_cache_path(meta: dict) -> str: def onnx_cache_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths from iqpilot.system.hardware.hw import Paths
return os.path.join(Paths.model_root(), f"{meta['model_name']}_{meta['sha256'][:8]}.onnx") return os.path.join(Paths.model_root(), f"{meta['model_name']}_{meta['sha256'][:8]}.onnx")
@@ -156,13 +161,21 @@ def download_onnx(meta: dict, progress_cb=None) -> str:
return path return path
def download_precompiled(meta: dict, progress_cb=None, policy: bool = False, oob: bool = False) -> str | None: ARTIFACT_PATHS = {
field = "egpu_oob_artifact" if oob else "egpu_policy_artifact" if policy else "egpu_artifact" "egpu_model_oob_artifact": egpu_model_oob_pkl_path,
"egpu_oob_artifact": egpu_oob_pkl_path,
"egpu_policy_artifact": egpu_policy_pkl_path,
"egpu_artifact": egpu_pkl_path,
}
def download_precompiled(meta: dict, progress_cb=None, policy: bool = False, oob: bool = False, field: str | None = None) -> str | None:
field = field or ("egpu_oob_artifact" if oob else "egpu_policy_artifact" if policy else "egpu_artifact")
art = meta.get(field) art = meta.get(field)
if not art or not (art.get("objects") or art.get("hf_path")): if not art or not (art.get("objects") or art.get("hf_path")):
return None return None
from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file, download_lfs_bundle from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file, download_lfs_bundle
dest = egpu_oob_pkl_path(meta) if oob else egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta) dest = ARTIFACT_PATHS[field](meta)
if art.get("hf_path"): if art.get("hf_path"):
try: try:
return download_hf_file(art["hf_path"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb) return download_hf_file(art["hf_path"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb)

View File

@@ -248,16 +248,19 @@ def populate_drive_messages(primary_msg: capnp._DynamicStructBuilder, extended_m
def populate_odometry_message(msg: capnp._DynamicStructBuilder, outputs: dict[str, np.ndarray], def populate_odometry_message(msg: capnp._DynamicStructBuilder, outputs: dict[str, np.ndarray],
vipc_frame_id: int, vipc_dropped_frames: int, vipc_frame_id: int, vipc_dropped_frames: int,
timestamp_eof: int, live_calib_seen: bool) -> None: timestamp_eof: int, live_calib_seen: bool) -> None:
msg.valid = live_calib_seen & (vipc_dropped_frames < 1) pose = outputs["pose"][0, :6]
pose_stds = outputs["pose_stds"][0, :6]
pose_finite = bool(np.isfinite(pose).all() and np.isfinite(pose_stds).all())
msg.valid = live_calib_seen & (vipc_dropped_frames < 1) & pose_finite
odo = msg.cameraOdometry odo = msg.cameraOdometry
odo.frameId = vipc_frame_id odo.frameId = vipc_frame_id
odo.timestampEof = timestamp_eof odo.timestampEof = timestamp_eof
odo.trans = outputs["pose"][0, :3].tolist() odo.trans = pose[:3].tolist()
odo.rot = outputs["pose"][0, 3:].tolist() odo.rot = pose[3:6].tolist()
odo.wideFromDeviceEuler = outputs["wide_from_device_euler"][0, :].tolist() odo.wideFromDeviceEuler = outputs["wide_from_device_euler"][0, :].tolist()
odo.roadTransformTrans = outputs["road_transform"][0, :3].tolist() odo.roadTransformTrans = outputs["road_transform"][0, :3].tolist()
odo.transStd = outputs["pose_stds"][0, :3].tolist() odo.transStd = pose_stds[:3].tolist()
odo.rotStd = outputs["pose_stds"][0, 3:].tolist() odo.rotStd = pose_stds[3:6].tolist()
odo.wideFromDeviceEulerStd = outputs["wide_from_device_euler_stds"][0, :].tolist() odo.wideFromDeviceEulerStd = outputs["wide_from_device_euler_stds"][0, :].tolist()
odo.roadTransformTransStd = outputs["road_transform_stds"][0, :3].tolist() odo.roadTransformTransStd = outputs["road_transform_stds"][0, :3].tolist()

View File

@@ -176,8 +176,8 @@
}, },
"python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "aec72c78d58c37afb1b57bea4a29d322032fffdfa59deabdc85b4322b5ca3787", "sha256": "665a6a5163899f37bb7e41fd42fe79ecd77e50496285b60c6555971b8337f463",
"size": 67616 "size": 67568
}, },
"python/iqpilot_private/models/__init__.py": { "python/iqpilot_private/models/__init__.py": {
"mode": 420, "mode": 420,
@@ -186,38 +186,38 @@
}, },
"python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "30a25ebd3b37e2307c3a80ae0fea70b314ab71427d313ba5e2d540c4ba7658ed", "sha256": "8a63da11107132869d7ea4e391197ca8bde2d13d6d92b09a83484503910a75d5",
"size": 133840 "size": 133856
}, },
"python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "e8cb29388e31f636d19f916d5a1d32f38c38113f5fa882344d4effceb6e37d85", "sha256": "7090be17ce2063905fc364ee925384708895150f4d6eef642a94b941d3c6c88d",
"size": 68112 "size": 68096
}, },
"python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "b7938d9521352d5c1fa6b0d7ff8f3f11062e33bcc0d9a72ffb0edfb525423b6e", "sha256": "c2691e7a3e77e78d20d21cbf0d0bc4d59fd294935c7ebd0cc7225b0d77619914",
"size": 68264 "size": 134032
}, },
"python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "dad0c3c3a73338647da25dead17da0db6cebb24527bc9db381cd16753741f676", "sha256": "d085741bb6a7d2a94d616d53e78e0ae76d381ca926017646ec33931375fb5963",
"size": 136200 "size": 136200
}, },
"python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "ce815034dd8bdadb79131e0b51013a9f9772c4421dfb96b62431d84125089ad2", "sha256": "951ecc3665d6a2232f50ca3002f7f84753789ecd1adfc06cc85884b42c47e05c",
"size": 68056 "size": 68032
}, },
"python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "798843f52120210c19da5b6c98723ca15d782ea9cd445cc8f96009471990d618", "sha256": "8670d22574d59c9b2f43c0fa047967a3236fc4eb0ca315a61fe0c9f2636d886a",
"size": 137944 "size": 203512
}, },
"python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "96be19ef4f59b87e44092c61f70676468617365923c4c8b91cf7e0b88cc62c05", "sha256": "c0fcfd5193cd11e52f22d8c804ace3b436f49aaa6655689c2f1903931544a977",
"size": 340000 "size": 339976
}, },
"runtime": { "runtime": {
"entries": { "entries": {
@@ -228,13 +228,13 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "CHPHmtjVAGTdtbWnwE5awrEKFJHs06YK4Gao9IKVaHZYXyj5WAc/d4t7hOKM3lm2/UfBaraTdVTdeSOij3dpCQ==", "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "BaGn+mLXPAGLqnVJhDUt8C2ZEm1E9+MUCPoOnJhQGQybFOr36MuoubfpEgPoy1993Tm4IGD3pZASyFefJNXtBQ==",
"python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "Ekiv6tp3mDx+7Z4lNI+byVomzjShe93UkYQEOhYwl6kHlpxN06A3IqV8y5pwlJII4ZpE5yYEX9qp+VMXsdm9Aw==", "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "DfeUTDggp3zRuVHoECcYdCliGhB1A/BdQoicWHddyTS4YptXxaow5Rads9qnmRIiBpSgR8d0vH9fwNxpQl0aBw==",
"python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "njYujmFBsfewILpjLI0nUN34rqzMNITattpewwxE4LuTcdkAgihQ3w/S/aYetSJzUtV/SH9mk26DhMciwn8KAw==", "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "9sIFDj6iIVEUE+lxa3Ryk/djxgiPMu78MM/WQdjYfpVQNv2OQ7IkBu121iNa5XIv/CTFMl1G6KGTWO5/Qm9iBw==",
"python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "zE2s6oQ3YqanaFG4RMNPydekiblRQVBk7QEJ5G30W2thGqzWt+1BW+75XdwaCigw//rINxKHbt3Neu5rub7PAw==", "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "CjvP1I4QBJu6zRW75XHEUWX6F6Wks9xtnuu1hdjv/V2Vw11Vf+Wa8zCztpKBgMx3dzbQ/p++xpTf5p7TCoMyAA==",
"python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "hBKd/smbmwEszsGjU7FP2RNVmrINBpU4NPZMGDjEHAF7Rx1WBF49+pH1s0k571RBqdtqN7nya2HCnKKO0tHUBw==", "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "WNq4sNwhPeu7NuW+hj1UOv1lAiQN2k8lPwImfeS7vFLY0G+pUBpnCVhD49SmjMmPyfCoxJkxD+wdhogPT2u6DQ==",
"python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "eKa9qfsfNNaSQpqggWSGw5YV/6cJk5iJcmhY+N/v3xdFvEMfCsIf+N3BhP0S3ZDO5T2CeE/I/Xc+1QbZh7oNDQ==", "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "FJ7yptMwvQxz5kB6XLjNJKBDy3+jrwWsPe/KBa8ki/e6uNavH63Umu18z5e6aY4AYp9D8z0pBOt5lk1X+bFvBg==",
"python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "k4BcKQAua+1YSC6P8jVEu2wBrI+WL8Bb5kKQbOnBYn/lupnrA96vMRYu4XVVDLGBoIqK8L7K/PYCYxu9Cx4RDA==", "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "9sX2Peumx+BT3G+LRIbPtcs42mULnRiXJMbQq3esj7yfaGsbf3l5XQ/9BDr8U/A6aFWfatOYSwbFD37ZGdLxCg==",
"python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "+53NsV73jkF/1x9Bzsw99oZRhVBtqlQTeXaPfHydTAssd2V3UMO+BWqagtGbVG5qIOcgjaNweNExGu+Abo7iDA==" "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "uyUgRSqgfhMcboJeDLTF1vAuw6thTLubkKp4wsn3i3VFAGkLzE0fEkhzoRo1TSn8hkb787kNQs+Z/woCPxi5AQ=="
} }
} }

View File

@@ -1 +1 @@
8t0c8OQ6AY5JlVXqGQkiF2rlj3kN3MIwGHVz4TOgaudkW7XegnRhUqngcpNo5C74wFDKNGWsvXOJ4WtIiIrfCg== aZ2PpZxlTDJ1Nffu+hFtfKK0qyUkkllomgYgTX69J4YU4/aODiwBvJDwHZ6+1nlvv4fDA10s7SPNxwwP9j0OBA==

View File

@@ -186,8 +186,8 @@
}, },
"python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "6e6de2f3270ad097c9b847fa10e833bac6b8eaab8ddad955603d163474a3c846", "sha256": "10896807faa5044f6d42807fb7e5efefc6ef64243edea9da5639332964e1cb19",
"size": 67616 "size": 67568
}, },
"python/iqpilot_private/navd/__init__.py": { "python/iqpilot_private/navd/__init__.py": {
"mode": 420, "mode": 420,
@@ -196,123 +196,128 @@
}, },
"python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "516370d0603f5134955826a3d8e9fea8ec0a9d27c116ad05af149a5c7ac15bcc", "sha256": "5faf834f012075671a390de2fe4ebb09f9c7d4c05406fa89407aa9e011e9d38c",
"size": 269936 "size": 269896
}, },
"python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "dc8f27970c61a3b60c383ea69804896ee356d76c8bf7af5532a9338525fbc956", "sha256": "a8361fe53878b61d30d430d0b0488eaabe1641e11b7e584e1ad3566370e55085",
"size": 202040 "size": 202088
}, },
"python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "96eb02a86cab08d557f8df0adebd2cc72232b9348e9896acd9b71784a86b42d5", "sha256": "47c673a898e6ceb321f01eff980250bbc85ddd058a8161098e8e91cb02f8f4a6",
"size": 136960 "size": 136928
}, },
"python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "8fb8cc47f47971c530a68ccd81b9b5d9fc8ce82cdd1270967fa845399009e519", "sha256": "55a8b9eb2e17f9baa1a851919d3ce908b69004bfbd09591985195af2da4cc40c",
"size": 135960 "size": 135928
}, },
"python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "b49f7ce7e9a33777ffa896a95fc4a5580988b4966d5a9330efd8c79a5116be91", "sha256": "115b57594d411fde87f66d0d030cd5de043bf8d986274dfb8fa7007b8f752c87",
"size": 135584 "size": 135496
}, },
"python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "747ae769bb35650dcd16b94a0a6260213373e352ede88bc1e57f114f27761d5a", "sha256": "90c8b38b5c0a328c9ccce27292f8399546a237cc72caf837ffa4c1a55a529c47",
"size": 135632 "size": 135600
}, },
"python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "eae425cd9bf60286fff4b980c6057351cb940f28981556269f05022258d80f0c", "sha256": "6d80f03b5a4da8083d4b7f2d4b165748556230859bcdb96c2ff5164aa5500b6c",
"size": 69752 "size": 135256
}, },
"python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "0fc4356fe57a11538e4a248dc803ceee61781d7c700640f78241a2d57cbb4574", "sha256": "a6809b5c118c603706e6b709597a29daa2345fd53d736526f6a055a009ad70ac",
"size": 68144 "size": 68128
}, },
"python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "92521da14a5bf334324f32315b13773d37d5f7d209d4bad6d62afc12ab5d1705", "sha256": "05f7b0433be88afdae23a68f7acd92ff0c8ce6b04d83fc46ad2233c3df51dea0",
"size": 203056 "size": 203024
}, },
"python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "67454e461abb76c7a3053a7f728baa0beb30fba235639a0a2a927a9c80443a4a", "sha256": "96414985cacbd85d02f69473b84ff8215fbf959996ca1f1f0dfdf7c77503c9cc",
"size": 135560 "size": 135536
},
"python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "8bd53fba3f00e3f8320f873e72b3665f22b6065ee10425d270167b21d2348ad9",
"size": 136576
}, },
"python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "9e0d5e929bfcd960fdba11804d1f70848ba40a68a65093c025a6d48171d0b2cc", "sha256": "d395b00b81da1acdab40d6033b5ccf084effcc6531978af63d568d4ba17ee894",
"size": 797392 "size": 862920
}, },
"python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "33d40decd32e8cfe3f9a267ebfccf48e8bfc67bf9609206ef230a9566bf5581d", "sha256": "43bdd7f93c317f26bfa9203953b3a35bf91bd0475dc3777aa5fc3e991216c3f4",
"size": 135344 "size": 135464
}, },
"python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "edf0d16511686ed3d4d7d6bf2b6b44fd8d484af348dab0eb415999656eb1d0af", "sha256": "4678f2e256e0fd2b01d4441af848056b173d7d2d6c8ba590ad32b8e23ee8591e",
"size": 135584 "size": 135552
}, },
"python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "b11bec6f1b49f169f218a87467781b6d62d64c367e0c3df5356341adce8c3f6b", "sha256": "52e4d03639c9d9be88209ab2e4a1f0f23c0adf81631983248f7d069d46cf61a5",
"size": 202384 "size": 267936
}, },
"python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "d38218a6787234730ed39bdb04c00ab0778b1b56eefcedcb9ecfb31346d36378", "sha256": "cb066c1e5637b8a6b60b44753b6a0855a04ce7f5771464d71fc9da245de4a56e",
"size": 136216 "size": 136224
}, },
"python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "f89f69e128b02eebc31b4c3702d62e12747f837863ad390026ea5004e506ea7c", "sha256": "2e226e29757151bc3cf350a933f6e8f402d7149b0b9e66b82ac9cb721cadef8e",
"size": 203136 "size": 203096
}, },
"python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "311926ffa2b4474320411240e6656ee676ce8b61d1cea444c34cd9520113a6a6", "sha256": "2b12153db93198ec46c268384cfbd65a9ae526faa584274987ed8a289531547c",
"size": 943640 "size": 1009128
}, },
"python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "dc08b2e6bf7be3fe0b4a7ccdfceab6c434317e7626efd9b40ea5bf15739647ba", "sha256": "6ba9e9ae810a4e283a8ad544e1f42e899e2e98939754f9b88ffc21f320a8514f",
"size": 135760 "size": 135752
}, },
"python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "f47146eddd7c8b499359d411150533c0e28e80851096d076ee8552a4d45175d9", "sha256": "0e233af11a3c07d6469e221b5f02cebee4addfe1706d635f8ed7624eb16ff908",
"size": 69720 "size": 69688
}, },
"python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "98845f8be1fd44009acf88fe685f6985ffb996d5d508858bdbb3edd2984a5146", "sha256": "8485347d40907cd0e654b93e1e872dffd156278c3868c640f7d2e51884d52a00",
"size": 135968 "size": 135944
}, },
"python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "15aba494968b9673987d2d0168d797bfbd307046555cb89efa2378acc8bb042a", "sha256": "c8b907c51c69c7950bc99ff577ddf4473dbb42f90e53252e3dd325df2a4f9ed3",
"size": 136936 "size": 136944
}, },
"python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "715f94a985599a983750affa5bf36e1fc2e6502871f1af7de553f9aaf29028cb", "sha256": "c13b5fdec4a8d4a33dbd7c25d9ffba30f6faffa1d286438d80c4d9a615b31145",
"size": 69768 "size": 135280
}, },
"python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "2658d1a9f08f49e1294a3c26be733170ac7943970a79ee2969d9404cd4b55758", "sha256": "695322ac198a75121f05989387bd32a733f18921f01f373d45e22f7ec839e8a5",
"size": 204072 "size": 204064
}, },
"python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "d03bf4d8d7d1547c5fcd085dea2ff78f8071e04e51e7f4d002f7ba23b431038e", "sha256": "24d967c264e1b879b0517da9cd6fddefdc4787efe6a52adcd2429efaac2db3db",
"size": 201400 "size": 201368
}, },
"runtime": { "runtime": {
"entries": { "entries": {
@@ -351,30 +356,31 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "9i3/SfFfCLGyTzyQQYpsSxbA3ASc1OSK7fCYXpIiIMnX+jKB288Qy7TzJnr3uINt+LhRW1wwnMI6hWBXjgv5AA==", "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "3zeWvLW0gfxiEeHYfOO7atzH0V9/YxbbPpLV6cdokoSP+UlxRJUrMTkJiNCiLBzXuhnNbEhj0Y6q9dLK8QNDDw==",
"python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": "5K4w4dDRtts8QvxBAXL0qrYofb/bg9PV/rKn/GrVmtAVDMd0DhAiKAugoxkhl8n4JviFY8ZmsHwWBrQPIMdkAA==", "python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": "1vJWjDPECvOrFe5UfWFN3rFeZSfO3Te9o3Xj0ue1Q+2Zt+gMkvaXgOZG3DNfbs7yaZ2yheabOhKRdwvwHGumCA==",
"python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "X+hzm32FyMwE9fSIaGZH/GQqHn78izqrX8o1FdATb0Fg4+62tcgI9LHe9PIM8VY6+IaryaWyKqFtATaq6BZrDw==", "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "6IWRbqN7iDaCKDPQ3Ho6cEhWrP0Tp6f5nKoo5sXEEAtYfUJVZADBHXY7Qk5MszNoa13Sn+8H2eE0imUwjGUtDA==",
"python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "dQnA8CCeRMXqiARr/Kzf2x736DuaT1/ALC8ZLDZep2/nY2IJ7StZeZ8DwOBuRPRK8KJ5HNU424QMZkVzQcDnBg==", "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "gakLE/auDWpkOm9vOzHryjZm5dAIln/GbASMoFl4iMiMRVmFmBjdV0uyY6bkVDKPVHX6FRr65f1yuJbF1d09DA==",
"python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "tdLykhS2t9M7R115Rse80M2Urg/8sNQTLaOz3YFyUTTkEzpaBlk3JnbgOQVpzDjrPB7y22y6mNORCkYI3cKfCw==", "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "yOqbMSdUVZSlHAiw08Yb+vSyEr2YoHBsSIWtFQa1RqoSmLbeLUeFpTiuczVaW/HtCNeZZU8mJYXWyTUM1N0xCQ==",
"python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "MJ/i1ZvGb3bYBVxKvT/zBk43CM/XT37GZa6la52Tfmqp9mrx6oz98PpvLZMgJ+18J4lSdMf2uGuJ2IHx5wWTAQ==", "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "8uEYXTWYXVzhDctzoEBbSc5wJ0mqX8qAe9DuXeKgfmpPsM6rR8jHpjHiyjkLAAPorWDRVN3a5W3iyH2u2tD6Cw==",
"python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "+ULQz9iDSDBtYRYSVVWz63TImB3DizGV2oFER6pmNttfJ6vA4SBVToxKV1YjHAebdMiSrTBrEkgPd5K4unE0Bg==", "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "+kTCQnh0TKGtTVl/XqtKJfrFDVJloVVhsUmt793WbvaULUK9aAJmtaEJRvTNV/DiOjYGdgL1texfnBw3x3CkDw==",
"python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "PB+q882Oo/cOi+fN0IahlaBP7xEQrjF7BykAnCsxrnK8zJGkFZdrVUcLCS4T5DYjXhwlXbdveb3ZHdG/LoMcBQ==", "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "oFJS3s2xxAmMxBpLAiaFklhstR8719Ah/GtUy+YMPdaDZBP6TYLIe89aqmPjW7LUHagkmxsJogppJXl3YtFUAg==",
"python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "vqQRU0t5AS/oUs1JWxZlwHC5mrpaaHD10jgQCLzDGUL4Yv1aFHWAoM6KfFdUldmdSG1d4e6VGu+Y2bMdBIwAAw==", "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "rsDD81jJXWuvS+IYSDwKVZy/C2lvnmmEh+wYucqLuUg64BIW5Q8YpdtRM9Z++fYpSut90FeYHtrIJsAavTmOAA==",
"python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "OfuUD6/swlx5ggtvLfUVjGqpJbZWf+Ezwia5fE81FXA6RcuR4SMx1t/D38+TaMKDGvaBV31s08TJgh+/hXiiDA==", "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "5CiNWqZqmmiNfnj1plhm67WEF5PXkPGr6dMsVui4VplsrVwaWCSQYJ4ywPHlsoUeX+c4FoCX7HJSNqwxjn5IBg==",
"python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "WQOzntcyeraddagnvnxAwH/ciLFMztjfEhg07HFHy+++XJPCSGctdaxlgPWX6xq9kiE7aEkcTsW2CwOwYo3tDw==", "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "dkNKNQEHnq0Wzw/Usmwf/qNxN72cpBZBnPHRKN1ZEq5nExMqrrraTe6bn+EWM2yo1hMsXsep5JPpNGxpqqusBQ==",
"python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "5c68ZgFSkAXf9m32zKbBDSNHF0rVTwZPVu0puTiIcM7laHyHVtkQJduq6g4SUm+wz634nLK2u5pggaNf+mkhAw==", "python/iqpilot_private/navd/navbridge_source.cpython-312-aarch64-linux-gnu.so": "0PDFbzaD2KoWZRAP6I38SKdUG0T11j86Ue27RgjziWLAPuxDSd6ogJPbTuPxKLV6pBIASN6y1KU3Cf4BLOkKBg==",
"python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "LBV8K1SPj+bN8idsPR/3B24grXQYRNarYsCUbVaRdkfORuib70gRlGz4KtyOyYT/vpCU4iuDhnSRsd4ltH4RAg==", "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "WikwGS5js59suyyzVj90X0dYz6I+wBb7N/KHRGkD4kYEX59jP8hAK+MlLsUAr9Vbmss131S8z5inlifsCjN4BA==",
"python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "ZT4bVD2W2w9w2AdbKkZ7AgfQv+zqTmJH5126ngiDDRFFuW2Bnp+E/4M+6U912uGZsV5xayCw8QmJbM9EeSqgDQ==", "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "LOnh9gEJeT8leh4xo0gabomCwTOo1Xhp2oALjrsL0Ij1MCur9bLl5QE33iFVcomQr8jGH0iw7Vq1vXDMlIRuBg==",
"python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "jjmBJtLWRVkPsO4n7/N/pDVD9jrC4SCMm0lIT/P8HCzsMhIIAkXhRAMGKdl1kYDxK8VMGRMMF7eoiCKEFPq2AA==", "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "KthwWou6yvP+17m4g/3PGY7aPTKKjUqLT8KTtsiTr8/y3Le6YTntbn680C1hJU/xKm1j+IcnAomDw7wdma1PDA==",
"python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "A0FeGUh384rTKZJLOL6wNdX3bLTTsmUX7kF3ysax7WjnyUSlctbms15E+3Ww87qmXwjMGbyPdlTdsnhvpcstAA==", "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "cdd78rET10BlVNmGXsNJgYpbS9VCSNEB5HJQ9krzeMu05ikqVnI7vHA/zlxmuHHFiHnZyAN00c16mEmgf62dDw==",
"python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "J/I+XcODFE7LKvtyImt/Dw8fh2uvQeEhZ1IWx2IHZPH8sXhuVGVQIECVFGQJGfsckjzoUpgZq8AYNReRF62sBw==", "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "QyrJUPOGnI39p3n24ZCKdHG+MI/bqIvBwUdFyGO527eqHVVWJyinfneK+X7vARV+HLwxqO5bBcgwqHLdi9enAQ==",
"python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "n/RFD32mlinaYGrWvKWGv8ziTTSFP8+qCS/I1ibFXRYU6gtwx/+03eUR+p2jh5hzPteo5RxLUfHDwxFvJMJzDA==", "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "YkM9q3yJaDmYhExd3Te/7tpFQXXrt+/82M0QRZrVxjKRKOxrR7xu3jdjXQIT67I6WxpHzsNIFhnzX2Bl2/9eBQ==",
"python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "/5mYKn9um2aBLipkFsEptfM3sOId3SKkV8AHXGslWdH9Evp2ll8XENvirAX5tV1X35WslGYIybK3Xv3cZ4o8DQ==", "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "+Uu9KthRwOh5+YJaj1/SD/QuSOfEuT0d4CwO//wB7sCueUKsVx7+CcyAC5T2YB8bRN+42NJDn8pQeA9JcrPoDg==",
"python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "kfl1Mm7G3CguQGtUvDutvbd4StDnKr0pW7ojLn6gydVTyxEiD/VAEFfTKBK/CAyUcw3eeX5+U8Qq2+6o0zEUBA==", "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "BoQYpXsWgO+RdUhI5HL4WSk8La2HJ8V8DSFWvAylCaDNWJX3yaClso603Nf0CcKd1MDycb8J33hiHvJSd7R7AQ==",
"python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so": "SzicVJ2/F8aMjcOHVCmEeJ952a+JPL8NLI6wsVkA2f5xiOSKMdynvcf7EgvrvBA4bgTRUGOmtpoqzMiCcNh2AA==", "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "5+ip8rEk+Upsvyp2tlfdVhmIdQnlamloY1SCuwFzkSM39y+W9tvo4r7JTeb5pcMiUijmwK54m4Rvx0XWvxz5AA==",
"python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so": "tMmnyV9knGA0eoRq1+bYLaQRAmLLrpSC9KraH9iNWMgleg+xdY3dIpx+uyP77de/oOQ2Bovsp3Vq2MobcDACCQ==", "python/iqpilot_private/navd/traffic_sources.cpython-312-aarch64-linux-gnu.so": "WtPKZHGDG8iQj7q1UsXw6jTXxJsWLfNaw9kfJ3huWYjxWcHz0K7ibJ5JzEGSc5l5u3LdRkt6/aZHlBMrDPBaBA==",
"python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "kFb5U48pTMYRWee+JbH6CbOpE2je60Xk5ntJZIUCw66KWLTOUGFtQqMcUUcQ1b2/3xnCz7sb3Yt0GdbyyspMDg==", "python/iqpilot_private/navd/traffic_writer.cpython-312-aarch64-linux-gnu.so": "VVFiWnkiwn7nwhq+6q3aYsnxMW1GLdfaGB/a/h8JV46OoSn0NOTVA72jPYlzs0d4bacEN+IgMAelCjnMlqiBBA==",
"python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so": "u6o5lRGFT3PA7XPM0IXyr7iEsngyLKqh+kJhHNW7U7+iTI3hH25ag73YFQjnCfIuzPk2NtB4v2JKqcqBaNaQAQ==", "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "upuJWa/0B6du9cu3JwdzK1cSqSAORKfvd6ir7sh/oWSRPEhkxyjPmo9/uoNw4Qeq2zoh3iKkV2udK5gue/PzCQ==",
"python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "4AB7oakyIWLG7OFuGpO+SidZnJzq3rC7/uWemSbhFVn9oOZBMQZ5IWN5Y3WfImyqoZXjZVGqxE9Yf0d8gmWdDA==" "python/iqpilot_private/navd/valhalla_process.cpython-312-aarch64-linux-gnu.so": "DXSI5ljQTXPHV+QvQCQu1+oOfLKcPXMkx7qx6id0NTFPHI/M5KIooBYKgJjbN4abl+mzUXKrtOmGF/y3f0lwDw==",
"python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "h4bQodknw1y0emCI3aJYviy3kzaQH8hElJMFmpS/sMvvC+sCHfOGUo+8BK+T7JGZ9/VzNXBzP8RKkHG5cAf0Cg=="
} }
} }

View File

@@ -1 +1 @@
NH/aexXGu1jWh6ks/x7lbVEUX9VZPhETrwkIozXSjmPR2isUyVlaZppveq23NB7BeIQTJuS/+KAuZ/pbL/kaDQ== kmjHw5sbr1uhbckWG6FoCjTR0idPL+RGlQ19Wri9phayqsni9P3V2rqLDUPeZ0ds+CRqMb378Jgghj6eJ6gxCQ==

View File

@@ -238,6 +238,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IQEmacEnabled", {PERSISTENT, BOOL, "0"}}, {"IQEmacEnabled", {PERSISTENT, BOOL, "0"}},
{"IQEmacHost", {PERSISTENT, STRING}}, {"IQEmacHost", {PERSISTENT, STRING}},
{"IQEmacModel", {PERSISTENT, STRING}}, {"IQEmacModel", {PERSISTENT, STRING}},
{"IQEmacSmallModel", {PERSISTENT, BOOL, "0"}},
{"IQEmacCatalogCache", {PERSISTENT, STRING}}, {"IQEmacCatalogCache", {PERSISTENT, STRING}},
{"MacModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "1.0"}}, {"MacModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "1.0"}},
{"MacModelStatus", {CLEAR_ON_MANAGER_START, STRING}}, {"MacModelStatus", {CLEAR_ON_MANAGER_START, STRING}},
@@ -315,6 +316,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IQEmacEnabled", {PERSISTENT, BOOL, "0"}}, {"IQEmacEnabled", {PERSISTENT, BOOL, "0"}},
{"IQEmacHost", {PERSISTENT, STRING}}, {"IQEmacHost", {PERSISTENT, STRING}},
{"IQEmacModel", {PERSISTENT, STRING}}, {"IQEmacModel", {PERSISTENT, STRING}},
{"IQEmacSmallModel", {PERSISTENT, BOOL, "0"}},
{"IQEmacCatalogCache", {PERSISTENT, JSON}}, {"IQEmacCatalogCache", {PERSISTENT, JSON}},
{"IQEgpuDisabled", {PERSISTENT, BOOL, "0"}}, {"IQEgpuDisabled", {PERSISTENT, BOOL, "0"}},
{"IQEgpuEnabled", {PERSISTENT, BOOL, "0"}}, {"IQEgpuEnabled", {PERSISTENT, BOOL, "0"}},
@@ -359,6 +361,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
// iqpilot model params // iqpilot model params
{"CameraOffset", {PERSISTENT, FLOAT, "0.0"}}, {"CameraOffset", {PERSISTENT, FLOAT, "0.0"}},
{"IQAndroidNav", {PERSISTENT, BOOL, "0"}},
{"IQAndroidNavStatus", {PERSISTENT, STRING}},
// "waze" or "maps": exactly one runs, the other is disabled outright.
{"IQAndroidNavApp", {PERSISTENT, STRING, "waze"}},
// Consumed by androidd to sign the container in once, then cleared: the credential is
// never meant to survive a manager restart or reach a log.
{"IQAndroidNavEmail", {CLEAR_ON_MANAGER_START | DONT_LOG, STRING}},
{"IQAndroidNavPassword", {CLEAR_ON_MANAGER_START | DONT_LOG, STRING}},
{"IQLiveSteerDelay", {PERSISTENT, BOOL, "1"}}, {"IQLiveSteerDelay", {PERSISTENT, BOOL, "1"}},
{"IQLateralAccelSlew", {PERSISTENT, BOOL, "0"}}, {"IQLateralAccelSlew", {PERSISTENT, BOOL, "0"}},
{"IQLateralCurvatureLookahead", {PERSISTENT, BOOL, "0"}}, {"IQLateralCurvatureLookahead", {PERSISTENT, BOOL, "0"}},

View File

@@ -235,6 +235,7 @@ class Controls(IQControlsLayer):
actuators = CC.actuators actuators = CC.actuators
actuators.longControlState = self.LoC.long_control_state actuators.longControlState = self.LoC.long_control_state
actuators.speed = float(max(long_plan.speeds, default=0.0))
if not CC.latActive: if not CC.latActive:
self.LaC.reset() self.LaC.reset()

View File

@@ -63,6 +63,11 @@ def resolve_model_name(params, keys) -> str:
from iqpilot.selfdrive.iqmodeld.egpu_model import DEFAULT_EGPU_MODEL, resolve_egpu_model from iqpilot.selfdrive.iqmodeld.egpu_model import DEFAULT_EGPU_MODEL, resolve_egpu_model
resolved = resolve_egpu_model(params, allow_refresh=False) resolved = resolve_egpu_model(params, allow_refresh=False)
return resolved["key"] if resolved else DEFAULT_EGPU_MODEL return resolved["key"] if resolved else DEFAULT_EGPU_MODEL
if params.get_bool("IQEmacSmallModel"):
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
bundle = get_active_bundle(params)
if bundle is not None and (bundle.internalName or bundle.displayName):
return bundle.internalName or bundle.displayName
name = params.get("IQEmacModel") or b"lebrowski" name = params.get("IQEmacModel") or b"lebrowski"
return name.decode() if isinstance(name, bytes) else name return name.decode() if isinstance(name, bytes) else name
@@ -168,7 +173,7 @@ def _patch_and_send(pm: PubMaster, payload: dict, frame_drop_perc: float, select
if mismatch is None: if mismatch is None:
mismatch = source_lag > 0 mismatch = source_lag > 0
big = payload.get("source") in BIG_SOURCES big = bool(payload.get("big", payload.get("source") in BIG_SOURCES))
model_msg = log_from_bytes(msgs["modelV2"]).as_builder() model_msg = log_from_bytes(msgs["modelV2"]).as_builder()
if mismatch: if mismatch:
model_msg.modelV2.frameId = target model_msg.modelV2.frameId = target

View File

@@ -272,6 +272,23 @@ class TestChannelContract:
assert sent["modelV2"].modelV2.frameDropPerc == 0.0 assert sent["modelV2"].modelV2.frameDropPerc == 0.0
assert sent["cameraOdometry"].valid assert sent["cameraOdometry"].valid
def test_selector_big_flag_follows_payload_then_source(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import _patch_and_send
sent = {}
class PM:
def send(self, service, msg):
sent[service] = msg
payload = make_big_channel_payload(42, True, 0.03, 25.0, self._real_msgs())
_patch_and_send(PM(), payload, frame_drop_perc=0.0, selector_dropped=0, target=42, source_lag=0)
assert sent["modelV2"].modelV2.big and sent["drivingModelData"].drivingModelData.big
small_on_mac = {**make_big_channel_payload(43, True, 0.03, 25.0, self._real_msgs()), "source": "mac_big", "big": False}
_patch_and_send(PM(), small_on_mac, frame_drop_perc=0.0, selector_dropped=0, target=43, source_lag=0)
assert not sent["modelV2"].modelV2.big and not sent["drivingModelData"].drivingModelData.big
def test_selector_lag_patches_frame_id(self): def test_selector_lag_patches_frame_id(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import _patch_and_send from iqpilot.selfdrive.iqmodeld.modeld_selector import _patch_and_send

View File

@@ -9,6 +9,7 @@ LONGITUDINAL_MODE_STOCK = 0
LONGITUDINAL_MODE_CHILL = 1 LONGITUDINAL_MODE_CHILL = 1
LONGITUDINAL_MODE_DYNAMIC = 2 LONGITUDINAL_MODE_DYNAMIC = 2
LONGITUDINAL_MODE_PILOT = 3 LONGITUDINAL_MODE_PILOT = 3
IQ_LONGITUDINAL_MODES = (LONGITUDINAL_MODE_CHILL, LONGITUDINAL_MODE_DYNAMIC, LONGITUDINAL_MODE_PILOT)
PERSONALITY_AGGRESSIVE = log.LongitudinalPersonality.schema.enumerants["aggressive"] PERSONALITY_AGGRESSIVE = log.LongitudinalPersonality.schema.enumerants["aggressive"]
PERSONALITY_STANDARD = log.LongitudinalPersonality.schema.enumerants["standard"] PERSONALITY_STANDARD = log.LongitudinalPersonality.schema.enumerants["standard"]
@@ -60,6 +61,20 @@ def apply_longitudinal_mode(params, mode: int) -> None:
raise ValueError(f"invalid longitudinal mode: {mode}") raise ValueError(f"invalid longitudinal mode: {mode}")
def longitudinal_mode_needs_cycle(previous: int, mode: int) -> bool:
return (previous == LONGITUDINAL_MODE_STOCK) != (mode == LONGITUDINAL_MODE_STOCK)
def next_longitudinal_mode(current: int, onroad: bool, iq_modes_available: bool) -> int:
if onroad:
order = list(IQ_LONGITUDINAL_MODES)
else:
order = [LONGITUDINAL_MODE_STOCK] + (list(IQ_LONGITUDINAL_MODES) if iq_modes_available else [])
if current not in order:
return current if onroad else order[0]
return order[(order.index(current) + 1) % len(order)]
def get_follow_distance_state(params) -> tuple[int | None, bool]: def get_follow_distance_state(params) -> tuple[int | None, bool]:
mode = get_longitudinal_mode(params) mode = get_longitudinal_mode(params)
if mode == LONGITUDINAL_MODE_STOCK: if mode == LONGITUDINAL_MODE_STOCK:

View File

@@ -7,6 +7,7 @@ import pytest
from iqpilot.selfdrive.longitudinal_settings import ( from iqpilot.selfdrive.longitudinal_settings import (
LONGITUDINAL_MODE_CHILL, LONGITUDINAL_MODE_CHILL,
LONGITUDINAL_MODE_DYNAMIC, LONGITUDINAL_MODE_DYNAMIC,
LONGITUDINAL_MODE_PILOT,
LONGITUDINAL_MODE_STOCK, LONGITUDINAL_MODE_STOCK,
PERSONALITY_AGGRESSIVE, PERSONALITY_AGGRESSIVE,
PERSONALITY_RELAXED, PERSONALITY_RELAXED,
@@ -16,6 +17,8 @@ from iqpilot.selfdrive.longitudinal_settings import (
get_follow_distance_state, get_follow_distance_state,
get_longitudinal_mode, get_longitudinal_mode,
get_runtime_personality, get_runtime_personality,
longitudinal_mode_needs_cycle,
next_longitudinal_mode,
set_valid_personality, set_valid_personality,
) )
@@ -108,3 +111,31 @@ def test_dynamic_and_pilot_enable_valid_personality_selection():
params.values["IQDynamicMode"] = False params.values["IQDynamicMode"] = False
assert get_follow_distance_state(params) == (PERSONALITY_STANDARD, True) assert get_follow_distance_state(params) == (PERSONALITY_STANDARD, True)
def test_onroad_cycles_only_between_iq_modes():
assert next_longitudinal_mode(LONGITUDINAL_MODE_CHILL, True, True) == LONGITUDINAL_MODE_DYNAMIC
assert next_longitudinal_mode(LONGITUDINAL_MODE_DYNAMIC, True, True) == LONGITUDINAL_MODE_PILOT
assert next_longitudinal_mode(LONGITUDINAL_MODE_PILOT, True, True) == LONGITUDINAL_MODE_CHILL
def test_onroad_stock_acc_is_locked():
assert next_longitudinal_mode(LONGITUDINAL_MODE_STOCK, True, True) == LONGITUDINAL_MODE_STOCK
assert next_longitudinal_mode(LONGITUDINAL_MODE_STOCK, True, False) == LONGITUDINAL_MODE_STOCK
def test_offroad_cycles_through_stock_acc():
assert next_longitudinal_mode(LONGITUDINAL_MODE_PILOT, False, True) == LONGITUDINAL_MODE_STOCK
assert next_longitudinal_mode(LONGITUDINAL_MODE_STOCK, False, True) == LONGITUDINAL_MODE_CHILL
def test_offroad_without_iq_modes_only_offers_stock_acc():
assert next_longitudinal_mode(LONGITUDINAL_MODE_STOCK, False, False) == LONGITUDINAL_MODE_STOCK
assert next_longitudinal_mode(LONGITUDINAL_MODE_PILOT, False, False) == LONGITUDINAL_MODE_STOCK
def test_cycle_only_when_crossing_stock_boundary():
assert longitudinal_mode_needs_cycle(LONGITUDINAL_MODE_STOCK, LONGITUDINAL_MODE_CHILL)
assert longitudinal_mode_needs_cycle(LONGITUDINAL_MODE_PILOT, LONGITUDINAL_MODE_STOCK)
assert not longitudinal_mode_needs_cycle(LONGITUDINAL_MODE_CHILL, LONGITUDINAL_MODE_PILOT)
assert not longitudinal_mode_needs_cycle(LONGITUDINAL_MODE_DYNAMIC, LONGITUDINAL_MODE_CHILL)

View File

@@ -6,13 +6,17 @@ from iqpilot.common.params import Params, UnknownKeyName
from iqpilot.selfdrive.longitudinal_settings import ( from iqpilot.selfdrive.longitudinal_settings import (
LONGITUDINAL_MODE_DYNAMIC, LONGITUDINAL_MODE_DYNAMIC,
LONGITUDINAL_MODE_PILOT, LONGITUDINAL_MODE_PILOT,
LONGITUDINAL_MODE_STOCK,
PERSONALITY_VALUES, PERSONALITY_VALUES,
apply_longitudinal_mode, apply_longitudinal_mode,
get_follow_distance_state, get_follow_distance_state,
get_longitudinal_mode, get_longitudinal_mode,
longitudinal_mode_needs_cycle,
next_longitudinal_mode,
set_valid_personality, set_valid_personality,
) )
from iqpilot.selfdrive.ui.mici.widgets.stock_button import BigButton, BigMultiToggle, BigToggle, BigParamControl from iqpilot.selfdrive.ui.mici.widgets.stock_button import BigButton, BigMultiToggle, BigToggle, BigParamControl
from iqpilot.selfdrive.ui.ui_state import ui_state
from iqpilot.system.ui.lib.multilang import tr from iqpilot.system.ui.lib.multilang import tr
@@ -138,24 +142,48 @@ class IQModeSelector(BigMultiToggle):
super().__init__(tr("IQ Mode"), self._display_options) super().__init__(tr("IQ Mode"), self._display_options)
self._params = Params() self._params = Params()
self._mode_callback = mode_callback self._mode_callback = mode_callback
self._mode = LONGITUDINAL_MODE_STOCK
self._iq_modes_available = False
self.refresh() self.refresh()
self.set_enabled(lambda: self._next() != self._mode)
def _index(self) -> int: def _index(self) -> int:
return get_longitudinal_mode(self._params) return get_longitudinal_mode(self._params)
def is_dynamic(self) -> bool: def is_dynamic(self) -> bool:
return self._index() == 2 return self._mode == LONGITUDINAL_MODE_DYNAMIC
def _toyota_factory_long_forced(self) -> bool:
cp = ui_state.CP
return bool(cp is not None and cp.brand == "toyota" and self._params.get_bool("IQToyotaFactoryLong"))
def _read_iq_modes_available(self) -> bool:
cp = ui_state.CP
alpha_available = bool(cp is not None and cp.alphaLongitudinalAvailable)
return alpha_available or self._params.get_bool("AlphaLongitudinalEnabled") or self._toyota_factory_long_forced()
def _next(self) -> int:
return next_longitudinal_mode(self._mode, ui_state.is_onroad(), self._iq_modes_available)
def refresh(self): def refresh(self):
self.set_value(self._display_options[self._index()]) self._mode = self._index()
self._iq_modes_available = self._read_iq_modes_available()
self.set_value(self._display_options[self._mode])
def _apply(self, idx: int): def _apply(self, idx: int):
previous = self._mode
toyota_forced = self._toyota_factory_long_forced()
apply_longitudinal_mode(self._params, idx) apply_longitudinal_mode(self._params, idx)
self._params.put_bool("OnroadCycleRequested", True) if idx != LONGITUDINAL_MODE_STOCK and toyota_forced:
self._params.put_bool("IQToyotaFactoryLong", False)
if longitudinal_mode_needs_cycle(previous, idx) or (idx != LONGITUDINAL_MODE_STOCK and toyota_forced):
self._params.put_bool("OnroadCycleRequested", True)
def _handle_mouse_release(self, mouse_pos): def _handle_mouse_release(self, mouse_pos):
nxt = (self._index() + 1) % len(self.OPTIONS) nxt = self._next()
if nxt == self._mode:
return
self._apply(nxt) self._apply(nxt)
self.set_value(self._display_options[nxt]) self.refresh()
if self._mode_callback: if self._mode_callback:
self._mode_callback() self._mode_callback()

View File

@@ -128,6 +128,8 @@ class ModelsLayoutMici(NavScroller):
self._big = BigButton(tr("big model")) self._big = BigButton(tr("big model"))
self._big.set_click_callback(self._show_big_models) self._big.set_click_callback(self._show_big_models)
self._small_on_mac = BigParamControl(tr("active model on eMac"), "IQEmacSmallModel", toggle_callback=self._small_on_mac_toggled)
self._cancel = BigButton(tr("stop download")) self._cancel = BigButton(tr("stop download"))
self._cancel.set_click_callback(self._cancel_model_request) self._cancel.set_click_callback(self._cancel_model_request)
self._cancel.set_visible(self._is_downloading) self._cancel.set_visible(self._is_downloading)
@@ -158,7 +160,8 @@ class ModelsLayoutMici(NavScroller):
self._lane_speed = MappedParamToggle(tr("lane turn speed"), "IQLaneTurnValue", [tr("slow"), tr("normal"), tr("fast")], _LANE_TURN_VALUES) self._lane_speed = MappedParamToggle(tr("lane turn speed"), "IQLaneTurnValue", [tr("slow"), tr("normal"), tr("fast")], _LANE_TURN_VALUES)
self._lane_speed.set_visible(lambda: self._lane_turn._checked) self._lane_speed.set_visible(lambda: self._lane_turn._checked)
self._main_items = [self._current, self._big, self._cancel, self._supercombo, self._vision, self._policy, self._redownload, self._refresh, self._clear, self._main_items = [self._current, self._big, self._small_on_mac, self._cancel, self._supercombo, self._vision, self._policy,
self._redownload, self._refresh, self._clear,
self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed] self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed]
self._scroller.add_widgets(self._main_items) self._scroller.add_widgets(self._main_items)
@@ -394,9 +397,28 @@ class ModelsLayoutMici(NavScroller):
except (TypeError, ValueError): except (TypeError, ValueError):
return None return None
def _small_on_mac_toggled(self, checked: bool) -> None:
p = ui_state.params
if checked:
p.put_bool("IQEmacEnabled", True)
else:
p.put_bool("IQEmacEnabled", bool(p.get("IQEmacModel")))
def _small_on_mac_value(self) -> str:
try:
active = self.model_manager.activeBundle
name = _display_model_name(active) if active and active.ref else ""
except Exception:
name = ""
return f"{name} ({tr('eMac')})" if name else tr("active model")
def _big_model_value(self) -> str: def _big_model_value(self) -> str:
p = ui_state.params p = ui_state.params
dock = bool(getattr(ui_state.sm["deviceState"], "egpuDockPresent", False)) dock = bool(getattr(ui_state.sm["deviceState"], "egpuDockPresent", False))
if p.get_bool("IQEmacEnabled") and p.get_bool("IQEmacSmallModel"):
progress = self._big_setup_progress()
value = self._small_on_mac_value()
return f"{value} {int(progress * 100)}%" if progress is not None and progress < 1.0 else value
if not p.get_bool("IQEmacEnabled") and not dock: if not p.get_bool("IQEmacEnabled") and not dock:
return tr("Off") return tr("Off")
key = p.get("IQEmacModel") key = p.get("IQEmacModel")
@@ -577,5 +599,5 @@ class ModelsLayoutMici(NavScroller):
def show_event(self): def show_event(self):
super().show_event() super().show_event()
for w in (self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed): for w in (self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed, self._small_on_mac):
w.refresh() w.refresh()

View File

View File

@@ -0,0 +1,200 @@
# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
from __future__ import annotations
import json
import os
import socket
import subprocess
import time
from iqpilot.cereal import messaging
from iqpilot.common.params import Params
from iqpilot.common.realtime import Ratekeeper
from iqpilot.common.swaglog import cloudlog
ANDROID_ROOT = "/data/android"
HEADLESS = f"{ANDROID_ROOT}/waydroid_headless.sh"
TRIM = f"{ANDROID_ROOT}/android_trim.sh"
GUARD = f"{ANDROID_ROOT}/android_guard.sh"
LXC_ATTACH = f"{ANDROID_ROOT}/root/usr/bin/lxc-attach"
LD = f"{ANDROID_ROOT}/root/usr/lib/aarch64-linux-gnu"
# Waze's launcher is FreeMapAppActivity; there is no com.waze.MainActivity, and asking for
# one fails with "Activity class does not exist" rather than anything that names the problem.
NAV_APPS = {
"waze": ("com.waze", "com.waze/.FreeMapAppActivity"),
"maps": ("com.google.android.apps.maps", "com.google.android.apps.maps/com.google.android.maps.MapsActivity"),
}
BRIDGE_HOST = "192.168.240.112"
BRIDGE_PORT = 8099
GPS_SOURCES = ["iqLiveLocation", "liveLocationKalman", "gpsLocationExternal", "gpsLocation"]
PROVIDERS = ("gps", "fused", "network")
GPS_INTERVAL_S = 0.5
def attach(*args: str, timeout: float = 10.0) -> subprocess.CompletedProcess:
cmd = ["sudo", "-n", "env", f"LD_LIBRARY_PATH={LD}", LXC_ATTACH,
"-P", "/var/lib/waydroid/lxc", "-n", "waydroid", "--", *args]
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=False)
def container_booted() -> bool:
try:
return attach("/system/bin/getprop", "sys.boot_completed", timeout=8).stdout.strip() == "1"
except (subprocess.SubprocessError, OSError):
return False
def bring_up() -> None:
# AGNOS mounts / read-only, so no unit file can be installed; a transient unit is the only
# way the compositor and lxc-start survive this process exiting.
subprocess.run(["sudo", "-n", "systemd-run", "--unit=iq-android", "--service-type=oneshot",
"--remain-after-exit", HEADLESS, "up"],
capture_output=True, text=True, timeout=120, check=False)
def start_guard() -> None:
subprocess.run(["sudo", "-n", "systemd-run", "--unit=iq-android-guard", GUARD],
capture_output=True, text=True, timeout=30, check=False)
def trim(nav_app: str) -> None:
subprocess.run(["sudo", "-n", "env", f"ANDROID_NAV_APP={nav_app}", TRIM],
capture_output=True, timeout=240, check=False)
def enable_mock_location() -> None:
# Wiped by every container restart, and the package form does not cover uid 0: without the
# --uid form every injection fails with SecurityException and the apps just say "no GPS".
attach("/system/bin/cmd", "appops", "set", "--uid", "0", "android:mock_location", "allow")
for p in PROVIDERS:
attach("/system/bin/cmd", "location", "providers", "add-test-provider", p)
attach("/system/bin/cmd", "location", "providers", "set-test-provider-enabled", p, "true")
# The a11y tree is only updated while the display is awake; asleep it emits nothing at all.
attach("/system/bin/svc", "power", "stayon", "true")
def inject(lat: float, lon: float) -> None:
for p in PROVIDERS:
attach("/system/bin/cmd", "location", "providers", "set-test-provider-location", p,
"--location", f"{lat:.6f},{lon:.6f}", "--accuracy", "4", timeout=6)
def read_position(sm: messaging.SubMaster) -> tuple[float, float] | None:
for src in GPS_SOURCES:
if src not in sm.data or not sm.valid.get(src, False):
continue
msg = sm[src]
for lat_a, lon_a in (("latitude", "longitude"), ("lat", "lon")):
lat = getattr(msg, lat_a, None)
lon = getattr(msg, lon_a, None)
if lat is not None and lon is not None and (lat or lon):
return float(lat), float(lon)
return None
class AndroidNavDaemon:
def __init__(self) -> None:
self.params = Params()
self.sources = [s for s in GPS_SOURCES if s in messaging.SERVICE_LIST]
self.sm = messaging.SubMaster(self.sources) if self.sources else None
self.last_inject = 0.0
self.up = False
def status(self, text: str) -> None:
self.params.put("IQAndroidNavStatus", text)
def sign_in(self) -> None:
email = self.params.get("IQAndroidNavEmail", encoding="utf8")
password = self.params.get("IQAndroidNavPassword", encoding="utf8")
if not email or not password:
return
try:
with socket.create_connection((BRIDGE_HOST, BRIDGE_PORT), timeout=10) as s:
s.sendall(json.dumps({"cmd": "signin", "email": email, "password": password}).encode() + b"\n")
self.status("sign-in requested")
except OSError:
cloudlog.exception("androidd: sign-in request failed")
self.status("sign-in failed")
finally:
# Never let the credential outlive the one use it was handed over for.
self.params.remove("IQAndroidNavEmail")
self.params.remove("IQAndroidNavPassword")
def nav_app(self) -> str:
value = self.params.get("IQAndroidNavApp", encoding="utf8") or "waze"
return value if value in NAV_APPS else "waze"
def enforce_single_app(self, nav_app: str) -> None:
for name, (pkg, _) in NAV_APPS.items():
if name != nav_app:
attach("/system/bin/am", "force-stop", pkg)
def ensure_app_running(self, nav_app: str) -> None:
pkg, component = NAV_APPS[nav_app]
if attach("/system/bin/pidof", pkg, timeout=8).stdout.strip():
return
attach("/system/bin/am", "start", "-n", component, timeout=20)
def ensure_up(self) -> None:
if container_booted():
if not self.up:
nav_app = self.nav_app()
enable_mock_location()
trim(nav_app)
self.enforce_single_app(nav_app)
start_guard()
self.status(f"running:{nav_app}")
self.up = True
return
self.up = False
self.status("starting")
bring_up()
def step(self) -> None:
if not self.params.get_bool("IQAndroidNav"):
if self.up:
subprocess.run(["sudo", "-n", "systemctl", "stop", "iq-android", "iq-android-guard"],
capture_output=True, timeout=60, check=False)
self.up = False
self.status("disabled")
return
self.ensure_up()
if not self.up:
return
self.sign_in()
self.ensure_app_running(self.nav_app())
if self.sm is None:
return
self.sm.update(0)
now = time.monotonic()
if now - self.last_inject < GPS_INTERVAL_S:
return
position = read_position(self.sm)
if position is not None:
self.last_inject = now
try:
inject(*position)
except subprocess.SubprocessError:
cloudlog.exception("androidd: location inject failed")
def main() -> None:
if not os.path.exists(HEADLESS):
cloudlog.warning("androidd: android stack not staged, exiting")
return
daemon = AndroidNavDaemon()
rk = Ratekeeper(2.0, print_delay_threshold=None)
while True:
try:
daemon.step()
except Exception:
cloudlog.exception("androidd: step failed")
rk.keep_time()
if __name__ == "__main__":
main()

View File

@@ -67,6 +67,9 @@ def qcomgps(started: bool, params: Params, CP: car.CarParams) -> bool:
def always_run(started: bool, params: Params, CP: car.CarParams) -> bool: def always_run(started: bool, params: Params, CP: car.CarParams) -> bool:
return True return True
def android_nav(started: bool, params: Params, CP: car.CarParams) -> bool:
return params.get_bool("IQAndroidNav")
def only_onroad(started: bool, params: Params, CP: car.CarParams) -> bool: def only_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
return started return started
@@ -176,6 +179,7 @@ procs = [
PythonProcess("journald", "iqpilot.system.journald", only_onroad, platform.system() != "Darwin"), PythonProcess("journald", "iqpilot.system.journald", only_onroad, platform.system() != "Darwin"),
PythonProcess("micd", "iqpilot.system.micd", or_(iscar, livestream)), PythonProcess("micd", "iqpilot.system.micd", or_(iscar, livestream)),
PythonProcess("timed", "iqpilot.system.timed", always_run, enabled=not PC), PythonProcess("timed", "iqpilot.system.timed", always_run, enabled=not PC),
PythonProcess("androidd", "iqpilot.system.android.androidd", android_nav, 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),

View File

@@ -1,10 +1,6 @@
import os import os
import sys import sys
venv_site_packages = os.path.join(Dir("#").abspath, ".venv", "lib", "python3.12", "site-packages")
if os.path.isdir(venv_site_packages) and venv_site_packages not in sys.path:
sys.path.insert(0, venv_site_packages)
import imgui import imgui
import iqdbc import iqdbc
import libusb import libusb

View File

@@ -49,7 +49,7 @@ void ReplayStream::mergeSegments() {
} }
bool ReplayStream::loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags, bool auto_source) { bool ReplayStream::loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags, bool auto_source) {
replay.reset(new Replay(route, {"can", "narrowRoadEncodeIdx", "cabinEncodeIdx", "wideRoadEncodeIdx", "carParams"}, replay.reset(new Replay(route, {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"},
{}, nullptr, replay_flags, data_dir, auto_source)); {}, nullptr, replay_flags, data_dir, auto_source));
replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->setSegmentCacheLimit(settings.max_cached_minutes);
replay->installEventFilter([this](const Event *event) { return eventFilter(event); }); replay->installEventFilter([this](const Event *event) { return eventFilter(event); });

View File

@@ -90,3 +90,16 @@ class TestFrontendRemoval:
def test_imgui_frontend_is_present(self): def test_imgui_frontend_is_present(self):
assert (CABANA_DIR / "ui" / "app.cc").is_file() assert (CABANA_DIR / "ui" / "app.cc").is_file()
assert (CABANA_DIR / "ui" / "main.cc").is_file() assert (CABANA_DIR / "ui" / "main.cc").is_file()
def test_stream_selector_stays_in_main_window(self):
app = read("ui/app.cc")
assert "io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable" not in app
class TestReplayVideo:
def test_iqpilot_camera_index_services_are_replayed(self):
source = read("streams/replaystream.cc")
for service in ("roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx"):
assert f'"{service}"' in source
assert '"narrowRoadEncodeIdx"' not in source
assert '"cabinEncodeIdx"' not in source

View File

@@ -144,8 +144,6 @@ public:
ImPlot::CreateContext(); ImPlot::CreateContext();
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
io.ConfigViewportsNoDecoration = false;
io.IniFilename = nullptr; io.IniFilename = nullptr;
io.LogFilename = nullptr; io.LogFilename = nullptr;
if (!ImGui_ImplGlfw_InitForOpenGL(window, true)) { if (!ImGui_ImplGlfw_InitForOpenGL(window, true)) {

View File

@@ -15,19 +15,19 @@
#include "tools/cabana/utils/util.h" #include "tools/cabana/utils/util.h"
void OpenReplayWidget::draw() { void OpenReplayWidget::draw() {
ImGui::AlignTextToFramePadding(); ImGui::TextDisabled("Replay a local or Konn3kt route with optional camera streams.");
ImGui::Spacing();
ImGui::TextUnformatted("Route"); ImGui::TextUnformatted("Route");
ImGui::SameLine(); ImGui::SetNextItemWidth(-1.0f);
ImGui::SetNextItemWidth(-250.0f);
inputText("##route", &route_, "Enter route name or browse for local/remote route"); inputText("##route", &route_, "Enter route name or browse for local/remote route");
ImGui::SameLine(); const float route_button_width = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
if (ImGui::Button("Remote route...")) { if (ImGui::Button("Browse Konn3kt routes", ImVec2(route_button_width, 34.0f))) {
routes_dialog_.open(utils::guarded(alive_, [this](bool accepted, const std::string &route) { routes_dialog_.open(utils::guarded(alive_, [this](bool accepted, const std::string &route) {
if (accepted) route_ = route; if (accepted) route_ = route;
})); }));
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Local route...")) { if (ImGui::Button("Choose local route", ImVec2(-1.0f, 34.0f))) {
FileDialog::getExistingDirectory("Open Local Route", settings.last_route_dir, utils::guarded(alive_, [this](const std::string &dir) { FileDialog::getExistingDirectory("Open Local Route", settings.last_route_dir, utils::guarded(alive_, [this](const std::string &dir) {
if (!dir.empty()) { if (!dir.empty()) {
route_ = dir; route_ = dir;
@@ -35,6 +35,8 @@ void OpenReplayWidget::draw() {
} }
})); }));
} }
ImGui::Spacing();
ImGui::SeparatorText("Camera streams");
checkBox("Road camera", &cameras_[0]); checkBox("Road camera", &cameras_[0]);
ImGui::SameLine(); ImGui::SameLine();
checkBox("Driver camera", &cameras_[1]); checkBox("Driver camera", &cameras_[1]);
@@ -126,6 +128,8 @@ void OpenPandaWidget::buildConfigForm() {
} }
void OpenPandaWidget::draw() { void OpenPandaWidget::draw() {
ImGui::TextDisabled("Connect directly to a Panda and configure each CAN bus.");
ImGui::Spacing();
if (already_connected_) { if (already_connected_) {
ImGui::Text("Already connected to %s.", can->routeName().c_str()); ImGui::Text("Already connected to %s.", can->routeName().c_str());
ImGui::TextUnformatted("Close the current connection via [File menu -> Close Stream] before connecting to another Panda."); ImGui::TextUnformatted("Close the current connection via [File menu -> Close Stream] before connecting to another Panda.");
@@ -184,6 +188,8 @@ std::unique_ptr<AbstractStream> OpenPandaWidget::open() {
} }
void OpenDeviceWidget::draw() { void OpenDeviceWidget::draw() {
ImGui::TextDisabled("Connect to a running IQ.Pilot instance or a local message queue.");
ImGui::Spacing();
ImGui::RadioButton("MSGQ", &mode_, 0); ImGui::RadioButton("MSGQ", &mode_, 0);
ImGui::RadioButton("ZMQ", &mode_, 1); ImGui::RadioButton("ZMQ", &mode_, 1);
ImGui::RadioButton("Bridge", &mode_, 2); ImGui::RadioButton("Bridge", &mode_, 2);
@@ -226,6 +232,8 @@ void OpenSocketCanWidget::refreshDevices() {
} }
void OpenSocketCanWidget::draw() { void OpenSocketCanWidget::draw() {
ImGui::TextDisabled("Read CAN traffic from a Linux SocketCAN interface.");
ImGui::Spacing();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Device"); ImGui::TextUnformatted("Device");
ImGui::SameLine(); ImGui::SameLine();
@@ -248,7 +256,6 @@ std::unique_ptr<AbstractStream> OpenSocketCanWidget::open() {
void StreamSelector::open(Callback on_done) { void StreamSelector::open(Callback on_done) {
on_done_ = std::move(on_done); on_done_ = std::move(on_done);
open_ = true; open_ = true;
popup_.reset();
first_frame_ = true; first_frame_ = true;
dbc_file_.clear(); dbc_file_.clear();
widgets_.clear(); widgets_.clear();
@@ -264,32 +271,65 @@ void StreamSelector::open(Callback on_done) {
void StreamSelector::draw() { void StreamSelector::draw() {
if (!open_) return; if (!open_) return;
if (!beginDialog("Open stream", &popup_, ImVec2(640.0f, 0.0f))) return; const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
const ImGuiWindowFlags page_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking;
if (!ImGui::Begin("##stream_selector_page", nullptr, page_flags)) {
ImGui::End();
ImGui::PopStyleVar(2);
return;
}
ImGui::PopStyleVar(2);
const ImVec2 available = ImGui::GetContentRegionAvail();
const ImVec2 card_size(std::clamp(available.x - 80.0f, 680.0f, 920.0f),
std::clamp(available.y - 80.0f, 470.0f, 570.0f));
ImGui::SetCursorPos(ImVec2(std::max(24.0f, (available.x - card_size.x) * 0.5f),
std::max(24.0f, (available.y - card_size.y) * 0.5f)));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 12.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(28.0f, 24.0f));
ImGui::BeginChild("##stream_selector_card", card_size, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar);
ImGui::PopStyleVar(3);
ImGui::PushFont(boldFont(), 26.0f);
ImGui::TextUnformatted("Open Cabana");
ImGui::PopFont();
ImGui::TextDisabled("Choose a data source to inspect CAN traffic, signals, and video.");
ImGui::Spacing();
ImGui::Spacing();
AbstractOpenStreamWidget *current = nullptr; AbstractOpenStreamWidget *current = nullptr;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(16.0f, 7.0f));
if (ImGui::BeginTabBar("streams")) { if (ImGui::BeginTabBar("streams")) {
for (auto &w : widgets_) { for (auto &w : widgets_) {
ImGuiTabItemFlags tab_flags = (first_frame_ && w == widgets_.front()) ? ImGuiTabItemFlags_SetSelected : 0; ImGuiTabItemFlags tab_flags = (first_frame_ && w == widgets_.front()) ? ImGuiTabItemFlags_SetSelected : 0;
if (ImGui::BeginTabItem(w->title(), nullptr, tab_flags)) { if (ImGui::BeginTabItem(w->title(), nullptr, tab_flags)) {
current = w.get(); current = w.get();
ImGui::BeginChild("tab", ImVec2(0, 130.0f)); const float content_height = std::max(170.0f, card_size.y - 300.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(18.0f, 16.0f));
ImGui::BeginChild("tab", ImVec2(0, content_height), ImGuiChildFlags_Borders);
w->draw(); w->draw();
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
} }
ImGui::EndTabBar(); ImGui::EndTabBar();
} }
ImGui::PopStyleVar();
first_frame_ = false; first_frame_ = false;
ImGui::AlignTextToFramePadding(); ImGui::SeparatorText("DBC file");
ImGui::TextUnformatted("dbc File"); ImGui::SetNextItemWidth(-120.0f);
ImGui::SameLine();
ImGui::SetNextItemWidth(-90.0f);
inputText("##dbc", &dbc_file_, "Choose a dbc file to open", ImGuiInputTextFlags_ReadOnly); inputText("##dbc", &dbc_file_, "Choose a dbc file to open", ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Browse...")) { if (ImGui::Button("Browse...", ImVec2(-1.0f, 0.0f))) {
FileDialog::getOpenFileName("Open File", settings.last_dir, ".dbc", [this](const std::string &fn) { FileDialog::getOpenFileName("Open File", settings.last_dir, ".dbc", [this](const std::string &fn) {
if (!fn.empty()) { if (!fn.empty()) {
dbc_file_ = fn; dbc_file_ = fn;
@@ -302,7 +342,13 @@ void StreamSelector::draw() {
bool accepted = false, rejected = false; bool accepted = false, rejected = false;
std::unique_ptr<AbstractStream> stream; std::unique_ptr<AbstractStream> stream;
bool open_clicked = false; bool open_clicked = false;
dialogButtons("Open", &open_clicked, &rejected, current != nullptr && current->openEnabled()); const float open_width = 180.0f;
if (ImGui::Button("Cancel", ImVec2(100.0f, 38.0f))) rejected = true;
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - open_width);
ImGui::BeginDisabled(current == nullptr || !current->openEnabled());
if (ImGui::Button("Open source", ImVec2(open_width, 38.0f))) open_clicked = true;
ImGui::EndDisabled();
if (open_clicked) { if (open_clicked) {
if (stream = current->open(); stream) accepted = true; if (stream = current->open(); stream) accepted = true;
} }
@@ -312,8 +358,8 @@ void StreamSelector::draw() {
FileDialog::draw(); FileDialog::draw();
MessageBox::draw(); MessageBox::draw();
if (accepted || rejected) ImGui::CloseCurrentPopup(); ImGui::EndChild();
ImGui::EndPopup(); ImGui::End();
if (accepted || rejected) { if (accepted || rejected) {
open_ = false; open_ = false;
widgets_.clear(); widgets_.clear();

View File

@@ -98,7 +98,6 @@ public:
private: private:
bool open_ = false; bool open_ = false;
PopupOwner popup_;
bool first_frame_ = false; bool first_frame_ = false;
std::string dbc_file_; std::string dbc_file_;
std::vector<std::unique_ptr<AbstractOpenStreamWidget>> widgets_; std::vector<std::unique_ptr<AbstractOpenStreamWidget>> widgets_;

View File

@@ -44,6 +44,20 @@ def host():
class TestFileDownload: class TestFileDownload:
def test_head_connection_released(self, monkeypatch):
class Response:
status = 200
headers = {"content-length": "4"}
released = False
def release_conn(self):
self.released = True
response = Response()
monkeypatch.setattr(URLFile, "_request", lambda self, method, url, headers=None: response)
assert URLFile("https://example.com/test").get_length_online() == 4
assert response.released
def test_pipeline_defaults(self, host): def test_pipeline_defaults(self, host):
# TODO: parameterize the defaults so we don't rely on hard-coded values in xx # TODO: parameterize the defaults so we don't rely on hard-coded values in xx

View File

@@ -128,10 +128,13 @@ class URLFile:
def get_length_online(self) -> int: def get_length_online(self) -> int:
response = self._request('HEAD', self._url) response = self._request('HEAD', self._url)
if not (200 <= response.status <= 299): try:
return -1 if not (200 <= response.status <= 299):
length = response.headers.get('content-length', 0) return -1
return int(length) length = response.headers.get('content-length', 0)
return int(length)
finally:
response.release_conn()
def get_length(self) -> int: def get_length(self) -> int:
if self._length is not None: if self._length is not None:

View File

@@ -40,6 +40,36 @@ def write_rlog(path: Path, n_frames: int = 200):
f.write(msg.to_bytes()) f.write(msg.to_bytes())
def write_video_rlog(path: Path, n_frames: int):
with open(path, "wb") as f:
cp = messaging.new_message('carParams')
cp.logMonoTime = 1_000_000_000
cp.carParams.carFingerprint = "TOYOTA_RAV4_TSS2"
cp.carParams.brand = "toyota"
f.write(cp.to_bytes())
for i in range(n_frames):
timestamp = 1_000_000_000 + i * 50_000_000
msg = messaging.new_message('can', 1)
msg.logMonoTime = timestamp
msg.can[0].address = 0x1D2
msg.can[0].src = 0
msg.can[0].dat = bytes([i % 256] * 8)
f.write(msg.to_bytes())
idx = messaging.new_message('roadEncodeIdx')
idx.logMonoTime = timestamp
idx.roadEncodeIdx.frameId = i
idx.roadEncodeIdx.type = 'fullHEVC'
idx.roadEncodeIdx.encodeId = i
idx.roadEncodeIdx.segmentNum = 0
idx.roadEncodeIdx.segmentId = i
idx.roadEncodeIdx.segmentIdEncode = i
idx.roadEncodeIdx.timestampSof = timestamp
idx.roadEncodeIdx.timestampEof = timestamp + 10_000_000
f.write(idx.to_bytes())
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def local_route(tmp_path_factory): def local_route(tmp_path_factory):
data_dir = tmp_path_factory.mktemp("routes") data_dir = tmp_path_factory.mktemp("routes")
@@ -50,6 +80,25 @@ def local_route(tmp_path_factory):
return data_dir return data_dir
@pytest.fixture(scope="module")
def local_video_route(tmp_path_factory):
data_dir = tmp_path_factory.mktemp("video_routes")
seg_dir = data_dir / f"{DONGLE_ID}|{TIMESTAMP}--0"
seg_dir.mkdir()
frame_count = 20
result = subprocess.run([
"ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "lavfi",
"-i", "testsrc=size=320x180:rate=20", "-frames:v", str(frame_count),
"-pix_fmt", "yuv420p", "-c:v", "libx265", "-preset", "ultrafast",
"-x265-params", "pools=1:frame-threads=1:log-level=error", "-f", "hevc",
str(seg_dir / "fcamera.hevc"),
], capture_output=True, text=True)
if result.returncode != 0:
pytest.skip(result.stderr)
write_video_rlog(seg_dir / "rlog", frame_count)
return data_dir
def run(cmd, timeout=180): def run(cmd, timeout=180):
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, env=os.environ.copy(), return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, env=os.environ.copy(),
cwd=TOOLS_DIR.parent) cwd=TOOLS_DIR.parent)
@@ -62,6 +111,33 @@ def cabana_command(*args):
return command return command
def cabana_output_until(args, expected, timeout=60):
master, slave = pty.openpty()
proc = subprocess.Popen(cabana_command(*args), stdout=slave, stderr=slave,
env=os.environ.copy(), cwd=TOOLS_DIR.parent)
os.close(slave)
output = bytearray()
deadline = time.monotonic() + timeout
try:
while time.monotonic() < deadline:
ready, _, _ = select.select([master], [], [], min(1, deadline - time.monotonic()))
if not ready:
if proc.poll() is not None:
break
continue
try:
output.extend(os.read(master, 4096))
except OSError:
break
if expected.encode() in output:
break
finally:
proc.kill()
proc.wait()
os.close(master)
return output.decode(errors="replace")
def test_jotpluggler_renders_a_local_route(local_route, tmp_path): def test_jotpluggler_renders_a_local_route(local_route, tmp_path):
assert JOTPLUGGLER_BIN.exists(), "jotpluggler not built" assert JOTPLUGGLER_BIN.exists(), "jotpluggler not built"
out = tmp_path / "plot.png" out = tmp_path / "plot.png"
@@ -74,38 +150,20 @@ def test_jotpluggler_renders_a_local_route(local_route, tmp_path):
def test_cabana_loads_a_local_route(local_route): def test_cabana_loads_a_local_route(local_route):
assert CABANA_BIN.exists(), "cabana not built" assert CABANA_BIN.exists(), "cabana not built"
master, slave = pty.openpty()
proc = subprocess.Popen(cabana_command("--data_dir", str(local_route), "--no-vipc", ROUTE),
stdout=slave, stderr=slave,
env=os.environ.copy(), cwd=TOOLS_DIR.parent)
os.close(slave)
loaded = f"loaded route {ROUTE} with 2 valid segments" loaded = f"loaded route {ROUTE} with 2 valid segments"
output = bytearray() out = cabana_output_until(("--data_dir", str(local_route), "--no-vipc", ROUTE), loaded)
deadline = time.monotonic() + 60
try:
while time.monotonic() < deadline:
ready, _, _ = select.select([master], [], [], min(1, deadline - time.monotonic()))
if not ready:
if proc.poll() is not None:
break
continue
try:
output.extend(os.read(master, 4096))
except OSError:
break
if loaded.encode() in output:
break
finally:
proc.kill()
proc.wait()
os.close(master)
out = output.decode(errors="replace")
assert "failed to load route" not in out, out assert "failed to load route" not in out, out
assert "invalid route format" not in out, out assert "invalid route format" not in out, out
assert loaded in out, out assert loaded in out, out
def test_cabana_replays_local_video(local_video_route):
expected = "camera[0] vipc send #1"
out = cabana_output_until(("--data_dir", str(local_video_route), ROUTE), expected)
assert "failed to get frame" not in out, out
assert expected in out, out
def test_replay_logreader_reports_load_stats(local_route): def test_replay_logreader_reports_load_stats(local_route):
assert shutil.which("python3") is not None assert shutil.which("python3") is not None
header = (TOOLS_DIR / "replay" / "logreader.h").read_text() header = (TOOLS_DIR / "replay" / "logreader.h").read_text()

View File

@@ -70,7 +70,7 @@ from iqpilot.system.ui.iqwidgets.widgets.list_view import (
Spacer, Spacer,
) )
from iqpilot.system.ui.iqwidgets.widgets.list_view import IQListItem from iqpilot.system.ui.iqwidgets.widgets.list_view import IQListItem
from iqpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator from iqpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator, toggle_item_iq
from iqpilot.system.ui.iqwidgets.widgets.list_view import button_item, toggle_item from iqpilot.system.ui.iqwidgets.widgets.list_view import button_item, toggle_item
from iqpilot.system.ui.iqwidgets.widgets.list_view import NoticeModal from iqpilot.system.ui.iqwidgets.widgets.list_view import NoticeModal
from iqpilot.system.ui.iqwidgets.widgets.list_view import PickerDialog, PickerItem, PickerGroup from iqpilot.system.ui.iqwidgets.widgets.list_view import PickerDialog, PickerItem, PickerGroup
@@ -1655,6 +1655,14 @@ class ModelsLayout(Widget):
) )
self.big_model_item.action_item.set_value(self._big_model_value()) self.big_model_item.action_item.set_value(self._big_model_value())
self.small_on_mac_item = toggle_item_iq(
lambda: tr("Active Model on eMac"),
tr("Run the selected small model on the Mac instead of a big model."),
initial_state=ui_state.params.get_bool("IQEmacSmallModel"),
callback=self._on_small_on_mac_toggled,
param="IQEmacSmallModel",
)
self.supercombo_label = progress_item(tr("Combined Model")) self.supercombo_label = progress_item(tr("Combined Model"))
self.vision_label = progress_item(tr("Vision Weights")) self.vision_label = progress_item(tr("Vision Weights"))
self.policy_label = progress_item(tr("Policy Weights")) self.policy_label = progress_item(tr("Policy Weights"))
@@ -1671,7 +1679,7 @@ class ModelsLayout(Widget):
self.redownload_item = button_item(lambda: tr("Redownload Current Model"), lambda: tr("REDOWNLOAD"), "", self._redownload_model) self.redownload_item = button_item(lambda: tr("Redownload Current Model"), lambda: tr("REDOWNLOAD"), "", self._redownload_model)
self.cancel_download_item = button_item(tr("Stop Download"), tr("Cancel"), "", self._cancel_model_request) self.cancel_download_item = button_item(tr("Stop Download"), tr("Cancel"), "", self._cancel_model_request)
self.items = [self.current_model_item, self.big_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label, self.items = [self.current_model_item, self.big_model_item, self.small_on_mac_item, self.cancel_download_item, self.supercombo_label, self.vision_label,
self.policy_label, self.redownload_item, self.refresh_item, self.clear_cache_item] self.policy_label, self.redownload_item, self.refresh_item, self.clear_cache_item]
def _is_downloading(self): def _is_downloading(self):
@@ -1921,10 +1929,18 @@ class ModelsLayout(Widget):
get_folders_fn=self._get_folders, on_exit=self._on_model_selected) get_folders_fn=self._get_folders, on_exit=self._on_model_selected)
gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected) gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected)
def _on_small_on_mac_toggled(self, state: bool) -> None:
p = ui_state.params
p.put_bool("IQEmacSmallModel", bool(state))
p.put_bool("IQEmacEnabled", True if state else bool(p.get("IQEmacModel")))
self.big_model_item.action_item.set_value(self._big_model_value())
@staticmethod @staticmethod
def _big_model_value() -> str: def _big_model_value() -> str:
if not ui_state.params.get_bool("IQEmacEnabled"): if not ui_state.params.get_bool("IQEmacEnabled"):
return tr("Off") return tr("Off")
if ui_state.params.get_bool("IQEmacSmallModel"):
return tr("Active model (eMac)")
key = ui_state.params.get("IQEmacModel") key = ui_state.params.get("IQEmacModel")
key = key.decode() if isinstance(key, bytes) else (key or "") key = key.decode() if isinstance(key, bytes) else (key or "")
return _big_model_label(key) if key in [n for n, _ in _big_model_options()] else tr("Off") return _big_model_label(key) if key in [n for n, _ in _big_model_options()] else tr("Off")

View File

@@ -2,6 +2,7 @@
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
""" """
import math
import time import time
import pyray as rl import pyray as rl
@@ -17,6 +18,9 @@ from iqpilot.system.ui.widgets import Widget
_POLL_S = 1.0 _POLL_S = 1.0
_FONT_SIZE = 70 _FONT_SIZE = 70
_ICON_H = 76
_ICONS = {"MAC": ("mac", 62 / 46), "GPU": ("egpu", 1.0)}
_GREY = rl.Color(165, 165, 170, 235)
class EmacStatusRenderer(Widget): class EmacStatusRenderer(Widget):
@@ -27,6 +31,21 @@ class EmacStatusRenderer(Widget):
self._last_poll = 0.0 self._last_poll = 0.0
self._label = "" self._label = ""
self._state = SourceState.HIDDEN self._state = SourceState.HIDDEN
self._icons: dict[str, dict[str, rl.Texture]] = {}
def _icon_set(self, label: str) -> dict[str, rl.Texture] | None:
spec = _ICONS.get(label)
if spec is None:
return None
if label not in self._icons:
base, aspect = spec
w = int(_ICON_H * aspect)
self._icons[label] = {
"base": gui_app.texture(f"icons_mici/{base}.png", w, _ICON_H),
"green": gui_app.texture(f"icons_mici/{base}_green.png", w, _ICON_H),
"orange": gui_app.texture(f"icons_mici/{base}_orange.png", int(w * 1.26), _ICON_H),
}
return self._icons[label]
def update(self): def update(self):
now = time.monotonic() now = time.monotonic()
@@ -38,7 +57,25 @@ class EmacStatusRenderer(Widget):
def _render(self, rect: rl.Rectangle): def _render(self, rect: rl.Rectangle):
if self._state == SourceState.HIDDEN: if self._state == SourceState.HIDDEN:
return return
size = measure_text_cached(self._font, self._label, _FONT_SIZE) icons = self._icon_set(self._label)
x = rect.x + UI_BORDER_SIZE + BTN_SIZE // 2 - size.x / 2 if icons is None:
y = rect.y + rect.height / 2 - size.y / 2 size = measure_text_cached(self._font, self._label, _FONT_SIZE)
draw_source_label(self._font, self._label, self._state, rl.Vector2(x, y), _FONT_SIZE) x = rect.x + UI_BORDER_SIZE + BTN_SIZE // 2 - size.x / 2
y = rect.y + rect.height / 2 - size.y / 2
draw_source_label(self._font, self._label, self._state, rl.Vector2(x, y), _FONT_SIZE)
return
if self._state == SourceState.ACTIVE:
tex, tint = icons["green"], rl.Color(255, 255, 255, 255)
elif self._state == SourceState.FAILED:
tex, tint = icons["orange"], rl.Color(255, 255, 255, 255)
elif self._state == SourceState.CROSSED:
tex, tint = icons["base"], rl.Color(255, 255, 255, 165)
else:
pulse = 0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0))
tex, tint = icons["base"], rl.Color(_GREY.r, _GREY.g, _GREY.b, int(_GREY.a * pulse))
x = int(rect.x + UI_BORDER_SIZE + BTN_SIZE // 2 - tex.width / 2)
y = int(rect.y + rect.height / 2 - tex.height / 2)
rl.draw_texture(tex, x, y, tint)
if self._state == SourceState.CROSSED:
cy = y + tex.height // 2
rl.draw_line_ex(rl.Vector2(x - 4, cy), rl.Vector2(x + tex.width + 4, cy), 4, rl.Color(255, 255, 255, 165))